mirror of
https://github.com/QM4RS/FridaBox.git
synced 2026-10-21 23:32:36 +02:00
2.0-r1beta the new blackbox development will be continue here
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
package top.niunaijun.blackboxa.app
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import top.niunaijun.blackbox.BlackBoxCore
|
||||
|
||||
/**
|
||||
*
|
||||
* @Description:
|
||||
* @Author: wukaicheng
|
||||
* @CreateDate: 2021/4/29 21:21
|
||||
*/
|
||||
class App : Application() {
|
||||
|
||||
companion object {
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
@Volatile
|
||||
private lateinit var mContext: Context
|
||||
|
||||
@JvmStatic
|
||||
fun getContext(): Context {
|
||||
return mContext
|
||||
}
|
||||
}
|
||||
|
||||
override fun attachBaseContext(base: Context?) {
|
||||
try {
|
||||
super.attachBaseContext(base)
|
||||
|
||||
// Initialize BlackBoxCore with error handling
|
||||
try {
|
||||
BlackBoxCore.get().closeCodeInit()
|
||||
} catch (e: Exception) {
|
||||
Log.e("App", "Error in closeCodeInit: ${e.message}")
|
||||
}
|
||||
|
||||
try {
|
||||
BlackBoxCore.get().onBeforeMainApplicationAttach(this, base)
|
||||
} catch (e: Exception) {
|
||||
Log.e("App", "Error in onBeforeMainApplicationAttach: ${e.message}")
|
||||
}
|
||||
|
||||
mContext = base!!
|
||||
|
||||
try {
|
||||
AppManager.doAttachBaseContext(base)
|
||||
} catch (e: Exception) {
|
||||
Log.e("App", "Error in doAttachBaseContext: ${e.message}")
|
||||
}
|
||||
|
||||
try {
|
||||
BlackBoxCore.get().onAfterMainApplicationAttach(this, base)
|
||||
} catch (e: Exception) {
|
||||
Log.e("App", "Error in onAfterMainApplicationAttach: ${e.message}")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("App", "Critical error in attachBaseContext: ${e.message}")
|
||||
// Ensure we still set the context even if other initialization fails
|
||||
if (base != null) {
|
||||
mContext = base
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
try {
|
||||
super.onCreate()
|
||||
AppManager.doOnCreate(mContext)
|
||||
} catch (e: Exception) {
|
||||
Log.e("App", "Error in onCreate: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package top.niunaijun.blackboxa.app
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.util.Log
|
||||
import top.niunaijun.blackbox.BlackBoxCore
|
||||
import top.niunaijun.blackboxa.view.main.BlackBoxLoader
|
||||
|
||||
object AppManager {
|
||||
private const val TAG = "AppManager"
|
||||
|
||||
@JvmStatic
|
||||
val mBlackBoxLoader by lazy {
|
||||
try {
|
||||
BlackBoxLoader()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error creating BlackBoxLoader: ${e.message}")
|
||||
|
||||
BlackBoxLoader() // Try again, but this might fail
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
val mBlackBoxCore by lazy {
|
||||
try {
|
||||
mBlackBoxLoader.getBlackBoxCore()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error getting BlackBoxCore: ${e.message}")
|
||||
throw e // Re-throw as this is critical
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
val mRemarkSharedPreferences: SharedPreferences by lazy {
|
||||
try {
|
||||
App.getContext().getSharedPreferences("UserRemark", Context.MODE_PRIVATE)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error creating SharedPreferences: ${e.message}")
|
||||
throw e // Re-throw as this is critical
|
||||
}
|
||||
}
|
||||
|
||||
fun doAttachBaseContext(context: Context) {
|
||||
try {
|
||||
mBlackBoxLoader.attachBaseContext(context)
|
||||
mBlackBoxLoader.addLifecycleCallback()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error in doAttachBaseContext: ${e.message}")
|
||||
// Don't re-throw as this might not be critical for app startup
|
||||
}
|
||||
}
|
||||
|
||||
fun doOnCreate(context: Context) {
|
||||
try {
|
||||
mBlackBoxLoader.doOnCreate(context)
|
||||
initThirdService(context)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error in doOnCreate: ${e.message}")
|
||||
// Don't re-throw as this might not be critical for app startup
|
||||
}
|
||||
}
|
||||
|
||||
private fun initThirdService(context: Context) {
|
||||
try {
|
||||
// Initialize any third-party services here
|
||||
// Currently empty, but with error handling for future additions
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error in initThirdService: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package top.niunaijun.blackboxa.app.rocker
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.Application
|
||||
import android.os.Bundle
|
||||
|
||||
/**
|
||||
*
|
||||
* @Description:
|
||||
* @Author: kotlinMiku
|
||||
* @CreateDate: 2022/3/19 20:08
|
||||
*/
|
||||
interface BaseActivityLifecycleCallback : Application.ActivityLifecycleCallbacks {
|
||||
|
||||
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
|
||||
|
||||
}
|
||||
override fun onActivityStarted(activity: Activity) {
|
||||
|
||||
}
|
||||
override fun onActivityResumed(activity: Activity) {
|
||||
|
||||
}
|
||||
override fun onActivityPaused(activity: Activity) {
|
||||
|
||||
}
|
||||
override fun onActivityStopped(activity: Activity) {
|
||||
|
||||
}
|
||||
override fun onActivitySaveInstanceState(activity: Activity, p1: Bundle) {
|
||||
|
||||
}
|
||||
override fun onActivityDestroyed(activity: Activity) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package top.niunaijun.blackboxa.app.rocker
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.Application
|
||||
import android.util.Log
|
||||
import android.view.Gravity
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.RelativeLayout
|
||||
import com.imuxuan.floatingview.FloatingMagnetView
|
||||
import com.imuxuan.floatingview.FloatingView
|
||||
import top.niunaijun.blackbox.entity.location.BLocation
|
||||
import top.niunaijun.blackbox.fake.frameworks.BLocationManager
|
||||
import top.niunaijun.blackboxa.app.App
|
||||
import top.niunaijun.blackboxa.widget.EnFloatView
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.sin
|
||||
|
||||
/**
|
||||
*
|
||||
* @Description:
|
||||
* @Author: kotlinMiku
|
||||
* @CreateDate: 2022/3/19 19:37
|
||||
*/
|
||||
object RockerManager {
|
||||
|
||||
private const val TAG = "RockerManager"
|
||||
|
||||
|
||||
private const val Ea = 6378137 // 赤道半径
|
||||
|
||||
private const val Eb = 6356725 // 极半径
|
||||
|
||||
fun init(application: Application?, userId: Int) {
|
||||
|
||||
if (application == null || !BLocationManager.isFakeLocationEnable()) {
|
||||
return
|
||||
}
|
||||
|
||||
val enFloatView = initFloatView()
|
||||
|
||||
if (enFloatView is EnFloatView) {
|
||||
enFloatView.setListener { angle: Float, distance: Float ->
|
||||
changeLocation(distance, angle, application.packageName,userId)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
application.registerActivityLifecycleCallbacks(object : BaseActivityLifecycleCallback {
|
||||
|
||||
override fun onActivityStarted(activity: Activity) {
|
||||
super.onActivityStarted(activity)
|
||||
FloatingView.get().attach(activity)
|
||||
}
|
||||
|
||||
override fun onActivityStopped(activity: Activity) {
|
||||
super.onActivityStopped(activity)
|
||||
FloatingView.get().detach(activity)
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
private fun initFloatView(): FloatingMagnetView? {
|
||||
|
||||
val params = FrameLayout.LayoutParams(
|
||||
RelativeLayout.LayoutParams.WRAP_CONTENT,
|
||||
RelativeLayout.LayoutParams.WRAP_CONTENT
|
||||
)
|
||||
|
||||
params.gravity = Gravity.START or Gravity.CENTER
|
||||
val view = EnFloatView(App.getContext())
|
||||
view.layoutParams = params
|
||||
|
||||
FloatingView.get().customView(view)
|
||||
|
||||
return FloatingView.get().view
|
||||
}
|
||||
|
||||
private fun changeLocation(distance: Float, angle: Float, packageName: String, userId: Int) {
|
||||
val location = BLocationManager.get().getLocation(userId, packageName)
|
||||
|
||||
val dx = distance * sin(angle * Math.PI / 180.0)
|
||||
val dy = distance * cos(angle * Math.PI / 180.0)
|
||||
|
||||
|
||||
val ec = Eb + (Ea - Eb) * (90.0 - location.latitude) / 90.0
|
||||
val ed = ec * cos(location.latitude * Math.PI / 180)
|
||||
|
||||
val newLng = (dx / ed + location.longitude * Math.PI / 180.0) * 180.0 / Math.PI
|
||||
|
||||
val newLat = (dy / ec + location.latitude * Math.PI / 180.0) * 180.0 / Math.PI
|
||||
val newLocation = BLocation(newLat, newLng)
|
||||
|
||||
BLocationManager.get().setLocation(userId, packageName, newLocation)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package top.niunaijun.blackboxa.bean
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
|
||||
/**
|
||||
*
|
||||
* @Description:
|
||||
* @Author: wukaicheng
|
||||
* @CreateDate: 2021/4/29 21:57
|
||||
*/
|
||||
data class AppInfo(val name:String,val icon:Drawable?,val packageName:String,val sourceDir:String,val isXpModule:Boolean)
|
||||
@@ -0,0 +1,15 @@
|
||||
package top.niunaijun.blackboxa.bean
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
import top.niunaijun.blackbox.entity.location.BLocation
|
||||
|
||||
data class FakeLocationBean(
|
||||
val userID: Int,
|
||||
val name: String,
|
||||
val icon: Drawable,
|
||||
val packageName: String,
|
||||
var fakeLocationPattern: Int,
|
||||
var fakeLocation: BLocation?
|
||||
)
|
||||
|
||||
data class FakeLocationBeanInstallBean(val userID: Int, val success: Boolean, val msg: String)
|
||||
@@ -0,0 +1,12 @@
|
||||
package top.niunaijun.blackboxa.bean
|
||||
|
||||
/**
|
||||
*
|
||||
* @Description:
|
||||
* @Author: BlackBox
|
||||
* @CreateDate: 2022/3/2 21:30
|
||||
*/
|
||||
data class GmsBean(val userID:Int,val userName:String,var isInstalledGms:Boolean)
|
||||
|
||||
|
||||
data class GmsInstallBean(val userID: Int,val success:Boolean,val msg:String)
|
||||
@@ -0,0 +1,11 @@
|
||||
package top.niunaijun.blackboxa.bean
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
|
||||
/**
|
||||
*
|
||||
* @Description:
|
||||
* @Author: wukaicheng
|
||||
* @CreateDate: 2022/3/6 19:26
|
||||
*/
|
||||
data class InstalledAppBean(val name:String, val icon: Drawable?, val packageName:String, val sourceDir:String, val isInstall:Boolean)
|
||||
@@ -0,0 +1,18 @@
|
||||
package top.niunaijun.blackboxa.bean
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
|
||||
/**
|
||||
*
|
||||
* @Description:
|
||||
* @Author: wukaicheng
|
||||
* @CreateDate: 2021/5/2 21:03
|
||||
*/
|
||||
data class XpModuleInfo(
|
||||
val name: String,
|
||||
val desc: String,
|
||||
val packageName: String,
|
||||
val version: String,
|
||||
var enable:Boolean,
|
||||
val icon: Drawable
|
||||
)
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
package top.niunaijun.blackboxa.biz.cache
|
||||
|
||||
import android.content.Context
|
||||
import android.text.TextUtils
|
||||
import androidx.core.content.edit
|
||||
import kotlin.properties.ReadWriteProperty
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
/**
|
||||
*
|
||||
* @desc:目前只支持 5种基本数据类型,如果要支持obj,请继承该类并重写他的相关方法 findData/putData
|
||||
*
|
||||
* @author: mini
|
||||
* @created by 2021/5/10
|
||||
*/
|
||||
open class AppSharedPreferenceDelegate<Data>(context: Context, private val default: Data, spName: String? = null) : ReadWriteProperty<Any, Data?> {
|
||||
|
||||
private val mSharedPreferences by lazy {
|
||||
val tmpCacheName = if (TextUtils.isEmpty(spName)) {
|
||||
AppSharedPreferenceDelegate::class.java.simpleName
|
||||
} else {
|
||||
spName
|
||||
}
|
||||
return@lazy context.getSharedPreferences(tmpCacheName, Context.MODE_PRIVATE)
|
||||
}
|
||||
|
||||
override fun getValue(thisRef: Any, property: KProperty<*>): Data {
|
||||
return findData(property.name, default)
|
||||
}
|
||||
|
||||
override fun setValue(thisRef: Any, property: KProperty<*>, value: Data?) {
|
||||
putData(property.name, value)
|
||||
}
|
||||
|
||||
protected fun findData(key: String, default: Data): Data {
|
||||
with(mSharedPreferences) {
|
||||
val result: Any = when (default) {
|
||||
is Int -> getInt(key, default)
|
||||
is Long -> getLong(key, default)
|
||||
is Float -> getFloat(key, default)
|
||||
is String -> getString(key, default)!!
|
||||
is Boolean -> getBoolean(key, default)
|
||||
else -> throw IllegalArgumentException("This type $default can not be saved into sharedPreferences")
|
||||
}
|
||||
return result as? Data ?: default
|
||||
}
|
||||
}
|
||||
|
||||
protected fun putData(key: String, value: Data?) {
|
||||
mSharedPreferences.edit {
|
||||
if (value == null) {
|
||||
remove(key)
|
||||
} else {
|
||||
when (value) {
|
||||
is Int -> putInt(key, value)
|
||||
is Long -> putLong(key, value)
|
||||
is Float -> putFloat(key, value)
|
||||
is String -> putString(key, value)
|
||||
is Boolean -> putBoolean(key, value)
|
||||
else -> throw IllegalArgumentException("This type $default can not be saved into Preferences")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,363 @@
|
||||
package top.niunaijun.blackboxa.data
|
||||
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import android.webkit.URLUtil
|
||||
import androidx.core.content.edit
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import top.niunaijun.blackbox.BlackBoxCore
|
||||
import top.niunaijun.blackbox.BlackBoxCore.getPackageManager
|
||||
import top.niunaijun.blackbox.utils.AbiUtils
|
||||
import top.niunaijun.blackboxa.R
|
||||
import top.niunaijun.blackboxa.app.AppManager
|
||||
import top.niunaijun.blackboxa.bean.AppInfo
|
||||
import top.niunaijun.blackboxa.bean.InstalledAppBean
|
||||
import top.niunaijun.blackboxa.util.getString
|
||||
import top.niunaijun.blackbox.fake.frameworks.BResourcesManager
|
||||
import java.io.File
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @Description:
|
||||
* @Author: wukaicheng
|
||||
* @CreateDate: 2021/4/29 23:05
|
||||
*/
|
||||
|
||||
class AppsRepository {
|
||||
val TAG: String = "AppsRepository"
|
||||
private var mInstalledList = mutableListOf<AppInfo>()
|
||||
|
||||
/**
|
||||
* Safely load app label with fallback to package name
|
||||
*/
|
||||
private fun safeLoadAppLabel(applicationInfo: ApplicationInfo): String {
|
||||
return try {
|
||||
getPackageManager().getApplicationLabel(applicationInfo).toString()
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Failed to load label for ${applicationInfo.packageName}: ${e.message}")
|
||||
applicationInfo.packageName // Fallback to package name
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely load app icon with fallback to null
|
||||
*/
|
||||
private fun safeLoadAppIcon(applicationInfo: ApplicationInfo): android.graphics.drawable.Drawable? {
|
||||
return try {
|
||||
getPackageManager().getApplicationIcon(applicationInfo)
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Failed to load icon for ${applicationInfo.packageName}: ${e.message}")
|
||||
null // Fallback to null icon
|
||||
}
|
||||
}
|
||||
|
||||
fun previewInstallList() {
|
||||
try {
|
||||
synchronized(mInstalledList) {
|
||||
val installedApplications: List<ApplicationInfo> =
|
||||
getPackageManager().getInstalledApplications(0)
|
||||
val installedList = mutableListOf<AppInfo>()
|
||||
|
||||
for (installedApplication in installedApplications) {
|
||||
try {
|
||||
val file = File(installedApplication.sourceDir)
|
||||
|
||||
if ((installedApplication.flags and ApplicationInfo.FLAG_SYSTEM) != 0) continue
|
||||
|
||||
if (!AbiUtils.isSupport(file)) continue
|
||||
|
||||
val isXpModule = BlackBoxCore.get().isXposedModule(file)
|
||||
|
||||
val info = AppInfo(
|
||||
safeLoadAppLabel(installedApplication),
|
||||
safeLoadAppIcon(installedApplication), // Remove the !! operator to allow null icons
|
||||
installedApplication.packageName,
|
||||
installedApplication.sourceDir,
|
||||
isXpModule
|
||||
)
|
||||
installedList.add(info)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error processing app ${installedApplication.packageName}: ${e.message}")
|
||||
}
|
||||
}
|
||||
this.mInstalledList.clear()
|
||||
this.mInstalledList.addAll(installedList)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error in previewInstallList: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
fun getInstalledAppList(
|
||||
userID: Int,
|
||||
loadingLiveData: MutableLiveData<Boolean>,
|
||||
appsLiveData: MutableLiveData<List<InstalledAppBean>>
|
||||
) {
|
||||
try {
|
||||
loadingLiveData.postValue(true)
|
||||
synchronized(mInstalledList) {
|
||||
val blackBoxCore = BlackBoxCore.get()
|
||||
Log.d(TAG, mInstalledList.joinToString(","))
|
||||
val newInstalledList = mInstalledList.map {
|
||||
InstalledAppBean(
|
||||
it.name,
|
||||
it.icon, // Remove the !! operator to allow null icons
|
||||
it.packageName,
|
||||
it.sourceDir,
|
||||
blackBoxCore.isInstalled(it.packageName, userID)
|
||||
)
|
||||
}
|
||||
appsLiveData.postValue(newInstalledList)
|
||||
loadingLiveData.postValue(false)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error in getInstalledAppList: ${e.message}")
|
||||
loadingLiveData.postValue(false)
|
||||
appsLiveData.postValue(emptyList())
|
||||
}
|
||||
}
|
||||
|
||||
fun getInstalledModuleList(
|
||||
loadingLiveData: MutableLiveData<Boolean>,
|
||||
appsLiveData: MutableLiveData<List<InstalledAppBean>>
|
||||
) {
|
||||
try {
|
||||
loadingLiveData.postValue(true)
|
||||
synchronized(mInstalledList) {
|
||||
val blackBoxCore = BlackBoxCore.get()
|
||||
val moduleList = mInstalledList.filter {
|
||||
it.isXpModule
|
||||
}.map {
|
||||
InstalledAppBean(
|
||||
it.name,
|
||||
it.icon, // Remove the !! operator to allow null icons
|
||||
it.packageName,
|
||||
it.sourceDir,
|
||||
blackBoxCore.isInstalledXposedModule(it.packageName)
|
||||
)
|
||||
}
|
||||
appsLiveData.postValue(moduleList)
|
||||
loadingLiveData.postValue(false)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error in getInstalledModuleList: ${e.message}")
|
||||
loadingLiveData.postValue(false)
|
||||
appsLiveData.postValue(emptyList())
|
||||
}
|
||||
}
|
||||
|
||||
fun getVmInstallList(userId: Int, appsLiveData: MutableLiveData<List<AppInfo>>) {
|
||||
try {
|
||||
val blackBoxCore = BlackBoxCore.get()
|
||||
|
||||
// Add debugging for users
|
||||
val users = blackBoxCore.users
|
||||
Log.d(TAG, "getVmInstallList: userId=$userId, total users=${users.size}")
|
||||
users.forEach { user ->
|
||||
Log.d(TAG, "User: id=${user.id}, name=${user.name}")
|
||||
}
|
||||
|
||||
val sortListData =
|
||||
AppManager.mRemarkSharedPreferences.getString("AppList$userId", "")
|
||||
val sortList = sortListData?.split(",")
|
||||
|
||||
val applicationList = blackBoxCore.getInstalledApplications(0, userId)
|
||||
|
||||
// Add null check for applicationList
|
||||
if (applicationList == null) {
|
||||
Log.e(TAG, "getVmInstallList: applicationList is null for userId=$userId")
|
||||
appsLiveData.postValue(emptyList())
|
||||
return
|
||||
}
|
||||
|
||||
// Add debugging
|
||||
Log.d(TAG, "getVmInstallList: userId=$userId, applicationList.size=${applicationList.size}")
|
||||
if (applicationList.isNotEmpty()) {
|
||||
Log.d(TAG, "First app: ${applicationList.first().packageName}")
|
||||
} else {
|
||||
Log.w(TAG, "getVmInstallList: No applications found for userId=$userId")
|
||||
}
|
||||
|
||||
val appInfoList = mutableListOf<AppInfo>()
|
||||
|
||||
// Sort the application list if sort data exists
|
||||
val sortedApplicationList = if (!sortList.isNullOrEmpty()) {
|
||||
applicationList.sortedWith(AppsSortComparator(sortList))
|
||||
} else {
|
||||
applicationList
|
||||
}
|
||||
|
||||
// Process each application
|
||||
sortedApplicationList.forEach { applicationInfo ->
|
||||
try {
|
||||
// Add null check for applicationInfo
|
||||
if (applicationInfo == null) {
|
||||
Log.w(TAG, "getVmInstallList: Skipping null applicationInfo")
|
||||
return@forEach
|
||||
}
|
||||
|
||||
val info = AppInfo(
|
||||
safeLoadAppLabel(applicationInfo),
|
||||
safeLoadAppIcon(applicationInfo), // Remove the !! operator to allow null icons
|
||||
applicationInfo.packageName,
|
||||
applicationInfo.sourceDir,
|
||||
isInstalledXpModule(applicationInfo.packageName)
|
||||
)
|
||||
|
||||
appInfoList.add(info)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "getVmInstallList: Error processing app ${applicationInfo?.packageName}: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
Log.d(TAG, "getVmInstallList: processed ${appInfoList.size} apps")
|
||||
appsLiveData.postValue(appInfoList)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error in getVmInstallList: ${e.message}")
|
||||
appsLiveData.postValue(emptyList())
|
||||
}
|
||||
}
|
||||
|
||||
private fun isInstalledXpModule(packageName: String): Boolean {
|
||||
return try {
|
||||
BlackBoxCore.get().installedXPModules.forEach {
|
||||
if (packageName == it.packageName) {
|
||||
return@isInstalledXpModule true
|
||||
}
|
||||
}
|
||||
false
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error checking Xposed module: ${e.message}")
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fun installApk(source: String, userId: Int, resultLiveData: MutableLiveData<String>) {
|
||||
try {
|
||||
val blackBoxCore = BlackBoxCore.get()
|
||||
val installResult = if (URLUtil.isValidUrl(source)) {
|
||||
val uri = Uri.parse(source)
|
||||
blackBoxCore.installPackageAsUser(uri, userId)
|
||||
} else {
|
||||
blackBoxCore.installPackageAsUser(source, userId)
|
||||
}
|
||||
|
||||
if (installResult.success) {
|
||||
updateAppSortList(userId, installResult.packageName, true)
|
||||
resultLiveData.postValue(getString(R.string.install_success))
|
||||
} else {
|
||||
resultLiveData.postValue(getString(R.string.install_fail, installResult.msg))
|
||||
}
|
||||
scanUser()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error installing APK: ${e.message}")
|
||||
resultLiveData.postValue("Installation failed: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
fun unInstall(packageName: String, userID: Int, resultLiveData: MutableLiveData<String>) {
|
||||
try {
|
||||
BlackBoxCore.get().uninstallPackageAsUser(packageName, userID)
|
||||
updateAppSortList(userID, packageName, false)
|
||||
scanUser()
|
||||
resultLiveData.postValue(getString(R.string.uninstall_success))
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error uninstalling APK: ${e.message}")
|
||||
resultLiveData.postValue("Uninstallation failed: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
fun launchApk(packageName: String, userId: Int, launchLiveData: MutableLiveData<Boolean>) {
|
||||
try {
|
||||
val result = BlackBoxCore.get().launchApk(packageName, userId)
|
||||
launchLiveData.postValue(result)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error launching APK: ${e.message}")
|
||||
launchLiveData.postValue(false)
|
||||
}
|
||||
}
|
||||
|
||||
fun clearApkData(packageName: String, userID: Int, resultLiveData: MutableLiveData<String>) {
|
||||
try {
|
||||
BlackBoxCore.get().clearPackage(packageName, userID)
|
||||
resultLiveData.postValue(getString(R.string.clear_success))
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error clearing APK data: ${e.message}")
|
||||
resultLiveData.postValue("Clear failed: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 倒序递归扫描用户,
|
||||
* 如果用户是空的,就删除用户,删除用户备注,删除应用排序列表
|
||||
*/
|
||||
private fun scanUser() {
|
||||
try {
|
||||
val blackBoxCore = BlackBoxCore.get()
|
||||
val userList = blackBoxCore.users
|
||||
|
||||
if (userList.isEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
val id = userList.last().id
|
||||
|
||||
if (blackBoxCore.getInstalledApplications(0, id).isEmpty()) {
|
||||
blackBoxCore.deleteUser(id)
|
||||
AppManager.mRemarkSharedPreferences.edit {
|
||||
remove("Remark$id")
|
||||
remove("AppList$id")
|
||||
}
|
||||
scanUser()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error in scanUser: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新排序列表
|
||||
* @param userID Int
|
||||
* @param pkg String
|
||||
* @param isAdd Boolean true是添加,false是移除
|
||||
*/
|
||||
private fun updateAppSortList(userID: Int, pkg: String, isAdd: Boolean) {
|
||||
try {
|
||||
val savedSortList =
|
||||
AppManager.mRemarkSharedPreferences.getString("AppList$userID", "")
|
||||
|
||||
val sortList = linkedSetOf<String>()
|
||||
if (savedSortList != null) {
|
||||
sortList.addAll(savedSortList.split(","))
|
||||
}
|
||||
|
||||
if (isAdd) {
|
||||
sortList.add(pkg)
|
||||
} else {
|
||||
sortList.remove(pkg)
|
||||
}
|
||||
|
||||
AppManager.mRemarkSharedPreferences.edit {
|
||||
putString("AppList$userID", sortList.joinToString(","))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error updating app sort list: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存排序后的apk顺序
|
||||
*/
|
||||
fun updateApkOrder(userID: Int, dataList: List<AppInfo>) {
|
||||
try {
|
||||
AppManager.mRemarkSharedPreferences.edit {
|
||||
putString("AppList$userID",
|
||||
dataList.joinToString(",") { it.packageName })
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error updating APK order: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package top.niunaijun.blackboxa.data
|
||||
|
||||
import android.content.pm.ApplicationInfo
|
||||
|
||||
/**
|
||||
*
|
||||
* @Description: app sort
|
||||
* @Author: BlackBox
|
||||
* @CreateDate: 2022/2/27 23:21
|
||||
*/
|
||||
class AppsSortComparator(private val sortedList: List<String>) : Comparator<ApplicationInfo> {
|
||||
override fun compare(o1: ApplicationInfo?, o2: ApplicationInfo?): Int {
|
||||
if (o1 == null || o2 == null) {
|
||||
return 0
|
||||
}
|
||||
|
||||
val first = sortedList.indexOf(o1.packageName)
|
||||
val second = sortedList.indexOf(o2.packageName)
|
||||
return first - second
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package top.niunaijun.blackboxa.data
|
||||
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import top.niunaijun.blackbox.BlackBoxCore
|
||||
import top.niunaijun.blackbox.entity.location.BLocation
|
||||
import top.niunaijun.blackbox.fake.frameworks.BLocationManager
|
||||
import top.niunaijun.blackboxa.bean.FakeLocationBean
|
||||
|
||||
/**
|
||||
* getInstalledApplications and query fake location of each of them.
|
||||
* mode and location configuration are respectively concept.
|
||||
* Location config just store the location but mode decides whether turns on it
|
||||
* each application has three pattern.Global: use global fake location,
|
||||
* self: use own config, close: use real config
|
||||
* @Description:
|
||||
* @Author: BlackBoxing
|
||||
* @CreateDate: 2022/3/12 21:14
|
||||
*/
|
||||
class FakeLocationRepository {
|
||||
val TAG: String = "FakeLocationRepository"
|
||||
|
||||
fun setPattern(userId: Int, pkg: String, pattern: Int) {
|
||||
BLocationManager.get().setPattern(userId, pkg, pattern)
|
||||
}
|
||||
|
||||
private fun getPattern(userId: Int, pkg: String): Int {
|
||||
return BLocationManager.get().getPattern(userId, pkg)
|
||||
}
|
||||
|
||||
private fun getLocation(userId: Int, pkg: String): BLocation? {
|
||||
return BLocationManager.get().getLocation(userId, pkg)
|
||||
}
|
||||
|
||||
fun setLocation(userId: Int, pkg: String, location: BLocation) {
|
||||
BLocationManager.get().setLocation(userId, pkg, location)
|
||||
}
|
||||
|
||||
fun getInstalledAppList(
|
||||
userID: Int,
|
||||
appsFakeLiveData: MutableLiveData<List<FakeLocationBean>>
|
||||
) {
|
||||
val installedList = mutableListOf<FakeLocationBean>()
|
||||
val installedApplications: List<ApplicationInfo> =
|
||||
BlackBoxCore.get().getInstalledApplications(0, userID)
|
||||
// List<ApplicationInfo> -> List<FakeLocationBean>
|
||||
for (installedApplication in installedApplications) {
|
||||
// val file = File(installedApplication.sourceDir)
|
||||
//
|
||||
// if ((installedApplication.flags and ApplicationInfo.FLAG_SYSTEM) != 0) continue
|
||||
//
|
||||
// if (!AbiUtils.isSupport(file)) continue
|
||||
//
|
||||
// val isXpModule = BlackBoxCore.get().isXposedModule(file)
|
||||
|
||||
val info = FakeLocationBean(
|
||||
userID,
|
||||
installedApplication.loadLabel(BlackBoxCore.getPackageManager()).toString(),
|
||||
installedApplication.loadIcon(BlackBoxCore.getPackageManager()),
|
||||
installedApplication.packageName,
|
||||
getPattern(userID, installedApplication.packageName),
|
||||
getLocation(userID, installedApplication.packageName)
|
||||
)
|
||||
installedList.add(info)
|
||||
}
|
||||
|
||||
Log.d(TAG, installedList.joinToString(","))
|
||||
appsFakeLiveData.postValue(installedList)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package top.niunaijun.blackboxa.data
|
||||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import top.niunaijun.blackbox.BlackBoxCore
|
||||
import top.niunaijun.blackboxa.R
|
||||
import top.niunaijun.blackboxa.app.AppManager
|
||||
import top.niunaijun.blackboxa.bean.GmsBean
|
||||
import top.niunaijun.blackboxa.bean.GmsInstallBean
|
||||
import top.niunaijun.blackboxa.util.getString
|
||||
|
||||
/**
|
||||
*
|
||||
* @Description:
|
||||
* @Author: BlackBox
|
||||
* @CreateDate: 2022/3/2 21:14
|
||||
*/
|
||||
class GmsRepository {
|
||||
|
||||
|
||||
fun getGmsInstalledList(mInstalledLiveData: MutableLiveData<List<GmsBean>>) {
|
||||
val userList = arrayListOf<GmsBean>()
|
||||
|
||||
BlackBoxCore.get().users.forEach {
|
||||
val userId = it.id
|
||||
val userName =
|
||||
AppManager.mRemarkSharedPreferences.getString("Remark$userId", "User $userId") ?: ""
|
||||
val isInstalled = BlackBoxCore.get().isInstallGms(userId)
|
||||
val bean = GmsBean(userId, userName, isInstalled)
|
||||
userList.add(bean)
|
||||
}
|
||||
|
||||
mInstalledLiveData.postValue(userList)
|
||||
}
|
||||
|
||||
fun installGms(
|
||||
userID: Int,
|
||||
mUpdateInstalledLiveData: MutableLiveData<GmsInstallBean>
|
||||
) {
|
||||
val installResult = BlackBoxCore.get().installGms(userID)
|
||||
|
||||
val result = if (installResult.success) {
|
||||
getString(R.string.install_success)
|
||||
} else {
|
||||
getString(R.string.install_fail, installResult.msg)
|
||||
}
|
||||
|
||||
val bean = GmsInstallBean(userID,installResult.success,result)
|
||||
mUpdateInstalledLiveData.postValue(bean)
|
||||
}
|
||||
|
||||
fun uninstallGms(
|
||||
userID: Int,
|
||||
mUpdateInstalledLiveData: MutableLiveData<GmsInstallBean>
|
||||
) {
|
||||
var isSuccess = false
|
||||
if (BlackBoxCore.get().isInstallGms(userID)) {
|
||||
isSuccess = BlackBoxCore.get().uninstallGms(userID)
|
||||
}
|
||||
|
||||
val result = if (isSuccess) {
|
||||
getString(R.string.uninstall_success)
|
||||
} else {
|
||||
getString(R.string.uninstall_fail)
|
||||
}
|
||||
|
||||
val bean = GmsInstallBean(userID,isSuccess,result)
|
||||
|
||||
mUpdateInstalledLiveData.postValue(bean)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package top.niunaijun.blackboxa.data
|
||||
|
||||
import android.net.Uri
|
||||
import android.webkit.URLUtil
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import top.niunaijun.blackbox.BlackBoxCore
|
||||
import top.niunaijun.blackbox.BlackBoxCore.getPackageManager
|
||||
import top.niunaijun.blackboxa.R
|
||||
import top.niunaijun.blackboxa.bean.XpModuleInfo
|
||||
import top.niunaijun.blackboxa.util.getString
|
||||
|
||||
/**
|
||||
*
|
||||
* @Description:
|
||||
* @Author: wukaicheng
|
||||
* @CreateDate: 2021/5/2 20:55
|
||||
*/
|
||||
class XpRepository {
|
||||
fun getInstallModules(modulesLiveData: MutableLiveData<List<XpModuleInfo>>) {
|
||||
val moduleList = BlackBoxCore.get().installedXPModules
|
||||
val result = mutableListOf<XpModuleInfo>()
|
||||
moduleList.forEach {
|
||||
val info = XpModuleInfo(
|
||||
it.name,
|
||||
it.desc,
|
||||
it.packageName,
|
||||
it.packageInfo.versionName.toString(),
|
||||
it.enable,
|
||||
it.application.loadIcon(getPackageManager())
|
||||
)
|
||||
result.add(info)
|
||||
}
|
||||
|
||||
modulesLiveData.postValue(result)
|
||||
}
|
||||
|
||||
fun installModule(source: String, resultLiveData: MutableLiveData<String>) {
|
||||
val blackBoxCore = BlackBoxCore.get()
|
||||
|
||||
val installResult = if (URLUtil.isValidUrl(source)) {
|
||||
val uri = Uri.parse(source)
|
||||
blackBoxCore.installXPModule(uri)
|
||||
} else {
|
||||
//source == packageName
|
||||
blackBoxCore.installXPModule(source)
|
||||
}
|
||||
|
||||
if(installResult.success){
|
||||
resultLiveData.postValue(getString(R.string.install_success))
|
||||
}else{
|
||||
resultLiveData.postValue(getString(R.string.install_fail, installResult.msg))
|
||||
}
|
||||
}
|
||||
|
||||
fun unInstallModule(packageName: String, resultLiveData: MutableLiveData<String>) {
|
||||
BlackBoxCore.get().uninstallXPModule(packageName)
|
||||
resultLiveData.postValue(getString(R.string.remove_success))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package top.niunaijun.blackboxa.util
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.provider.Settings
|
||||
|
||||
/**
|
||||
*
|
||||
* @Description:
|
||||
* @Author: kotlinMiku
|
||||
* @CreateDate: 2022/4/17 16:32
|
||||
*/
|
||||
object ContextUtil {
|
||||
|
||||
fun Context.openAppSystemSettings() {
|
||||
startActivity(Intent().apply {
|
||||
action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
|
||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
data = Uri.fromParts("package", packageName, null)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package top.niunaijun.blackboxa.util
|
||||
|
||||
import top.niunaijun.blackboxa.data.AppsRepository
|
||||
import top.niunaijun.blackboxa.data.FakeLocationRepository
|
||||
import top.niunaijun.blackboxa.data.GmsRepository
|
||||
import top.niunaijun.blackboxa.data.XpRepository
|
||||
import top.niunaijun.blackboxa.view.apps.AppsFactory
|
||||
import top.niunaijun.blackboxa.view.fake.FakeLocationFactory
|
||||
import top.niunaijun.blackboxa.view.gms.GmsFactory
|
||||
import top.niunaijun.blackboxa.view.list.ListFactory
|
||||
import top.niunaijun.blackboxa.view.xp.XpFactory
|
||||
|
||||
/**
|
||||
*
|
||||
* @Description:
|
||||
* @Author: wukaicheng
|
||||
* @CreateDate: 2021/4/29 22:38
|
||||
*/
|
||||
object InjectionUtil {
|
||||
|
||||
private val appsRepository = AppsRepository()
|
||||
|
||||
private val xpRepository = XpRepository()
|
||||
|
||||
private val gmsRepository = GmsRepository()
|
||||
|
||||
private val fakeLocationRepository = FakeLocationRepository()
|
||||
|
||||
fun getAppsFactory() : AppsFactory {
|
||||
return AppsFactory(appsRepository)
|
||||
}
|
||||
|
||||
fun getListFactory(): ListFactory {
|
||||
return ListFactory(appsRepository)
|
||||
}
|
||||
|
||||
fun getXpFactory():XpFactory{
|
||||
return XpFactory(xpRepository)
|
||||
}
|
||||
|
||||
fun getGmsFactory():GmsFactory{
|
||||
return GmsFactory(gmsRepository)
|
||||
}
|
||||
|
||||
fun getFakeLocationFactory():FakeLocationFactory{
|
||||
return FakeLocationFactory(fakeLocationRepository)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package top.niunaijun.blackboxa.util;
|
||||
|
||||
import android.graphics.Point;
|
||||
import android.graphics.PointF;
|
||||
|
||||
public class MathUtil {
|
||||
|
||||
public MathUtil() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the distance between two points.
|
||||
*
|
||||
* @param A Point A
|
||||
* @param B Point B
|
||||
* @return the distance between point A and point B.
|
||||
*/
|
||||
public static int getDistance(PointF A, PointF B) {
|
||||
return (int) Math.sqrt(Math.pow(A.x - B.x, 2) + Math.pow(A.y - B.y, 2));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the distance between two points.
|
||||
*/
|
||||
public static int getDistance(float x1, float y1, float x2, float y2) {
|
||||
return (int) Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the coordinates of a point on the line by cut length.
|
||||
*
|
||||
* @param A Point A
|
||||
* @param B Point B
|
||||
* @param cutLength cut length
|
||||
* @return the point.
|
||||
*/
|
||||
public static Point getPointByCutLength(Point A, Point B, int cutLength) {
|
||||
float radian = getRadian(A, B);
|
||||
return new Point(A.x + (int) (cutLength * Math.cos(radian)), A.y + (int) (cutLength * Math.sin(radian)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the radian between current line(determined by point A and B) and horizontal line.
|
||||
*
|
||||
* @param A point A
|
||||
* @param B point B
|
||||
* @return the radian
|
||||
*/
|
||||
public static float getRadian(Point A, Point B) {
|
||||
float lenA = B.x - A.x;
|
||||
float lenB = B.y - A.y;
|
||||
float lenC = (float) Math.sqrt(lenA * lenA + lenB * lenB);
|
||||
float radian = (float) Math.acos(lenA / lenC);
|
||||
radian = radian * (B.y < A.y ? -1 : 1);
|
||||
return radian;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* angle to radian
|
||||
*
|
||||
* @param angle angle
|
||||
* @return radian
|
||||
*/
|
||||
public static double angle2Radian(double angle) {
|
||||
return angle / 180 * Math.PI;
|
||||
}
|
||||
|
||||
/**
|
||||
* radian to angle
|
||||
*
|
||||
* @param radian radian
|
||||
* @return angle
|
||||
*/
|
||||
public static double radian2Angle(double radian) {
|
||||
return radian / Math.PI * 180;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package top.niunaijun.blackboxa.util
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import top.niunaijun.blackboxa.app.App
|
||||
|
||||
|
||||
fun getString(@StringRes id:Int,vararg arg:String):String{
|
||||
if(arg.isEmpty()){
|
||||
return App.getContext().getString(id)
|
||||
}
|
||||
return App.getContext().getString(id,*arg)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,332 @@
|
||||
package top.niunaijun.blackboxa.util;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.KeyguardManager;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Point;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.Display;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
public class Resolution {
|
||||
private static final String TAG = "UtilsScreen";
|
||||
|
||||
/**
|
||||
* Gets the width of the display, in pixels.
|
||||
* <p>
|
||||
* Note that this value should not be used for computing layouts, since a
|
||||
* device will typically have screen decoration (such as a status bar) along
|
||||
* the edges of the display that reduce the amount of application space
|
||||
* available from the size returned here. Layouts should instead use the
|
||||
* window size.
|
||||
* <p>
|
||||
* The size is adjusted based on the current rotation of the display.
|
||||
* <p>
|
||||
* The size returned by this method does not necessarily represent the
|
||||
* actual raw size (native resolution) of the display. The returned size may
|
||||
* be adjusted to exclude certain system decoration elements that are always
|
||||
* visible. It may also be scaled to provide compatibility with older
|
||||
* applications that were originally designed for smaller displays.
|
||||
*
|
||||
* @return Screen width in pixels.
|
||||
*/
|
||||
public static int getScreenWidth(Context context) {
|
||||
return getScreenSize(context, null).x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the height of the display, in pixels.
|
||||
* <p>
|
||||
* Note that this value should not be used for computing layouts, since a
|
||||
* device will typically have screen decoration (such as a status bar) along
|
||||
* the edges of the display that reduce the amount of application space
|
||||
* available from the size returned here. Layouts should instead use the
|
||||
* window size.
|
||||
* <p>
|
||||
* The size is adjusted based on the current rotation of the display.
|
||||
* <p>
|
||||
* The size returned by this method does not necessarily represent the
|
||||
* actual raw size (native resolution) of the display. The returned size may
|
||||
* be adjusted to exclude certain system decoration elements that are always
|
||||
* visible. It may also be scaled to provide compatibility with older
|
||||
* applications that were originally designed for smaller displays.
|
||||
*
|
||||
* @return Screen height in pixels.
|
||||
*/
|
||||
public static int getScreenHeight(Context context) {
|
||||
return getScreenSize(context, null).y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the size of the display, in pixels.
|
||||
* <p>
|
||||
* Note that this value should not be used for computing layouts, since a
|
||||
* device will typically have screen decoration (such as a status bar) along
|
||||
* the edges of the display that reduce the amount of application space
|
||||
* available from the size returned here. Layouts should instead use the
|
||||
* window size.
|
||||
* <p>
|
||||
* The size is adjusted based on the current rotation of the display.
|
||||
* <p>
|
||||
* The size returned by this method does not necessarily represent the
|
||||
* actual raw size (native resolution) of the display. The returned size may
|
||||
* be adjusted to exclude certain system decoration elements that are always
|
||||
* visible. It may also be scaled to provide compatibility with older
|
||||
* applications that were originally designed for smaller displays.
|
||||
*
|
||||
* @param outSize null-ok. If it is null, will create a Point instance inside,
|
||||
* otherwise use it to fill the output. NOTE if it is not null,
|
||||
* it will be the returned value.
|
||||
* @return Screen size in pixels, the x is the width, the y is the height.
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
public static Point getScreenSize(Context context, Point outSize) {
|
||||
WindowManager wm = (WindowManager) context
|
||||
.getSystemService(Context.WINDOW_SERVICE);
|
||||
Point ret = outSize == null ? new Point() : outSize;
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 30) {
|
||||
// Use the modern approach for Android 11+
|
||||
android.view.WindowMetrics windowMetrics = wm.getCurrentWindowMetrics();
|
||||
android.graphics.Rect bounds = windowMetrics.getBounds();
|
||||
ret.x = bounds.width();
|
||||
ret.y = bounds.height();
|
||||
} else if (Build.VERSION.SDK_INT >= 13) {
|
||||
// Use getSize for Android 3.0+
|
||||
@SuppressWarnings("deprecation")
|
||||
final Display defaultDisplay = wm.getDefaultDisplay();
|
||||
defaultDisplay.getSize(ret);
|
||||
} else {
|
||||
// Fallback for older versions
|
||||
@SuppressWarnings("deprecation")
|
||||
final Display defaultDisplay = wm.getDefaultDisplay();
|
||||
ret.x = defaultDisplay.getWidth();
|
||||
ret.y = defaultDisplay.getHeight();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method converts dp unit to equivalent pixels, depending on device density.
|
||||
*
|
||||
* @param dp A value in dp (density independent pixels) unit. Which we need to convert into pixels
|
||||
* @param context Context to get resources and device specific display metrics
|
||||
* @return A float value to represent px equivalent to dp depending on device density
|
||||
*/
|
||||
public static float convertDpToPixel(float dp, Context context) {
|
||||
Resources resources = context.getResources();
|
||||
DisplayMetrics metrics = resources.getDisplayMetrics();
|
||||
float px = dp * (metrics.densityDpi / 160f);
|
||||
return px;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method converts device specific pixels to density independent pixels.
|
||||
*
|
||||
* @param px A value in px (pixels) unit. Which we need to convert into db
|
||||
* @param context Context to get resources and device specific display metrics
|
||||
* @return A float value to represent dp equivalent to px value
|
||||
*/
|
||||
public static float convertPixelsToDp(float px, Context context) {
|
||||
Resources resources = context.getResources();
|
||||
DisplayMetrics metrics = resources.getDisplayMetrics();
|
||||
float dp = px / (metrics.densityDpi / 160f);
|
||||
return dp;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* 获取屏幕密度
|
||||
*/
|
||||
public static float getDensity(Context context) {
|
||||
float density = 0f;
|
||||
if (context== null) {
|
||||
return density;
|
||||
}
|
||||
try {
|
||||
density = context.getResources().getDisplayMetrics().density;
|
||||
} catch (Exception e) {
|
||||
|
||||
}
|
||||
return density;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查分辨率是否为本机
|
||||
*/
|
||||
public static boolean checkPix(Activity context, int width, int height) {
|
||||
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN) {
|
||||
DisplayMetrics metrics = new DisplayMetrics();
|
||||
context.getWindowManager().getDefaultDisplay().getRealMetrics(metrics);
|
||||
return metrics.widthPixels == width && metrics.heightPixels == height;
|
||||
} else {
|
||||
return getScreenPixWidth(context) == width && getScreenPixHeight(context) == height;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取屏幕分辨率:宽
|
||||
*/
|
||||
public static int getScreenPixWidth(Context context) {
|
||||
return context.getResources().getDisplayMetrics().widthPixels;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取屏幕分辨率:高
|
||||
*/
|
||||
public static int getScreenPixHeight(Context context) {
|
||||
return context.getResources().getDisplayMetrics().heightPixels;
|
||||
}
|
||||
|
||||
/**
|
||||
* dipתpx
|
||||
*/
|
||||
public static int dipToPx(Context context, int dip) {
|
||||
return (int) (dip * context.getResources().getDisplayMetrics().density + 0.5f);
|
||||
}
|
||||
|
||||
/**
|
||||
* pxתdip
|
||||
*/
|
||||
public static int pxToDip(Context context, float pxValue) {
|
||||
final float scale = context.getResources().getDisplayMetrics().density;
|
||||
return (int) (pxValue / scale + 0.5f);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将sp值转换为px值,保证文字大小不变
|
||||
*
|
||||
* @param context
|
||||
* @param spValue
|
||||
* @return
|
||||
*/
|
||||
public static int sp2px(Context context, float spValue) {
|
||||
final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
|
||||
return (int) (spValue * fontScale + 0.5f);
|
||||
}
|
||||
|
||||
/**
|
||||
* 隐藏软键盘
|
||||
*/
|
||||
public static void hideInputMethod(View view) {
|
||||
InputMethodManager imm = (InputMethodManager) view.getContext()
|
||||
.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
if (imm != null) {
|
||||
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示软键盘
|
||||
*/
|
||||
public static void showInputMethod(View view) {
|
||||
InputMethodManager imm = (InputMethodManager) view.getContext()
|
||||
.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
if (imm != null) {
|
||||
imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 多少时间后显示软键盘
|
||||
*/
|
||||
public static void showInputMethod(final View view, long delayMillis) {
|
||||
// 显示输入法
|
||||
new Handler().postDelayed(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Resolution.showInputMethod(view);
|
||||
|
||||
}
|
||||
}, delayMillis);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断手机是否在锁屏状态 true锁屏 false未锁屏
|
||||
*/
|
||||
public static boolean isScreenLocked(Context c) {
|
||||
KeyguardManager mKeyguardManager = (KeyguardManager) c
|
||||
.getSystemService(Context.KEYGUARD_SERVICE);
|
||||
boolean bResult = !mKeyguardManager.inKeyguardRestrictedInputMode();
|
||||
|
||||
return bResult;
|
||||
}
|
||||
|
||||
public static int getBarHeight(Context context) {
|
||||
Class<?> c = null;
|
||||
Object obj = null;
|
||||
Field field = null;
|
||||
int x = 0, sbar = 38;//默认为38,貌似大部分是这样的
|
||||
|
||||
try {
|
||||
c = Class.forName("com.android.internal.R$dimen");
|
||||
obj = c.newInstance();
|
||||
field = c.getField("status_bar_height");
|
||||
x = Integer.parseInt(field.get(obj).toString());
|
||||
sbar = context.getResources().getDimensionPixelSize(x);
|
||||
} catch (Exception e1) {
|
||||
e1.printStackTrace();
|
||||
}
|
||||
return sbar;
|
||||
}
|
||||
|
||||
//http://stackoverflow.com/questions/20264268/how-to-get-height-and-width-of-navigation-bar-programmatically
|
||||
//获取屏幕下方导航栏高度
|
||||
public static Point getNavigationBarSize(Context context) {
|
||||
Point appUsableSize = getScreenSize(context, null);
|
||||
Point realScreenSize = getRealScreenSize(context);
|
||||
|
||||
// // navigation bar on the right
|
||||
// if (appUsableSize.x < realScreenSize.x) {
|
||||
// return new Point(realScreenSize.x - appUsableSize.x, appUsableSize.y);
|
||||
// }
|
||||
|
||||
// navigation bar at the bottom
|
||||
if (appUsableSize.y < realScreenSize.y) {
|
||||
return new Point(appUsableSize.x, realScreenSize.y - appUsableSize.y);
|
||||
}
|
||||
|
||||
// navigation bar is not present
|
||||
return new Point();
|
||||
}
|
||||
|
||||
|
||||
public static Point getRealScreenSize(Context context) {
|
||||
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
|
||||
Point size = new Point();
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 30) {
|
||||
// Use the modern approach for Android 11+
|
||||
android.view.WindowMetrics windowMetrics = windowManager.getCurrentWindowMetrics();
|
||||
android.graphics.Rect bounds = windowMetrics.getBounds();
|
||||
size.x = bounds.width();
|
||||
size.y = bounds.height();
|
||||
} else if (Build.VERSION.SDK_INT >= 17) {
|
||||
// Use getRealSize for Android 4.2+
|
||||
@SuppressWarnings("deprecation")
|
||||
Display display = windowManager.getDefaultDisplay();
|
||||
display.getRealSize(size);
|
||||
} else if (Build.VERSION.SDK_INT >= 14) {
|
||||
// Fallback for older versions
|
||||
@SuppressWarnings("deprecation")
|
||||
Display display = windowManager.getDefaultDisplay();
|
||||
try {
|
||||
size.x = (Integer) Display.class.getMethod("getRawWidth").invoke(display);
|
||||
size.y = (Integer) Display.class.getMethod("getRawHeight").invoke(display);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package top.niunaijun.blackboxa.util
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.core.content.pm.ShortcutInfoCompat
|
||||
import androidx.core.content.pm.ShortcutManagerCompat
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
import com.afollestad.materialdialogs.input.input
|
||||
import top.niunaijun.blackboxa.R
|
||||
import top.niunaijun.blackboxa.app.App
|
||||
import top.niunaijun.blackboxa.app.AppManager
|
||||
import top.niunaijun.blackboxa.bean.AppInfo
|
||||
import top.niunaijun.blackboxa.util.ContextUtil.openAppSystemSettings
|
||||
import top.niunaijun.blackboxa.view.main.ShortcutActivity
|
||||
|
||||
/**
|
||||
*
|
||||
* @Description: 桌面快捷方式 工具类
|
||||
* @Author: BlackBox
|
||||
* @CreateDate: 2022/2/27 22:56
|
||||
*/
|
||||
object ShortcutUtil {
|
||||
|
||||
|
||||
/**
|
||||
* 创建桌面快捷方式
|
||||
* @param userID Int userID
|
||||
* @param info AppInfo
|
||||
*/
|
||||
fun createShortcut(context: Context,userID: Int, info: AppInfo) {
|
||||
|
||||
if (ShortcutManagerCompat.isRequestPinShortcutSupported(context)) {
|
||||
val labelName = info.name + userID
|
||||
val intent = Intent(context, ShortcutActivity::class.java)
|
||||
.setAction(Intent.ACTION_MAIN)
|
||||
.putExtra("pkg", info.packageName)
|
||||
.putExtra("userId", userID)
|
||||
MaterialDialog(context).show {
|
||||
title(res = R.string.app_shortcut)
|
||||
input(
|
||||
hintRes = R.string.shortcut_name,
|
||||
prefill = labelName
|
||||
) { _, input ->
|
||||
|
||||
val shortcutInfo: ShortcutInfoCompat =
|
||||
ShortcutInfoCompat.Builder(context, info.packageName + userID)
|
||||
.setIntent(intent)
|
||||
.setShortLabel(input)
|
||||
.setLongLabel(input)
|
||||
.setIcon(IconCompat.createWithBitmap(info.icon!!.toBitmap()))
|
||||
.build()
|
||||
|
||||
ShortcutManagerCompat.requestPinShortcut(context, shortcutInfo, null)
|
||||
showAllowPermissionDialog(context)
|
||||
}
|
||||
positiveButton(R.string.done)
|
||||
negativeButton(R.string.cancel)
|
||||
}
|
||||
|
||||
} else {
|
||||
toast(R.string.cannot_create_shortcut)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showAllowPermissionDialog(context: Context){
|
||||
if (!AppManager.mBlackBoxLoader.showShortcutPermissionDialog()){
|
||||
return
|
||||
}
|
||||
|
||||
MaterialDialog(context).show {
|
||||
title(R.string.try_add_shortcut)
|
||||
message(R.string.add_shortcut_fail_msg)
|
||||
positiveButton(R.string.done)
|
||||
negativeButton(R.string.permission_setting){
|
||||
App.getContext().openAppSystemSettings()
|
||||
}
|
||||
|
||||
neutralButton(R.string.no_reminders){
|
||||
AppManager.mBlackBoxLoader.invalidShortcutPermissionDialog(false)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package top.niunaijun.blackboxa.util
|
||||
|
||||
import android.content.Context
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.StringRes
|
||||
import top.niunaijun.blackboxa.app.App
|
||||
|
||||
/**
|
||||
*
|
||||
* @Description:
|
||||
* @Author: wukaicheng
|
||||
* @CreateDate: 2021/5/2 0:13
|
||||
*/
|
||||
var toastImpl:Toast? = null
|
||||
|
||||
fun Context.toast(msg:String){
|
||||
toastImpl?.cancel()
|
||||
toastImpl = Toast.makeText(this,msg,Toast.LENGTH_SHORT)
|
||||
toastImpl?.show()
|
||||
}
|
||||
|
||||
fun toast(msg: String){
|
||||
App.getContext().toast(msg)
|
||||
}
|
||||
|
||||
fun toast(@StringRes msgID:Int){
|
||||
toast(getString(msgID))
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package top.niunaijun.blackboxa.util
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.Dialog
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.viewbinding.ViewBinding
|
||||
|
||||
/**
|
||||
*
|
||||
* @Description: viewBinding 扩展类
|
||||
* @Author: wukaicheng
|
||||
* @CreateDate: 2021/4/29 21:23
|
||||
*/
|
||||
|
||||
inline fun <reified T : ViewBinding> Activity.inflate(): Lazy<T> = lazy {
|
||||
inflateBinding(layoutInflater)
|
||||
}
|
||||
|
||||
inline fun <reified T : ViewBinding> Fragment.inflate(): Lazy<T> = lazy {
|
||||
inflateBinding(layoutInflater)
|
||||
}
|
||||
|
||||
inline fun <reified T : ViewBinding> Dialog.inflate(): Lazy<T> = lazy {
|
||||
inflateBinding(layoutInflater)
|
||||
}
|
||||
|
||||
|
||||
inline fun <reified T : ViewBinding> inflateBinding(layoutInflater: LayoutInflater): T {
|
||||
val method = T::class.java.getMethod("inflate", LayoutInflater::class.java)
|
||||
return method.invoke(null, layoutInflater) as T
|
||||
}
|
||||
|
||||
inline fun <reified T : ViewBinding> newBindingViewHolder(viewGroup: ViewGroup, attachToParent:Boolean = false): T {
|
||||
val method = T::class.java.getMethod("inflate",
|
||||
LayoutInflater::class.java,
|
||||
ViewGroup::class.java,
|
||||
Boolean::class.java)
|
||||
return method.invoke(null,LayoutInflater.from(viewGroup.context),viewGroup,attachToParent) as T
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package top.niunaijun.blackboxa.view.apps
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import cbfg.rvadapter.RVHolder
|
||||
import cbfg.rvadapter.RVHolderFactory
|
||||
import top.niunaijun.blackboxa.R
|
||||
import top.niunaijun.blackboxa.bean.AppInfo
|
||||
import top.niunaijun.blackboxa.databinding.ItemAppBinding
|
||||
|
||||
/**
|
||||
*
|
||||
* @Description: 软件显示界面适配器
|
||||
* @Author: wukaicheng
|
||||
* @CreateDate: 2021/4/29 21:52
|
||||
*/
|
||||
|
||||
class AppsAdapter : RVHolderFactory() {
|
||||
|
||||
override fun createViewHolder(parent: ViewGroup?, viewType: Int, item: Any): RVHolder<out Any> {
|
||||
return AppsVH(inflate(R.layout.item_app,parent))
|
||||
}
|
||||
|
||||
|
||||
class AppsVH(itemView:View):RVHolder<AppInfo>(itemView){
|
||||
|
||||
val binding = ItemAppBinding.bind(itemView)
|
||||
|
||||
override fun setContent(item: AppInfo, isSelected: Boolean, payload: Any?) {
|
||||
try {
|
||||
// Safely set the icon with null check
|
||||
if (item.icon != null) {
|
||||
binding.icon.setImageDrawable(item.icon)
|
||||
} else {
|
||||
// Set a default icon or clear the image view
|
||||
binding.icon.setImageDrawable(null)
|
||||
}
|
||||
|
||||
binding.name.text = item.name
|
||||
if(item.isXpModule){
|
||||
binding.cornerLabel.visibility = View.VISIBLE
|
||||
}else{
|
||||
binding.cornerLabel.visibility = View.INVISIBLE
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
// Log error and set safe defaults
|
||||
android.util.Log.e("AppsAdapter", "Error setting content for ${item.packageName}: ${e.message}")
|
||||
binding.icon.setImageDrawable(null)
|
||||
binding.name.text = item.name ?: "Unknown App"
|
||||
binding.cornerLabel.visibility = View.INVISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package top.niunaijun.blackboxa.view.apps
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import top.niunaijun.blackboxa.data.AppsRepository
|
||||
|
||||
/**
|
||||
*
|
||||
* @Description:
|
||||
* @Author: wukaicheng
|
||||
* @CreateDate: 2021/4/29 22:36
|
||||
*/
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
class AppsFactory(private val appsRepository: AppsRepository) : ViewModelProvider.NewInstanceFactory() {
|
||||
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
return AppsViewModel(appsRepository) as T
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,445 @@
|
||||
package top.niunaijun.blackboxa.view.apps
|
||||
|
||||
import android.graphics.Point
|
||||
import android.os.Bundle
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import cbfg.rvadapter.RVAdapter
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
import top.niunaijun.blackbox.BlackBoxCore
|
||||
import top.niunaijun.blackboxa.R
|
||||
import top.niunaijun.blackboxa.bean.AppInfo
|
||||
import top.niunaijun.blackboxa.databinding.FragmentAppsBinding
|
||||
import top.niunaijun.blackboxa.util.InjectionUtil
|
||||
import top.niunaijun.blackboxa.util.ShortcutUtil
|
||||
import top.niunaijun.blackboxa.util.inflate
|
||||
import top.niunaijun.blackboxa.util.toast
|
||||
import top.niunaijun.blackboxa.view.base.LoadingActivity
|
||||
import top.niunaijun.blackboxa.view.main.MainActivity
|
||||
import java.util.*
|
||||
import kotlin.math.abs
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @Description:
|
||||
* @Author: wukaicheng
|
||||
* @CreateDate: 2021/4/29 22:21
|
||||
*/
|
||||
class AppsFragment : Fragment() {
|
||||
|
||||
var userID: Int = 0
|
||||
|
||||
private lateinit var viewModel: AppsViewModel
|
||||
|
||||
private lateinit var mAdapter: RVAdapter<AppInfo>
|
||||
|
||||
private val viewBinding: FragmentAppsBinding by inflate()
|
||||
|
||||
private var popupMenu: PopupMenu? = null
|
||||
|
||||
companion object {
|
||||
private const val TAG = "AppsFragment"
|
||||
|
||||
fun newInstance(userID:Int): AppsFragment {
|
||||
val fragment = AppsFragment()
|
||||
val bundle = bundleOf("userID" to userID)
|
||||
fragment.arguments = bundle
|
||||
return fragment
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
try {
|
||||
super.onCreate(savedInstanceState)
|
||||
viewModel =
|
||||
ViewModelProvider(this, InjectionUtil.getAppsFactory()).get(AppsViewModel::class.java)
|
||||
userID = requireArguments().getInt("userID", 0)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error in onCreate: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
try {
|
||||
viewBinding.stateView.showEmpty()
|
||||
|
||||
mAdapter =
|
||||
RVAdapter<AppInfo>(requireContext(), AppsAdapter()).bind(viewBinding.recyclerView)
|
||||
|
||||
viewBinding.recyclerView.adapter = mAdapter
|
||||
viewBinding.recyclerView.layoutManager = GridLayoutManager(requireContext(), 4)
|
||||
|
||||
val touchCallBack = AppsTouchCallBack { from, to ->
|
||||
try {
|
||||
onItemMove(from, to)
|
||||
viewModel.updateSortLiveData.postValue(true)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error in touch callback: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
val itemTouchHelper = ItemTouchHelper(touchCallBack)
|
||||
itemTouchHelper.attachToRecyclerView(viewBinding.recyclerView)
|
||||
|
||||
mAdapter.setItemClickListener { _, data, _ ->
|
||||
try {
|
||||
showLoading()
|
||||
viewModel.launchApk(data.packageName, userID)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error launching app: ${e.message}")
|
||||
hideLoading()
|
||||
}
|
||||
}
|
||||
|
||||
interceptTouch()
|
||||
setOnLongClick()
|
||||
return viewBinding.root
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error in onCreateView: ${e.message}")
|
||||
// Return a simple view to prevent crash
|
||||
return View(requireContext())
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
try {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
initData()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error in onViewCreated: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
try {
|
||||
super.onStart()
|
||||
viewModel.getInstalledApps(userID)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error in onStart: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 拖拽优化
|
||||
*/
|
||||
private fun interceptTouch() {
|
||||
try {
|
||||
val point = Point()
|
||||
viewBinding.recyclerView.setOnTouchListener { v, e ->
|
||||
try {
|
||||
when (e.action) {
|
||||
MotionEvent.ACTION_UP -> {
|
||||
if (!isMove(point, e)) {
|
||||
popupMenu?.show()
|
||||
}
|
||||
popupMenu = null
|
||||
point.set(0, 0)
|
||||
}
|
||||
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
if (point.x == 0 && point.y == 0) {
|
||||
point.x = e.rawX.toInt()
|
||||
point.y = e.rawY.toInt()
|
||||
}
|
||||
isDownAndUp(point, e)
|
||||
|
||||
if (isMove(point, e)) {
|
||||
popupMenu?.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error in touch listener: ${e.message}")
|
||||
}
|
||||
return@setOnTouchListener false
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error in interceptTouch: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun isMove(point: Point, e: MotionEvent): Boolean {
|
||||
return try {
|
||||
val max = 40
|
||||
|
||||
val x = point.x
|
||||
val y = point.y
|
||||
|
||||
val xU = abs(x - e.rawX)
|
||||
val yU = abs(y - e.rawY)
|
||||
xU > max || yU > max
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error in isMove: ${e.message}")
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private fun isDownAndUp(point: Point, e: MotionEvent) {
|
||||
try {
|
||||
val min = 10
|
||||
val y = point.y
|
||||
val yU = y - e.rawY
|
||||
|
||||
if (abs(yU) > min) {
|
||||
(requireActivity() as? MainActivity)?.showFloatButton(yU < 0)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error in isDownAndUp: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun onItemMove(fromPosition:Int, toPosition:Int){
|
||||
try {
|
||||
if (fromPosition < toPosition) {
|
||||
for (i in fromPosition until toPosition) {
|
||||
Collections.swap(mAdapter.getItems(), i, i + 1)
|
||||
}
|
||||
} else {
|
||||
for (i in fromPosition downTo toPosition + 1) {
|
||||
Collections.swap(mAdapter.getItems(), i, i - 1)
|
||||
}
|
||||
}
|
||||
mAdapter.notifyItemMoved(fromPosition, toPosition)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error in onItemMove: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun setOnLongClick() {
|
||||
try {
|
||||
mAdapter.setItemLongClickListener { view, data, _ ->
|
||||
try {
|
||||
popupMenu = PopupMenu(requireContext(),view).also {
|
||||
it.inflate(R.menu.app_menu)
|
||||
it.setOnMenuItemClickListener { item ->
|
||||
try {
|
||||
when (item.itemId) {
|
||||
R.id.app_remove -> {
|
||||
if (data.isXpModule) {
|
||||
toast(R.string.uninstall_module_toast)
|
||||
} else {
|
||||
unInstallApk(data)
|
||||
}
|
||||
}
|
||||
|
||||
R.id.app_clear -> {
|
||||
clearApk(data)
|
||||
}
|
||||
|
||||
R.id.app_stop -> {
|
||||
stopApk(data)
|
||||
}
|
||||
|
||||
R.id.app_shortcut -> {
|
||||
ShortcutUtil.createShortcut(requireContext(), userID, data)
|
||||
}
|
||||
}
|
||||
return@setOnMenuItemClickListener true
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error in menu item click: ${e.message}")
|
||||
return@setOnMenuItemClickListener false
|
||||
}
|
||||
}
|
||||
it.show()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error in long click: ${e.message}")
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error in setOnLongClick: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun initData() {
|
||||
try {
|
||||
viewBinding.stateView.showLoading()
|
||||
viewModel.getInstalledApps(userID)
|
||||
viewModel.appsLiveData.observe(viewLifecycleOwner) {
|
||||
try {
|
||||
if (it != null) {
|
||||
mAdapter.setItems(it)
|
||||
if (it.isEmpty()) {
|
||||
viewBinding.stateView.showEmpty()
|
||||
} else {
|
||||
viewBinding.stateView.showContent()
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error observing apps data: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.resultLiveData.observe(viewLifecycleOwner) {
|
||||
try {
|
||||
if (!TextUtils.isEmpty(it)) {
|
||||
hideLoading()
|
||||
requireContext().toast(it)
|
||||
viewModel.getInstalledApps(userID)
|
||||
scanUser()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error observing result data: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.launchLiveData.observe(viewLifecycleOwner) {
|
||||
try {
|
||||
it?.run {
|
||||
hideLoading()
|
||||
if (!it) {
|
||||
toast(R.string.start_fail)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error observing launch data: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.updateSortLiveData.observe(viewLifecycleOwner) {
|
||||
try {
|
||||
if (this::mAdapter.isInitialized) {
|
||||
viewModel.updateApkOrder(userID, mAdapter.getItems())
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error observing sort data: ${e.message}")
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error in initData: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
try {
|
||||
super.onStop()
|
||||
viewModel.resultLiveData.value = null
|
||||
viewModel.launchLiveData.value = null
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error in onStop: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun unInstallApk(info: AppInfo) {
|
||||
try {
|
||||
MaterialDialog(requireContext()).show {
|
||||
title(R.string.uninstall_app)
|
||||
message(text = getString(R.string.uninstall_app_hint, info.name))
|
||||
positiveButton(R.string.done) {
|
||||
try {
|
||||
showLoading()
|
||||
viewModel.unInstall(info.packageName, userID)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error uninstalling app: ${e.message}")
|
||||
hideLoading()
|
||||
}
|
||||
}
|
||||
negativeButton(R.string.cancel)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error showing uninstall dialog: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 强行停止软件
|
||||
* @param info AppInfo
|
||||
*/
|
||||
private fun stopApk(info: AppInfo) {
|
||||
try {
|
||||
MaterialDialog(requireContext()).show {
|
||||
title(R.string.app_stop)
|
||||
message(text = getString(R.string.app_stop_hint,info.name))
|
||||
positiveButton(R.string.done) {
|
||||
try {
|
||||
BlackBoxCore.get().stopPackage(info.packageName, userID)
|
||||
toast(getString(R.string.is_stop,info.name))
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error stopping app: ${e.message}")
|
||||
}
|
||||
}
|
||||
negativeButton(R.string.cancel)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error showing stop dialog: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除软件数据
|
||||
* @param info AppInfo
|
||||
*/
|
||||
private fun clearApk(info: AppInfo) {
|
||||
try {
|
||||
MaterialDialog(requireContext()).show {
|
||||
title(R.string.app_clear)
|
||||
message(text = getString(R.string.app_clear_hint,info.name))
|
||||
positiveButton(R.string.done) {
|
||||
try {
|
||||
showLoading()
|
||||
viewModel.clearApkData(info.packageName, userID)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error clearing app data: ${e.message}")
|
||||
hideLoading()
|
||||
}
|
||||
}
|
||||
negativeButton(R.string.cancel)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error showing clear dialog: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
fun installApk(source: String) {
|
||||
try {
|
||||
showLoading()
|
||||
viewModel.install(source, userID)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error installing APK: ${e.message}")
|
||||
hideLoading()
|
||||
}
|
||||
}
|
||||
|
||||
private fun scanUser() {
|
||||
try {
|
||||
(requireActivity() as? MainActivity)?.scanUser()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error scanning user: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun showLoading() {
|
||||
try {
|
||||
if(requireActivity() is LoadingActivity){
|
||||
(requireActivity() as LoadingActivity).showLoading()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error showing loading: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun hideLoading() {
|
||||
try {
|
||||
if(requireActivity() is LoadingActivity){
|
||||
(requireActivity() as LoadingActivity).hideLoading()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error hiding loading: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package top.niunaijun.blackboxa.view.apps
|
||||
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
||||
|
||||
class AppsTouchCallBack(private val onMoveBlock: (from: Int, to: Int) -> Unit) :
|
||||
ItemTouchHelper.Callback() {
|
||||
|
||||
|
||||
override fun getMovementFlags(
|
||||
recyclerView: RecyclerView,
|
||||
viewHolder: RecyclerView.ViewHolder
|
||||
): Int {
|
||||
return makeMovementFlags(ItemTouchHelper.UP or ItemTouchHelper.DOWN or ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT, 0)
|
||||
|
||||
}
|
||||
|
||||
|
||||
override fun onMove(
|
||||
recyclerView: RecyclerView,
|
||||
viewHolder: RecyclerView.ViewHolder,
|
||||
target: RecyclerView.ViewHolder
|
||||
): Boolean {
|
||||
val fromPosition = viewHolder.bindingAdapterPosition
|
||||
val toPosition = target.bindingAdapterPosition
|
||||
onMoveBlock(fromPosition, toPosition)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package top.niunaijun.blackboxa.view.apps
|
||||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import top.niunaijun.blackboxa.bean.AppInfo
|
||||
import top.niunaijun.blackboxa.data.AppsRepository
|
||||
import top.niunaijun.blackboxa.view.base.BaseViewModel
|
||||
|
||||
/**
|
||||
*
|
||||
* @Description:
|
||||
* @Author: wukaicheng
|
||||
* @CreateDate: 2021/4/29 22:36
|
||||
*/
|
||||
class AppsViewModel(private val repo: AppsRepository) : BaseViewModel() {
|
||||
|
||||
val appsLiveData = MutableLiveData<List<AppInfo>>()
|
||||
|
||||
val resultLiveData = MutableLiveData<String>()
|
||||
|
||||
val launchLiveData = MutableLiveData<Boolean>()
|
||||
|
||||
//利用LiveData只更新最后一次的特性,用来保存app顺序
|
||||
val updateSortLiveData = MutableLiveData<Boolean>()
|
||||
|
||||
fun getInstalledApps(userId: Int) {
|
||||
launchOnUI {
|
||||
repo.getVmInstallList(userId, appsLiveData)
|
||||
}
|
||||
}
|
||||
|
||||
fun install(source: String, userID: Int) {
|
||||
launchOnUI {
|
||||
repo.installApk(source, userID, resultLiveData)
|
||||
}
|
||||
}
|
||||
|
||||
fun unInstall(packageName: String, userID: Int) {
|
||||
launchOnUI {
|
||||
repo.unInstall(packageName, userID, resultLiveData)
|
||||
}
|
||||
}
|
||||
|
||||
fun clearApkData(packageName: String,userID: Int){
|
||||
launchOnUI {
|
||||
repo.clearApkData(packageName,userID,resultLiveData)
|
||||
}
|
||||
}
|
||||
|
||||
fun launchApk(packageName: String, userID: Int) {
|
||||
launchOnUI {
|
||||
repo.launchApk(packageName, userID, launchLiveData)
|
||||
}
|
||||
}
|
||||
|
||||
fun updateApkOrder(userID: Int,dataList:List<AppInfo>){
|
||||
launchOnUI {
|
||||
repo.updateApkOrder(userID,dataList)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package top.niunaijun.blackboxa.view.base
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
|
||||
/**
|
||||
*
|
||||
* @Description:BaseActivity
|
||||
* @Author: wukaicheng
|
||||
* @CreateDate: 2021/5/4 15:58
|
||||
*/
|
||||
open class BaseActivity : AppCompatActivity() {
|
||||
|
||||
protected fun initToolbar(toolbar: Toolbar,title:Int, showBack: Boolean = false, onBack: (() -> Unit)? = null) {
|
||||
setSupportActionBar(toolbar)
|
||||
toolbar.setTitle(title)
|
||||
if (showBack) {
|
||||
supportActionBar?.let {
|
||||
it.setDisplayHomeAsUpEnabled(true)
|
||||
toolbar.setNavigationOnClickListener {
|
||||
if (onBack != null) {
|
||||
onBack()
|
||||
}
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected fun currentUserID():Int{
|
||||
return intent.getIntExtra("userID", 0)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package top.niunaijun.blackboxa.view.base
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.*
|
||||
|
||||
/**
|
||||
*
|
||||
* @Description:
|
||||
* @Author: wukaicheng
|
||||
* @CreateDate: 2021/4/29 23:33
|
||||
*/
|
||||
open class BaseViewModel : ViewModel() {
|
||||
|
||||
fun launchOnUI(block: suspend CoroutineScope.() -> Unit) {
|
||||
viewModelScope.launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
try {
|
||||
block()
|
||||
} catch (e: Throwable) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
viewModelScope.cancel()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package top.niunaijun.blackboxa.view.base
|
||||
|
||||
import android.view.KeyEvent
|
||||
import com.roger.catloadinglibrary.CatLoadingView
|
||||
import top.niunaijun.blackboxa.R
|
||||
|
||||
/**
|
||||
*
|
||||
* @Description: loading activity
|
||||
* @Author: BlackBox
|
||||
* @CreateDate: 2022/3/2 21:49
|
||||
*/
|
||||
abstract class LoadingActivity : BaseActivity() {
|
||||
|
||||
private lateinit var loadingView: CatLoadingView
|
||||
|
||||
|
||||
fun showLoading() {
|
||||
if (!this::loadingView.isInitialized) {
|
||||
loadingView = CatLoadingView()
|
||||
}
|
||||
|
||||
if (!loadingView.isAdded) {
|
||||
loadingView.setBackgroundColor(R.color.primary)
|
||||
loadingView.show(supportFragmentManager, "")
|
||||
supportFragmentManager.executePendingTransactions()
|
||||
loadingView.setClickCancelAble(false)
|
||||
loadingView.dialog?.setOnKeyListener { _, keyCode, _ ->
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE) {
|
||||
return@setOnKeyListener true
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun hideLoading() {
|
||||
if (this::loadingView.isInitialized) {
|
||||
loadingView.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package top.niunaijun.blackboxa.view.fake
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import cbfg.rvadapter.RVHolder
|
||||
import cbfg.rvadapter.RVHolderFactory
|
||||
import top.niunaijun.blackbox.fake.frameworks.BLocationManager
|
||||
import top.niunaijun.blackboxa.R
|
||||
import top.niunaijun.blackboxa.bean.FakeLocationBean
|
||||
import top.niunaijun.blackboxa.databinding.ItemFakeBinding
|
||||
import top.niunaijun.blackboxa.util.getString
|
||||
|
||||
/**
|
||||
*
|
||||
* @Description: 软件显示界面适配器
|
||||
* @Author: BlackBoxing
|
||||
* @CreateDate: 2022/3/14
|
||||
*/
|
||||
|
||||
class FakeLocationAdapter : RVHolderFactory() {
|
||||
|
||||
override fun createViewHolder(parent: ViewGroup?, viewType: Int, item: Any): RVHolder<out Any> {
|
||||
return FakeLocationVH(inflate(R.layout.item_fake,parent))
|
||||
}
|
||||
|
||||
class FakeLocationVH(itemView:View):RVHolder<FakeLocationBean>(itemView){
|
||||
|
||||
private val binding = ItemFakeBinding.bind(itemView)
|
||||
|
||||
override fun setContent(item: FakeLocationBean, isSelected: Boolean, payload: Any?) {
|
||||
binding.icon.setImageDrawable(item.icon)
|
||||
binding.name.text = item.name
|
||||
if (item.fakeLocation == null || item.fakeLocationPattern == BLocationManager.CLOSE_MODE) {
|
||||
binding.fakeLocation.text = getString(R.string.real_location)
|
||||
} else {
|
||||
binding.fakeLocation.text =
|
||||
String.format("%f, %f", item.fakeLocation!!.latitude, item.fakeLocation!!.longitude)
|
||||
}
|
||||
binding.cornerLabel.visibility = View.VISIBLE
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package top.niunaijun.blackboxa.view.fake
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import top.niunaijun.blackboxa.data.FakeLocationRepository
|
||||
|
||||
/**
|
||||
*
|
||||
* @Author: BlackBoxing
|
||||
* @CreateDate: 2022/3/14
|
||||
*/
|
||||
class FakeLocationFactory(private val repo: FakeLocationRepository) :
|
||||
ViewModelProvider.NewInstanceFactory() {
|
||||
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
return FakeLocationViewModel(repo) as T
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package top.niunaijun.blackboxa.view.fake
|
||||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import top.niunaijun.blackbox.entity.location.BLocation
|
||||
import top.niunaijun.blackboxa.bean.FakeLocationBean
|
||||
import top.niunaijun.blackboxa.data.FakeLocationRepository
|
||||
import top.niunaijun.blackboxa.view.base.BaseViewModel
|
||||
|
||||
/**
|
||||
*
|
||||
* @Author: BlackBoxing
|
||||
* @CreateDate: 2022/3/14
|
||||
*/
|
||||
class FakeLocationViewModel(private val mRepo: FakeLocationRepository) : BaseViewModel() {
|
||||
|
||||
val appsLiveData = MutableLiveData<List<FakeLocationBean>>()
|
||||
|
||||
|
||||
fun getInstallAppList(userID: Int) {
|
||||
launchOnUI {
|
||||
mRepo.getInstalledAppList(userID, appsLiveData)
|
||||
}
|
||||
}
|
||||
|
||||
fun setPattern(userId: Int, pkg: String, pattern: Int) {
|
||||
launchOnUI {
|
||||
mRepo.setPattern(userId, pkg, pattern)
|
||||
}
|
||||
}
|
||||
|
||||
fun setLocation(userId: Int, pkg: String, location: BLocation) {
|
||||
launchOnUI {
|
||||
mRepo.setLocation(userId, pkg, location)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
package top.niunaijun.blackboxa.view.fake
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import cbfg.rvadapter.RVAdapter
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
import com.ferfalk.simplesearchview.SimpleSearchView
|
||||
import top.niunaijun.blackbox.entity.location.BLocation
|
||||
import top.niunaijun.blackbox.fake.frameworks.BLocationManager
|
||||
import top.niunaijun.blackboxa.R
|
||||
import top.niunaijun.blackboxa.bean.FakeLocationBean
|
||||
import top.niunaijun.blackboxa.databinding.ActivityListBinding
|
||||
import top.niunaijun.blackboxa.util.InjectionUtil
|
||||
import top.niunaijun.blackboxa.util.inflate
|
||||
import top.niunaijun.blackboxa.util.toast
|
||||
import top.niunaijun.blackboxa.view.base.BaseActivity
|
||||
|
||||
/**
|
||||
*
|
||||
* @Author: BlackBoxing
|
||||
* @CreateDate: 2022/3/14
|
||||
*/
|
||||
class FakeManagerActivity : BaseActivity() {
|
||||
val TAG: String = "FakeManagerActivity"
|
||||
|
||||
private val viewBinding: ActivityListBinding by inflate()
|
||||
|
||||
// private lateinit var mAdapter: ListAdapter
|
||||
private lateinit var mAdapter: RVAdapter<FakeLocationBean>
|
||||
|
||||
private lateinit var viewModel: FakeLocationViewModel
|
||||
|
||||
private var appList: List<FakeLocationBean> = ArrayList()
|
||||
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(viewBinding.root)
|
||||
|
||||
initToolbar(viewBinding.toolbarLayout.toolbar, R.string.fake_location, true)
|
||||
|
||||
mAdapter = RVAdapter<FakeLocationBean>(this,FakeLocationAdapter()).bind(viewBinding.recyclerView)
|
||||
.setItemClickListener { _, data, _ ->
|
||||
|
||||
val intent = Intent(this, FollowMyLocationOverlay::class.java)
|
||||
intent.putExtra("location", data.fakeLocation)
|
||||
intent.putExtra("pkg", data.packageName)
|
||||
|
||||
locationResult.launch(intent)
|
||||
}.setItemLongClickListener { _, item, position ->
|
||||
disableFakeLocation(item,position)
|
||||
}
|
||||
|
||||
viewBinding.recyclerView.layoutManager = LinearLayoutManager(this)
|
||||
|
||||
|
||||
initSearchView()
|
||||
initViewModel()
|
||||
}
|
||||
|
||||
private fun disableFakeLocation(item: FakeLocationBean,position:Int) {
|
||||
MaterialDialog(this).show {
|
||||
title(R.string.close_fake_location)
|
||||
message(text = getString(R.string.close_app_fake_location,item.name))
|
||||
negativeButton(R.string.cancel)
|
||||
positiveButton(R.string.done){
|
||||
BLocationManager.disableFakeLocation(currentUserID(),item.packageName)
|
||||
toast(getString(R.string.close_fake_location_success,item.name))
|
||||
item.fakeLocationPattern = BLocationManager.CLOSE_MODE
|
||||
mAdapter.replaceAt(position,item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun initSearchView() {
|
||||
viewBinding.searchView.setOnQueryTextListener(object :
|
||||
SimpleSearchView.OnQueryTextListener {
|
||||
override fun onQueryTextChange(newText: String): Boolean {
|
||||
filterApp(newText)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onQueryTextCleared(): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onQueryTextSubmit(query: String): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
private fun initViewModel() {
|
||||
viewModel = ViewModelProvider(this, InjectionUtil.getFakeLocationFactory()).get(
|
||||
FakeLocationViewModel::class.java
|
||||
)
|
||||
loadAppList()
|
||||
viewBinding.toolbarLayout.toolbar.setTitle(R.string.fake_location)
|
||||
|
||||
viewModel.appsLiveData.observe(this) {
|
||||
if (it != null) {
|
||||
this.appList = it
|
||||
viewBinding.searchView.setQuery("", false)
|
||||
filterApp("")
|
||||
if (it.isNotEmpty()) {
|
||||
viewBinding.stateView.showContent()
|
||||
} else {
|
||||
viewBinding.stateView.showEmpty()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadAppList() {
|
||||
viewBinding.stateView.showLoading()
|
||||
viewModel.getInstallAppList(currentUserID())
|
||||
}
|
||||
|
||||
private val locationResult =
|
||||
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
if (it.resultCode == RESULT_OK) {
|
||||
|
||||
it.data?.let { data ->
|
||||
val latitude = data.getDoubleExtra("latitude", 0.0)
|
||||
val longitude = data.getDoubleExtra("longitude", 0.0)
|
||||
val pkg = data.getStringExtra("pkg")
|
||||
|
||||
viewModel.setPattern(currentUserID(), pkg.toString(), BLocationManager.OWN_MODE)
|
||||
viewModel.setLocation(currentUserID(), pkg.toString(), BLocation(latitude, longitude))
|
||||
|
||||
toast(getString(R.string.set_location,latitude.toString(), longitude.toString()))
|
||||
|
||||
loadAppList()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun filterApp(newText: String) {
|
||||
val newList = this.appList.filter {
|
||||
it.name.contains(newText, true) or it.packageName.contains(newText, true)
|
||||
}
|
||||
mAdapter.setItems(newList)
|
||||
}
|
||||
|
||||
private fun finishWithResult(source: String) {
|
||||
intent.putExtra("source", source)
|
||||
setResult(Activity.RESULT_OK, intent)
|
||||
val imm: InputMethodManager = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
window.peekDecorView()?.run {
|
||||
imm.hideSoftInputFromWindow(windowToken, 0)
|
||||
}
|
||||
finish()
|
||||
}
|
||||
|
||||
|
||||
override fun onBackPressed() {
|
||||
if (viewBinding.searchView.isSearchOpen) {
|
||||
viewBinding.searchView.closeSearch()
|
||||
} else {
|
||||
super.onBackPressed()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||
menuInflater.inflate(R.menu.menu_search, menu)
|
||||
val item = menu!!.findItem(R.id.list_search)
|
||||
viewBinding.searchView.setMenuItem(item)
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
fun start(context: Context) {
|
||||
val intent = Intent(context, FakeManagerActivity::class.java)
|
||||
context.startActivity(intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
package top.niunaijun.blackboxa.view.fake
|
||||
|
||||
|
||||
import android.app.Activity
|
||||
import android.os.Bundle
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.preference.PreferenceManager
|
||||
import org.osmdroid.config.Configuration
|
||||
import org.osmdroid.events.MapEventsReceiver
|
||||
import org.osmdroid.tileprovider.tilesource.TileSourceFactory
|
||||
import org.osmdroid.util.GeoPoint
|
||||
import org.osmdroid.views.overlay.MapEventsOverlay
|
||||
import org.osmdroid.views.overlay.Marker
|
||||
import top.niunaijun.blackbox.entity.location.BLocation
|
||||
import top.niunaijun.blackboxa.databinding.ActivityOsmdroidBinding
|
||||
import top.niunaijun.blackboxa.util.inflate
|
||||
import top.niunaijun.blackboxa.util.toast
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @Author: BlackBoxing
|
||||
* @CreateDate: 2022/3/14
|
||||
*/
|
||||
class FollowMyLocationOverlay : AppCompatActivity() {
|
||||
val TAG: String = "FollowMyLocationOverlay"
|
||||
|
||||
private val REQUEST_PERMISSIONS_REQUEST_CODE = 1
|
||||
|
||||
private val binding: ActivityOsmdroidBinding by inflate()
|
||||
|
||||
lateinit var startPoint: GeoPoint
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
//handle permissions first, before map is created. not depicted here
|
||||
|
||||
//load/initialize the osmdroid configuration, this can be done
|
||||
// This won't work unless you have imported this: org.osmdroid.config.Configuration.*
|
||||
Configuration.getInstance().load(this, PreferenceManager.getDefaultSharedPreferences(this))
|
||||
//setting this before the layout is inflated is a good idea
|
||||
//it 'should' ensure that the map has a writable location for the map cache, even without permissions
|
||||
//if no tiles are displayed, you can try overriding the cache path using Configuration.getInstance().setCachePath
|
||||
//see also StorageUtils
|
||||
//note, the load method also sets the HTTP User Agent to your application's package name, if you abuse osm's
|
||||
//tile servers will get you banned based on this string.
|
||||
|
||||
//inflate and create the map
|
||||
setContentView(binding.root)
|
||||
|
||||
val location: BLocation? = intent.getParcelableExtra("location")
|
||||
|
||||
startPoint = if (location == null) {
|
||||
GeoPoint(30.2736, 120.1563)
|
||||
} else {
|
||||
GeoPoint(location.latitude, location.longitude)
|
||||
}
|
||||
|
||||
|
||||
val startMarker = Marker(binding.map)
|
||||
startMarker.position = startPoint
|
||||
startMarker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM)
|
||||
|
||||
binding.map.overlays.add(startMarker)
|
||||
val mReceive: MapEventsReceiver = object : MapEventsReceiver {
|
||||
override fun singleTapConfirmedHelper(p: GeoPoint): Boolean {
|
||||
startPoint = p
|
||||
startMarker.position = p
|
||||
startMarker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM)
|
||||
binding.map.overlays.add(startMarker)
|
||||
toast(p.latitude.toString() + " - " + p.longitude)
|
||||
return false
|
||||
}
|
||||
|
||||
override fun longPressHelper(p: GeoPoint): Boolean {
|
||||
return false
|
||||
}
|
||||
}
|
||||
binding.map.overlays.add(MapEventsOverlay(mReceive))
|
||||
val mapController = binding.map.controller
|
||||
mapController.setZoom(12.5)
|
||||
// val startPoint = GeoPoint(30.2736, 120.1563)
|
||||
mapController.setCenter(startPoint)
|
||||
binding.map.setTileSource(TileSourceFactory.MAPNIK)
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
finishWithResult(startPoint)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
//this will refresh the osmdroid configuration on resuming.
|
||||
//if you make changes to the configuration, use
|
||||
//SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
//Configuration.getInstance().load(this, PreferenceManager.getDefaultSharedPreferences(this))
|
||||
binding.map.onResume() //needed for compass, my location overlays, v6.0.0 and up
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
//this will refresh the osmdroid configuration on resuming.
|
||||
//if you make changes to the configuration, use
|
||||
//SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
//Configuration.getInstance().save(this, prefs)
|
||||
binding.map.onPause() //needed for compass, my location overlays, v6.0.0 and up
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(
|
||||
requestCode: Int,
|
||||
permissions: Array<out String>,
|
||||
grantResults: IntArray
|
||||
) {
|
||||
val permissionsToRequest = ArrayList<String>()
|
||||
var i = 0
|
||||
while (i < grantResults.size) {
|
||||
permissionsToRequest.add(permissions[i])
|
||||
i++
|
||||
}
|
||||
if (permissionsToRequest.size > 0) {
|
||||
ActivityCompat.requestPermissions(
|
||||
this,
|
||||
permissionsToRequest.toTypedArray(),
|
||||
REQUEST_PERMISSIONS_REQUEST_CODE
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun finishWithResult(geoPoint: GeoPoint) {
|
||||
intent.putExtra("latitude", geoPoint.latitude)
|
||||
intent.putExtra("longitude", geoPoint.longitude)
|
||||
setResult(Activity.RESULT_OK, intent)
|
||||
val imm: InputMethodManager = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
window.peekDecorView()?.run {
|
||||
imm.hideSoftInputFromWindow(windowToken, 0)
|
||||
}
|
||||
finish()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package top.niunaijun.blackboxa.view.gms
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import cbfg.rvadapter.RVHolder
|
||||
import cbfg.rvadapter.RVHolderFactory
|
||||
import top.niunaijun.blackboxa.R
|
||||
import top.niunaijun.blackboxa.bean.GmsBean
|
||||
import top.niunaijun.blackboxa.databinding.ItemGmsBinding
|
||||
|
||||
/**
|
||||
*
|
||||
* @Description:
|
||||
* @Author: BlackBox
|
||||
* @CreateDate: 2022/3/2 21:13
|
||||
*/
|
||||
class GmsAdapter : RVHolderFactory() {
|
||||
|
||||
override fun createViewHolder(parent: ViewGroup?, viewType: Int, item: Any): RVHolder<out Any> {
|
||||
return GmsVH(inflate(R.layout.item_gms,parent))
|
||||
}
|
||||
|
||||
|
||||
class GmsVH(itemView:View):RVHolder<GmsBean>(itemView){
|
||||
|
||||
private val binding = ItemGmsBinding.bind(itemView)
|
||||
override fun setContent(item: GmsBean, isSelected: Boolean, payload: Any?) {
|
||||
binding.tvTitle.text = item.userName
|
||||
binding.checkbox.isChecked = item.isInstalledGms
|
||||
binding.checkbox.setOnCheckedChangeListener { buttonView, _ ->
|
||||
if(buttonView.isPressed){
|
||||
binding.root.performClick()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package top.niunaijun.blackboxa.view.gms
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import top.niunaijun.blackboxa.data.GmsRepository
|
||||
|
||||
/**
|
||||
*
|
||||
* @Description:
|
||||
* @Author: BlackBox
|
||||
* @CreateDate: 2022/3/2 21:15
|
||||
*/
|
||||
class GmsFactory(private val repo:GmsRepository): ViewModelProvider.NewInstanceFactory() {
|
||||
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
return GmsViewModel(repo) as T
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package top.niunaijun.blackboxa.view.gms
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.widget.Switch
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import cbfg.rvadapter.RVAdapter
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
import top.niunaijun.blackboxa.R
|
||||
import top.niunaijun.blackboxa.bean.GmsBean
|
||||
import top.niunaijun.blackboxa.databinding.ActivityGmsBinding
|
||||
import top.niunaijun.blackboxa.util.InjectionUtil
|
||||
import top.niunaijun.blackboxa.util.inflate
|
||||
import top.niunaijun.blackboxa.util.toast
|
||||
import top.niunaijun.blackboxa.view.base.LoadingActivity
|
||||
|
||||
/**
|
||||
*
|
||||
* @Description: gms manager activity
|
||||
* @Author: BlackBox
|
||||
* @CreateDate: 2022/3/2 21:06
|
||||
*/
|
||||
class GmsManagerActivity : LoadingActivity() {
|
||||
|
||||
private lateinit var viewModel: GmsViewModel
|
||||
|
||||
private lateinit var mAdapter: RVAdapter<GmsBean>
|
||||
|
||||
private val viewBinding: ActivityGmsBinding by inflate()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(viewBinding.root)
|
||||
initToolbar(viewBinding.toolbarLayout.toolbar, R.string.gms_manager, true)
|
||||
initViewModel()
|
||||
|
||||
initRecyclerView()
|
||||
}
|
||||
|
||||
private fun initViewModel() {
|
||||
viewModel = ViewModelProvider(this, InjectionUtil.getGmsFactory())[GmsViewModel::class.java]
|
||||
showLoading()
|
||||
|
||||
viewModel.mInstalledLiveData.observe(this) {
|
||||
hideLoading()
|
||||
mAdapter.setItems(it)
|
||||
}
|
||||
|
||||
viewModel.mUpdateInstalledLiveData.observe(this) { result ->
|
||||
if (result == null) {
|
||||
return@observe
|
||||
}
|
||||
|
||||
val items = mAdapter.getItems()
|
||||
for (index in items.indices) {
|
||||
val bean = items[index]
|
||||
if (bean.userID == result.userID) {
|
||||
if (result.success) {
|
||||
bean.isInstalledGms = !bean.isInstalledGms
|
||||
}
|
||||
mAdapter.replaceAt( index,bean)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
hideLoading()
|
||||
|
||||
if (result.success) {
|
||||
toast(result.msg)
|
||||
} else {
|
||||
MaterialDialog(this).show {
|
||||
title(R.string.gms_manager)
|
||||
message(text = result.msg)
|
||||
positiveButton(R.string.done)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.getInstalledUser()
|
||||
}
|
||||
|
||||
private fun initRecyclerView() {
|
||||
mAdapter = RVAdapter<GmsBean>(this, GmsAdapter()).bind(viewBinding.recyclerView)
|
||||
.setItemClickListener { view, item, _ ->
|
||||
val checkbox = view.findViewById<Switch>(R.id.checkbox)
|
||||
if (item.isInstalledGms) {
|
||||
uninstallGms(item.userID, checkbox)
|
||||
} else {
|
||||
installGms(item.userID, checkbox)
|
||||
}
|
||||
}
|
||||
viewBinding.recyclerView.layoutManager = LinearLayoutManager(this)
|
||||
|
||||
}
|
||||
|
||||
private fun installGms(userID: Int, checkbox: Switch){
|
||||
MaterialDialog(this).show {
|
||||
title(R.string.enable_gms)
|
||||
message(R.string.enable_gms_hint)
|
||||
positiveButton(R.string.done){
|
||||
showLoading()
|
||||
viewModel.installGms(userID)
|
||||
}
|
||||
negativeButton(R.string.cancel){
|
||||
checkbox.isChecked = !checkbox.isChecked
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun uninstallGms(userID: Int, checkbox: Switch){
|
||||
MaterialDialog(this).show {
|
||||
title(R.string.disable_gms)
|
||||
message(R.string.disable_gms_hint)
|
||||
positiveButton(R.string.done){
|
||||
showLoading()
|
||||
viewModel.uninstallGms(userID)
|
||||
}
|
||||
negativeButton(R.string.cancel){
|
||||
checkbox.isChecked = !checkbox.isChecked
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
companion object{
|
||||
fun start(context: Context){
|
||||
val intent = Intent(context,GmsManagerActivity::class.java)
|
||||
context.startActivity(intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package top.niunaijun.blackboxa.view.gms
|
||||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import top.niunaijun.blackboxa.bean.GmsBean
|
||||
import top.niunaijun.blackboxa.bean.GmsInstallBean
|
||||
import top.niunaijun.blackboxa.data.GmsRepository
|
||||
import top.niunaijun.blackboxa.view.base.BaseViewModel
|
||||
|
||||
/**
|
||||
*
|
||||
* @Description: gms viewModel
|
||||
* @Author: BlackBox
|
||||
* @CreateDate: 2022/3/2 21:11
|
||||
*/
|
||||
class GmsViewModel(private val mRepo: GmsRepository) : BaseViewModel() {
|
||||
|
||||
val mInstalledLiveData = MutableLiveData<List<GmsBean>>()
|
||||
|
||||
val mUpdateInstalledLiveData = MutableLiveData<GmsInstallBean>()
|
||||
|
||||
fun getInstalledUser() {
|
||||
launchOnUI {
|
||||
mRepo.getGmsInstalledList(mInstalledLiveData)
|
||||
}
|
||||
}
|
||||
|
||||
fun installGms(userID: Int) {
|
||||
launchOnUI {
|
||||
mRepo.installGms(userID,mUpdateInstalledLiveData)
|
||||
}
|
||||
}
|
||||
|
||||
fun uninstallGms(userID: Int) {
|
||||
launchOnUI {
|
||||
mRepo.uninstallGms(userID,mUpdateInstalledLiveData)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
package top.niunaijun.blackboxa.view.list
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import cbfg.rvadapter.RVAdapter
|
||||
import com.ferfalk.simplesearchview.SimpleSearchView
|
||||
import top.niunaijun.blackboxa.R
|
||||
import top.niunaijun.blackboxa.bean.InstalledAppBean
|
||||
import top.niunaijun.blackboxa.databinding.ActivityListBinding
|
||||
import top.niunaijun.blackboxa.util.InjectionUtil
|
||||
import top.niunaijun.blackboxa.util.inflate
|
||||
import top.niunaijun.blackboxa.view.base.BaseActivity
|
||||
|
||||
|
||||
class ListActivity : BaseActivity() {
|
||||
|
||||
private val viewBinding: ActivityListBinding by inflate()
|
||||
|
||||
private lateinit var mAdapter: RVAdapter<InstalledAppBean>
|
||||
|
||||
private lateinit var viewModel: ListViewModel
|
||||
|
||||
private var appList: List<InstalledAppBean> = ArrayList()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(viewBinding.root)
|
||||
|
||||
initToolbar(viewBinding.toolbarLayout.toolbar, R.string.installed_app, true)
|
||||
|
||||
mAdapter = RVAdapter<InstalledAppBean>(this,ListAdapter()).bind(viewBinding.recyclerView).setItemClickListener { _, item, _ ->
|
||||
finishWithResult(item.packageName)
|
||||
}
|
||||
|
||||
viewBinding.recyclerView.layoutManager = LinearLayoutManager(this)
|
||||
|
||||
|
||||
initSearchView()
|
||||
initViewModel()
|
||||
}
|
||||
|
||||
private fun initSearchView() {
|
||||
viewBinding.searchView.setOnQueryTextListener(object : SimpleSearchView.OnQueryTextListener {
|
||||
override fun onQueryTextChange(newText: String): Boolean {
|
||||
filterApp(newText)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onQueryTextCleared(): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onQueryTextSubmit(query: String): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
private fun initViewModel() {
|
||||
viewModel = ViewModelProvider(this, InjectionUtil.getListFactory()).get(ListViewModel::class.java)
|
||||
val onlyShowXp = intent.getBooleanExtra("onlyShowXp", false)
|
||||
val userID = intent.getIntExtra("userID",0)
|
||||
|
||||
if (onlyShowXp) {
|
||||
viewModel.getInstalledModules()
|
||||
viewBinding.toolbarLayout.toolbar.setTitle(R.string.installed_module)
|
||||
} else {
|
||||
viewModel.getInstallAppList(userID)
|
||||
viewBinding.toolbarLayout.toolbar.setTitle(R.string.installed_app)
|
||||
}
|
||||
|
||||
viewModel.loadingLiveData.observe(this) {
|
||||
if (it) {
|
||||
viewBinding.stateView.showLoading()
|
||||
} else {
|
||||
viewBinding.stateView.showContent()
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.appsLiveData.observe(this) {
|
||||
if (it != null) {
|
||||
this.appList = it
|
||||
viewBinding.searchView.setQuery("", false)
|
||||
filterApp("")
|
||||
if (it.isNotEmpty()) {
|
||||
viewBinding.stateView.showContent()
|
||||
viewModel.previewInstalledList()
|
||||
} else {
|
||||
viewBinding.stateView.showEmpty()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun filterApp(newText: String) {
|
||||
val newList = this.appList.filter {
|
||||
it.name.contains(newText, true) or it.packageName.contains(newText, true)
|
||||
}
|
||||
mAdapter.setItems(newList)
|
||||
}
|
||||
|
||||
private val openDocumentedResult = registerForActivityResult(ActivityResultContracts.GetContent()) {
|
||||
it?.run {
|
||||
finishWithResult(it.toString())
|
||||
}
|
||||
}
|
||||
|
||||
private fun finishWithResult(source: String) {
|
||||
intent.putExtra("source", source)
|
||||
setResult(Activity.RESULT_OK, intent)
|
||||
val imm: InputMethodManager = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
window.peekDecorView()?.run {
|
||||
imm.hideSoftInputFromWindow(windowToken, 0)
|
||||
}
|
||||
finish()
|
||||
}
|
||||
|
||||
|
||||
override fun onBackPressed() {
|
||||
if (viewBinding.searchView.isSearchOpen) {
|
||||
viewBinding.searchView.closeSearch()
|
||||
} else {
|
||||
super.onBackPressed()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
if (item.itemId == R.id.list_choose) {
|
||||
openDocumentedResult.launch("application/vnd.android.package-archive")
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||
menuInflater.inflate(R.menu.menu_list, menu)
|
||||
val item = menu!!.findItem(R.id.list_search)
|
||||
viewBinding.searchView.setMenuItem(item)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
viewModel.loadingLiveData.postValue(true)
|
||||
viewModel.loadingLiveData.removeObservers(this)
|
||||
viewModel.appsLiveData.postValue(null)
|
||||
viewModel.appsLiveData.removeObservers(this)
|
||||
}
|
||||
|
||||
|
||||
companion object{
|
||||
fun start(context: Context,onlyShowXp:Boolean){
|
||||
val intent = Intent(context,ListActivity::class.java)
|
||||
intent.putExtra("onlyShowXp",onlyShowXp)
|
||||
context.startActivity(intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package top.niunaijun.blackboxa.view.list
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import cbfg.rvadapter.RVHolder
|
||||
import cbfg.rvadapter.RVHolderFactory
|
||||
import top.niunaijun.blackboxa.R
|
||||
import top.niunaijun.blackboxa.bean.InstalledAppBean
|
||||
import top.niunaijun.blackboxa.databinding.ItemPackageBinding
|
||||
|
||||
/**
|
||||
*
|
||||
* @Description: 软件显示界面适配器
|
||||
* @Author: wukaicheng
|
||||
* @CreateDate: 2021/4/29 21:52
|
||||
*/
|
||||
|
||||
class ListAdapter : RVHolderFactory() {
|
||||
|
||||
override fun createViewHolder(parent: ViewGroup?, viewType: Int, item: Any): RVHolder<out Any> {
|
||||
return ListVH(inflate(R.layout.item_package,parent))
|
||||
}
|
||||
|
||||
class ListVH(itemView:View) :RVHolder<InstalledAppBean>(itemView){
|
||||
|
||||
val binding = ItemPackageBinding.bind(itemView)
|
||||
override fun setContent(item: InstalledAppBean, isSelected: Boolean, payload: Any?) {
|
||||
binding.icon.setImageDrawable(item.icon)
|
||||
binding.name.text = item.name
|
||||
binding.packageName.text = item.packageName
|
||||
binding.cornerLabel.visibility = if (item.isInstall) {
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package top.niunaijun.blackboxa.view.list
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import top.niunaijun.blackboxa.data.AppsRepository
|
||||
|
||||
/**
|
||||
*
|
||||
* @Description:
|
||||
* @Author: wukaicheng
|
||||
* @CreateDate: 2021/4/29 22:36
|
||||
*/
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
class ListFactory(private val appsRepository: AppsRepository) : ViewModelProvider.NewInstanceFactory() {
|
||||
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
return ListViewModel(appsRepository) as T
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package top.niunaijun.blackboxa.view.list
|
||||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import top.niunaijun.blackboxa.bean.InstalledAppBean
|
||||
import top.niunaijun.blackboxa.data.AppsRepository
|
||||
import top.niunaijun.blackboxa.view.base.BaseViewModel
|
||||
|
||||
/**
|
||||
*
|
||||
* @Description:
|
||||
* @Author: wukaicheng
|
||||
* @CreateDate: 2021/4/29 22:36
|
||||
*/
|
||||
class ListViewModel(private val repo: AppsRepository) : BaseViewModel() {
|
||||
|
||||
val appsLiveData = MutableLiveData<List<InstalledAppBean>>()
|
||||
|
||||
val loadingLiveData = MutableLiveData<Boolean>()
|
||||
|
||||
fun previewInstalledList() {
|
||||
launchOnUI{
|
||||
repo.previewInstallList()
|
||||
}
|
||||
}
|
||||
|
||||
fun getInstallAppList(userID:Int){
|
||||
launchOnUI {
|
||||
repo.getInstalledAppList(userID,loadingLiveData,appsLiveData)
|
||||
}
|
||||
}
|
||||
|
||||
fun getInstalledModules() {
|
||||
launchOnUI {
|
||||
repo.getInstalledModuleList(loadingLiveData, appsLiveData)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
package top.niunaijun.blackboxa.view.main
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import top.niunaijun.blackbox.BlackBoxCore
|
||||
import top.niunaijun.blackbox.app.BActivityThread
|
||||
import top.niunaijun.blackbox.app.configuration.AppLifecycleCallback
|
||||
import top.niunaijun.blackbox.app.configuration.ClientConfiguration
|
||||
import top.niunaijun.blackboxa.app.App
|
||||
import top.niunaijun.blackboxa.biz.cache.AppSharedPreferenceDelegate
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
*
|
||||
* @Description:
|
||||
* @Author: wukaicheng
|
||||
* @CreateDate: 2021/5/6 23:38
|
||||
*/
|
||||
class BlackBoxLoader {
|
||||
|
||||
private var mHideRoot by AppSharedPreferenceDelegate(App.getContext(), false)
|
||||
private var mHideXposed by AppSharedPreferenceDelegate(App.getContext(), false)
|
||||
private var mDaemonEnable by AppSharedPreferenceDelegate(App.getContext(), false)
|
||||
private var mShowShortcutPermissionDialog by AppSharedPreferenceDelegate(App.getContext(), true)
|
||||
|
||||
fun hideRoot(): Boolean {
|
||||
return try {
|
||||
mHideRoot
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error getting hideRoot: ${e.message}")
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fun invalidHideRoot(hideRoot: Boolean) {
|
||||
try {
|
||||
this.mHideRoot = hideRoot
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error setting hideRoot: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
fun hideXposed(): Boolean {
|
||||
return try {
|
||||
mHideXposed
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error getting hideXposed: ${e.message}")
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fun invalidHideXposed(hideXposed: Boolean) {
|
||||
try {
|
||||
this.mHideXposed = hideXposed
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error setting hideXposed: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
fun daemonEnable(): Boolean {
|
||||
return try {
|
||||
mDaemonEnable
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error getting daemonEnable: ${e.message}")
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fun invalidDaemonEnable(enable: Boolean) {
|
||||
try {
|
||||
this.mDaemonEnable = enable
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error setting daemonEnable: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
fun showShortcutPermissionDialog(): Boolean {
|
||||
return try {
|
||||
mShowShortcutPermissionDialog
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error getting showShortcutPermissionDialog: ${e.message}")
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
fun invalidShortcutPermissionDialog(show: Boolean) {
|
||||
try {
|
||||
this.mShowShortcutPermissionDialog = show
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error setting showShortcutPermissionDialog: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
fun getBlackBoxCore(): BlackBoxCore {
|
||||
return try {
|
||||
BlackBoxCore.get()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error getting BlackBoxCore: ${e.message}")
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
fun addLifecycleCallback() {
|
||||
try {
|
||||
BlackBoxCore.get().addAppLifecycleCallback(object : AppLifecycleCallback() {
|
||||
override fun beforeCreateApplication(
|
||||
packageName: String?,
|
||||
processName: String?,
|
||||
context: Context?,
|
||||
userId: Int
|
||||
) {
|
||||
try {
|
||||
Log.d(
|
||||
TAG,
|
||||
"beforeCreateApplication: pkg $packageName, processName $processName,userID:${BActivityThread.getUserId()}"
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error in beforeCreateApplication: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
override fun beforeApplicationOnCreate(
|
||||
packageName: String?,
|
||||
processName: String?,
|
||||
application: Application?,
|
||||
userId: Int
|
||||
) {
|
||||
try {
|
||||
Log.d(TAG, "beforeApplicationOnCreate: pkg $packageName, processName $processName")
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error in beforeApplicationOnCreate: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
override fun afterApplicationOnCreate(
|
||||
packageName: String?,
|
||||
processName: String?,
|
||||
application: Application?,
|
||||
userId: Int
|
||||
) {
|
||||
try {
|
||||
Log.d(TAG, "afterApplicationOnCreate: pkg $packageName, processName $processName")
|
||||
// RockerManager.init(application,userId)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error in afterApplicationOnCreate: ${e.message}")
|
||||
}
|
||||
}
|
||||
})
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error adding lifecycle callback: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
fun attachBaseContext(context: Context) {
|
||||
try {
|
||||
BlackBoxCore.get().doAttachBaseContext(context, object : ClientConfiguration() {
|
||||
override fun getHostPackageName(): String {
|
||||
return try {
|
||||
context.packageName
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error getting package name: ${e.message}")
|
||||
"unknown"
|
||||
}
|
||||
}
|
||||
|
||||
override fun isHideRoot(): Boolean {
|
||||
return try {
|
||||
mHideRoot
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error checking hideRoot: ${e.message}")
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override fun isHideXposed(): Boolean {
|
||||
return try {
|
||||
mHideXposed
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error checking hideXposed: ${e.message}")
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override fun isEnableDaemonService(): Boolean {
|
||||
return try {
|
||||
mDaemonEnable
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error checking daemonEnable: ${e.message}")
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override fun requestInstallPackage(file: File?, userId: Int): Boolean {
|
||||
return try {
|
||||
if (file == null) {
|
||||
Log.w(TAG, "requestInstallPackage: file is null")
|
||||
return false
|
||||
}
|
||||
val packageInfo =
|
||||
context.packageManager.getPackageArchiveInfo(file.absolutePath, 0)
|
||||
false
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error in requestInstallPackage: ${e.message}")
|
||||
false
|
||||
}
|
||||
}
|
||||
})
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error in attachBaseContext: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
fun doOnCreate(context: Context) {
|
||||
try {
|
||||
BlackBoxCore.get().doCreate()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error in doOnCreate: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val TAG: String = BlackBoxLoader::class.java.simpleName
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
package top.niunaijun.blackboxa.view.main
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.core.content.edit
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
import com.afollestad.materialdialogs.input.input
|
||||
import top.niunaijun.blackbox.BlackBoxCore
|
||||
import top.niunaijun.blackboxa.R
|
||||
import top.niunaijun.blackboxa.app.App
|
||||
import top.niunaijun.blackboxa.app.AppManager
|
||||
import top.niunaijun.blackboxa.databinding.ActivityMainBinding
|
||||
import top.niunaijun.blackboxa.util.Resolution
|
||||
import top.niunaijun.blackboxa.util.inflate
|
||||
import top.niunaijun.blackboxa.view.apps.AppsFragment
|
||||
import top.niunaijun.blackboxa.view.base.LoadingActivity
|
||||
import top.niunaijun.blackboxa.view.fake.FakeManagerActivity
|
||||
import top.niunaijun.blackboxa.view.list.ListActivity
|
||||
import top.niunaijun.blackboxa.view.setting.SettingActivity
|
||||
|
||||
|
||||
class MainActivity : LoadingActivity() {
|
||||
|
||||
private val viewBinding: ActivityMainBinding by inflate()
|
||||
|
||||
private lateinit var mViewPagerAdapter: ViewPagerAdapter
|
||||
|
||||
private val fragmentList = mutableListOf<AppsFragment>()
|
||||
|
||||
private var currentUser = 0
|
||||
|
||||
companion object {
|
||||
private const val TAG = "MainActivity"
|
||||
|
||||
fun start(context: Context) {
|
||||
val intent = Intent(context, MainActivity::class.java)
|
||||
context.startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
try {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
try {
|
||||
BlackBoxCore.get().onBeforeMainActivityOnCreate(this)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error in onBeforeMainActivityOnCreate: ${e.message}")
|
||||
}
|
||||
|
||||
setContentView(viewBinding.root)
|
||||
initToolbar(viewBinding.toolbarLayout.toolbar, R.string.app_name)
|
||||
initViewPager()
|
||||
initFab()
|
||||
initToolbarSubTitle()
|
||||
|
||||
try {
|
||||
BlackBoxCore.get().onAfterMainActivityOnCreate(this)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error in onAfterMainActivityOnCreate: ${e.message}")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Critical error in onCreate: ${e.message}")
|
||||
// Show error dialog to user
|
||||
showErrorDialog("Failed to initialize app: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun showErrorDialog(message: String) {
|
||||
try {
|
||||
MaterialDialog(this).show {
|
||||
title(text = "Error")
|
||||
message(text = message)
|
||||
positiveButton(text = "OK") {
|
||||
finish()
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error showing error dialog: ${e.message}")
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
private fun initToolbarSubTitle() {
|
||||
try {
|
||||
updateUserRemark(0)
|
||||
//hack code
|
||||
viewBinding.toolbarLayout.toolbar.getChildAt(1)?.setOnClickListener {
|
||||
try {
|
||||
MaterialDialog(this).show {
|
||||
title(res = R.string.userRemark)
|
||||
input(
|
||||
hintRes = R.string.userRemark,
|
||||
prefill = viewBinding.toolbarLayout.toolbar.subtitle
|
||||
) { _, input ->
|
||||
try {
|
||||
AppManager.mRemarkSharedPreferences.edit {
|
||||
putString("Remark$currentUser", input.toString())
|
||||
viewBinding.toolbarLayout.toolbar.subtitle = input
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error saving user remark: ${e.message}")
|
||||
}
|
||||
}
|
||||
positiveButton(res = R.string.done)
|
||||
negativeButton(res = R.string.cancel)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error showing remark dialog: ${e.message}")
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error in initToolbarSubTitle: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun initViewPager() {
|
||||
try {
|
||||
val userList = BlackBoxCore.get().users
|
||||
userList.forEach {
|
||||
fragmentList.add(AppsFragment.newInstance(it.id))
|
||||
}
|
||||
|
||||
currentUser = userList.firstOrNull()?.id ?: 0
|
||||
fragmentList.add(AppsFragment.newInstance(userList.size))
|
||||
|
||||
mViewPagerAdapter = ViewPagerAdapter(this)
|
||||
mViewPagerAdapter.replaceData(fragmentList)
|
||||
viewBinding.viewPager.adapter = mViewPagerAdapter
|
||||
viewBinding.dotsIndicator.setViewPager2(viewBinding.viewPager)
|
||||
viewBinding.viewPager.registerOnPageChangeCallback(object :
|
||||
ViewPager2.OnPageChangeCallback() {
|
||||
override fun onPageSelected(position: Int) {
|
||||
try {
|
||||
super.onPageSelected(position)
|
||||
currentUser = fragmentList[position].userID
|
||||
updateUserRemark(currentUser)
|
||||
showFloatButton(true)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error in onPageSelected: ${e.message}")
|
||||
}
|
||||
}
|
||||
})
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error in initViewPager: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun initFab() {
|
||||
try {
|
||||
viewBinding.fab.setOnClickListener {
|
||||
try {
|
||||
val userId = viewBinding.viewPager.currentItem
|
||||
val intent = Intent(this, ListActivity::class.java)
|
||||
intent.putExtra("userID", userId)
|
||||
apkPathResult.launch(intent)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error launching ListActivity: ${e.message}")
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error in initFab: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
fun showFloatButton(show: Boolean) {
|
||||
try {
|
||||
val tranY: Float = Resolution.convertDpToPixel(120F, App.getContext())
|
||||
val time = 200L
|
||||
if (show) {
|
||||
viewBinding.fab.animate().translationY(0f).alpha(1f).setDuration(time)
|
||||
.start()
|
||||
} else {
|
||||
viewBinding.fab.animate().translationY(tranY).alpha(0f).setDuration(time)
|
||||
.start()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error in showFloatButton: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
fun scanUser() {
|
||||
try {
|
||||
val userList = BlackBoxCore.get().users
|
||||
|
||||
if (fragmentList.size == userList.size) {
|
||||
fragmentList.add(AppsFragment.newInstance(fragmentList.size))
|
||||
} else if (fragmentList.size > userList.size + 1) {
|
||||
fragmentList.removeLast()
|
||||
}
|
||||
|
||||
mViewPagerAdapter.notifyDataSetChanged()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error in scanUser: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateUserRemark(userId: Int) {
|
||||
try {
|
||||
var remark = AppManager.mRemarkSharedPreferences.getString("Remark$userId", "User $userId")
|
||||
if (remark.isNullOrEmpty()) {
|
||||
remark = "User $userId"
|
||||
}
|
||||
|
||||
viewBinding.toolbarLayout.toolbar.subtitle = remark
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error updating user remark: ${e.message}")
|
||||
viewBinding.toolbarLayout.toolbar.subtitle = "User $userId"
|
||||
}
|
||||
}
|
||||
|
||||
private val apkPathResult =
|
||||
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
try {
|
||||
if (it.resultCode == RESULT_OK) {
|
||||
it.data?.let { data ->
|
||||
val userId = data.getIntExtra("userID", 0)
|
||||
val source = data.getStringExtra("source")
|
||||
if (source != null) {
|
||||
fragmentList[userId].installApk(source)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error handling APK path result: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||
try {
|
||||
menuInflater.inflate(R.menu.menu_main, menu)
|
||||
return true
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error creating options menu: ${e.message}")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
try {
|
||||
when (item.itemId) {
|
||||
R.id.main_git -> {
|
||||
val intent =
|
||||
Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/ALEX5402/NewBlackbox"))
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
R.id.main_setting -> {
|
||||
SettingActivity.start(this)
|
||||
}
|
||||
|
||||
R.id.main_tg -> {
|
||||
val intent =
|
||||
Intent(Intent.ACTION_VIEW, Uri.parse("https://t.me/newblackboxa"))
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
R.id.fake_location -> {
|
||||
// toast("Still Developing")
|
||||
val intent = Intent(this, FakeManagerActivity::class.java)
|
||||
intent.putExtra("userID", 0)
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error handling menu item selection: ${e.message}")
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package top.niunaijun.blackboxa.view.main
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import kotlinx.coroutines.launch
|
||||
import top.niunaijun.blackbox.BlackBoxCore
|
||||
|
||||
/**
|
||||
*
|
||||
* @Description: 快捷方式跳转activity
|
||||
* @Author: wukaicheng
|
||||
* @CreateDate: 2022/2/11 23:13
|
||||
*/
|
||||
class ShortcutActivity:AppCompatActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
val pkg = intent.getStringExtra("pkg")
|
||||
val userID = intent.getIntExtra("userId",0)
|
||||
|
||||
lifecycleScope.launch {
|
||||
BlackBoxCore.get().launchApk(pkg,userID)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package top.niunaijun.blackboxa.view.main
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||
import top.niunaijun.blackboxa.view.apps.AppsFragment
|
||||
|
||||
/**
|
||||
*
|
||||
* @Description:
|
||||
* @Author: wukaicheng
|
||||
* @CreateDate: 2021/4/29 22:00
|
||||
*/
|
||||
|
||||
class ViewPagerAdapter(appCompatActivity: AppCompatActivity) : FragmentStateAdapter(appCompatActivity) {
|
||||
|
||||
private var fragmentList = mutableListOf<AppsFragment>()
|
||||
|
||||
fun replaceData(list: MutableList<AppsFragment>){
|
||||
this.fragmentList = list
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return fragmentList.size
|
||||
}
|
||||
|
||||
override fun createFragment(position: Int): Fragment {
|
||||
return fragmentList[position]
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package top.niunaijun.blackboxa.view.main
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import top.niunaijun.blackbox.BlackBoxCore
|
||||
import top.niunaijun.blackboxa.util.InjectionUtil
|
||||
import top.niunaijun.blackboxa.view.list.ListViewModel
|
||||
|
||||
class WelcomeActivity : AppCompatActivity() {
|
||||
|
||||
override fun onNewIntent(intent: Intent?) {
|
||||
super.onNewIntent(intent)
|
||||
jump()
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
previewInstalledAppList()
|
||||
jump()
|
||||
}
|
||||
|
||||
private fun jump() {
|
||||
MainActivity.start(this)
|
||||
finish()
|
||||
}
|
||||
|
||||
private fun previewInstalledAppList(){
|
||||
val viewModel = ViewModelProvider(this,InjectionUtil.getListFactory()).get(ListViewModel::class.java)
|
||||
viewModel.previewInstalledList()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package top.niunaijun.blackboxa.view.setting
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import top.niunaijun.blackboxa.R
|
||||
import top.niunaijun.blackboxa.databinding.ActivitySettingBinding
|
||||
import top.niunaijun.blackboxa.util.inflate
|
||||
import top.niunaijun.blackboxa.view.base.BaseActivity
|
||||
|
||||
class SettingActivity : BaseActivity() {
|
||||
|
||||
private val viewBinding: ActivitySettingBinding by inflate()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(viewBinding.root)
|
||||
initToolbar(viewBinding.toolbarLayout.toolbar, R.string.setting, true)
|
||||
supportFragmentManager.beginTransaction()
|
||||
.replace(R.id.fragment, SettingFragment())
|
||||
.commit()
|
||||
}
|
||||
|
||||
companion object{
|
||||
fun start(context: Context){
|
||||
val intent = Intent(context,SettingActivity::class.java)
|
||||
intent.action = Intent.ACTION_OPEN_DOCUMENT
|
||||
context.startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
package top.niunaijun.blackboxa.view.setting
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import androidx.preference.SwitchPreferenceCompat
|
||||
import top.niunaijun.blackbox.BlackBoxCore
|
||||
import top.niunaijun.blackboxa.R
|
||||
import top.niunaijun.blackboxa.app.AppManager
|
||||
import top.niunaijun.blackboxa.util.toast
|
||||
import top.niunaijun.blackboxa.view.gms.GmsManagerActivity
|
||||
import top.niunaijun.blackboxa.view.xp.XpActivity
|
||||
|
||||
/**
|
||||
*
|
||||
* @Description:
|
||||
* @Author: wukaicheng
|
||||
* @CreateDate: 2021/5/6 22:13
|
||||
*/
|
||||
class SettingFragment : PreferenceFragmentCompat() {
|
||||
|
||||
private lateinit var xpEnable: SwitchPreferenceCompat
|
||||
|
||||
private lateinit var xpModule: Preference
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.setting, rootKey)
|
||||
|
||||
xpEnable = findPreference("xp_enable")!!
|
||||
xpEnable.isChecked = BlackBoxCore.get().isXPEnable
|
||||
|
||||
xpEnable.setOnPreferenceChangeListener { _, newValue ->
|
||||
BlackBoxCore.get().isXPEnable = (newValue == true)
|
||||
true
|
||||
}
|
||||
//xp模块跳转
|
||||
xpModule = findPreference("xp_module")!!
|
||||
xpModule.setOnPreferenceClickListener {
|
||||
val intent = Intent(requireActivity(), XpActivity::class.java)
|
||||
requireContext().startActivity(intent)
|
||||
true
|
||||
}
|
||||
initGms()
|
||||
|
||||
invalidHideState{
|
||||
val xpHidePreference: Preference = (findPreference("xp_hide")!!)
|
||||
val hideXposed = AppManager.mBlackBoxLoader.hideXposed()
|
||||
xpHidePreference.setDefaultValue(hideXposed)
|
||||
xpHidePreference
|
||||
}
|
||||
|
||||
invalidHideState{
|
||||
val rootHidePreference: Preference = (findPreference("root_hide")!!)
|
||||
val hideRoot = AppManager.mBlackBoxLoader.hideRoot()
|
||||
rootHidePreference.setDefaultValue(hideRoot)
|
||||
rootHidePreference
|
||||
}
|
||||
|
||||
invalidHideState {
|
||||
val daemonPreference: Preference = (findPreference("daemon_enable")!!)
|
||||
val mDaemonEnable = AppManager.mBlackBoxLoader.daemonEnable()
|
||||
daemonPreference.setDefaultValue(mDaemonEnable)
|
||||
daemonPreference
|
||||
}
|
||||
}
|
||||
|
||||
private fun initGms() {
|
||||
val gmsManagerPreference: Preference = (findPreference("gms_manager")!!)
|
||||
|
||||
if (BlackBoxCore.get().isSupportGms) {
|
||||
|
||||
gmsManagerPreference.setOnPreferenceClickListener {
|
||||
GmsManagerActivity.start(requireContext())
|
||||
true
|
||||
}
|
||||
} else {
|
||||
gmsManagerPreference.summary = getString(R.string.no_gms)
|
||||
gmsManagerPreference.isEnabled = false
|
||||
}
|
||||
}
|
||||
|
||||
private fun invalidHideState(block: () -> Preference) {
|
||||
val pref = block()
|
||||
pref.setOnPreferenceChangeListener { preference, newValue ->
|
||||
val tmpHide = (newValue == true)
|
||||
when (preference.key) {
|
||||
"xp_hide" -> {
|
||||
AppManager.mBlackBoxLoader.invalidHideXposed(tmpHide)
|
||||
}
|
||||
|
||||
"root_hide" -> {
|
||||
|
||||
AppManager.mBlackBoxLoader.invalidHideRoot(tmpHide)
|
||||
}
|
||||
|
||||
"daemon_enable" -> {
|
||||
AppManager.mBlackBoxLoader.invalidDaemonEnable(tmpHide)
|
||||
}
|
||||
}
|
||||
|
||||
toast(R.string.restart_module)
|
||||
return@setOnPreferenceChangeListener true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
package top.niunaijun.blackboxa.view.xp
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.text.TextUtils
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import cbfg.rvadapter.RVAdapter
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
import top.niunaijun.blackbox.BlackBoxCore
|
||||
import top.niunaijun.blackboxa.R
|
||||
import top.niunaijun.blackboxa.bean.XpModuleInfo
|
||||
import top.niunaijun.blackboxa.databinding.ActivityXpBinding
|
||||
import top.niunaijun.blackboxa.util.InjectionUtil
|
||||
import top.niunaijun.blackboxa.util.inflate
|
||||
import top.niunaijun.blackboxa.util.toast
|
||||
import top.niunaijun.blackboxa.view.base.LoadingActivity
|
||||
import top.niunaijun.blackboxa.view.list.ListActivity
|
||||
|
||||
/**
|
||||
*
|
||||
* @Description: xposed模块管理界面
|
||||
* @Author: wukaicheng
|
||||
* @CreateDate: 2021/5/2 20:25
|
||||
*/
|
||||
class XpActivity : LoadingActivity() {
|
||||
|
||||
private val viewBinding: ActivityXpBinding by inflate()
|
||||
|
||||
|
||||
private lateinit var viewModel: XpViewModel
|
||||
|
||||
private lateinit var mAdapter: RVAdapter<XpModuleInfo>
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(viewBinding.root)
|
||||
initToolbar(viewBinding.toolbarLayout.toolbar, R.string.xp_setting, true)
|
||||
|
||||
viewModel = ViewModelProvider(this, InjectionUtil.getXpFactory()).get(XpViewModel::class.java)
|
||||
|
||||
initRecyclerView()
|
||||
initFab()
|
||||
}
|
||||
|
||||
|
||||
private fun observeLiveData() {
|
||||
|
||||
viewBinding.stateView.showLoading()
|
||||
viewModel.getInstalledModule()
|
||||
viewModel.appsLiveData.observe(this) {
|
||||
if (it.isNullOrEmpty()) {
|
||||
viewBinding.stateView.showEmpty()
|
||||
} else {
|
||||
mAdapter.setItems(it)
|
||||
viewBinding.stateView.showContent()
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.resultLiveData.observe(this) {
|
||||
if (!TextUtils.isEmpty(it)) {
|
||||
hideLoading()
|
||||
toast(it)
|
||||
viewModel.getInstalledModule()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun initRecyclerView() {
|
||||
|
||||
mAdapter = RVAdapter<XpModuleInfo>(this, XpAdapter()).bind(viewBinding.recyclerView)
|
||||
.setItemClickListener { view, item, position ->
|
||||
item.enable = !item.enable
|
||||
BlackBoxCore.get().setModuleEnable(item.packageName, item.enable)
|
||||
mAdapter.replaceAt(position, item)
|
||||
toast(R.string.restart_module)
|
||||
}.setItemLongClickListener { _, item, _ ->
|
||||
unInstallModule(item.packageName)
|
||||
}
|
||||
|
||||
viewBinding.recyclerView.layoutManager = LinearLayoutManager(this)
|
||||
viewBinding.stateView.showEmpty()
|
||||
}
|
||||
|
||||
private fun initFab() {
|
||||
viewBinding.fab.setOnClickListener {
|
||||
val intent = Intent(this, ListActivity::class.java)
|
||||
intent.putExtra("onlyShowXp", true)
|
||||
apkPathResult.launch(intent)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
observeLiveData()
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
viewModel.appsLiveData.value = null
|
||||
viewModel.appsLiveData.removeObservers(this)
|
||||
viewModel.resultLiveData.value = null
|
||||
viewModel.resultLiveData.removeObservers(this)
|
||||
}
|
||||
|
||||
|
||||
private fun unInstallModule(packageName: String) {
|
||||
MaterialDialog(this).show {
|
||||
title(R.string.uninstall_module)
|
||||
message(R.string.uninstall_module_hint)
|
||||
positiveButton(R.string.done) {
|
||||
showLoading()
|
||||
viewModel.unInstallModule(packageName)
|
||||
}
|
||||
negativeButton(R.string.cancel)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun installModule(source: String) {
|
||||
showLoading()
|
||||
viewModel.installModule(source)
|
||||
}
|
||||
|
||||
|
||||
private val apkPathResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
if (it.resultCode == RESULT_OK) {
|
||||
it.data?.let { data ->
|
||||
val source = data.getStringExtra("source")
|
||||
if (source != null) {
|
||||
installModule(source)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
companion object {
|
||||
fun start(context: Context) {
|
||||
val intent = Intent(context, XpActivity::class.java)
|
||||
context.startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package top.niunaijun.blackboxa.view.xp
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import cbfg.rvadapter.RVHolder
|
||||
import cbfg.rvadapter.RVHolderFactory
|
||||
import top.niunaijun.blackboxa.R
|
||||
import top.niunaijun.blackboxa.bean.XpModuleInfo
|
||||
import top.niunaijun.blackboxa.databinding.ItemXpBinding
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @Description:
|
||||
* @Author: wukaicheng
|
||||
* @CreateDate: 2021/5/2 21:32
|
||||
*/
|
||||
class XpAdapter : RVHolderFactory() {
|
||||
|
||||
override fun createViewHolder(parent: ViewGroup?, viewType: Int, item: Any): RVHolder<out Any> {
|
||||
return XpVH(inflate(R.layout.item_xp, parent))
|
||||
}
|
||||
|
||||
class XpVH(itemView: View) : RVHolder<XpModuleInfo>(itemView) {
|
||||
|
||||
private val binding = ItemXpBinding.bind(itemView)
|
||||
|
||||
override fun setContent(item: XpModuleInfo, isSelected: Boolean, payload: Any?) {
|
||||
binding.icon.setImageDrawable(item.icon)
|
||||
binding.name.text = item.name
|
||||
binding.desc.text = item.desc
|
||||
binding.enable.isChecked = item.enable
|
||||
binding.enable.setOnCheckedChangeListener { buttonView, _ ->
|
||||
if (buttonView.isPressed) {
|
||||
binding.root.performClick()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package top.niunaijun.blackboxa.view.xp
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import top.niunaijun.blackboxa.data.XpRepository
|
||||
|
||||
/**
|
||||
*
|
||||
* @Description:
|
||||
* @Author: wukaicheng
|
||||
* @CreateDate: 2021/5/2 20:56
|
||||
*/
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
class XpFactory(private val repo:XpRepository): ViewModelProvider.NewInstanceFactory() {
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
return XpViewModel(repo) as T
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package top.niunaijun.blackboxa.view.xp
|
||||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import top.niunaijun.blackboxa.bean.XpModuleInfo
|
||||
import top.niunaijun.blackboxa.data.XpRepository
|
||||
import top.niunaijun.blackboxa.view.base.BaseViewModel
|
||||
|
||||
/**
|
||||
*
|
||||
* @Description:
|
||||
* @Author: wukaicheng
|
||||
* @CreateDate: 2021/5/2 20:55
|
||||
*/
|
||||
class XpViewModel(private val repo:XpRepository):BaseViewModel() {
|
||||
|
||||
val appsLiveData = MutableLiveData<List<XpModuleInfo>>()
|
||||
|
||||
val resultLiveData = MutableLiveData<String>()
|
||||
|
||||
fun getInstalledModule() {
|
||||
launchOnUI {
|
||||
repo.getInstallModules(appsLiveData)
|
||||
}
|
||||
}
|
||||
|
||||
fun installModule(source:String) {
|
||||
launchOnUI {
|
||||
repo.installModule(source,resultLiveData)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun unInstallModule(packageName: String){
|
||||
launchOnUI {
|
||||
repo.unInstallModule(packageName,resultLiveData)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package top.niunaijun.blackboxa.widget
|
||||
|
||||
import android.content.Context
|
||||
import android.view.MotionEvent
|
||||
import com.imuxuan.floatingview.FloatingMagnetView
|
||||
import top.niunaijun.blackboxa.R
|
||||
|
||||
/**
|
||||
*
|
||||
* @Description: rocker parent
|
||||
* @Author: kotlinMiku
|
||||
* @CreateDate: 2022/3/20 16:58
|
||||
*/
|
||||
class EnFloatView(mContext: Context) : FloatingMagnetView(mContext) {
|
||||
|
||||
private val TAG = "RockerManager"
|
||||
|
||||
private var rockerView: RockerView? = null
|
||||
|
||||
private var mListener: LocationListener? = null
|
||||
|
||||
init {
|
||||
inflate(mContext, R.layout.view_float_rocker, this)
|
||||
initRockerView()
|
||||
}
|
||||
|
||||
private fun initRockerView() {
|
||||
|
||||
rockerView = findViewById(R.id.rocker)
|
||||
rockerView?.setListener { type, currentAngle, currentDistance ->
|
||||
if (type == RockerView.EVENT_CLOCK && currentAngle != -1F) {
|
||||
val realAngle = currentAngle
|
||||
val realDistance = currentDistance * 0.001F
|
||||
//拉满的话,大概就是一秒五米
|
||||
|
||||
mListener?.invoke(realAngle, realDistance)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTouchEvent(event: MotionEvent?): Boolean {
|
||||
if (event?.action == MotionEvent.ACTION_DOWN) {
|
||||
rockerView?.setCanMove(false)
|
||||
} else if (event?.action == MotionEvent.ACTION_UP) {
|
||||
rockerView?.setCanMove(true)
|
||||
}
|
||||
return super.onTouchEvent(event)
|
||||
}
|
||||
|
||||
fun setListener(listener: LocationListener) {
|
||||
this.mListener = listener
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
typealias LocationListener = (angle: Float, distance: Float) -> Unit
|
||||
@@ -0,0 +1,465 @@
|
||||
package top.niunaijun.blackboxa.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.Rect;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.SurfaceView;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import top.niunaijun.blackboxa.util.MathUtil;
|
||||
|
||||
|
||||
/**
|
||||
* A custom view for game or others.
|
||||
* <p/>
|
||||
* Author: GcsSloop
|
||||
* Created Date: 16/5/24
|
||||
* Copyright (C) 2016 GcsSloop.
|
||||
* GitHub: https://github.com/GcsSloop
|
||||
*/
|
||||
public class RockerView extends SurfaceView implements Runnable, SurfaceHolder.Callback {
|
||||
|
||||
private static final int DEFAULT_AREA_RADIUS = 100;
|
||||
private static final int DEFAULT_ROCKER_RADIUS = 35;
|
||||
|
||||
private static final int DEFAULT_AREA_COLOR = Color.argb(128,0,0,0);
|
||||
private static final int DEFAULT_ROCKER_COLOR = Color.argb(128,0,0,0);
|
||||
|
||||
private static final int DEFAULT_REFRESH_CYCLE = 30;
|
||||
private static final int DEFAULT_CALLBACK_CYCLE = 300;
|
||||
|
||||
private SurfaceHolder mHolder;
|
||||
private static Thread mDrawThread;
|
||||
private static Thread mCallbackThread;
|
||||
private static boolean mDrawOk = true;
|
||||
private static boolean mCallbackOk = true;
|
||||
|
||||
private Paint mPaint;
|
||||
|
||||
/**
|
||||
* The rocker active area center position.
|
||||
* usually, it is the center of this view.
|
||||
*/
|
||||
private Point mAreaPosition;
|
||||
|
||||
/**
|
||||
* The Rocker position.
|
||||
* usually, it as same asmAreaPosition .
|
||||
* if this view touched, it will follow the touch position.
|
||||
* <p/>
|
||||
* we get position information from this.
|
||||
*/
|
||||
private Point mRockerPosition;
|
||||
|
||||
|
||||
private int mAreaRadius = -1;
|
||||
private int mRockerRadius = -1;
|
||||
|
||||
private int mAreaColor;
|
||||
private int mRockerColor;
|
||||
private Bitmap mAreaBitmap;
|
||||
private Bitmap mRockerBitmap;
|
||||
|
||||
private boolean canMove = true;
|
||||
|
||||
|
||||
private RockerListener mListener;
|
||||
public static final int EVENT_ACTION = 1;
|
||||
public static final int EVENT_CLOCK = 2;
|
||||
|
||||
private int mRefreshCycle = DEFAULT_REFRESH_CYCLE;
|
||||
private int mCallbackCycle = DEFAULT_CALLBACK_CYCLE;
|
||||
|
||||
|
||||
/*Life Cycle***********************************************************************************/
|
||||
|
||||
public RockerView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public RockerView(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public RockerView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
|
||||
// init attrs
|
||||
initAttrs(context, attrs);
|
||||
|
||||
// set paint
|
||||
setPaint();
|
||||
|
||||
if (isInEditMode()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// config surfaceView
|
||||
configSurfaceView();
|
||||
|
||||
// config surfaceHolder
|
||||
configSurfaceHolder();
|
||||
}
|
||||
|
||||
private void initAttrs(Context context, AttributeSet attrs) {
|
||||
mAreaColor = DEFAULT_AREA_COLOR;
|
||||
mRockerColor = DEFAULT_ROCKER_COLOR;
|
||||
mAreaRadius = DEFAULT_AREA_RADIUS;
|
||||
mRockerRadius = DEFAULT_ROCKER_RADIUS;
|
||||
|
||||
}
|
||||
|
||||
private void setPaint() {
|
||||
mPaint = new Paint();
|
||||
mPaint.setAntiAlias(true);
|
||||
}
|
||||
|
||||
private void configSurfaceView() {
|
||||
setKeepScreenOn(true); // do not lock screen when surfaceView is running.
|
||||
setFocusable(true); // make sure this surfaceView can get focus from keyboard.
|
||||
setFocusableInTouchMode(true); // make sure this surfaceView can get focus from touch.
|
||||
setZOrderOnTop(true); // make sure this surface is placed on top of the window
|
||||
}
|
||||
|
||||
private void configSurfaceHolder() {
|
||||
mHolder = getHolder();
|
||||
mHolder.addCallback(this);
|
||||
mHolder.setFormat(PixelFormat.TRANSPARENT); //设置背景透明
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
int measureWidth = 0, measureHeight = 0;
|
||||
int defaultWidth = (mAreaRadius + mRockerRadius) * 2;
|
||||
int defalutHeight = defaultWidth;
|
||||
|
||||
int widthsize = MeasureSpec.getSize(widthMeasureSpec); //取出宽度的确切数值
|
||||
int widthmode = MeasureSpec.getMode(widthMeasureSpec); //取出宽度的测量模式
|
||||
|
||||
int heightsize = MeasureSpec.getSize(heightMeasureSpec); //取出高度的确切数值
|
||||
int heightmode = MeasureSpec.getMode(heightMeasureSpec); //取出高度的测量模式
|
||||
|
||||
if (widthmode == MeasureSpec.AT_MOST || widthmode == MeasureSpec.UNSPECIFIED || widthsize < 0) {
|
||||
measureWidth = defaultWidth;
|
||||
} else {
|
||||
measureWidth = widthsize;
|
||||
}
|
||||
|
||||
|
||||
if (heightmode == MeasureSpec.AT_MOST || heightmode == MeasureSpec.UNSPECIFIED || heightsize < 0) {
|
||||
measureHeight = defalutHeight;
|
||||
} else {
|
||||
measureHeight = heightsize;
|
||||
}
|
||||
|
||||
setMeasuredDimension(measureWidth, measureHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||
super.onSizeChanged(w, h, oldw, oldh);
|
||||
|
||||
mAreaPosition = new Point(w / 2, h / 2);
|
||||
mRockerPosition = new Point(mAreaPosition);
|
||||
|
||||
// this need subtract the view padding
|
||||
int tempRadius = Math.min(w - getPaddingLeft() - getPaddingRight(), h - getPaddingTop() - getPaddingBottom());
|
||||
tempRadius /= 2;
|
||||
if (mAreaRadius == -1)
|
||||
mAreaRadius = (int) (tempRadius * 0.75);
|
||||
if (mRockerRadius == -1)
|
||||
mRockerRadius = (int) (tempRadius * 0.25);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceCreated(SurfaceHolder holder) {
|
||||
try {
|
||||
mDrawThread = new Thread(this);
|
||||
mDrawThread.start();
|
||||
|
||||
mCallbackThread = new Thread(() -> {
|
||||
while (mCallbackOk) {
|
||||
|
||||
// listener callback
|
||||
listenerCallback();
|
||||
|
||||
try {
|
||||
Thread.sleep(mCallbackCycle);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
mCallbackThread.start();
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceDestroyed(SurfaceHolder holder) {
|
||||
mDrawOk = false;
|
||||
mCallbackOk = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onVisibilityChanged(@NonNull View changedView, int visibility) {
|
||||
super.onVisibilityChanged(changedView, visibility);
|
||||
if (visibility == VISIBLE) {
|
||||
mDrawOk = true;
|
||||
mCallbackOk = true;
|
||||
} else {
|
||||
mDrawOk = false;
|
||||
mCallbackOk = false;
|
||||
}
|
||||
}
|
||||
|
||||
/*Event Response*******************************************************************************/
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
try {
|
||||
int len = MathUtil.getDistance(mAreaPosition.x, mAreaPosition.y, event.getX(), event.getY());
|
||||
|
||||
if (event.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
//如果屏幕接触点不在摇杆挥动范围内,则不处理
|
||||
if (len > mAreaRadius) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (event.getAction() == MotionEvent.ACTION_MOVE) {
|
||||
if (len <= mAreaRadius) {
|
||||
//如果手指在摇杆活动范围内,则摇杆处于手指触摸位置
|
||||
mRockerPosition.set((int) event.getX(), (int) event.getY());
|
||||
|
||||
} else {
|
||||
//设置摇杆位置,使其处于手指触摸方向的 摇杆活动范围边缘
|
||||
mRockerPosition = MathUtil.getPointByCutLength(mAreaPosition,
|
||||
new Point((int) event.getX(), (int) event.getY()), mAreaRadius);
|
||||
}
|
||||
if (mListener != null) {
|
||||
float radian = MathUtil.getRadian(mAreaPosition, new Point((int) event.getX(), (int) event.getY()));
|
||||
float angle = RockerView.this.getAngleConvert(radian);
|
||||
float distance = MathUtil.getDistance(mAreaPosition.x, mAreaPosition.y, event.getX(), event.getY());
|
||||
mListener.callback(EVENT_ACTION, angle, distance);
|
||||
}
|
||||
}
|
||||
//如果手指离开屏幕,则摇杆返回初始位置
|
||||
if (event.getAction() == MotionEvent.ACTION_UP) {
|
||||
mRockerPosition = new Point(mAreaPosition);
|
||||
if (mListener != null) {
|
||||
mListener.callback(EVENT_ACTION, -1, 0);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*Thread - draw view***************************************************************************/
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (isInEditMode()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Canvas canvas = null;
|
||||
|
||||
while (mDrawOk) {
|
||||
boolean canMove = this.canMove;
|
||||
try {
|
||||
if (canMove) {
|
||||
canvas = mHolder.lockCanvas();
|
||||
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
|
||||
|
||||
drawArea(canvas);
|
||||
drawRocker(canvas);
|
||||
}
|
||||
Thread.sleep(mRefreshCycle); // 休眠
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
if (canvas != null && canMove) {
|
||||
mHolder.unlockCanvasAndPost(canvas);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void drawArea(Canvas canvas) {
|
||||
|
||||
if (null != mAreaBitmap) {
|
||||
mPaint.setColor(Color.BLACK);
|
||||
Rect src = new Rect(0, 0, mAreaBitmap.getWidth(), mAreaBitmap.getHeight());
|
||||
Rect dst = new Rect(
|
||||
mAreaPosition.x - mAreaRadius,
|
||||
mAreaPosition.y - mAreaRadius,
|
||||
mAreaPosition.x + mAreaRadius,
|
||||
mAreaPosition.y + mAreaRadius);
|
||||
canvas.drawBitmap(mAreaBitmap, src, dst, mPaint);
|
||||
} else {
|
||||
mPaint.setColor(mAreaColor);
|
||||
canvas.drawCircle(mAreaPosition.x, mAreaPosition.y, mAreaRadius, mPaint);
|
||||
}
|
||||
}
|
||||
|
||||
private void drawRocker(Canvas canvas) {
|
||||
if (null != mRockerBitmap) {
|
||||
mPaint.setColor(Color.BLACK);
|
||||
Rect src = new Rect(0, 0, mRockerBitmap.getWidth(), mRockerBitmap.getHeight());
|
||||
Rect dst = new Rect(
|
||||
mRockerPosition.x - mRockerRadius,
|
||||
mRockerPosition.y - mRockerRadius,
|
||||
mRockerPosition.x + mRockerRadius,
|
||||
mRockerPosition.y + mRockerRadius);
|
||||
canvas.drawBitmap(mRockerBitmap, src, dst, mPaint);
|
||||
} else {
|
||||
mPaint.setColor(mRockerColor);
|
||||
canvas.drawCircle(mRockerPosition.x, mRockerPosition.y, mRockerRadius, mPaint);
|
||||
}
|
||||
}
|
||||
|
||||
private void listenerCallback() {
|
||||
if (mListener != null) {
|
||||
if (mRockerPosition.x == mAreaPosition.x && mRockerPosition.y == mAreaPosition.y) {
|
||||
mListener.callback(EVENT_CLOCK, -1, 0);
|
||||
} else {
|
||||
float radian = MathUtil.getRadian(mAreaPosition, new Point(mRockerPosition.x, mRockerPosition.y));
|
||||
float angle = RockerView.this.getAngleConvert(radian);
|
||||
float distance = MathUtil.getDistance(mAreaPosition.x, mAreaPosition.y, mRockerPosition.x, mRockerPosition.y);
|
||||
mListener.callback(EVENT_CLOCK, angle, distance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//获取摇杆偏移角度 上方中间为0,左为负,右为正
|
||||
private float getAngleConvert(float radian) {
|
||||
return 90 + Math.round(radian / Math.PI * 180);
|
||||
}
|
||||
|
||||
// for preview
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
if (isInEditMode()) {
|
||||
canvas.drawColor(Color.WHITE);
|
||||
drawArea(canvas);
|
||||
drawRocker(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
/*Getter Setter********************************************************************************/
|
||||
|
||||
public void setCanMove(boolean isMove) {
|
||||
this.canMove = isMove;
|
||||
}
|
||||
|
||||
public int getAreaRadius() {
|
||||
return mAreaRadius;
|
||||
}
|
||||
|
||||
public void setAreaRadius(int areaRadius) {
|
||||
mAreaRadius = areaRadius;
|
||||
}
|
||||
|
||||
public int getRockerRadius() {
|
||||
return mRockerRadius;
|
||||
}
|
||||
|
||||
public void setRockerRadius(int rockerRadius) {
|
||||
mRockerRadius = rockerRadius;
|
||||
}
|
||||
|
||||
public Bitmap getAreaBitmap() {
|
||||
return mAreaBitmap;
|
||||
}
|
||||
|
||||
public void setAreaBitmap(Bitmap areaBitmap) {
|
||||
mAreaBitmap = areaBitmap;
|
||||
}
|
||||
|
||||
public Bitmap getRockerBitmap() {
|
||||
return mRockerBitmap;
|
||||
}
|
||||
|
||||
public void setRockerBitmap(Bitmap rockerBitmap) {
|
||||
mRockerBitmap = rockerBitmap;
|
||||
}
|
||||
|
||||
public int getRefreshCycle() {
|
||||
return mRefreshCycle;
|
||||
}
|
||||
|
||||
public void setRefreshCycle(int refreshCycle) {
|
||||
mRefreshCycle = refreshCycle;
|
||||
}
|
||||
|
||||
public int getCallbackCycle() {
|
||||
return mCallbackCycle;
|
||||
}
|
||||
|
||||
public void setCallbackCycle(int callbackCycle) {
|
||||
mCallbackCycle = callbackCycle;
|
||||
}
|
||||
|
||||
public int getAreaColor() {
|
||||
return mAreaColor;
|
||||
}
|
||||
|
||||
public void setAreaColor(int areaColor) {
|
||||
mAreaColor = areaColor;
|
||||
mAreaBitmap = null;
|
||||
}
|
||||
|
||||
public int getRockerColor() {
|
||||
return mRockerColor;
|
||||
}
|
||||
|
||||
public void setRockerColor(int rockerColor) {
|
||||
mRockerColor = rockerColor;
|
||||
mRockerBitmap = null;
|
||||
}
|
||||
|
||||
public void setListener(@NonNull RockerListener listener) {
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
/*Rocker Listener******************************************************************************/
|
||||
|
||||
/**
|
||||
* rocker listener
|
||||
*/
|
||||
public interface RockerListener {
|
||||
|
||||
/**
|
||||
* you can get some event from this method
|
||||
*
|
||||
* @param eventType The event type, EVENT_ACTION or EVENT_CLOCK
|
||||
* @param currentAngle The current angle
|
||||
* @param currentDistance The current distance (px)
|
||||
*/
|
||||
void callback(int eventType, float currentAngle, float currentDistance);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user