feat: Enhance core framework with improved service faking, package management, and build system updates.

intigrated disable flag secure

remove comments cleanup codes
This commit is contained in:
ArtRuntime
2026-02-04 22:09:34 +05:30
parent e770372144
commit 9c744770f8
389 changed files with 4621 additions and 7834 deletions
@@ -6,13 +6,7 @@ import android.content.Context
import android.util.Log
import top.niunaijun.blackbox.BlackBoxCore
/**
*
* @Description:
* @Author: alex5402
* @CreateDate: Saturday, August 16, 2025
*
*/
class App : Application() {
companion object {
@@ -5,10 +5,7 @@ import android.content.SharedPreferences
import android.util.Log
import top.niunaijun.blackboxa.view.main.BlackBoxLoader
/**
* updated by alex5402 on 4/9/21.
* * ∧_∧ (`・ω・∥ 丶 つ0 しーJ
*/
object AppManager {
private const val TAG = "AppManager"
@@ -19,7 +16,7 @@ object AppManager {
} catch (e: Exception) {
Log.e(TAG, "Error creating BlackBoxLoader: ${e.message}")
BlackBoxLoader() // Try again, but this might fail
BlackBoxLoader()
}
}
@@ -29,7 +26,7 @@ object AppManager {
mBlackBoxLoader.getBlackBoxCore()
} catch (e: Exception) {
Log.e(TAG, "Error getting BlackBoxCore: ${e.message}")
throw e // Re-throw as this is critical
throw e
}
}
@@ -39,7 +36,7 @@ object AppManager {
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
throw e
}
}
@@ -49,7 +46,7 @@ object AppManager {
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
}
}
@@ -59,14 +56,14 @@ object AppManager {
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}")
}
@@ -4,12 +4,7 @@ import android.app.Activity
import android.app.Application
import android.os.Bundle
/**
*
* @Description:
* @Author: alex5402
* @CreateDate: Saturday, August 16, 2025
*/
interface BaseActivityLifecycleCallback : Application.ActivityLifecycleCallbacks {
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
@@ -16,58 +16,16 @@ import top.niunaijun.blackbox.fake.frameworks.BLocationManager
import top.niunaijun.blackboxa.app.App
import top.niunaijun.blackboxa.widget.EnFloatView
/**
* RockerManager - Advanced GPS Location Spoofing with Floating Joystick Control
*
* @Description: Provides a floating joystick/rocker control for real-time GPS location manipulation
* ```
* in virtual apps. Allows users to move their fake GPS location by dragging a joystick.
*
* @Features
* ```
* :
* - Floating joystick that appears on screen
* - Real-time GPS coordinate updates
* - Precise distance and angle control
* - App-specific location spoofing
* - Automatic activity lifecycle management
*
* @Usage:
* 1. RockerManager automatically initializes when virtual apps start
* 2. A floating joystick appears on the left side of the screen
* 3. Drag the joystick to change GPS location:
* ```
* - Distance: How far to move (in meters)
* - Angle: Direction to move (0-360 degrees)
* ```
* 4. Location updates happen in real-time as you move the joystick
*
* @Permissions Required:
* - SYSTEM_ALERT_WINDOW: To show floating view over other apps
* - ACCESS_FINE_LOCATION: To access GPS location services
* - ACCESS_COARSE_LOCATION: For approximate location access
*
* @Requirements:
* - Fake location must be enabled in BlackBox
* - User must grant overlay and location permissions
* - Virtual app must be running
*
* @Author: kotlinMiku
* @CreateDate: 2022/3/19 19:37
* @LastModified: 2024 - Enhanced with better error handling and permissions
*/
/**
* updated by alex5402 on 4/9/21.
* * ∧_∧ (`・ω・∥ 丶 つ0 しーJ
*/
object RockerManager {
private const val TAG = "RockerManager"
private var isInitialized = false
// Earth radius constants for coordinate calculations
private const val Ea = 6378137.0 // Equator radius (meters)
private const val Eb = 6356725.0 // Polar radius (meters)
private const val Ea = 6378137.0
private const val Eb = 6356725.0
fun init(application: Application?, userId: Int) {
try {
@@ -81,7 +39,7 @@ object RockerManager {
return
}
// Check if required permissions are granted
if (!checkPermissions(application)) {
Log.w(TAG, "Required permissions not granted, RockerManager cannot initialize")
Log.w(TAG, "Please grant: ${getRequiredPermissions().joinToString(", ")}")
@@ -106,7 +64,7 @@ object RockerManager {
return
}
// Register activity lifecycle callbacks for floating view management
application.registerActivityLifecycleCallbacks(
object : BaseActivityLifecycleCallback {
override fun onActivityStarted(activity: Activity) {
@@ -189,11 +147,11 @@ object RockerManager {
"Changing location - Distance: ${distance}m, Angle: ${angle}°, Current: ${location.latitude}, ${location.longitude}"
)
// Calculate new coordinates based on joystick input
val dx = distance * sin(angle * Math.PI / 180.0)
val dy = distance * cos(angle * Math.PI / 180.0)
// Use ellipsoid model for more accurate coordinate calculations
val ec = Eb + (Ea - Eb) * (90.0 - location.latitude) / 90.0
val ed = ec * cos(location.latitude * Math.PI / 180)
@@ -202,7 +160,7 @@ object RockerManager {
val newLocation = BLocation(newLat, newLng)
// Update the location
BLocationManager.get().setLocation(userId, packageName, newLocation)
Log.d(TAG, "Location updated - New: ${newLat}, ${newLng}")
@@ -212,15 +170,15 @@ object RockerManager {
}
}
/** Check if RockerManager is currently initialized and active */
fun isActive(): Boolean {
return isInitialized
}
/** Check if required permissions are granted for RockerManager to work */
fun checkPermissions(context: Context): Boolean {
return try {
// Check if overlay permission is granted (required for floating view)
val hasOverlayPermission = android.provider.Settings.canDrawOverlays(context)
if (!hasOverlayPermission) {
Log.w(
@@ -230,7 +188,7 @@ object RockerManager {
return false
}
// Check if location permissions are granted
val hasLocationPermission =
context.checkSelfPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) ==
android.content.pm.PackageManager.PERMISSION_GRANTED
@@ -247,7 +205,7 @@ object RockerManager {
}
}
/** Get a list of required permissions for RockerManager */
fun getRequiredPermissions(): List<String> {
return listOf(
android.Manifest.permission.SYSTEM_ALERT_WINDOW,
@@ -256,7 +214,7 @@ object RockerManager {
)
}
/** Clean up resources (useful for testing or when disabling the feature) */
fun cleanup() {
try {
isInitialized = false
@@ -2,10 +2,5 @@ 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)
@@ -1,11 +1,6 @@
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)
@@ -2,10 +2,5 @@ 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)
@@ -2,12 +2,7 @@ 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,
@@ -6,10 +6,7 @@ import androidx.core.content.edit
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
/**
* updated by alex5402 on 4/9/21.
* * ∧_∧ (`・ω・∥ 丶 つ0 しーJ
*/
open class AppSharedPreferenceDelegate<Data>(
context: Context,
private val default: Data,
@@ -15,32 +15,27 @@ import top.niunaijun.blackboxa.bean.InstalledAppBean
import top.niunaijun.blackboxa.util.MemoryManager
import top.niunaijun.blackboxa.util.getString
/**
*
* @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 {
BlackBoxCore.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
applicationInfo.packageName
}
}
/** 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,
@@ -51,10 +46,10 @@ class AppsRepository {
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 =
@@ -79,7 +74,7 @@ class AppsRepository {
}
} catch (e: Exception) {
Log.w(TAG, "Failed to load icon for ${applicationInfo.packageName}: ${e.message}")
null // Fallback to null icon
null
}
}
@@ -99,7 +94,7 @@ class AppsRepository {
if (!AbiUtils.isSupport(file)) continue
// Filter out BlackBox apps to prevent cloning
if (BlackBoxCore.get().isBlackBoxApp(installedApplication.packageName)) {
Log.d(
TAG,
@@ -115,7 +110,7 @@ class AppsRepository {
safeLoadAppLabel(installedApplication),
safeLoadAppIcon(
installedApplication
), // Remove the !! operator to allow null icons
),
installedApplication.packageName,
installedApplication.sourceDir,
isXpModule
@@ -150,7 +145,7 @@ class AppsRepository {
mInstalledList.map {
InstalledAppBean(
it.name,
it.icon, // Remove the !! operator to allow null icons
it.icon,
it.packageName,
it.sourceDir,
blackBoxCore.isInstalled(it.packageName, userID)
@@ -168,7 +163,7 @@ class AppsRepository {
fun getVmInstallList(userId: Int, appsLiveData: MutableLiveData<List<AppInfo>>) {
try {
// Check memory status before starting
if (MemoryManager.isMemoryCritical()) {
Log.w(
TAG,
@@ -179,7 +174,7 @@ class AppsRepository {
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}") }
@@ -187,7 +182,7 @@ class AppsRepository {
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
@@ -201,7 +196,7 @@ class AppsRepository {
"getVmInstallList: Attempt ${retryCount + 1} returned null, retrying..."
)
retryCount++
Thread.sleep(100) // Small delay before retry
Thread.sleep(100)
}
} catch (e: Exception) {
Log.e(
@@ -210,12 +205,12 @@ class AppsRepository {
)
retryCount++
if (retryCount < maxRetries) {
Thread.sleep(200) // Longer delay for errors
Thread.sleep(200)
}
}
}
// Add null check for applicationList
if (applicationList == null) {
Log.e(
TAG,
@@ -225,7 +220,7 @@ class AppsRepository {
return
}
// Add debugging
Log.d(
TAG,
"getVmInstallList: userId=$userId, applicationList.size=${applicationList.size}"
@@ -238,23 +233,23 @@ 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
applicationList
}
} else {
applicationList
}
// Process each application with enhanced error handling
sortedApplicationList.forEachIndexed { index, applicationInfo ->
try {
// Check memory periodically during processing
if (index > 0 && index % 25 == 0) {
if (MemoryManager.isMemoryCritical()) {
Log.w(TAG, "Memory critical during processing, forcing GC")
@@ -262,7 +257,7 @@ class AppsRepository {
}
}
// Add null check for applicationInfo
if (applicationInfo == null) {
Log.w(
TAG,
@@ -271,7 +266,7 @@ class AppsRepository {
return@forEachIndexed
}
// Validate package name
if (applicationInfo.packageName.isNullOrBlank()) {
Log.w(
TAG,
@@ -285,7 +280,7 @@ class AppsRepository {
safeLoadAppLabel(applicationInfo),
safeLoadAppIcon(
applicationInfo
), // Remove the !! operator to allow null icons
),
applicationInfo.packageName,
applicationInfo.sourceDir ?: "",
false
@@ -293,7 +288,7 @@ class AppsRepository {
appInfoList.add(info)
// Log progress for large lists
if (index > 0 && index % 50 == 0) {
Log.d(
TAG,
@@ -305,7 +300,7 @@ class AppsRepository {
TAG,
"getVmInstallList: Error processing app at index $index (${applicationInfo?.packageName}): ${e.message}"
)
// Continue processing other apps instead of failing completely
}
}
@@ -314,8 +309,8 @@ class AppsRepository {
"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,
@@ -328,12 +323,12 @@ class AppsRepository {
)
}
// Post the result safely
try {
appsLiveData.postValue(appInfoList)
} catch (e: Exception) {
Log.e(TAG, "getVmInstallList: Error posting to LiveData: ${e.message}")
// Try to post on main thread as fallback
try {
android.os.Handler(android.os.Looper.getMainLooper()).post {
try {
@@ -364,18 +359,18 @@ class AppsRepository {
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")
) {
// 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()) {
@@ -449,7 +444,7 @@ class AppsRepository {
}
}
/** 倒序递归扫描用户, 如果用户是空的,就删除用户,删除用户备注,删除应用排序列表 */
private fun scanUser() {
try {
val blackBoxCore = BlackBoxCore.get()
@@ -475,12 +470,7 @@ class AppsRepository {
}
}
/**
* 更新排序列表
* @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", "")
@@ -505,7 +495,7 @@ class AppsRepository {
}
}
/** 保存排序后的apk顺序 */
fun updateApkOrder(userID: Int, dataList: List<AppInfo>) {
try {
AppManager.mRemarkSharedPreferences.edit().apply {
@@ -2,12 +2,7 @@ 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) {
@@ -8,16 +8,7 @@ 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"
@@ -44,15 +35,15 @@ class FakeLocationRepository {
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,
@@ -8,12 +8,7 @@ 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 {
@@ -5,12 +5,7 @@ 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() {
@@ -10,12 +10,7 @@ import top.niunaijun.blackboxa.view.gms.GmsFactory
import top.niunaijun.blackboxa.view.list.ListFactory
/**
*
* @Description:
* @Author: wukaicheng
* @CreateDate: 2021/4/29 22:38
*/
object InjectionUtil {
private val appsRepository = AppsRepository()
@@ -8,44 +8,23 @@ 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;
@@ -56,22 +35,12 @@ public class MathUtil {
}
/**
* 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;
}
@@ -3,18 +3,15 @@ package top.niunaijun.blackboxa.util
import android.util.Log
import java.lang.Runtime
/** Memory management utility to prevent crashes during memory-intensive operations */
/**
* created by alex5402 on 4/9/21.
* * ∧_∧ (`・ω・∥ 丶 つ0 しーJ
*/
object MemoryManager {
private const val TAG = "MemoryManager"
private const val MEMORY_THRESHOLD = 0.8 // 80% memory usage threshold
private const val CRITICAL_MEMORY_THRESHOLD = 0.9 // 90% memory usage threshold
private const val MEMORY_THRESHOLD = 0.8
private const val CRITICAL_MEMORY_THRESHOLD = 0.9
/** Check if memory usage is within safe limits */
fun isMemorySafe(): Boolean {
return try {
val runtime = Runtime.getRuntime()
@@ -25,11 +22,11 @@ object MemoryManager {
memoryUsage < MEMORY_THRESHOLD
} catch (e: Exception) {
Log.e(TAG, "Error checking memory: ${e.message}")
true // Assume safe if we can't check
true
}
}
/** Check if memory usage is critical */
fun isMemoryCritical(): Boolean {
return try {
val runtime = Runtime.getRuntime()
@@ -40,11 +37,11 @@ object MemoryManager {
memoryUsage > CRITICAL_MEMORY_THRESHOLD
} catch (e: Exception) {
Log.e(TAG, "Error checking critical memory: ${e.message}")
false // Assume not critical if we can't check
false
}
}
/** Get current memory usage percentage */
fun getMemoryUsagePercentage(): Int {
return try {
val runtime = Runtime.getRuntime()
@@ -59,7 +56,7 @@ object MemoryManager {
}
}
/** Force garbage collection if memory usage is high */
fun forceGarbageCollectionIfNeeded(): Boolean {
return try {
if (isMemoryCritical()) {
@@ -68,7 +65,7 @@ object MemoryManager {
"Memory usage critical (${getMemoryUsagePercentage()}%), forcing garbage collection"
)
System.gc()
Thread.sleep(100) // Give GC time to work
Thread.sleep(100)
true
} else {
false
@@ -79,7 +76,7 @@ object MemoryManager {
}
}
/** Optimize memory for RecyclerView operations */
fun optimizeMemoryForRecyclerView() {
try {
val memoryUsage = getMemoryUsagePercentage()
@@ -87,10 +84,10 @@ object MemoryManager {
if (memoryUsage > 70) {
Log.d(TAG, "Memory usage high (${memoryUsage}%), optimizing for RecyclerView")
// Force garbage collection
System.gc()
// Clear any caches if possible
try {
val runtime = Runtime.getRuntime()
runtime.gc()
@@ -103,18 +100,18 @@ object MemoryManager {
}
}
/** Check if we should skip icon loading to save memory */
fun shouldSkipIconLoading(): Boolean {
return try {
val memoryUsage = getMemoryUsagePercentage()
memoryUsage > 75 // Skip icon loading if memory usage > 75%
memoryUsage > 75
} catch (e: Exception) {
Log.e(TAG, "Error checking if should skip icon loading: ${e.message}")
false
}
}
/** Get memory info for debugging */
fun getMemoryInfo(): String {
return try {
val runtime = Runtime.getRuntime()
@@ -19,74 +19,17 @@ 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
@@ -94,18 +37,18 @@ public class Resolution {
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();
@@ -114,13 +57,7 @@ public class Resolution {
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();
@@ -128,13 +65,7 @@ public class Resolution {
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();
@@ -142,11 +73,9 @@ public class Resolution {
return dp;
}
///////////////////////////////////////////////////////////////////////
/**
* 获取屏幕密度
*/
public static float getDensity(Context context) {
float density = 0f;
if (context== null) {
@@ -160,9 +89,7 @@ public class Resolution {
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();
@@ -173,50 +100,34 @@ public class Resolution {
}
}
/**
* 获取屏幕分辨率:宽
*/
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);
@@ -225,9 +136,7 @@ public class Resolution {
}
}
/**
* 显示软键盘
*/
public static void showInputMethod(View view) {
InputMethodManager imm = (InputMethodManager) view.getContext()
.getSystemService(Context.INPUT_METHOD_SERVICE);
@@ -236,11 +145,9 @@ public class Resolution {
}
}
/**
* 多少时间后显示软键盘
*/
public static void showInputMethod(final View view, long delayMillis) {
// 显示输入法
new Handler().postDelayed(new Runnable() {
@Override
@@ -251,9 +158,7 @@ public class Resolution {
}, delayMillis);
}
/**
* 判断手机是否在锁屏状态 true锁屏 false未锁屏
*/
public static boolean isScreenLocked(Context c) {
KeyguardManager mKeyguardManager = (KeyguardManager) c
.getSystemService(Context.KEYGUARD_SERVICE);
@@ -266,7 +171,7 @@ public class Resolution {
Class<?> c = null;
Object obj = null;
Field field = null;
int x = 0, sbar = 38;//默认为38,貌似大部分是这样的
int x = 0, sbar = 38;
try {
c = Class.forName("com.android.internal.R$dimen");
@@ -280,23 +185,23 @@ public class Resolution {
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();
}
@@ -306,18 +211,18 @@ public class Resolution {
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 {
@@ -15,20 +15,11 @@ 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)) {
@@ -5,12 +5,7 @@ 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){
@@ -7,12 +7,7 @@ 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)
@@ -18,18 +18,13 @@ import android.graphics.Color
import android.view.ViewTreeObserver
import androidx.recyclerview.widget.RecyclerView
/**
*
* @Description: 软件显示界面适配器
* @Author: wukaicheng
* @CreateDate: 2021/4/29 21:52
*/
class AppsAdapter : RVHolderFactory() {
companion object {
private const val TAG = "AppsAdapter"
private const val MAX_ICON_SIZE = 96 // 48dp * 2 for high density
private const val MAX_ICON_SIZE = 96
private val DEFAULT_ICON_COLOR = Color.parseColor("#CCCCCC")
}
@@ -38,7 +33,7 @@ class AppsAdapter : RVHolderFactory() {
AppsVH(inflate(R.layout.item_app, parent))
} catch (e: Exception) {
Log.e(TAG, "Error creating ViewHolder: ${e.message}")
// Return a fallback ViewHolder to prevent crash
FallbackAppsVH(inflate(R.layout.item_app, parent))
}
}
@@ -50,10 +45,10 @@ class AppsAdapter : RVHolderFactory() {
init {
try {
// Optimize icon loading for better scrolling performance
binding.icon.scaleType = ImageView.ScaleType.CENTER_CROP
// Add view tree observer to optimize icon loading
itemView.viewTreeObserver.addOnPreDrawListener(object : ViewTreeObserver.OnPreDrawListener {
override fun onPreDraw(): Boolean {
if (isAttached) {
@@ -69,20 +64,20 @@ class AppsAdapter : RVHolderFactory() {
override fun setContent(item: AppInfo, isSelected: Boolean, payload: Any?) {
try {
// Safely set the icon with optimized loading
setIconSafely(item.icon, item.packageName)
// Safely set the name
binding.name.text = item.name ?: "Unknown App"
// Handle XP module label
if (item.isXpModule) {
binding.cornerLabel.visibility = View.VISIBLE
} else {
binding.cornerLabel.visibility = View.INVISIBLE
}
// Mark as attached for optimization
isAttached = true
} catch (e: Exception) {
@@ -94,12 +89,12 @@ class AppsAdapter : RVHolderFactory() {
private fun setIconSafely(icon: Drawable?, packageName: String) {
try {
if (icon != null) {
// Optimize icon for memory efficiency
val optimizedIcon = optimizeIcon(icon)
binding.icon.setImageDrawable(optimizedIcon)
currentIcon = optimizedIcon
} else {
// Set a default placeholder icon
binding.icon.setImageDrawable(createDefaultIcon())
currentIcon = null
}
@@ -112,11 +107,11 @@ class AppsAdapter : RVHolderFactory() {
private fun optimizeIcon(icon: Drawable): Drawable {
return try {
// If it's a BitmapDrawable, ensure it's not too large
if (icon is BitmapDrawable) {
val bitmap = icon.bitmap
if (bitmap.width > MAX_ICON_SIZE || bitmap.height > MAX_ICON_SIZE) {
// Create a scaled down version
val scaledBitmap = Bitmap.createScaledBitmap(
bitmap, MAX_ICON_SIZE, MAX_ICON_SIZE, true
)
@@ -153,13 +148,13 @@ class AppsAdapter : RVHolderFactory() {
}
}
// Fallback ViewHolder for emergency cases
class FallbackAppsVH(itemView: View) : RVHolder<AppInfo>(itemView) {
val binding = ItemAppBinding.bind(itemView)
override fun setContent(item: AppInfo, isSelected: Boolean, payload: Any?) {
try {
// Set minimal content to prevent crash
binding.icon.setImageDrawable(ColorDrawable(DEFAULT_ICON_COLOR))
binding.name.text = item.name ?: "Unknown App"
binding.cornerLabel.visibility = View.INVISIBLE
@@ -4,12 +4,7 @@ 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() {
@@ -32,12 +32,7 @@ import java.util.*
import kotlin.math.abs
/**
*
* @Description:
* @Author: wukaicheng
* @CreateDate: 2021/4/29 22:21
*/
class AppsFragment : Fragment() {
var userID: Int = 0
@@ -85,33 +80,33 @@ class AppsFragment : Fragment() {
viewBinding.recyclerView.adapter = mAdapter
// Enhanced RecyclerView configuration for better performance and crash prevention
val layoutManager = GridLayoutManager(requireContext(), 4)
layoutManager.isItemPrefetchEnabled = true
layoutManager.initialPrefetchItemCount = 8
viewBinding.recyclerView.layoutManager = layoutManager
// Enable view cache for better scrolling performance
viewBinding.recyclerView.setItemViewCacheSize(20)
viewBinding.recyclerView.setHasFixedSize(true)
// Add scroll listener for crash detection and prevention
viewBinding.recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
try {
super.onScrollStateChanged(recyclerView, newState)
when (newState) {
RecyclerView.SCROLL_STATE_IDLE -> {
// Scrolling stopped, optimize memory
MemoryManager.optimizeMemoryForRecyclerView()
}
RecyclerView.SCROLL_STATE_DRAGGING -> {
// User is scrolling, ensure smooth performance
// Note: Drawing cache is deprecated, using modern alternatives
}
RecyclerView.SCROLL_STATE_SETTLING -> {
// Scrolling is settling, prepare for idle state
// Note: Drawing cache is deprecated, using modern alternatives
}
}
} catch (e: Exception) {
@@ -122,11 +117,11 @@ class AppsFragment : Fragment() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
try {
super.onScrolled(recyclerView, dx, dy)
// Monitor scroll performance
if (Math.abs(dy) > 100) {
// Fast scrolling detected, optimize memory
// Check memory usage during fast scrolling
if (MemoryManager.isMemoryCritical()) {
Log.w(TAG, "Memory critical during fast scrolling, forcing GC")
MemoryManager.forceGarbageCollectionIfNeeded()
@@ -165,7 +160,7 @@ class AppsFragment : Fragment() {
return viewBinding.root
} catch (e: Exception) {
Log.e(TAG, "Error in onCreateView: ${e.message}")
// Return a simple view to prevent crash
return View(requireContext())
}
}
@@ -183,11 +178,11 @@ class AppsFragment : Fragment() {
try {
super.onStart()
// Register callback to refresh app list when services become available
try {
BlackBoxCore.get().addServiceAvailableCallback {
Log.d(TAG, "Services became available, refreshing app list")
// Refresh the app list when services are ready
viewModel.getInstalledAppsWithRetry(userID)
}
} catch (e: Exception) {
@@ -200,9 +195,7 @@ class AppsFragment : Fragment() {
}
}
/**
* 拖拽优化
*/
private fun interceptTouch() {
try {
val point = Point()
@@ -213,7 +206,7 @@ class AppsFragment : Fragment() {
try {
when (e.action) {
MotionEvent.ACTION_DOWN -> {
// Reset scroll state
isScrolling = false
scrollStartTime = System.currentTimeMillis()
point.set(0, 0)
@@ -222,7 +215,7 @@ class AppsFragment : Fragment() {
MotionEvent.ACTION_UP -> {
val scrollDuration = System.currentTimeMillis() - scrollStartTime
// Only show popup if it wasn't a scroll gesture
if (!isScrolling && !isMove(point, e) && scrollDuration < 500) {
try {
popupMenu?.show()
@@ -242,13 +235,13 @@ class AppsFragment : Fragment() {
point.y = e.rawY.toInt()
}
// Check if this is a scroll gesture
if (isMove(point, e)) {
isScrolling = true
popupMenu?.dismiss()
}
// Handle float button visibility
isDownAndUp(point, e)
}
}
@@ -298,7 +291,7 @@ class AppsFragment : Fragment() {
private fun onItemMove(fromPosition: Int, toPosition: Int) {
try {
// Validate positions to prevent crashes
val items = mAdapter.getItems()
if (fromPosition < 0 || toPosition < 0 ||
fromPosition >= items.size || toPosition >= items.size) {
@@ -330,7 +323,7 @@ class AppsFragment : Fragment() {
mAdapter.notifyItemMoved(fromPosition, toPosition)
} catch (e: Exception) {
Log.e(TAG, "Error notifying item moved: ${e.message}")
// Fallback to full refresh if move notification fails
mAdapter.notifyDataSetChanged()
}
} catch (e: Exception) {
@@ -474,10 +467,7 @@ class AppsFragment : Fragment() {
}
}
/**
* 强行停止软件
* @param info AppInfo
*/
private fun stopApk(info: AppInfo) {
try {
MaterialDialog(requireContext()).show {
@@ -498,10 +488,7 @@ class AppsFragment : Fragment() {
}
}
/**
* 清除软件数据
* @param info AppInfo
*/
private fun clearApk(info: AppInfo) {
try {
MaterialDialog(requireContext()).show {
@@ -23,7 +23,7 @@ class AppsTouchCallBack(private val onMoveBlock: (from: Int, to: Int) -> Unit) :
)
} catch (e: Exception) {
Log.e(TAG, "Error getting movement flags: ${e.message}")
// Return safe default flags
makeMovementFlags(0, 0)
}
}
@@ -37,15 +37,15 @@ class AppsTouchCallBack(private val onMoveBlock: (from: Int, to: Int) -> Unit) :
val fromPosition = viewHolder.bindingAdapterPosition
val toPosition = target.bindingAdapterPosition
// Validate positions
if (fromPosition == RecyclerView.NO_POSITION || toPosition == RecyclerView.NO_POSITION) {
Log.w(TAG, "Invalid positions: from=$fromPosition, to=$toPosition")
false
} else if (fromPosition == toPosition) {
// Prevent moving to the same position
false
} else {
// Call the move callback
onMoveBlock(fromPosition, toPosition)
true
}
@@ -56,7 +56,7 @@ class AppsTouchCallBack(private val onMoveBlock: (from: Int, to: Int) -> Unit) :
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
// Not implemented for this use case - drag and drop only
}
override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
@@ -65,11 +65,11 @@ class AppsTouchCallBack(private val onMoveBlock: (from: Int, to: Int) -> Unit) :
when (actionState) {
ItemTouchHelper.ACTION_STATE_DRAG -> {
// Item is being dragged
viewHolder?.itemView?.alpha = 0.8f
}
ItemTouchHelper.ACTION_STATE_IDLE -> {
// Item is no longer being dragged
viewHolder?.itemView?.alpha = 1.0f
}
}
@@ -81,7 +81,7 @@ class AppsTouchCallBack(private val onMoveBlock: (from: Int, to: Int) -> Unit) :
override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
try {
super.clearView(recyclerView, viewHolder)
// Reset the view to its normal state
viewHolder.itemView.alpha = 1.0f
} catch (e: Exception) {
Log.e(TAG, "Error in clearView: ${e.message}")
@@ -94,7 +94,7 @@ class AppsTouchCallBack(private val onMoveBlock: (from: Int, to: Int) -> Unit) :
target: RecyclerView.ViewHolder
): Boolean {
return try {
// Only allow dropping on valid targets
val targetPosition = target.bindingAdapterPosition
targetPosition != RecyclerView.NO_POSITION
} catch (e: Exception) {
@@ -6,12 +6,7 @@ import top.niunaijun.blackboxa.data.AppsRepository
import top.niunaijun.blackboxa.view.base.BaseViewModel
import android.util.Log
/**
*
* @Description:
* @Author: wukaicheng
* @CreateDate: 2021/4/29 22:36
*/
class AppsViewModel(private val repo: AppsRepository) : BaseViewModel() {
val appsLiveData = MutableLiveData<List<AppInfo>>()
@@ -20,7 +15,7 @@ class AppsViewModel(private val repo: AppsRepository) : BaseViewModel() {
val launchLiveData = MutableLiveData<Boolean>()
//利用LiveData只更新最后一次的特性,用来保存app顺序
val updateSortLiveData = MutableLiveData<Boolean>()
fun getInstalledApps(userId: Int) {
@@ -29,9 +24,7 @@ class AppsViewModel(private val repo: AppsRepository) : BaseViewModel() {
}
}
/**
* Get installed apps with retry mechanism for when services are not ready
*/
fun getInstalledAppsWithRetry(userId: Int, maxRetries: Int = 3) {
var retryCount = 0
@@ -39,16 +32,16 @@ class AppsViewModel(private val repo: AppsRepository) : BaseViewModel() {
launchOnUI {
repo.getVmInstallList(userId, appsLiveData)
// Check if we got any apps, if not and we haven't exceeded retries, try again
val currentApps = appsLiveData.value
if ((currentApps == null || currentApps.isEmpty()) && retryCount < maxRetries) {
retryCount++
Log.d("AppsViewModel", "No apps loaded, retrying... (${retryCount}/${maxRetries})")
// Wait a bit before retrying
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
attemptLoad()
}, 1000) // Wait 1 second before retry
}, 1000)
}
}
}
@@ -3,12 +3,7 @@ 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) {
@@ -4,12 +4,7 @@ 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) {
@@ -4,12 +4,7 @@ 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
@@ -10,12 +10,7 @@ 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() {
@@ -4,11 +4,7 @@ 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() {
@@ -6,11 +6,7 @@ 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>>()
@@ -22,17 +22,13 @@ 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
@@ -19,11 +19,7 @@ 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"
@@ -36,19 +32,19 @@ class FollowMyLocationOverlay : AppCompatActivity() {
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")
@@ -82,7 +78,7 @@ class FollowMyLocationOverlay : AppCompatActivity() {
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)
}
@@ -93,20 +89,20 @@ class FollowMyLocationOverlay : AppCompatActivity() {
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
binding.map.onResume()
}
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
binding.map.onPause()
}
override fun onRequestPermissionsResult(
@@ -8,12 +8,7 @@ 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> {
@@ -4,12 +4,7 @@ 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 {
@@ -16,12 +16,7 @@ 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
@@ -6,12 +6,7 @@ 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>>()
@@ -8,12 +8,7 @@ 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() {
@@ -4,12 +4,7 @@ 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() {
@@ -5,12 +5,7 @@ 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>>()
@@ -12,12 +12,7 @@ import top.niunaijun.blackboxa.app.App
import top.niunaijun.blackboxa.app.rocker.RockerManager
import top.niunaijun.blackboxa.biz.cache.AppSharedPreferenceDelegate
/**
*
* @Description:
* @Author: wukaicheng
* @CreateDate: 2021/5/6 23:38
*/
class BlackBoxLoader {
private var mHideRoot by AppSharedPreferenceDelegate(App.getContext(), false)
@@ -25,9 +20,11 @@ class BlackBoxLoader {
private var mDaemonEnable by AppSharedPreferenceDelegate(App.getContext(), false)
private var mShowShortcutPermissionDialog by AppSharedPreferenceDelegate(App.getContext(), true)
// VPN network mode: true = use VPN, false = normal network
private var mUseVpnNetwork by AppSharedPreferenceDelegate(App.getContext(), false)
private var mDisableFlagSecure by AppSharedPreferenceDelegate(App.getContext(), false)
fun hideRoot(): Boolean {
return try {
mHideRoot
@@ -45,6 +42,23 @@ class BlackBoxLoader {
}
}
fun disableFlagSecure(): Boolean {
return try {
mDisableFlagSecure
} catch (e: Exception) {
Log.e(TAG, "Error getting disableFlagSecure: ${e.message}")
false
}
}
fun invalidDisableFlagSecure(disable: Boolean) {
try {
this.mDisableFlagSecure = disable
} catch (e: Exception) {
Log.e(TAG, "Error setting disableFlagSecure: ${e.message}")
}
}
fun daemonEnable(): Boolean {
return try {
mDaemonEnable
@@ -174,9 +188,9 @@ class BlackBoxLoader {
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"
@@ -185,11 +199,11 @@ class BlackBoxLoader {
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(
@@ -248,6 +262,15 @@ class BlackBoxLoader {
}
}
override fun isDisableFlagSecure(): Boolean {
return try {
mDisableFlagSecure
} catch (e: Exception) {
Log.e(TAG, "Error checking disableFlagSecure: ${e.message}")
false
}
}
override fun requestInstallPackage(
file: File?,
userId: Int
@@ -279,12 +302,12 @@ class BlackBoxLoader {
try {
BlackBoxCore.get().doCreate()
// Register callback to refresh app list when services become available
try {
BlackBoxCore.get().addServiceAvailableCallback {
Log.d(TAG, "Services became available, triggering app list refresh")
// This will be called when services are ready
// The UI components can listen for this and refresh their data
}
} catch (e: Exception) {
Log.e(TAG, "Error registering service available callback: ${e.message}")
@@ -63,10 +63,10 @@ class MainActivity : LoadingActivity() {
initFab()
initToolbarSubTitle()
// Check and request storage permission on Android 11+
checkStoragePermission()
// Check and request VPN permission
checkVpnPermission()
try {
@@ -76,7 +76,7 @@ class MainActivity : LoadingActivity() {
}
} catch (e: Exception) {
Log.e(TAG, "Critical error in onCreate: ${e.message}")
// Show error dialog to user
showErrorDialog("Failed to initialize app: ${e.message}")
}
}
@@ -84,13 +84,13 @@ class MainActivity : LoadingActivity() {
private fun checkStoragePermission() {
try {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
// Android 11+: Need MANAGE_EXTERNAL_STORAGE
if (!android.os.Environment.isExternalStorageManager()) {
Log.w(TAG, "MANAGE_EXTERNAL_STORAGE permission not granted")
showStoragePermissionDialog()
}
} else {
// Android 10 and below: Need READ/WRITE_EXTERNAL_STORAGE
if (androidx.core.content.ContextCompat.checkSelfPermission(
this,
android.Manifest.permission.WRITE_EXTERNAL_STORAGE
@@ -176,7 +176,7 @@ class MainActivity : LoadingActivity() {
}
} catch (e: Exception) {
Log.e(TAG, "Error opening storage settings: ${e.message}")
// Fallback: open general manage all files settings
try {
val intent =
Intent(android.provider.Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION)
@@ -202,19 +202,16 @@ class MainActivity : LoadingActivity() {
}
}
/**
* Check and request VPN permission if needed. VpnService.prepare() must be called from an
* Activity before VPN can be established.
*/
private fun checkVpnPermission() {
try {
val vpnIntent = VpnService.prepare(this)
if (vpnIntent != null) {
// User needs to grant VPN permission
Log.d(TAG, "VPN permission not granted, requesting...")
vpnPermissionResult.launch(vpnIntent)
} else {
// VPN permission already granted
Log.d(TAG, "VPN permission already granted")
}
} catch (e: Exception) {
@@ -227,7 +224,7 @@ class MainActivity : LoadingActivity() {
try {
if (result.resultCode == RESULT_OK) {
Log.d(TAG, "VPN permission granted!")
// VPN service will now be able to establish connection
} else {
Log.w(TAG, "VPN permission denied by user")
}
@@ -252,7 +249,7 @@ class MainActivity : LoadingActivity() {
private fun initToolbarSubTitle() {
try {
updateUserRemark(0)
// hack code
viewBinding.toolbarLayout.toolbar.getChildAt(1)?.setOnClickListener {
try {
MaterialDialog(this).show {
@@ -421,7 +418,7 @@ class MainActivity : LoadingActivity() {
startActivity(intent)
}
R.id.fake_location -> {
// toast("Still Developing")
val intent = Intent(this, FakeManagerActivity::class.java)
intent.putExtra("userID", 0)
startActivity(intent)
@@ -6,12 +6,7 @@ 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?) {
@@ -5,12 +5,7 @@ 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) {
@@ -37,6 +37,13 @@ class SettingFragment : PreferenceFragmentCompat() {
vpnPreference
}
invalidHideState {
val disableFlagSecurePreference: Preference = (findPreference("disable_flag_secure")!!)
val mDisableFlagSecure = AppManager.mBlackBoxLoader.disableFlagSecure()
disableFlagSecurePreference.setDefaultValue(mDisableFlagSecure)
disableFlagSecurePreference
}
initSendLogs()
}
@@ -70,6 +77,9 @@ class SettingFragment : PreferenceFragmentCompat() {
"use_vpn_network" -> {
AppManager.mBlackBoxLoader.invalidUseVpnNetwork(tmpHide)
}
"disable_flag_secure" -> {
AppManager.mBlackBoxLoader.invalidDisableFlagSecure(tmpHide)
}
}
toast(R.string.restart_module)
@@ -79,7 +89,21 @@ class SettingFragment : PreferenceFragmentCompat() {
private fun initSendLogs() {
val sendLogsPreference: Preference? = findPreference("send_logs")
sendLogsPreference?.setOnPreferenceClickListener {
BlackBoxCore.get().sendLogs("Manual Log Upload from Settings", true)
it.isEnabled = false
BlackBoxCore.get()
.sendLogs(
"Manual Log Upload from Settings",
true,
object : BlackBoxCore.LogSendListener {
override fun onSuccess() {
activity?.runOnUiThread { sendLogsPreference.isEnabled = true }
}
override fun onFailure(error: String?) {
activity?.runOnUiThread { sendLogsPreference.isEnabled = true }
}
}
)
toast("Sending logs... (Check notifications for status)")
true
}
@@ -5,12 +5,7 @@ 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"
@@ -31,7 +26,7 @@ class EnFloatView(mContext: Context) : FloatingMagnetView(mContext) {
if (type == RockerView.EVENT_CLOCK && currentAngle != -1F) {
val realAngle = currentAngle
val realDistance = currentDistance * 0.001F
//拉满的话,大概就是一秒五米
mListener?.invoke(realAngle, realDistance)
@@ -21,14 +21,7 @@ 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;
@@ -48,19 +41,10 @@ public class RockerView extends SurfaceView implements Runnable, SurfaceHolder.C
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;
@@ -83,7 +67,7 @@ public class RockerView extends SurfaceView implements Runnable, SurfaceHolder.C
private int mCallbackCycle = DEFAULT_CALLBACK_CYCLE;
/*Life Cycle***********************************************************************************/
public RockerView(Context context) {
this(context, null);
@@ -96,20 +80,20 @@ public class RockerView extends SurfaceView implements Runnable, SurfaceHolder.C
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();
}
@@ -127,16 +111,16 @@ public class RockerView extends SurfaceView implements Runnable, SurfaceHolder.C
}
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
setKeepScreenOn(true);
setFocusable(true);
setFocusableInTouchMode(true);
setZOrderOnTop(true);
}
private void configSurfaceHolder() {
mHolder = getHolder();
mHolder.addCallback(this);
mHolder.setFormat(PixelFormat.TRANSPARENT); //设置背景透明
mHolder.setFormat(PixelFormat.TRANSPARENT);
}
@Override
@@ -145,11 +129,11 @@ public class RockerView extends SurfaceView implements Runnable, SurfaceHolder.C
int defaultWidth = (mAreaRadius + mRockerRadius) * 2;
int defalutHeight = defaultWidth;
int widthsize = MeasureSpec.getSize(widthMeasureSpec); //取出宽度的确切数值
int widthmode = MeasureSpec.getMode(widthMeasureSpec); //取出宽度的测量模式
int widthsize = MeasureSpec.getSize(widthMeasureSpec);
int widthmode = MeasureSpec.getMode(widthMeasureSpec);
int heightsize = MeasureSpec.getSize(heightMeasureSpec); //取出高度的确切数值
int heightmode = MeasureSpec.getMode(heightMeasureSpec); //取出高度的测量模式
int heightsize = MeasureSpec.getSize(heightMeasureSpec);
int heightmode = MeasureSpec.getMode(heightMeasureSpec);
if (widthmode == MeasureSpec.AT_MOST || widthmode == MeasureSpec.UNSPECIFIED || widthsize < 0) {
measureWidth = defaultWidth;
@@ -174,7 +158,7 @@ public class RockerView extends SurfaceView implements Runnable, SurfaceHolder.C
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)
@@ -192,7 +176,7 @@ public class RockerView extends SurfaceView implements Runnable, SurfaceHolder.C
mCallbackThread = new Thread(() -> {
while (mCallbackOk) {
// listener callback
listenerCallback();
try {
@@ -231,7 +215,7 @@ public class RockerView extends SurfaceView implements Runnable, SurfaceHolder.C
}
}
/*Event Response*******************************************************************************/
@Override
public boolean onTouchEvent(MotionEvent event) {
@@ -239,7 +223,7 @@ public class RockerView extends SurfaceView implements Runnable, SurfaceHolder.C
int len = MathUtil.getDistance(mAreaPosition.x, mAreaPosition.y, event.getX(), event.getY());
if (event.getAction() == MotionEvent.ACTION_DOWN) {
//如果屏幕接触点不在摇杆挥动范围内,则不处理
if (len > mAreaRadius) {
return true;
}
@@ -247,11 +231,11 @@ public class RockerView extends SurfaceView implements Runnable, SurfaceHolder.C
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);
}
@@ -262,7 +246,7 @@ public class RockerView extends SurfaceView implements Runnable, SurfaceHolder.C
mListener.callback(EVENT_ACTION, angle, distance);
}
}
//如果手指离开屏幕,则摇杆返回初始位置
if (event.getAction() == MotionEvent.ACTION_UP) {
mRockerPosition = new Point(mAreaPosition);
if (mListener != null) {
@@ -276,7 +260,7 @@ public class RockerView extends SurfaceView implements Runnable, SurfaceHolder.C
}
/*Thread - draw view***************************************************************************/
@Override
public void run() {
@@ -296,7 +280,7 @@ public class RockerView extends SurfaceView implements Runnable, SurfaceHolder.C
drawArea(canvas);
drawRocker(canvas);
}
Thread.sleep(mRefreshCycle); // 休眠
Thread.sleep(mRefreshCycle);
} catch (Exception e) {
e.printStackTrace();
@@ -354,12 +338,12 @@ public class RockerView extends SurfaceView implements Runnable, SurfaceHolder.C
}
}
//获取摇杆偏移角度 上方中间为0,左为负,右为正
private float getAngleConvert(float radian) {
return 90 + Math.round(radian / Math.PI * 180);
}
// for preview
@Override
protected void onDraw(Canvas canvas) {
if (isInEditMode()) {
@@ -369,7 +353,7 @@ public class RockerView extends SurfaceView implements Runnable, SurfaceHolder.C
}
}
/*Getter Setter********************************************************************************/
public void setCanMove(boolean isMove) {
this.canMove = isMove;
@@ -445,20 +429,12 @@ public class RockerView extends SurfaceView implements Runnable, SurfaceHolder.C
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);
}