mirror of
https://github.com/QM4RS/FridaBox.git
synced 2026-10-03 14:32:03 +02:00
3.0.7r3
This commit is contained in:
@@ -6,10 +6,6 @@
|
||||
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
|
||||
tools:ignore="QueryAllPackagesPermission" />
|
||||
|
||||
<!-- Internet permission for apps that need network access -->
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
|
||||
<application
|
||||
android:name=".app.App"
|
||||
android:allowBackup="true"
|
||||
|
||||
@@ -9,8 +9,8 @@ import top.niunaijun.blackbox.BlackBoxCore
|
||||
/**
|
||||
*
|
||||
* @Description:
|
||||
* @Author: wukaicheng
|
||||
* @CreateDate: 2021/4/29 21:21
|
||||
* @Author: alex5402
|
||||
* @CreateDate: Saturday, August 16, 2025
|
||||
*/
|
||||
class App : Application() {
|
||||
|
||||
@@ -30,7 +30,6 @@ class App : Application() {
|
||||
try {
|
||||
super.attachBaseContext(base)
|
||||
|
||||
// Initialize BlackBoxCore with error handling
|
||||
try {
|
||||
BlackBoxCore.get().closeCodeInit()
|
||||
} catch (e: Exception) {
|
||||
@@ -44,7 +43,7 @@ class App : Application() {
|
||||
}
|
||||
|
||||
mContext = base!!
|
||||
|
||||
|
||||
try {
|
||||
AppManager.doAttachBaseContext(base)
|
||||
} catch (e: Exception) {
|
||||
@@ -52,13 +51,16 @@ class App : Application() {
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
BlackBoxCore.get().onAfterMainApplicationAttach(this, base)
|
||||
|
||||
} catch (e: Exception) {
|
||||
|
||||
Log.e("App", "Error in onAfterMainApplicationAttach: ${e.message}")
|
||||
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("App", "Critical error in attachBaseContext: ${e.message}")
|
||||
// Ensure we still set the context even if other initialization fails
|
||||
if (base != null) {
|
||||
mContext = base
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package top.niunaijun.blackboxa.app
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.util.Log
|
||||
import top.niunaijun.blackbox.BlackBoxCore
|
||||
import top.niunaijun.blackboxa.view.main.BlackBoxLoader
|
||||
|
||||
object AppManager {
|
||||
|
||||
+2
-2
@@ -7,8 +7,8 @@ import android.os.Bundle
|
||||
/**
|
||||
*
|
||||
* @Description:
|
||||
* @Author: kotlinMiku
|
||||
* @CreateDate: 2022/3/19 20:08
|
||||
* @Author: alex5402
|
||||
* @CreateDate: Saturday, August 16, 2025
|
||||
*/
|
||||
interface BaseActivityLifecycleCallback : Application.ActivityLifecycleCallbacks {
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package top.niunaijun.blackboxa.app.rocker
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import android.view.Gravity
|
||||
import android.widget.FrameLayout
|
||||
@@ -16,84 +17,227 @@ import kotlin.math.cos
|
||||
import kotlin.math.sin
|
||||
|
||||
/**
|
||||
*
|
||||
* @Description:
|
||||
* 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
|
||||
*/
|
||||
object RockerManager {
|
||||
|
||||
private const val TAG = "RockerManager"
|
||||
private var isInitialized = false
|
||||
|
||||
|
||||
private const val Ea = 6378137 // 赤道半径
|
||||
|
||||
private const val Eb = 6356725 // 极半径
|
||||
// Earth radius constants for coordinate calculations
|
||||
private const val Ea = 6378137.0 // Equator radius (meters)
|
||||
private const val Eb = 6356725.0 // Polar radius (meters)
|
||||
|
||||
fun init(application: Application?, userId: Int) {
|
||||
try {
|
||||
if (isInitialized) {
|
||||
Log.d(TAG, "RockerManager already initialized, skipping...")
|
||||
return
|
||||
}
|
||||
|
||||
if (application == null || !BLocationManager.isFakeLocationEnable()) {
|
||||
return
|
||||
if (application == null) {
|
||||
Log.w(TAG, "Application is null, cannot initialize 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(", ")}")
|
||||
return
|
||||
}
|
||||
|
||||
if (!BLocationManager.isFakeLocationEnable()) {
|
||||
Log.d(TAG, "Fake location is not enabled, RockerManager will not initialize")
|
||||
return
|
||||
}
|
||||
|
||||
Log.d(TAG, "Initializing RockerManager for userId: $userId")
|
||||
|
||||
val enFloatView = initFloatView()
|
||||
if (enFloatView is EnFloatView) {
|
||||
enFloatView.setListener { angle: Float, distance: Float ->
|
||||
changeLocation(distance, angle, application.packageName, userId)
|
||||
}
|
||||
Log.d(TAG, "Floating view initialized successfully")
|
||||
} else {
|
||||
Log.w(TAG, "Failed to initialize floating view")
|
||||
return
|
||||
}
|
||||
|
||||
// Register activity lifecycle callbacks for floating view management
|
||||
application.registerActivityLifecycleCallbacks(object : BaseActivityLifecycleCallback {
|
||||
override fun onActivityStarted(activity: Activity) {
|
||||
super.onActivityStarted(activity)
|
||||
try {
|
||||
FloatingView.get().attach(activity)
|
||||
Log.d(TAG, "Floating view attached to activity: ${activity.javaClass.simpleName}")
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error attaching floating view to activity: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActivityStopped(activity: Activity) {
|
||||
super.onActivityStopped(activity)
|
||||
try {
|
||||
FloatingView.get().detach(activity)
|
||||
Log.d(TAG, "Floating view detached from activity: ${activity.javaClass.simpleName}")
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error detaching floating view from activity: ${e.message}")
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
isInitialized = true
|
||||
Log.d(TAG, "RockerManager initialized successfully - Floating GPS joystick is now active!")
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error initializing RockerManager: ${e.message}")
|
||||
Log.e(TAG, "Stack trace: ", e)
|
||||
}
|
||||
|
||||
val enFloatView = initFloatView()
|
||||
|
||||
if (enFloatView is EnFloatView) {
|
||||
enFloatView.setListener { angle: Float, distance: Float ->
|
||||
changeLocation(distance, angle, application.packageName,userId)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
application.registerActivityLifecycleCallbacks(object : BaseActivityLifecycleCallback {
|
||||
|
||||
override fun onActivityStarted(activity: Activity) {
|
||||
super.onActivityStarted(activity)
|
||||
FloatingView.get().attach(activity)
|
||||
}
|
||||
|
||||
override fun onActivityStopped(activity: Activity) {
|
||||
super.onActivityStopped(activity)
|
||||
FloatingView.get().detach(activity)
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
private fun initFloatView(): FloatingMagnetView? {
|
||||
return try {
|
||||
val params = FrameLayout.LayoutParams(
|
||||
RelativeLayout.LayoutParams.WRAP_CONTENT,
|
||||
RelativeLayout.LayoutParams.WRAP_CONTENT
|
||||
)
|
||||
|
||||
val params = FrameLayout.LayoutParams(
|
||||
RelativeLayout.LayoutParams.WRAP_CONTENT,
|
||||
RelativeLayout.LayoutParams.WRAP_CONTENT
|
||||
)
|
||||
params.gravity = Gravity.START or Gravity.CENTER
|
||||
val view = EnFloatView(App.getContext())
|
||||
view.layoutParams = params
|
||||
|
||||
params.gravity = Gravity.START or Gravity.CENTER
|
||||
val view = EnFloatView(App.getContext())
|
||||
view.layoutParams = params
|
||||
FloatingView.get().customView(view)
|
||||
Log.d(TAG, "Floating view created successfully")
|
||||
|
||||
FloatingView.get().customView(view)
|
||||
|
||||
return FloatingView.get().view
|
||||
FloatingView.get().view
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error creating floating view: ${e.message}")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun changeLocation(distance: Float, angle: Float, packageName: String, userId: Int) {
|
||||
val location = BLocationManager.get().getLocation(userId, packageName)
|
||||
try {
|
||||
val location = BLocationManager.get().getLocation(userId, packageName)
|
||||
if (location == null) {
|
||||
Log.w(TAG, "No current location found for package: $packageName, userId: $userId")
|
||||
return
|
||||
}
|
||||
|
||||
val dx = distance * sin(angle * Math.PI / 180.0)
|
||||
val dy = distance * cos(angle * Math.PI / 180.0)
|
||||
Log.d(TAG, "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)
|
||||
|
||||
val ec = Eb + (Ea - Eb) * (90.0 - location.latitude) / 90.0
|
||||
val ed = ec * cos(location.latitude * Math.PI / 180)
|
||||
// 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)
|
||||
|
||||
val newLng = (dx / ed + location.longitude * Math.PI / 180.0) * 180.0 / Math.PI
|
||||
val newLng = (dx / ed + location.longitude * Math.PI / 180.0) * 180.0 / Math.PI
|
||||
val newLat = (dy / ec + location.latitude * Math.PI / 180.0) * 180.0 / Math.PI
|
||||
|
||||
val newLocation = BLocation(newLat, newLng)
|
||||
|
||||
val newLat = (dy / ec + location.latitude * Math.PI / 180.0) * 180.0 / Math.PI
|
||||
val newLocation = BLocation(newLat, newLng)
|
||||
|
||||
BLocationManager.get().setLocation(userId, packageName, newLocation)
|
||||
// Update the location
|
||||
BLocationManager.get().setLocation(userId, packageName, newLocation)
|
||||
|
||||
Log.d(TAG, "Location updated - New: ${newLat}, ${newLng}")
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error changing location: ${e.message}")
|
||||
Log.e(TAG, "Stack trace: ", e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(TAG, "Overlay permission not granted - RockerManager cannot show floating view")
|
||||
return false
|
||||
}
|
||||
|
||||
// Check if location permissions are granted
|
||||
val hasLocationPermission = context.checkSelfPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) == android.content.pm.PackageManager.PERMISSION_GRANTED
|
||||
if (!hasLocationPermission) {
|
||||
Log.w(TAG, "Location permission not granted - RockerManager cannot access location")
|
||||
return false
|
||||
}
|
||||
|
||||
Log.d(TAG, "All required permissions are granted")
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error checking permissions: ${e.message}")
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of required permissions for RockerManager
|
||||
*/
|
||||
fun getRequiredPermissions(): List<String> {
|
||||
return listOf(
|
||||
android.Manifest.permission.SYSTEM_ALERT_WINDOW,
|
||||
android.Manifest.permission.ACCESS_FINE_LOCATION,
|
||||
android.Manifest.permission.ACCESS_COARSE_LOCATION
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up resources (useful for testing or when disabling the feature)
|
||||
*/
|
||||
fun cleanup() {
|
||||
try {
|
||||
isInitialized = false
|
||||
Log.d(TAG, "RockerManager cleaned up")
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error during cleanup: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -68,6 +68,12 @@ class AppsRepository {
|
||||
|
||||
if (!AbiUtils.isSupport(file)) continue
|
||||
|
||||
// Filter out BlackBox apps to prevent cloning
|
||||
if (BlackBoxCore.get().isBlackBoxApp(installedApplication.packageName)) {
|
||||
Log.d(TAG, "Filtering out BlackBox app: ${installedApplication.packageName}")
|
||||
continue
|
||||
}
|
||||
|
||||
val isXpModule = BlackBoxCore.get().isXposedModule(file)
|
||||
|
||||
val info = AppInfo(
|
||||
@@ -213,6 +219,15 @@ class AppsRepository {
|
||||
}
|
||||
|
||||
Log.d(TAG, "getVmInstallList: processed ${appInfoList.size} apps")
|
||||
|
||||
// If no virtual apps found, show empty list (correct behavior for new users)
|
||||
// Do NOT load regular installed apps as fallback - this causes the bug
|
||||
if (appInfoList.isEmpty()) {
|
||||
Log.d(TAG, "getVmInstallList: No virtual apps found for userId=$userId, showing empty list (correct for new users)")
|
||||
} else {
|
||||
Log.d(TAG, "getVmInstallList: Showing ${appInfoList.size} virtual apps for userId=$userId")
|
||||
}
|
||||
|
||||
appsLiveData.postValue(appInfoList)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error in getVmInstallList: ${e.message}")
|
||||
@@ -236,6 +251,30 @@ 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()) {
|
||||
val packageInfo = BlackBoxCore.getPackageManager().getPackageArchiveInfo(source, 0)
|
||||
if (packageInfo != null && packageInfo.packageName == hostPackageName) {
|
||||
resultLiveData.postValue("Cannot install BlackBox app from within BlackBox. This would create infinite recursion and is not allowed for security reasons.")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Could not verify if this is BlackBox app: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
val blackBoxCore = BlackBoxCore.get()
|
||||
val installResult = if (URLUtil.isValidUrl(source)) {
|
||||
val uri = Uri.parse(source)
|
||||
|
||||
@@ -128,7 +128,19 @@ class AppsFragment : Fragment() {
|
||||
override fun onStart() {
|
||||
try {
|
||||
super.onStart()
|
||||
viewModel.getInstalledApps(userID)
|
||||
|
||||
// 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) {
|
||||
Log.e(TAG, "Error registering service available callback: ${e.message}")
|
||||
}
|
||||
|
||||
viewModel.getInstalledAppsWithRetry(userID)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error in onStart: ${e.message}")
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import androidx.lifecycle.MutableLiveData
|
||||
import top.niunaijun.blackboxa.bean.AppInfo
|
||||
import top.niunaijun.blackboxa.data.AppsRepository
|
||||
import top.niunaijun.blackboxa.view.base.BaseViewModel
|
||||
import android.util.Log
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -27,6 +28,33 @@ class AppsViewModel(private val repo: AppsRepository) : BaseViewModel() {
|
||||
repo.getVmInstallList(userId, appsLiveData)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get installed apps with retry mechanism for when services are not ready
|
||||
*/
|
||||
fun getInstalledAppsWithRetry(userId: Int, maxRetries: Int = 3) {
|
||||
var retryCount = 0
|
||||
|
||||
fun attemptLoad() {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
attemptLoad()
|
||||
}
|
||||
|
||||
fun install(source: String, userID: Int) {
|
||||
launchOnUI {
|
||||
|
||||
@@ -8,6 +8,7 @@ import top.niunaijun.blackbox.app.BActivityThread
|
||||
import top.niunaijun.blackbox.app.configuration.AppLifecycleCallback
|
||||
import top.niunaijun.blackbox.app.configuration.ClientConfiguration
|
||||
import top.niunaijun.blackboxa.app.App
|
||||
import top.niunaijun.blackboxa.app.rocker.RockerManager
|
||||
import top.niunaijun.blackboxa.biz.cache.AppSharedPreferenceDelegate
|
||||
import java.io.File
|
||||
|
||||
@@ -141,7 +142,7 @@ class BlackBoxLoader {
|
||||
) {
|
||||
try {
|
||||
Log.d(TAG, "afterApplicationOnCreate: pkg $packageName, processName $processName")
|
||||
// RockerManager.init(application,userId)
|
||||
RockerManager.init(application,userId)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error in afterApplicationOnCreate: ${e.message}")
|
||||
}
|
||||
@@ -214,6 +215,17 @@ class BlackBoxLoader {
|
||||
fun doOnCreate(context: Context) {
|
||||
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}")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error in doOnCreate: ${e.message}")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user