mirror of
https://github.com/QM4RS/FridaBox.git
synced 2026-10-21 15:22:17 +02:00
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:
@@ -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}")
|
||||
}
|
||||
|
||||
+1
-6
@@ -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,
|
||||
|
||||
+1
-4
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user