refactor: remove all Xposed-related features and code from the application and core.

This commit is contained in:
alex5402
2026-01-30 22:36:30 +05:30
parent 4238d87083
commit 46d813480f
31 changed files with 398 additions and 1365 deletions
+1 -1
View File
@@ -35,7 +35,7 @@
</activity>
<activity android:name=".view.list.ListActivity" />
<activity android:name=".view.fake.FakeManagerActivity" />
<activity android:name=".view.xp.XpActivity" />
<activity
android:name=".view.main.ShortcutActivity"
android:excludeFromRecents="true"
@@ -1,10 +1,11 @@
package top.niunaijun.blackboxa.data
import android.content.Context
import android.content.pm.ApplicationInfo
import android.net.Uri
import android.util.Log
import android.webkit.URLUtil
import androidx.lifecycle.MutableLiveData
import java.io.File
import top.niunaijun.blackbox.BlackBoxCore
import top.niunaijun.blackbox.utils.AbiUtils
import top.niunaijun.blackboxa.R
@@ -12,11 +13,7 @@ import top.niunaijun.blackboxa.app.AppManager
import top.niunaijun.blackboxa.bean.AppInfo
import top.niunaijun.blackboxa.bean.InstalledAppBean
import top.niunaijun.blackboxa.util.MemoryManager
import top.niunaijun.blackboxa.util.inflate
import top.niunaijun.blackboxa.util.getString
import java.io.File
import android.webkit.URLUtil
/**
*
@@ -24,14 +21,11 @@ import android.webkit.URLUtil
* @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
*/
/** Safely load app label with fallback to package name */
private fun safeLoadAppLabel(applicationInfo: ApplicationInfo): String {
return try {
BlackBoxCore.getPackageManager().getApplicationLabel(applicationInfo).toString()
@@ -40,30 +34,41 @@ class AppsRepository {
applicationInfo.packageName // Fallback to package name
}
}
/**
* Safely load app icon with fallback to null
*/
private fun safeLoadAppIcon(applicationInfo: ApplicationInfo): android.graphics.drawable.Drawable? {
/** Safely load app icon with fallback to null */
private fun safeLoadAppIcon(
applicationInfo: ApplicationInfo
): android.graphics.drawable.Drawable? {
return try {
// Check if we should skip icon loading to save memory
if (MemoryManager.shouldSkipIconLoading()) {
Log.w(TAG, "Memory usage high (${MemoryManager.getMemoryUsagePercentage()}%), skipping icon for ${applicationInfo.packageName}")
Log.w(
TAG,
"Memory usage high (${MemoryManager.getMemoryUsagePercentage()}%), skipping icon for ${applicationInfo.packageName}"
)
return null
}
val icon = BlackBoxCore.getPackageManager().getApplicationIcon(applicationInfo)
// Optimize icon for memory efficiency
if (icon is android.graphics.drawable.BitmapDrawable) {
val bitmap = icon.bitmap
// If icon is too large, scale it down to save memory
if (bitmap.width > 96 || bitmap.height > 96) {
try {
val scaledBitmap = android.graphics.Bitmap.createScaledBitmap(bitmap, 96, 96, true)
android.graphics.drawable.BitmapDrawable(BlackBoxCore.getPackageManager().getResourcesForApplication(applicationInfo.packageName), scaledBitmap)
val scaledBitmap =
android.graphics.Bitmap.createScaledBitmap(bitmap, 96, 96, true)
android.graphics.drawable.BitmapDrawable(
BlackBoxCore.getPackageManager()
.getResourcesForApplication(applicationInfo.packageName),
scaledBitmap
)
} catch (e: Exception) {
Log.w(TAG, "Failed to scale icon for ${applicationInfo.packageName}: ${e.message}")
Log.w(
TAG,
"Failed to scale icon for ${applicationInfo.packageName}: ${e.message}"
)
icon
}
} else {
@@ -72,7 +77,6 @@ class AppsRepository {
} else {
icon
}
} catch (e: Exception) {
Log.w(TAG, "Failed to load icon for ${applicationInfo.packageName}: ${e.message}")
null // Fallback to null icon
@@ -83,35 +87,45 @@ class AppsRepository {
try {
synchronized(mInstalledList) {
val installedApplications: List<ApplicationInfo> =
BlackBoxCore.getPackageManager().getInstalledApplications(0)
BlackBoxCore.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 ((installedApplication.flags and ApplicationInfo.FLAG_SYSTEM) != 0)
continue
if (!AbiUtils.isSupport(file)) continue
// Filter out BlackBox apps to prevent cloning
if (BlackBoxCore.get().isBlackBoxApp(installedApplication.packageName)) {
Log.d(TAG, "Filtering out BlackBox app: ${installedApplication.packageName}")
Log.d(
TAG,
"Filtering out BlackBox app: ${installedApplication.packageName}"
)
continue
}
val isXpModule = BlackBoxCore.get().isXposedModule(file)
val isXpModule = false
val info = AppInfo(
safeLoadAppLabel(installedApplication),
safeLoadAppIcon(installedApplication), // Remove the !! operator to allow null icons
installedApplication.packageName,
installedApplication.sourceDir,
isXpModule
)
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}")
Log.e(
TAG,
"Error processing app ${installedApplication.packageName}: ${e.message}"
)
}
}
this.mInstalledList.clear()
@@ -123,24 +137,25 @@ class AppsRepository {
}
fun getInstalledAppList(
userID: Int,
loadingLiveData: MutableLiveData<Boolean>,
appsLiveData: MutableLiveData<List<InstalledAppBean>>
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)
)
}
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)
}
@@ -151,87 +166,70 @@ class AppsRepository {
}
}
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 {
// Check memory status before starting
if (MemoryManager.isMemoryCritical()) {
Log.w(TAG, "Memory critical (${MemoryManager.getMemoryUsagePercentage()}%), forcing garbage collection")
Log.w(
TAG,
"Memory critical (${MemoryManager.getMemoryUsagePercentage()}%), forcing garbage collection"
)
MemoryManager.forceGarbageCollectionIfNeeded()
}
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", "")
users.forEach { user -> Log.d(TAG, "User: id=${user.id}, name=${user.name}") }
val sortListData = AppManager.mRemarkSharedPreferences.getString("AppList$userId", "")
val sortList = sortListData?.split(",")
// Add retry mechanism for getting installed applications
var applicationList: List<ApplicationInfo>? = null
var retryCount = 0
val maxRetries = 3
while (applicationList == null && retryCount < maxRetries) {
try {
applicationList = blackBoxCore.getInstalledApplications(0, userId)
if (applicationList == null) {
Log.w(TAG, "getVmInstallList: Attempt ${retryCount + 1} returned null, retrying...")
Log.w(
TAG,
"getVmInstallList: Attempt ${retryCount + 1} returned null, retrying..."
)
retryCount++
Thread.sleep(100) // Small delay before retry
}
} catch (e: Exception) {
Log.e(TAG, "getVmInstallList: Error getting applications on attempt ${retryCount + 1}: ${e.message}")
Log.e(
TAG,
"getVmInstallList: Error getting applications on attempt ${retryCount + 1}: ${e.message}"
)
retryCount++
if (retryCount < maxRetries) {
Thread.sleep(200) // Longer delay for errors
}
}
}
// Add null check for applicationList
if (applicationList == null) {
Log.e(TAG, "getVmInstallList: applicationList is null for userId=$userId after $maxRetries attempts")
Log.e(
TAG,
"getVmInstallList: applicationList is null for userId=$userId after $maxRetries attempts"
)
appsLiveData.postValue(emptyList())
return
}
// Add debugging
Log.d(TAG, "getVmInstallList: userId=$userId, applicationList.size=${applicationList.size}")
Log.d(
TAG,
"getVmInstallList: userId=$userId, applicationList.size=${applicationList.size}"
)
if (applicationList.isNotEmpty()) {
Log.d(TAG, "First app: ${applicationList.first().packageName}")
} else {
@@ -239,19 +237,20 @@ class AppsRepository {
}
val appInfoList = mutableListOf<AppInfo>()
// Sort the application list if sort data exists
val sortedApplicationList = if (!sortList.isNullOrEmpty()) {
try {
applicationList.sortedWith(AppsSortComparator(sortList))
} catch (e: Exception) {
Log.e(TAG, "getVmInstallList: Error sorting applications: ${e.message}")
applicationList // Return unsorted list if sorting fails
}
} else {
applicationList
}
val sortedApplicationList =
if (!sortList.isNullOrEmpty()) {
try {
applicationList.sortedWith(AppsSortComparator(sortList))
} catch (e: Exception) {
Log.e(TAG, "getVmInstallList: Error sorting applications: ${e.message}")
applicationList // Return unsorted list if sorting fails
}
} else {
applicationList
}
// Process each application with enhanced error handling
sortedApplicationList.forEachIndexed { index, applicationInfo ->
try {
@@ -262,50 +261,73 @@ class AppsRepository {
MemoryManager.forceGarbageCollectionIfNeeded()
}
}
// Add null check for applicationInfo
if (applicationInfo == null) {
Log.w(TAG, "getVmInstallList: Skipping null applicationInfo at index $index")
Log.w(
TAG,
"getVmInstallList: Skipping null applicationInfo at index $index"
)
return@forEachIndexed
}
// Validate package name
if (applicationInfo.packageName.isNullOrBlank()) {
Log.w(TAG, "getVmInstallList: Skipping app with null/blank package name at index $index")
Log.w(
TAG,
"getVmInstallList: Skipping app with null/blank package name at index $index"
)
return@forEachIndexed
}
val info = AppInfo(
safeLoadAppLabel(applicationInfo),
safeLoadAppIcon(applicationInfo), // Remove the !! operator to allow null icons
applicationInfo.packageName,
applicationInfo.sourceDir ?: "",
isInstalledXpModule(applicationInfo.packageName)
)
val info =
AppInfo(
safeLoadAppLabel(applicationInfo),
safeLoadAppIcon(
applicationInfo
), // Remove the !! operator to allow null icons
applicationInfo.packageName,
applicationInfo.sourceDir ?: "",
false
)
appInfoList.add(info)
// Log progress for large lists
if (index > 0 && index % 50 == 0) {
Log.d(TAG, "getVmInstallList: Processed $index/${sortedApplicationList.size} apps - ${MemoryManager.getMemoryInfo()}")
Log.d(
TAG,
"getVmInstallList: Processed $index/${sortedApplicationList.size} apps - ${MemoryManager.getMemoryInfo()}"
)
}
} catch (e: Exception) {
Log.e(TAG, "getVmInstallList: Error processing app at index $index (${applicationInfo?.packageName}): ${e.message}")
Log.e(
TAG,
"getVmInstallList: Error processing app at index $index (${applicationInfo?.packageName}): ${e.message}"
)
// Continue processing other apps instead of failing completely
}
}
Log.d(TAG, "getVmInstallList: processed ${appInfoList.size} apps - ${MemoryManager.getMemoryInfo()}")
Log.d(
TAG,
"getVmInstallList: processed ${appInfoList.size} apps - ${MemoryManager.getMemoryInfo()}"
)
// If no virtual apps found, show empty list (correct behavior for new users)
// Do NOT load regular installed apps as fallback - this causes the bug
if (appInfoList.isEmpty()) {
Log.d(TAG, "getVmInstallList: No virtual apps found for userId=$userId, showing empty list (correct for new users)")
Log.d(
TAG,
"getVmInstallList: No virtual apps found for userId=$userId, showing empty list (correct for new users)"
)
} else {
Log.d(TAG, "getVmInstallList: Showing ${appInfoList.size} virtual apps for userId=$userId")
Log.d(
TAG,
"getVmInstallList: Showing ${appInfoList.size} virtual apps for userId=$userId"
)
}
// Post the result safely
try {
appsLiveData.postValue(appInfoList)
@@ -317,14 +339,19 @@ class AppsRepository {
try {
appsLiveData.postValue(appInfoList)
} catch (e2: Exception) {
Log.e(TAG, "getVmInstallList: Fallback posting also failed: ${e2.message}")
Log.e(
TAG,
"getVmInstallList: Fallback posting also failed: ${e2.message}"
)
}
}
} catch (e3: Exception) {
Log.e(TAG, "getVmInstallList: Could not schedule fallback posting: ${e3.message}")
Log.e(
TAG,
"getVmInstallList: Could not schedule fallback posting: ${e3.message}"
)
}
}
} catch (e: Exception) {
Log.e(TAG, "Error in getVmInstallList: ${e.message}")
try {
@@ -335,37 +362,30 @@ class AppsRepository {
}
}
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 {
// Check if this is an attempt to install BlackBox app
if (source.contains("blackbox") || source.contains("niunaijun") ||
source.contains("vspace") || source.contains("virtual")) {
if (source.contains("blackbox") ||
source.contains("niunaijun") ||
source.contains("vspace") ||
source.contains("virtual")
) {
// Additional check for the actual BlackBox app
try {
val blackBoxCore = BlackBoxCore.get()
val hostPackageName = BlackBoxCore.getHostPkg()
// If it's a file path, try to check the package name
if (!URLUtil.isValidUrl(source)) {
val file = File(source)
if (file.exists()) {
val packageInfo = BlackBoxCore.getPackageManager().getPackageArchiveInfo(source, 0)
val packageInfo =
BlackBoxCore.getPackageManager()
.getPackageArchiveInfo(source, 0)
if (packageInfo != null && packageInfo.packageName == hostPackageName) {
resultLiveData.postValue("Cannot install BlackBox app from within BlackBox. This would create infinite recursion and is not allowed for security reasons.")
resultLiveData.postValue(
"Cannot install BlackBox app from within BlackBox. This would create infinite recursion and is not allowed for security reasons."
)
return
}
}
@@ -374,14 +394,15 @@ class AppsRepository {
Log.w(TAG, "Could not verify if this is BlackBox app: ${e.message}")
}
}
val blackBoxCore = BlackBoxCore.get()
val installResult = if (URLUtil.isValidUrl(source)) {
val uri = Uri.parse(source)
blackBoxCore.installPackageAsUser(uri, userId)
} else {
blackBoxCore.installPackageAsUser(source, userId)
}
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)
@@ -428,10 +449,7 @@ class AppsRepository {
}
}
/**
* 倒序递归扫描用户,
* 如果用户是空的,就删除用户,删除用户备注,删除应用排序列表
*/
/** 倒序递归扫描用户, 如果用户是空的,就删除用户,删除用户备注,删除应用排序列表 */
private fun scanUser() {
try {
val blackBoxCore = BlackBoxCore.get()
@@ -465,8 +483,7 @@ class AppsRepository {
*/
private fun updateAppSortList(userID: Int, pkg: String, isAdd: Boolean) {
try {
val savedSortList =
AppManager.mRemarkSharedPreferences.getString("AppList$userID", "")
val savedSortList = AppManager.mRemarkSharedPreferences.getString("AppList$userID", "")
val sortList = linkedSetOf<String>()
if (savedSortList != null) {
@@ -488,14 +505,11 @@ class AppsRepository {
}
}
/**
* 保存排序后的apk顺序
*/
/** 保存排序后的apk顺序 */
fun updateApkOrder(userID: Int, dataList: List<AppInfo>) {
try {
AppManager.mRemarkSharedPreferences.edit().apply {
putString("AppList$userID",
dataList.joinToString(",") { it.packageName })
putString("AppList$userID", dataList.joinToString(",") { it.packageName })
apply()
}
} catch (e: Exception) {
@@ -1,59 +0,0 @@
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))
}
}
@@ -3,12 +3,12 @@ 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
/**
*
@@ -20,7 +20,7 @@ object InjectionUtil {
private val appsRepository = AppsRepository()
private val xpRepository = XpRepository()
private val gmsRepository = GmsRepository()
@@ -34,9 +34,6 @@ object InjectionUtil {
return ListFactory(appsRepository)
}
fun getXpFactory():XpFactory{
return XpFactory(xpRepository)
}
fun getGmsFactory():GmsFactory{
return GmsFactory(gmsRepository)
@@ -19,7 +19,6 @@ 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()
@@ -36,54 +35,49 @@ class ListActivity : BaseActivity() {
initToolbar(viewBinding.toolbarLayout.toolbar, R.string.installed_app, true)
mAdapter = RVAdapter<InstalledAppBean>(this,ListAdapter()).bind(viewBinding.recyclerView).setItemClickListener { _, item, _ ->
finishWithResult(item.packageName)
}
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
}
viewBinding.searchView.setOnQueryTextListener(
object : SimpleSearchView.OnQueryTextListener {
override fun onQueryTextChange(newText: String): Boolean {
filterApp(newText)
return true
}
override fun onQueryTextCleared(): Boolean {
return true
}
override fun onQueryTextCleared(): Boolean {
return true
}
override fun onQueryTextSubmit(query: String): 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 =
ViewModelProvider(this, InjectionUtil.getListFactory())
.get(ListViewModel::class.java)
val userID = intent.getIntExtra("userID", 0)
viewModel.getInstallAppList(userID)
viewBinding.toolbarLayout.toolbar.setTitle(R.string.installed_app)
viewModel.loadingLiveData.observe(this) {
if (it) {
viewBinding.stateView.showLoading()
} else {
viewBinding.stateView.showContent()
}
}
@@ -103,29 +97,26 @@ class ListActivity : BaseActivity() {
}
private fun filterApp(newText: String) {
val newList = this.appList.filter {
it.name.contains(newText, true) or it.packageName.contains(newText, true)
}
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 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)
}
window.peekDecorView()?.run { imm.hideSoftInputFromWindow(windowToken, 0) }
finish()
}
override fun onBackPressed() {
if (viewBinding.searchView.isSearchOpen) {
viewBinding.searchView.closeSearch()
@@ -157,12 +148,10 @@ class ListActivity : BaseActivity() {
viewModel.appsLiveData.removeObservers(this)
}
companion object{
fun start(context: Context,onlyShowXp:Boolean){
val intent = Intent(context,ListActivity::class.java)
intent.putExtra("onlyShowXp",onlyShowXp)
companion object {
fun start(context: Context) {
val intent = Intent(context, ListActivity::class.java)
context.startActivity(intent)
}
}
}
}
@@ -18,21 +18,10 @@ class ListViewModel(private val repo: AppsRepository) : BaseViewModel() {
val loadingLiveData = MutableLiveData<Boolean>()
fun previewInstalledList() {
launchOnUI{
repo.previewInstallList()
}
launchOnUI { repo.previewInstallList() }
}
fun getInstallAppList(userID:Int){
launchOnUI {
repo.getInstalledAppList(userID,loadingLiveData,appsLiveData)
}
fun getInstallAppList(userID: Int) {
launchOnUI { repo.getInstalledAppList(userID, loadingLiveData, appsLiveData) }
}
fun getInstalledModules() {
launchOnUI {
repo.getInstalledModuleList(loadingLiveData, appsLiveData)
}
}
}
}
@@ -3,6 +3,7 @@ package top.niunaijun.blackboxa.view.main
import android.app.Application
import android.content.Context
import android.util.Log
import java.io.File
import top.niunaijun.blackbox.BlackBoxCore
import top.niunaijun.blackbox.app.BActivityThread
import top.niunaijun.blackbox.app.configuration.AppLifecycleCallback
@@ -10,7 +11,6 @@ import top.niunaijun.blackbox.app.configuration.ClientConfiguration
import top.niunaijun.blackboxa.app.App
import top.niunaijun.blackboxa.app.rocker.RockerManager
import top.niunaijun.blackboxa.biz.cache.AppSharedPreferenceDelegate
import java.io.File
/**
*
@@ -21,7 +21,7 @@ import java.io.File
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)
@@ -42,23 +42,6 @@ class BlackBoxLoader {
}
}
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
@@ -104,70 +87,100 @@ class BlackBoxLoader {
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}")
}
}
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 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}")
}
}
override fun onStoragePermissionNeeded(packageName: String?, userId: Int): Boolean {
try {
Log.w(TAG, "Storage permission needed for launching: $packageName")
// Broadcast to request storage permission
// The main activity should listen for this and show permission dialog
val intent = android.content.Intent("top.niunaijun.blackboxa.REQUEST_STORAGE_PERMISSION")
intent.putExtra("package_name", packageName)
intent.putExtra("user_id", userId)
intent.setPackage(App.getContext().packageName)
App.getContext().sendBroadcast(intent)
// Return false to NOT block the launch - the app will launch anyway
// but the user will be notified to grant permission
// Change to 'true' if you want to block launch until permission is granted
return false
} catch (e: Exception) {
Log.e(TAG, "Error in onStoragePermissionNeeded: ${e.message}")
return false
}
}
})
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}"
)
}
}
override fun onStoragePermissionNeeded(
packageName: String?,
userId: Int
): Boolean {
try {
Log.w(
TAG,
"Storage permission needed for launching: $packageName"
)
// Broadcast to request storage permission
// The main activity should listen for this and show
// permission dialog
val intent =
android.content.Intent(
"top.niunaijun.blackboxa.REQUEST_STORAGE_PERMISSION"
)
intent.putExtra("package_name", packageName)
intent.putExtra("user_id", userId)
intent.setPackage(App.getContext().packageName)
App.getContext().sendBroadcast(intent)
// Return false to NOT block the launch - the app will
// launch anyway
// but the user will be notified to grant permission
// Change to 'true' if you want to block launch until
// permission is granted
return false
} catch (e: Exception) {
Log.e(
TAG,
"Error in onStoragePermissionNeeded: ${e.message}"
)
return false
}
}
}
)
} catch (e: Exception) {
Log.e(TAG, "Error adding lifecycle callback: ${e.message}")
}
@@ -175,58 +188,59 @@ class BlackBoxLoader {
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"
}
}
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 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 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
}
}
})
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}")
}
@@ -235,7 +249,7 @@ class BlackBoxLoader {
fun doOnCreate(context: Context) {
try {
BlackBoxCore.get().doCreate()
// Register callback to refresh app list when services become available
try {
BlackBoxCore.get().addServiceAvailableCallback {
@@ -254,4 +268,4 @@ class BlackBoxLoader {
companion object {
val TAG: String = BlackBoxLoader::class.java.simpleName
}
}
}
@@ -1,16 +1,13 @@
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
/**
*
@@ -20,37 +17,12 @@ import top.niunaijun.blackboxa.view.xp.XpActivity
*/
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{
invalidHideState {
val rootHidePreference: Preference = (findPreference("root_hide")!!)
val hideRoot = AppManager.mBlackBoxLoader.hideRoot()
rootHidePreference.setDefaultValue(hideRoot)
@@ -85,15 +57,10 @@ class SettingFragment : PreferenceFragmentCompat() {
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)
}
@@ -103,4 +70,4 @@ class SettingFragment : PreferenceFragmentCompat() {
return@setOnPreferenceChangeListener true
}
}
}
}
@@ -1,151 +0,0 @@
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)
}
}
}
@@ -1,42 +0,0 @@
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()
}
}
}
}
}
@@ -1,18 +0,0 @@
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
}
}
@@ -1,38 +0,0 @@
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)
}
}
}
+1 -15
View File
@@ -2,19 +2,7 @@
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<PreferenceCategory app:title="@string/xp_setting">
<SwitchPreferenceCompat
app:key="xp_enable"
app:title="@string/enable_xposed" />
<Preference
android:dependency="xp_enable"
app:key="xp_module"
app:summary="@string/jump_module"
app:title="@string/module_setting" />
</PreferenceCategory>
<PreferenceCategory app:title="@string/other">
@@ -23,9 +11,7 @@
android:title="@string/gms_manager"
android:summary="@string/jump_gms"/>
<SwitchPreferenceCompat
app:key="xp_hide"
app:title="@string/hide_xposed" />
<SwitchPreferenceCompat
app:key="root_hide"