refactor: remove legacy BlackBox app shell

This commit is contained in:
Mahdi Karzari
2026-07-20 04:25:09 +03:30
parent 165c014d75
commit bf5f73e07a
154 changed files with 217 additions and 7140 deletions
+1 -1
View File
@@ -53,7 +53,7 @@ static const char* blocked_files[] = {
"/data/data/com.lbe.parallel",
"/data/data/com.dual.dualspace",
"/data/data/com.ludashi.superboost",
"/data/data/top.niunaijun.blackboxa",
"/data/data/com.qm4rs.fridabox",
"/blackbox",
"/virtual",
@@ -1138,7 +1138,7 @@ public class BlackBoxCore extends ClientConfiguration {
try {
if (packageName.equals(getHostPkg())) {
return new InstallResult().installError("Cannot clone BlackBox app from within BlackBox. This would create infinite recursion and is not allowed for security reasons.");
return new InstallResult().installError("Cannot clone FridaBox from inside its own workspace. This would create infinite recursion and is not allowed for security reasons.");
}
PackageInfo packageInfo = getPackageManager().getPackageInfo(packageName, 0);
@@ -1156,7 +1156,7 @@ public class BlackBoxCore extends ClientConfiguration {
if (packageInfo != null) {
String packageName = packageInfo.packageName;
if (packageName.equals(getHostPkg())) {
return new InstallResult().installError("Cannot clone BlackBox app from within BlackBox. This would create infinite recursion and is not allowed for security reasons.");
return new InstallResult().installError("Cannot clone FridaBox from inside its own workspace. This would create infinite recursion and is not allowed for security reasons.");
}
}
} catch (Exception e) {
@@ -2126,7 +2126,7 @@ public class BlackBoxCore extends ClientConfiguration {
}
builder.setSmallIcon(android.R.drawable.stat_notify_error)
.setContentTitle("BlackBox Log Upload Failed")
.setContentTitle("FridaBox Log Upload Failed")
.setContentText(error)
.setAutoCancel(true);
@@ -2157,7 +2157,7 @@ public class BlackBoxCore extends ClientConfiguration {
}
builder.setSmallIcon(android.R.drawable.stat_sys_upload_done)
.setContentTitle("BlackBox Log Upload")
.setContentTitle("FridaBox Log Upload")
.setContentText("Logs sent successfully")
.setAutoCancel(true);
@@ -506,8 +506,8 @@ public class BPackageManager extends BlackManager<IBPackageManagerService> {
String packageName = packageInfo.packageName;
String hostPackageName = BlackBoxCore.getHostPkg();
if (packageName.equals(hostPackageName)) {
Log.w(TAG, "Attempt to install BlackBox app detected and blocked: " + packageName);
return new InstallResult().installError("Cannot clone BlackBox app from within BlackBox. This would create infinite recursion and is not allowed for security reasons.");
Log.w(TAG, "Attempt to install the FridaBox host app was blocked: " + packageName);
return new InstallResult().installError("Cannot clone FridaBox from inside its own workspace. This would create infinite recursion and is not allowed for security reasons.");
}
}
} catch (Exception e) {
@@ -768,8 +768,8 @@ public class IActivityManagerProxy extends ClassInvocationStub {
public static class getCurrentUser extends MethodHook {
@Override
protected Object hook(Object who, Method method, Object[] args) throws Throwable {
Object blackBox = BRUserInfo.get()._new(BActivityThread.getUserId(), "BlackBox", BRUserInfo.get().FLAG_PRIMARY());
return blackBox;
Object fridaBox = BRUserInfo.get()._new(BActivityThread.getUserId(), "FridaBox", BRUserInfo.get().FLAG_PRIMARY());
return fridaBox;
}
}
@@ -57,7 +57,7 @@ public class IDevicePolicyManagerProxy extends BinderInvocationStub {
@Override
protected Object hook(Object who, Method method, Object[] args) throws Throwable {
return "BlackBox";
return "FridaBox";
}
}
@@ -66,7 +66,7 @@ public class IDevicePolicyManagerProxy extends BinderInvocationStub {
@Override
protected Object hook(Object who, Method method, Object[] args) throws Throwable {
return "BlackBox";
return "FridaBox";
}
}
@@ -48,8 +48,8 @@ public class IUserManagerProxy extends BinderInvocationStub {
public static class GetProfileParent extends MethodHook {
@Override
protected Object hook(Object who, Method method, Object[] args) throws Throwable {
Object blackBox = BRUserInfo.get()._new(BActivityThread.getUserId(), "BlackBox", BRUserInfo.get().FLAG_PRIMARY());
return blackBox;
Object fridaBox = BRUserInfo.get()._new(BActivityThread.getUserId(), "FridaBox", BRUserInfo.get().FLAG_PRIMARY());
return fridaBox;
}
}
@@ -45,7 +45,7 @@ public class IWifiManagerProxy extends BinderInvocationStub {
WifiInfo wifiInfo = (WifiInfo) method.invoke(who, args);
BRWifiInfo.get(wifiInfo)._set_mBSSID("ac:62:5a:82:65:c4");
BRWifiInfo.get(wifiInfo)._set_mMacAddress("ac:62:5a:82:65:c4");
BRWifiInfo.get(wifiInfo)._set_mWifiSsid(BRWifiSsid.get().createFromAsciiEncoded("BlackBox_Wifi"));
BRWifiInfo.get(wifiInfo)._set_mWifiSsid(BRWifiSsid.get().createFromAsciiEncoded("FridaBox_Wifi"));
return wifiInfo;
}
@@ -139,9 +139,7 @@ public class WebViewProxy extends ClassInvocationStub {
String userAgent = settings.getUserAgentString();
if (userAgent != null && !userAgent.contains("BlackBox")) {
settings.setUserAgentString(userAgent + " BlackBox");
}
// Keep the guest's original user-agent; host branding must not leak into it.
try {
@@ -15,7 +15,7 @@ import top.niunaijun.blackbox.utils.Slog;
public class ProxyVpnService extends VpnService {
private static final String TAG = "ProxyVpnService";
private static final int NOTIFICATION_ID = 1001;
private static final String CHANNEL_ID = "BlackBoxVPN";
private static final String CHANNEL_ID = "FridaBoxVPN";
private ParcelFileDescriptor mVpnInterface = null;
private boolean mIsEstablished = false;
@@ -74,10 +74,10 @@ public class ProxyVpnService extends VpnService {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(
CHANNEL_ID,
"BlackBox VPN Service",
"FridaBox VPN Service",
NotificationManager.IMPORTANCE_LOW
);
channel.setDescription("VPN service for BlackBox network access");
channel.setDescription("VPN service for FridaBox network access");
channel.setShowBadge(false);
NotificationManager notificationManager = getSystemService(NotificationManager.class);
@@ -98,7 +98,7 @@ public class ProxyVpnService extends VpnService {
}
return builder
.setContentTitle("BlackBox VPN Active")
.setContentTitle("FridaBox VPN Active")
.setContentText("Managing network access for sandboxed apps")
.setSmallIcon(android.R.drawable.ic_dialog_info)
.setOngoing(true)
@@ -124,7 +124,7 @@ public class ProxyVpnService extends VpnService {
Builder builder = new Builder();
builder.setSession("BlackBox VPN");
builder.setSession("FridaBox VPN");
builder.addAddress("10.0.0.2", 32);
@@ -140,7 +140,7 @@ public class ProxyVpnService extends VpnService {
builder.addAllowedApplication(getPackageName());
builder.setSession("BlackBox Internet Access");
builder.setSession("FridaBox Internet Access");
Slog.d(TAG, "VPN builder configured, establishing interface...");
@@ -210,7 +210,7 @@ public class ProxyVpnService extends VpnService {
Slog.e(TAG, "Error in network monitoring: " + e.getMessage());
}
}
}, "BlackBoxNetworkHandler");
}, "FridaBoxNetworkHandler");
mNetworkThread.start();
Slog.d(TAG, "Network handling thread started");
-522
View File
@@ -1,522 +0,0 @@
# BlackBox Virtual Environment - Complete User Guide
## Table of Contents
1. [Overview](#overview)
2. [Installation & Setup](#installation--setup)
3. [App Management](#app-management)
4. [WebView & Browser Support](#webview--browser-support)
5. [Google Services Integration](#google-services-integration)
6. [Background Job Management](#background-job-management)
7. [Troubleshooting](#troubleshooting)
8. [Advanced Features](#advanced-features)
9. [API Reference](#api-reference)
10. [Frequently Asked Questions](#frequently-asked-questions)
---
## Overview
BlackBox is a comprehensive Android virtualization solution that creates isolated environments for running apps. The latest version includes significant improvements for:
- **App Installation & Management**: Robust app installation with cloning prevention
- **WebView Support**: Complete WebView compatibility for browsers and web apps
- **Google Services**: Enhanced Google account and GMS integration
- **Background Jobs**: WorkManager and JobScheduler compatibility
- **UID Management**: Smart UID spoofing for system compatibility
- **Crash Prevention**: Comprehensive error handling and recovery
---
## Installation & Setup
### Prerequisites
- Android 8.0+ (API 26+)
- Root access (recommended for full functionality)
- At least 2GB free storage space
- Internet connection for initial setup
### Basic Installation
1. **Download BlackBox APK** from the official source
2. **Install the APK** using your preferred method
3. **Grant Permissions** when prompted:
- Storage access
- System overlay (for floating features)
- Location (for GPS spoofing)
- Notification access (Android 12+)
### Initial Configuration
```bash
# First launch will create virtual environment
# Wait for initialization to complete
# Check logs for any setup issues
```
---
## App Management
### Installing Apps
#### Method 1: APK File Installation
```java
// Using BlackBoxCore API
BlackBoxCore.get().installPackageAsUser(apkFile, userId);
// Example with error handling
try {
InstallResult result = BlackBoxCore.get().installPackageAsUser(apkFile, 0);
if (result.isSuccess()) {
Log.d("BlackBox", "App installed successfully: " + result.getPackageName());
} else {
Log.e("BlackBox", "Installation failed: " + result.getErrorMessage());
}
} catch (Exception e) {
Log.e("BlackBox", "Installation error", e);
}
```
#### Method 2: Package Name Installation
```java
// Install from existing package
BlackBoxCore.get().installPackageAsUser("com.example.app", userId);
// Check if package exists first
if (BlackBoxCore.getPackageManager().getPackageInfo("com.example.app", 0) != null) {
BlackBoxCore.get().installPackageAsUser("com.example.app", userId);
}
```
#### Method 3: URI Installation
```java
// Install from content URI
Uri apkUri = Uri.parse("content://com.example.provider/app.apk");
BlackBoxCore.get().installPackageAsUser(apkUri, userId);
```
### App Removal
#### Uninstall Virtual App
```java
// Remove app from virtual environment
BlackBoxCore.get().uninstallPackage(packageName, userId);
// Force uninstall if needed
BlackBoxCore.get().uninstallPackage(packageName, userId, true);
```
#### Clean App Data
```java
// Clear app data without uninstalling
BlackBoxCore.get().clearAppData(packageName, userId);
// Clear specific data types
BlackBoxCore.get().clearAppData(packageName, userId, "cache");
BlackBoxCore.get().clearAppData(packageName, userId, "data");
```
### App Management Utilities
#### List Installed Apps
```java
// Get all virtual apps
List<AppInfo> virtualApps = BlackBoxCore.get().getInstalledApps(userId);
// Get specific app info
AppInfo appInfo = BlackBoxCore.get().getAppInfo(packageName, userId);
// Check if app is installed
boolean isInstalled = BlackBoxCore.get().isAppInstalled(packageName, userId);
```
#### App Configuration
```java
// Enable/disable app
BlackBoxCore.get().setAppEnabled(packageName, userId, true);
// Set app permissions
BlackBoxCore.get().setAppPermission(packageName, permission, userId, true);
// Configure app settings
BlackBoxCore.get().setAppSetting(packageName, setting, value, userId);
```
---
## WebView & Browser Support
### WebView Configuration
#### Automatic WebView Setup
The new WebView system automatically handles:
- **Unique Data Directories**: Each virtual app gets isolated WebView storage
- **Process Isolation**: WebView conflicts between apps are prevented
- **Data Persistence**: WebView data is preserved per app
#### Manual WebView Configuration
```java
// Set custom WebView data directory
WebView.setDataDirectorySuffix("custom_suffix");
// Configure WebView settings
WebView webView = new WebView(context);
WebSettings settings = webView.getSettings();
settings.setJavaScriptEnabled(true);
settings.setDomStorageEnabled(true);
settings.setDatabaseEnabled(true);
```
### Browser App Support
#### Chrome/Firefox Compatibility
```java
// Browser apps automatically get:
// - Isolated WebView instances
// - Separate cookie storage
// - Independent cache directories
// - Process isolation
```
#### Web App Support
```java
// Progressive Web Apps (PWAs) work with:
// - Service worker isolation
// - Cache storage separation
// - Background sync support
```
---
## Google Services Integration
### Google Account Management
#### Automatic Account Handling
```java
// Google accounts are automatically managed:
// - Mock Google accounts for virtual environment
// - Authentication token handling
// - Account synchronization
```
#### Custom Account Configuration
```java
// Add custom Google accounts
AccountManager accountManager = AccountManager.get(context);
Account account = new Account("user@gmail.com", "com.google");
accountManager.addAccountExplicitly(account, "password", null);
// Configure account sync
ContentResolver.setSyncAutomatically(account, "com.google", true);
```
### Google Play Services
#### GMS Compatibility
```java
// Google Play Services automatically:
// - Returns mock package info
// - Handles authentication requests
// - Provides fallback implementations
```
#### Custom GMS Configuration
```java
// Override GMS behavior if needed
GmsProxy.setCustomGmsInfo("com.example.gms", customInfo);
// Configure GMS permissions
GmsProxy.setGmsPermission("com.example.gms", permission, true);
```
---
## Background Job Management
### WorkManager Integration
#### Automatic WorkManager Handling
```java
// WorkManager automatically:
// - Handles UID validation issues
// - Provides fallback implementations
// - Prevents crashes on job scheduling
```
#### Custom Work Configuration
```java
// Configure custom work
WorkManager workManager = WorkManager.getInstance(context);
// Create work request
OneTimeWorkRequest workRequest = new OneTimeWorkRequest.Builder(MyWorker.class)
.setInputData(inputData)
.build();
// Enqueue work
workManager.enqueue(workRequest);
```
### JobScheduler Compatibility
#### Job Scheduling
```java
// Jobs are automatically handled with:
// - UID validation bypass
// - Fallback mechanisms
// - Error recovery
```
#### Custom Job Configuration
```java
// Create custom job
JobInfo.Builder builder = new JobInfo.Builder(jobId, componentName);
builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
builder.setRequiresCharging(true);
// Schedule job
JobScheduler scheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
scheduler.schedule(builder.build());
```
---
## Advanced Features
### UID Spoofing
#### Automatic UID Management
```java
// UID spoofing automatically:
// - Detects UID validation issues
// - Selects appropriate UIDs for operations
// - Provides fallback UIDs when needed
```
#### Custom UID Configuration
```java
// Configure custom UID for specific operations
UIDSpoofingHelper.setCustomUID("operation", "package", customUID);
// Override UID selection logic
UIDSpoofingHelper.setUIDStrategy("operation", customStrategy);
```
### Process Management
#### Virtual Process Control
```java
// Control virtual processes
BlackBoxCore.get().startVirtualProcess(packageName, userId);
BlackBoxCore.get().stopVirtualProcess(packageName, userId);
// Monitor process status
ProcessInfo processInfo = BlackBoxCore.get().getProcessInfo(packageName, userId);
```
#### Memory Management
```java
// Optimize memory usage
BlackBoxCore.get().optimizeMemory(userId);
// Clear unused resources
BlackBoxCore.get().clearUnusedResources(userId);
```
---
## Troubleshooting
### Common Issues
#### App Installation Failures
```bash
# Check logs for installation errors
adb logcat | grep "BlackBox"
# Common solutions:
# 1. Ensure sufficient storage space
# 2. Check APK file integrity
# 3. Verify package compatibility
# 4. Clear BlackBox cache
```
#### WebView Issues
```bash
# WebView troubleshooting:
# 1. Check WebView data directories
# 2. Verify WebView provider status
# 3. Clear WebView cache
# 4. Restart virtual environment
```
#### Google Services Problems
```bash
# GMS troubleshooting:
# 1. Check GMS proxy status
# 2. Verify account configuration
# 3. Clear GMS cache
# 4. Reinstall GMS components
```
### Debug Mode
#### Enable Debug Logging
```java
// Enable comprehensive logging
BlackBoxCore.setDebugMode(true);
// Set log level
Slog.setLogLevel(Slog.LEVEL_DEBUG);
// Enable specific debug features
BlackBoxCore.enableDebugFeature("webview", true);
BlackBoxCore.enableDebugFeature("gms", true);
```
#### Log Analysis
```bash
# Filter BlackBox logs
adb logcat | grep "BlackBox\|WebView\|GmsProxy\|WorkManager"
# Save logs to file
adb logcat > blackbox_logs.txt
# Analyze specific components
adb logcat | grep "JobServiceStub\|WebViewProxy\|GoogleAccountManagerProxy"
```
---
## API Reference
### Core Classes
#### BlackBoxCore
```java
// Main entry point
BlackBoxCore core = BlackBoxCore.get();
// Core methods
core.installPackageAsUser(apkFile, userId);
core.uninstallPackage(packageName, userId);
core.getInstalledApps(userId);
core.isAppInstalled(packageName, userId);
```
#### BActivityThread
```java
// Activity thread management
int userId = BActivityThread.getUserId();
String packageName = BActivityThread.getAppPackageName();
String processName = BActivityThread.getAppProcessName();
```
#### UIDSpoofingHelper
```java
// UID management utilities
int systemUID = UIDSpoofingHelper.getSystemUID();
int packageUID = UIDSpoofingHelper.getPackageUID(packageName);
boolean needsSpoofing = UIDSpoofingHelper.needsUIDSpoofing(operation, packageName);
```
### Service Proxies
#### WebViewProxy
```java
// WebView management
WebViewProxy.configureWebView(webView, context);
WebViewProxy.setDataDirectorySuffix(suffix);
String dataDir = WebViewProxy.getDataDirectory();
```
#### WorkManagerProxy
```java
// WorkManager compatibility
WorkManagerProxy.enqueueWork(workRequest);
WorkManagerProxy.cancelWork(workId);
List<WorkInfo> workInfos = WorkManagerProxy.getWorkInfos();
```
#### GoogleAccountManagerProxy
```java
// Google account management
Account[] accounts = GoogleAccountManagerProxy.getAccounts();
String token = GoogleAccountManagerProxy.getAuthToken(account, authTokenType);
boolean success = GoogleAccountManagerProxy.addAccount(account, password, extras);
```
---
## Frequently Asked Questions
### Q: Why do some apps show black screens?
**A**: This is usually caused by context or resource loading issues. The new BlackBox version includes comprehensive fixes for:
- Context management
- Resource loading
- Activity lifecycle
- Service initialization
### Q: How do I fix WebView issues in browsers?
**A**: The new WebView system automatically handles:
- Data directory conflicts
- Process isolation
- Provider issues
- Cache management
### Q: Why do background jobs fail?
**A**: Background job failures are now handled by:
- WorkManager compatibility layer
- JobScheduler UID validation bypass
- Smart UID spoofing
- Graceful fallback mechanisms
### Q: How do I prevent app cloning issues?
**A**: BlackBox now includes:
- Automatic cloning prevention
- Package validation
- Security checks
- Error messages for blocked installations
### Q: What if Google services don't work?
**A**: The new GMS system provides:
- Mock Google Play Services
- Account authentication fallbacks
- Token management
- Service compatibility layers
---
## Support & Updates
### Getting Help
- **Documentation**: Check this Docs.md file
- **Logs**: Enable debug mode and analyze logs
- **Community**: Join BlackBox user forums
- **Issues**: Report bugs with detailed logs
### Version History
- **v2.0**: Complete rewrite with new architecture
- **v2.1**: WebView and browser compatibility
- **v2.2**: Google services integration
- **v2.3**: Background job management
- **Current**: UID spoofing and crash prevention
### Future Features
- **Enhanced Security**: Additional anti-detection features
- **Performance**: Memory and CPU optimization
- **Compatibility**: Support for more Android versions
- **Integration**: Additional service proxies
---
## Conclusion
The new BlackBox virtual environment provides a robust, feature-rich solution for Android app virtualization. With comprehensive WebView support, Google services integration, and background job management, it offers enterprise-grade functionality for both developers and end users.
For the best experience:
1. **Keep BlackBox updated** to the latest version
2. **Enable debug logging** when troubleshooting
3. **Monitor system resources** for optimal performance
4. **Report issues** with detailed logs for faster resolution
Happy virtualizing! 🚀✨
+5 -6
View File
@@ -1,19 +1,18 @@
# FridaBox
FridaBox is an authorized mobile-security research MVP that runs an original,
unmodified APK inside BlackBox virtual processes and loads Frida Gadget before
FridaBox is an authorized mobile-security research workspace that runs an original,
unmodified APK inside private virtual processes and loads Frida Gadget before
the guest `Application` is created. It requires no root, frida-server, Magisk,
Zygisk, system-image changes, real PackageManager installation, APK patching,
repacking, or resigning.
The foundation is `ALEX5402/NewBlackbox` commit
`89b59836c66f173756a4ae258cf379a957649820`. The host application ID is
`com.qm4rs.fridabox`; existing engine namespaces remain unchanged.
The host application ID and Android namespace are both `com.qm4rs.fridabox`.
Third-party engine provenance is isolated in `THIRD_PARTY_NOTICES.md`.
## MVP capabilities
- SAF import of one base APK into app-private, read-only storage.
- SHA-256 verification before and after BlackBox virtual installation.
- SHA-256 verification before and after private virtual installation.
- ARM64 native-library inspection; pure Java/Kotlin guests are accepted and
native guests without `arm64-v8a` are rejected.
- Per-guest instrumented or non-instrumented launches with virtual process stop
-106
View File
@@ -1,106 +0,0 @@
# Release Notes - NewBlackbox
## Version: Latest Build (2026-01-31)
---
### New Features
#### VPN Network Mode Toggle
Added a new setting to choose between VPN and normal network mode for sandboxed apps.
- **Location:** Settings → Others → Use VPN Network
- **Default:** OFF (normal network mode)
- When enabled, traffic is routed through BlackBox's VPN service
- Requires app restart to take effect
**Files Changed:**
- `app/src/main/java/top/niunaijun/blackboxa/view/main/BlackBoxLoader.kt`
- `app/src/main/java/top/niunaijun/blackboxa/view/setting/SettingFragment.kt`
- `app/src/main/res/xml/setting.xml`
- `app/src/main/res/values/strings.xml`
- `Bcore/src/main/java/top/niunaijun/blackbox/app/configuration/ClientConfiguration.java`
- `Bcore/src/main/java/top/niunaijun/blackbox/BlackBoxCore.java`
#### Device Information Logging
Added comprehensive device info header in logcat for easier debugging:
- Android version, SDK level, security patch
- Device manufacturer, brand, model, hardware
- Supported CPU/ABIs (32-bit and 64-bit)
- Memory info (heap usage)
- App version and package info
- Build fingerprint and timestamps
---
### Bug Fixes
#### VPN Permission Fix
**Problem:** VPN service failed to establish interface (`builder.establish()` returned null).
**Root Cause:** Android requires `VpnService.prepare()` to be called from an Activity before VPN can be established.
**Solution:** Added VPN permission request to `MainActivity.kt` on app launch.
**Files Changed:**
- `app/src/main/java/top/niunaijun/blackboxa/view/main/MainActivity.kt`
---
#### Android 10 Black Screen Fix
**Problem:** Apps would show a black screen and timeout on Android 10 (API 29).
**Root Cause:**
- `BRAttributionSource.getRealClass()` returns `null` on Android < 31
- `SystemProviderStub.invoke()` crashed calling `.getName()` on null class
- `ClassInvocationStub.injectHook()` crashed when `getWho()` returned null
**Solution:**
- Added null checks in `SystemProviderStub.java` for API version checks
- Added null check in `ClassInvocationStub.java` to skip hooks when services don't exist
**Files Changed:**
- `Bcore/src/main/java/top/niunaijun/blackbox/fake/service/context/providers/SystemProviderStub.java`
- `Bcore/src/main/java/top/niunaijun/blackbox/fake/hook/ClassInvocationStub.java`
---
### Removed Features
#### Xposed Framework Support
- Removed `BXposedManagerService` and related AIDL interfaces
- Removed "Install Xposed Module" UI and Settings entries
- Cleaned up Xposed-related flags and package checks
---
### Stability Improvements
#### Anti-Detection Native Hook Stability
- Removed `LOGD` calls from critical native hooks to prevent infinite recursion
- Fixed syntax errors in hook implementations
- Hooks now silently return `ENOENT` for blocked paths
---
### Known Issues
#### Oppo/ColorOS Thermal Stats Error
On Oppo/ColorOS devices, you may see errors like:
```
OppoThermalStats: PackageManager$NameNotFoundException: top.niunaijun.blackboxa:p0
```
**This is harmless** - it's an Oppo system bug where their thermal management incorrectly uses process names (with `:p0` suffix) instead of package names. The app works normally.
---
### Compatibility
| Android Version | Status |
|-----------------|--------|
| Android 10 (Q) | ✅ Fixed |
| Android 11 (R) | ✅ Supported |
| Android 12 (S) | ✅ Supported |
| Android 13 (T) | ✅ Supported |
| Android 14 (U) | ✅ Supported |
| Android 15+ | ✅ Supported |
+1 -33
View File
@@ -15,7 +15,7 @@ if (releaseSigningValues.any { it != null && !it.trim().isEmpty() } && !releaseS
android {
namespace 'top.niunaijun.blackboxa'
namespace 'com.qm4rs.fridabox'
compileSdk rootProject.ext.compileSdkVersion
ndkVersion = "29.0.14206865"
defaultConfig {
@@ -170,7 +170,6 @@ tasks.named("check").configure {
}
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar", "*.aar"])
implementation project(':Bcore')
implementation libs.appcompat
@@ -180,35 +179,4 @@ dependencies {
testImplementation libs.junit
androidTestImplementation libs.ext.junit
androidTestImplementation libs.espresso.core
implementation "androidx.preference:preference-ktx:1.1.1"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.1"
implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation 'com.gitee.cbfg5210:RVAdapter:0.3.7'
implementation 'com.github.Othershe:CornerLabelView:1.0.0'
// implementation 'com.github.nukc.stateview:kotlin:2.2.0'
implementation 'com.github.Ferfalk:SimpleSearchView:0.2.0'
implementation 'com.tbuonomo:dotsindicator:4.2'
implementation 'org.osmdroid:osmdroid-android:6.1.11'
implementation 'com.afollestad.material-dialogs:core:3.3.0'
implementation 'com.afollestad.material-dialogs:input:3.3.0'
implementation 'androidx.work:work-runtime:2.7.1'
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
+3 -27
View File
@@ -13,13 +13,12 @@
<application
android:name=".app.App"
android:name=".FridaBoxApplication"
android:allowBackup="false"
android:extractNativeLibs="true"
android:fullBackupContent="false"
android:icon="@drawable/ic_fridabox_app"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"
android:label="@string/fb_brand"
android:roundIcon="@drawable/ic_fridabox_app"
android:supportsRtl="true"
android:theme="@style/Theme.FridaBox"
@@ -27,7 +26,7 @@
tools:replace="android:allowBackup"
tools:targetApi="n">
<activity
android:name=".fridabox.FridaBoxActivity"
android:name=".FridaBoxActivity"
android:exported="true"
android:launchMode="singleTop">
<intent-filter>
@@ -35,28 +34,5 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".view.fake.FollowMyLocationOverlay"
android:exported="false" />
<activity android:name=".view.setting.SettingActivity" />
<activity android:name=".view.gms.GmsManagerActivity" />
<activity
android:name=".view.main.WelcomeActivity"
android:exported="false"
android:launchMode="singleTop"
android:theme="@style/Theme.FridaBox" />
<activity android:name=".view.list.ListActivity" />
<activity android:name=".view.fake.FakeManagerActivity" />
<activity
android:name=".view.main.ShortcutActivity"
android:excludeFromRecents="true"
android:exported="true" />
<activity android:name=".view.main.MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
</intent-filter>
</activity>
</application>
</manifest>
@@ -1,4 +1,4 @@
package top.niunaijun.blackboxa.fridabox;
package com.qm4rs.fridabox;
import java.io.File;
import java.io.IOException;
@@ -1,4 +1,4 @@
package top.niunaijun.blackboxa.fridabox;
package com.qm4rs.fridabox;
import java.io.File;
import java.io.FileInputStream;
@@ -1,4 +1,4 @@
package top.niunaijun.blackboxa.fridabox
package com.qm4rs.fridabox
import android.content.ClipData
import android.content.ClipboardManager
@@ -38,9 +38,7 @@ import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout
import top.niunaijun.blackbox.BlackBoxCore
import top.niunaijun.blackbox.instrumentation.InstrumentationSettings
import top.niunaijun.blackboxa.BuildConfig
import top.niunaijun.blackboxa.R
import top.niunaijun.blackboxa.databinding.ActivityFridaboxBinding
import com.qm4rs.fridabox.databinding.ActivityFridaboxBinding
import java.io.File
import java.io.FileOutputStream
import java.security.MessageDigest
@@ -0,0 +1,36 @@
package com.qm4rs.fridabox
import android.app.Application
import android.content.Context
import android.util.Log
import top.niunaijun.blackbox.BlackBoxCore
class FridaBoxApplication : Application() {
override fun attachBaseContext(base: Context) {
super.attachBaseContext(base)
appContext = base
runCatching { BlackBoxCore.get().closeCodeInit() }
.onFailure { Log.e(TAG, "Native bootstrap failed", it) }
runCatching { BlackBoxCore.get().onBeforeMainApplicationAttach(this, base) }
.onFailure { Log.e(TAG, "Pre-attach hook failed", it) }
FridaBoxRuntime.attach(base)
runCatching { BlackBoxCore.get().onAfterMainApplicationAttach(this, base) }
.onFailure { Log.e(TAG, "Post-attach hook failed", it) }
}
override fun onCreate() {
super.onCreate()
FridaBoxRuntime.create()
}
companion object {
private const val TAG = "FridaBox.Application"
@Volatile
lateinit var appContext: Context
private set
}
}
@@ -0,0 +1,66 @@
package com.qm4rs.fridabox
import android.app.Application
import android.content.Context
import android.util.Log
import java.io.File
import top.niunaijun.blackbox.BlackBoxCore
import top.niunaijun.blackbox.app.BActivityThread
import top.niunaijun.blackbox.app.configuration.AppLifecycleCallback
import top.niunaijun.blackbox.app.configuration.ClientConfiguration
object FridaBoxRuntime {
private const val TAG = "FridaBox.Runtime"
fun attach(context: Context) {
BlackBoxCore.get().doAttachBaseContext(context, object : ClientConfiguration() {
override fun getHostPackageName(): String = context.packageName
override fun isHideRoot(): Boolean = false
override fun isEnableDaemonService(): Boolean = true
override fun isUseVpnNetwork(): Boolean = false
override fun isDisableFlagSecure(): Boolean = false
override fun requestInstallPackage(file: File?, userId: Int): Boolean = false
})
BlackBoxCore.get().addAppLifecycleCallback(object : AppLifecycleCallback() {
override fun beforeCreateApplication(
packageName: String?,
processName: String?,
context: Context?,
userId: Int
) {
Log.d(TAG, "beforeCreateApplication: package=$packageName process=$processName user=${BActivityThread.getUserId()}")
}
override fun beforeApplicationOnCreate(
packageName: String?,
processName: String?,
application: Application?,
userId: Int
) {
Log.d(TAG, "beforeApplicationOnCreate: package=$packageName process=$processName")
}
override fun afterApplicationOnCreate(
packageName: String?,
processName: String?,
application: Application?,
userId: Int
) {
Log.d(TAG, "afterApplicationOnCreate: package=$packageName process=$processName")
}
override fun onStoragePermissionNeeded(packageName: String?, userId: Int): Boolean {
Log.w(TAG, "Storage permission required by guest: package=$packageName user=$userId")
return false
}
})
}
fun create() {
BlackBoxCore.get().doCreate()
BlackBoxCore.get().addServiceAvailableCallback {
Log.d(TAG, "Virtual runtime services are ready")
}
}
}
@@ -1,4 +1,4 @@
package top.niunaijun.blackboxa.fridabox;
package com.qm4rs.fridabox;
public final class InstrumentationPreferenceParser {
private InstrumentationPreferenceParser() {
@@ -1,73 +0,0 @@
package top.niunaijun.blackboxa.app
import android.annotation.SuppressLint
import android.app.Application
import android.content.Context
import android.util.Log
import top.niunaijun.blackbox.BlackBoxCore
class App : Application() {
companion object {
@SuppressLint("StaticFieldLeak")
@Volatile
private lateinit var mContext: Context
@JvmStatic
fun getContext(): Context {
return mContext
}
}
override fun attachBaseContext(base: Context?) {
try {
super.attachBaseContext(base)
try {
BlackBoxCore.get().closeCodeInit()
} catch (e: Exception) {
Log.e("App", "Error in closeCodeInit: ${e.message}")
}
try {
BlackBoxCore.get().onBeforeMainApplicationAttach(this, base)
} catch (e: Exception) {
Log.e("App", "Error in onBeforeMainApplicationAttach: ${e.message}")
}
mContext = base!!
try {
AppManager.doAttachBaseContext(base)
} catch (e: Exception) {
Log.e("App", "Error in doAttachBaseContext: ${e.message}")
}
try {
BlackBoxCore.get().onAfterMainApplicationAttach(this, base)
} catch (e: Exception) {
Log.e("App", "Error in onAfterMainApplicationAttach: ${e.message}")
}
} catch (e: Exception) {
Log.e("App", "Critical error in attachBaseContext: ${e.message}")
if (base != null) {
mContext = base
}
}
}
override fun onCreate() {
try {
super.onCreate()
AppManager.doOnCreate(mContext)
} catch (e: Exception) {
Log.e("App", "Error in onCreate: ${e.message}")
}
}
}
@@ -1,71 +0,0 @@
package top.niunaijun.blackboxa.app
import android.content.Context
import android.content.SharedPreferences
import android.util.Log
import top.niunaijun.blackboxa.view.main.BlackBoxLoader
object AppManager {
private const val TAG = "AppManager"
@JvmStatic
val mBlackBoxLoader by lazy {
try {
BlackBoxLoader()
} catch (e: Exception) {
Log.e(TAG, "Error creating BlackBoxLoader: ${e.message}")
BlackBoxLoader()
}
}
@JvmStatic
val mBlackBoxCore by lazy {
try {
mBlackBoxLoader.getBlackBoxCore()
} catch (e: Exception) {
Log.e(TAG, "Error getting BlackBoxCore: ${e.message}")
throw e
}
}
@JvmStatic
val mRemarkSharedPreferences: SharedPreferences by lazy {
try {
App.getContext().getSharedPreferences("UserRemark", Context.MODE_PRIVATE)
} catch (e: Exception) {
Log.e(TAG, "Error creating SharedPreferences: ${e.message}")
throw e
}
}
fun doAttachBaseContext(context: Context) {
try {
mBlackBoxLoader.attachBaseContext(context)
mBlackBoxLoader.addLifecycleCallback()
} catch (e: Exception) {
Log.e(TAG, "Error in doAttachBaseContext: ${e.message}")
}
}
fun doOnCreate(context: Context) {
try {
mBlackBoxLoader.doOnCreate(context)
initThirdService(context)
} catch (e: Exception) {
Log.e(TAG, "Error in doOnCreate: ${e.message}")
}
}
private fun initThirdService(context: Context) {
try {
} catch (e: Exception) {
Log.e(TAG, "Error in initThirdService: ${e.message}")
}
}
}
@@ -1,31 +0,0 @@
package top.niunaijun.blackboxa.app.rocker
import android.app.Activity
import android.app.Application
import android.os.Bundle
interface BaseActivityLifecycleCallback : Application.ActivityLifecycleCallbacks {
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
}
override fun onActivityStarted(activity: Activity) {
}
override fun onActivityResumed(activity: Activity) {
}
override fun onActivityPaused(activity: Activity) {
}
override fun onActivityStopped(activity: Activity) {
}
override fun onActivitySaveInstanceState(activity: Activity, p1: Bundle) {
}
override fun onActivityDestroyed(activity: Activity) {
}
}
@@ -1,226 +0,0 @@
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
import android.widget.RelativeLayout
import com.imuxuan.floatingview.FloatingMagnetView
import com.imuxuan.floatingview.FloatingView
import kotlin.math.cos
import kotlin.math.sin
import top.niunaijun.blackbox.entity.location.BLocation
import top.niunaijun.blackbox.fake.frameworks.BLocationManager
import top.niunaijun.blackboxa.app.App
import top.niunaijun.blackboxa.widget.EnFloatView
object RockerManager {
private const val TAG = "RockerManager"
private var isInitialized = false
private const val Ea = 6378137.0
private const val Eb = 6356725.0
fun init(application: Application?, userId: Int) {
try {
if (isInitialized) {
Log.d(TAG, "RockerManager already initialized, skipping...")
return
}
if (application == null) {
Log.w(TAG, "Application is null, cannot initialize RockerManager")
return
}
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
}
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)
}
}
private fun initFloatView(): FloatingMagnetView? {
return try {
val params =
FrameLayout.LayoutParams(
RelativeLayout.LayoutParams.WRAP_CONTENT,
RelativeLayout.LayoutParams.WRAP_CONTENT
)
params.gravity = Gravity.START or Gravity.CENTER
val view = EnFloatView(App.getContext())
view.layoutParams = params
FloatingView.get().customView(view)
Log.d(TAG, "Floating view created successfully")
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) {
try {
val location = BLocationManager.get().getLocation(userId, packageName)
if (location == null) {
Log.w(TAG, "No current location found for package: $packageName, userId: $userId")
return
}
Log.d(
TAG,
"Changing location - Distance: ${distance}m, Angle: ${angle}°, Current: ${location.latitude}, ${location.longitude}"
)
val dx = distance * sin(angle * Math.PI / 180.0)
val dy = distance * cos(angle * Math.PI / 180.0)
val ec = Eb + (Ea - Eb) * (90.0 - location.latitude) / 90.0
val ed = ec * cos(location.latitude * Math.PI / 180)
val newLng = (dx / ed + location.longitude * Math.PI / 180.0) * 180.0 / Math.PI
val newLat = (dy / ec + location.latitude * Math.PI / 180.0) * 180.0 / Math.PI
val newLocation = BLocation(newLat, newLng)
BLocationManager.get().setLocation(userId, packageName, newLocation)
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)
}
}
fun isActive(): Boolean {
return isInitialized
}
fun checkPermissions(context: Context): Boolean {
return try {
val hasOverlayPermission = android.provider.Settings.canDrawOverlays(context)
if (!hasOverlayPermission) {
Log.w(
TAG,
"Overlay permission not granted - RockerManager cannot show floating view"
)
return false
}
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
}
}
fun getRequiredPermissions(): List<String> {
return listOf(
android.Manifest.permission.SYSTEM_ALERT_WINDOW,
android.Manifest.permission.ACCESS_FINE_LOCATION,
android.Manifest.permission.ACCESS_COARSE_LOCATION
)
}
fun cleanup() {
try {
isInitialized = false
Log.d(TAG, "RockerManager cleaned up")
} catch (e: Exception) {
Log.e(TAG, "Error during cleanup: ${e.message}")
}
}
}
@@ -1,6 +0,0 @@
package top.niunaijun.blackboxa.bean
import android.graphics.drawable.Drawable
data class AppInfo(val name:String,val icon:Drawable?,val packageName:String,val sourceDir:String,val isXpModule:Boolean)
@@ -1,15 +0,0 @@
package top.niunaijun.blackboxa.bean
import android.graphics.drawable.Drawable
import top.niunaijun.blackbox.entity.location.BLocation
data class FakeLocationBean(
val userID: Int,
val name: String,
val icon: Drawable,
val packageName: String,
var fakeLocationPattern: Int,
var fakeLocation: BLocation?
)
data class FakeLocationBeanInstallBean(val userID: Int, val success: Boolean, val msg: String)
@@ -1,7 +0,0 @@
package top.niunaijun.blackboxa.bean
data class GmsBean(val userID:Int,val userName:String,var isInstalledGms:Boolean)
data class GmsInstallBean(val userID: Int,val success:Boolean,val msg:String)
@@ -1,6 +0,0 @@
package top.niunaijun.blackboxa.bean
import android.graphics.drawable.Drawable
data class InstalledAppBean(val name:String, val icon: Drawable?, val packageName:String, val sourceDir:String, val isInstall:Boolean)
@@ -1,13 +0,0 @@
package top.niunaijun.blackboxa.bean
import android.graphics.drawable.Drawable
data class XpModuleInfo(
val name: String,
val desc: String,
val packageName: String,
val version: String,
var enable:Boolean,
val icon: Drawable
)
@@ -1,71 +0,0 @@
package top.niunaijun.blackboxa.biz.cache
import android.content.Context
import android.text.TextUtils
import androidx.core.content.edit
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
open class AppSharedPreferenceDelegate<Data>(
context: Context,
private val default: Data,
spName: String? = null
) : ReadWriteProperty<Any, Data?> {
private val mSharedPreferences by lazy {
val tmpCacheName =
if (TextUtils.isEmpty(spName)) {
AppSharedPreferenceDelegate::class.java.simpleName
} else {
spName
}
return@lazy context.getSharedPreferences(tmpCacheName, Context.MODE_PRIVATE)
}
override fun getValue(thisRef: Any, property: KProperty<*>): Data {
return findData(property.name, default)
}
override fun setValue(thisRef: Any, property: KProperty<*>, value: Data?) {
putData(property.name, value)
}
protected fun findData(key: String, default: Data): Data {
with(mSharedPreferences) {
val result: Any =
when (default) {
is Int -> getInt(key, default)
is Long -> getLong(key, default)
is Float -> getFloat(key, default)
is String -> getString(key, default)!!
is Boolean -> getBoolean(key, default)
else ->
throw IllegalArgumentException(
"This type $default can not be saved into sharedPreferences"
)
}
return result as? Data ?: default
}
}
protected fun putData(key: String, value: Data?) {
mSharedPreferences.edit {
if (value == null) {
remove(key)
} else {
when (value) {
is Int -> putInt(key, value)
is Long -> putLong(key, value)
is Float -> putFloat(key, value)
is String -> putString(key, value)
is Boolean -> putBoolean(key, value)
else ->
throw IllegalArgumentException(
"This type $default can not be saved into Preferences"
)
}
}
}
}
}
@@ -1,509 +0,0 @@
package top.niunaijun.blackboxa.data
import android.content.pm.ApplicationInfo
import android.net.Uri
import android.util.Log
import android.webkit.URLUtil
import androidx.lifecycle.MutableLiveData
import java.io.File
import top.niunaijun.blackbox.BlackBoxCore
import top.niunaijun.blackbox.utils.AbiUtils
import top.niunaijun.blackboxa.R
import top.niunaijun.blackboxa.app.AppManager
import top.niunaijun.blackboxa.bean.AppInfo
import top.niunaijun.blackboxa.bean.InstalledAppBean
import top.niunaijun.blackboxa.util.MemoryManager
import top.niunaijun.blackboxa.util.getString
class AppsRepository {
val TAG: String = "AppsRepository"
private var mInstalledList = mutableListOf<AppInfo>()
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
}
}
private fun safeLoadAppIcon(
applicationInfo: ApplicationInfo
): android.graphics.drawable.Drawable? {
return try {
if (MemoryManager.shouldSkipIconLoading()) {
Log.w(
TAG,
"Memory usage high (${MemoryManager.getMemoryUsagePercentage()}%), skipping icon for ${applicationInfo.packageName}"
)
return null
}
val icon = BlackBoxCore.getPackageManager().getApplicationIcon(applicationInfo)
if (icon is android.graphics.drawable.BitmapDrawable) {
val bitmap = icon.bitmap
if (bitmap.width > 96 || bitmap.height > 96) {
try {
val scaledBitmap =
android.graphics.Bitmap.createScaledBitmap(bitmap, 96, 96, true)
android.graphics.drawable.BitmapDrawable(
BlackBoxCore.getPackageManager()
.getResourcesForApplication(applicationInfo.packageName),
scaledBitmap
)
} catch (e: Exception) {
Log.w(
TAG,
"Failed to scale icon for ${applicationInfo.packageName}: ${e.message}"
)
icon
}
} else {
icon
}
} else {
icon
}
} catch (e: Exception) {
Log.w(TAG, "Failed to load icon for ${applicationInfo.packageName}: ${e.message}")
null
}
}
fun previewInstallList() {
try {
synchronized(mInstalledList) {
val installedApplications: List<ApplicationInfo> =
BlackBoxCore.getPackageManager().getInstalledApplications(0)
val installedList = mutableListOf<AppInfo>()
for (installedApplication in installedApplications) {
try {
val file = File(installedApplication.sourceDir)
if ((installedApplication.flags and ApplicationInfo.FLAG_SYSTEM) != 0)
continue
if (!AbiUtils.isSupport(file)) continue
if (BlackBoxCore.get().isBlackBoxApp(installedApplication.packageName)) {
Log.d(
TAG,
"Filtering out BlackBox app: ${installedApplication.packageName}"
)
continue
}
val isXpModule = false
val info =
AppInfo(
safeLoadAppLabel(installedApplication),
safeLoadAppIcon(
installedApplication
),
installedApplication.packageName,
installedApplication.sourceDir,
isXpModule
)
installedList.add(info)
} catch (e: Exception) {
Log.e(
TAG,
"Error processing app ${installedApplication.packageName}: ${e.message}"
)
}
}
this.mInstalledList.clear()
this.mInstalledList.addAll(installedList)
}
} catch (e: Exception) {
Log.e(TAG, "Error in previewInstallList: ${e.message}")
}
}
fun getInstalledAppList(
userID: Int,
loadingLiveData: MutableLiveData<Boolean>,
appsLiveData: MutableLiveData<List<InstalledAppBean>>
) {
try {
loadingLiveData.postValue(true)
synchronized(mInstalledList) {
val blackBoxCore = BlackBoxCore.get()
Log.d(TAG, mInstalledList.joinToString(","))
val newInstalledList =
mInstalledList.map {
InstalledAppBean(
it.name,
it.icon,
it.packageName,
it.sourceDir,
blackBoxCore.isInstalled(it.packageName, userID)
)
}
appsLiveData.postValue(newInstalledList)
loadingLiveData.postValue(false)
}
} catch (e: Exception) {
Log.e(TAG, "Error in getInstalledAppList: ${e.message}")
loadingLiveData.postValue(false)
appsLiveData.postValue(emptyList())
}
}
fun getVmInstallList(userId: Int, appsLiveData: MutableLiveData<List<AppInfo>>) {
try {
if (MemoryManager.isMemoryCritical()) {
Log.w(
TAG,
"Memory critical (${MemoryManager.getMemoryUsagePercentage()}%), forcing garbage collection"
)
MemoryManager.forceGarbageCollectionIfNeeded()
}
val blackBoxCore = BlackBoxCore.get()
val users = blackBoxCore.users
Log.d(TAG, "getVmInstallList: userId=$userId, total users=${users.size}")
users.forEach { user -> Log.d(TAG, "User: id=${user.id}, name=${user.name}") }
val sortListData = AppManager.mRemarkSharedPreferences.getString("AppList$userId", "")
val sortList = sortListData?.split(",")
var applicationList: List<ApplicationInfo>? = null
var retryCount = 0
val maxRetries = 3
while (applicationList == null && retryCount < maxRetries) {
try {
applicationList = blackBoxCore.getInstalledApplications(0, userId)
if (applicationList == null) {
Log.w(
TAG,
"getVmInstallList: Attempt ${retryCount + 1} returned null, retrying..."
)
retryCount++
Thread.sleep(100)
}
} catch (e: Exception) {
Log.e(
TAG,
"getVmInstallList: Error getting applications on attempt ${retryCount + 1}: ${e.message}"
)
retryCount++
if (retryCount < maxRetries) {
Thread.sleep(200)
}
}
}
if (applicationList == null) {
Log.e(
TAG,
"getVmInstallList: applicationList is null for userId=$userId after $maxRetries attempts"
)
appsLiveData.postValue(emptyList())
return
}
Log.d(
TAG,
"getVmInstallList: userId=$userId, applicationList.size=${applicationList.size}"
)
if (applicationList.isNotEmpty()) {
Log.d(TAG, "First app: ${applicationList.first().packageName}")
} else {
Log.w(TAG, "getVmInstallList: No applications found for userId=$userId")
}
val appInfoList = mutableListOf<AppInfo>()
val sortedApplicationList =
if (!sortList.isNullOrEmpty()) {
try {
applicationList.sortedWith(AppsSortComparator(sortList))
} catch (e: Exception) {
Log.e(TAG, "getVmInstallList: Error sorting applications: ${e.message}")
applicationList
}
} else {
applicationList
}
sortedApplicationList.forEachIndexed { index, applicationInfo ->
try {
if (index > 0 && index % 25 == 0) {
if (MemoryManager.isMemoryCritical()) {
Log.w(TAG, "Memory critical during processing, forcing GC")
MemoryManager.forceGarbageCollectionIfNeeded()
}
}
if (applicationInfo == null) {
Log.w(
TAG,
"getVmInstallList: Skipping null applicationInfo at index $index"
)
return@forEachIndexed
}
if (applicationInfo.packageName.isNullOrBlank()) {
Log.w(
TAG,
"getVmInstallList: Skipping app with null/blank package name at index $index"
)
return@forEachIndexed
}
val info =
AppInfo(
safeLoadAppLabel(applicationInfo),
safeLoadAppIcon(
applicationInfo
),
applicationInfo.packageName,
applicationInfo.sourceDir ?: "",
false
)
appInfoList.add(info)
if (index > 0 && index % 50 == 0) {
Log.d(
TAG,
"getVmInstallList: Processed $index/${sortedApplicationList.size} apps - ${MemoryManager.getMemoryInfo()}"
)
}
} catch (e: Exception) {
Log.e(
TAG,
"getVmInstallList: Error processing app at index $index (${applicationInfo?.packageName}): ${e.message}"
)
}
}
Log.d(
TAG,
"getVmInstallList: processed ${appInfoList.size} apps - ${MemoryManager.getMemoryInfo()}"
)
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"
)
}
try {
appsLiveData.postValue(appInfoList)
} catch (e: Exception) {
Log.e(TAG, "getVmInstallList: Error posting to LiveData: ${e.message}")
try {
android.os.Handler(android.os.Looper.getMainLooper()).post {
try {
appsLiveData.postValue(appInfoList)
} catch (e2: Exception) {
Log.e(
TAG,
"getVmInstallList: Fallback posting also failed: ${e2.message}"
)
}
}
} catch (e3: Exception) {
Log.e(
TAG,
"getVmInstallList: Could not schedule fallback posting: ${e3.message}"
)
}
}
} catch (e: Exception) {
Log.e(TAG, "Error in getVmInstallList: ${e.message}")
try {
appsLiveData.postValue(emptyList())
} catch (e2: Exception) {
Log.e(TAG, "getVmInstallList: Error posting empty list: ${e2.message}")
}
}
}
fun installApk(source: String, userId: Int, resultLiveData: MutableLiveData<String>) {
try {
if (source.contains("blackbox") ||
source.contains("niunaijun") ||
source.contains("vspace") ||
source.contains("virtual")
) {
try {
val blackBoxCore = BlackBoxCore.get()
val hostPackageName = BlackBoxCore.getHostPkg()
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)
blackBoxCore.installPackageAsUser(uri, userId)
} else {
blackBoxCore.installPackageAsUser(source, userId)
}
if (installResult.success) {
updateAppSortList(userId, installResult.packageName, true)
resultLiveData.postValue(getString(R.string.install_success))
} else {
resultLiveData.postValue(getString(R.string.install_fail, installResult.msg))
}
scanUser()
} catch (e: Exception) {
Log.e(TAG, "Error installing APK: ${e.message}")
resultLiveData.postValue("Installation failed: ${e.message}")
}
}
fun unInstall(packageName: String, userID: Int, resultLiveData: MutableLiveData<String>) {
try {
BlackBoxCore.get().uninstallPackageAsUser(packageName, userID)
updateAppSortList(userID, packageName, false)
scanUser()
resultLiveData.postValue(getString(R.string.uninstall_success))
} catch (e: Exception) {
Log.e(TAG, "Error uninstalling APK: ${e.message}")
resultLiveData.postValue("Uninstallation failed: ${e.message}")
}
}
fun launchApk(packageName: String, userId: Int, launchLiveData: MutableLiveData<Boolean>) {
try {
val result = BlackBoxCore.get().launchApk(packageName, userId)
launchLiveData.postValue(result)
} catch (e: Exception) {
Log.e(TAG, "Error launching APK: ${e.message}")
launchLiveData.postValue(false)
}
}
fun clearApkData(packageName: String, userID: Int, resultLiveData: MutableLiveData<String>) {
try {
BlackBoxCore.get().clearPackage(packageName, userID)
resultLiveData.postValue(getString(R.string.clear_success))
} catch (e: Exception) {
Log.e(TAG, "Error clearing APK data: ${e.message}")
resultLiveData.postValue("Clear failed: ${e.message}")
}
}
private fun scanUser() {
try {
val blackBoxCore = BlackBoxCore.get()
val userList = blackBoxCore.users
if (userList.isEmpty()) {
return
}
val id = userList.last().id
if (blackBoxCore.getInstalledApplications(0, id).isEmpty()) {
blackBoxCore.deleteUser(id)
AppManager.mRemarkSharedPreferences.edit().apply {
remove("Remark$id")
remove("AppList$id")
apply()
}
scanUser()
}
} catch (e: Exception) {
Log.e(TAG, "Error in scanUser: ${e.message}")
}
}
private fun updateAppSortList(userID: Int, pkg: String, isAdd: Boolean) {
try {
val savedSortList = AppManager.mRemarkSharedPreferences.getString("AppList$userID", "")
val sortList = linkedSetOf<String>()
if (savedSortList != null) {
sortList.addAll(savedSortList.split(","))
}
if (isAdd) {
sortList.add(pkg)
} else {
sortList.remove(pkg)
}
AppManager.mRemarkSharedPreferences.edit().apply {
putString("AppList$userID", sortList.joinToString(","))
apply()
}
} catch (e: Exception) {
Log.e(TAG, "Error updating app sort list: ${e.message}")
}
}
fun updateApkOrder(userID: Int, dataList: List<AppInfo>) {
try {
AppManager.mRemarkSharedPreferences.edit().apply {
putString("AppList$userID", dataList.joinToString(",") { it.packageName })
apply()
}
} catch (e: Exception) {
Log.e(TAG, "Error updating APK order: ${e.message}")
}
}
}
@@ -1,17 +0,0 @@
package top.niunaijun.blackboxa.data
import android.content.pm.ApplicationInfo
class AppsSortComparator(private val sortedList: List<String>) : Comparator<ApplicationInfo> {
override fun compare(o1: ApplicationInfo?, o2: ApplicationInfo?): Int {
if (o1 == null || o2 == null) {
return 0
}
val first = sortedList.indexOf(o1.packageName)
val second = sortedList.indexOf(o2.packageName)
return first - second
}
}
@@ -1,62 +0,0 @@
package top.niunaijun.blackboxa.data
import android.content.pm.ApplicationInfo
import android.util.Log
import androidx.lifecycle.MutableLiveData
import top.niunaijun.blackbox.BlackBoxCore
import top.niunaijun.blackbox.entity.location.BLocation
import top.niunaijun.blackbox.fake.frameworks.BLocationManager
import top.niunaijun.blackboxa.bean.FakeLocationBean
class FakeLocationRepository {
val TAG: String = "FakeLocationRepository"
fun setPattern(userId: Int, pkg: String, pattern: Int) {
BLocationManager.get().setPattern(userId, pkg, pattern)
}
private fun getPattern(userId: Int, pkg: String): Int {
return BLocationManager.get().getPattern(userId, pkg)
}
private fun getLocation(userId: Int, pkg: String): BLocation? {
return BLocationManager.get().getLocation(userId, pkg)
}
fun setLocation(userId: Int, pkg: String, location: BLocation) {
BLocationManager.get().setLocation(userId, pkg, location)
}
fun getInstalledAppList(
userID: Int,
appsFakeLiveData: MutableLiveData<List<FakeLocationBean>>
) {
val installedList = mutableListOf<FakeLocationBean>()
val installedApplications: List<ApplicationInfo> =
BlackBoxCore.get().getInstalledApplications(0, userID)
for (installedApplication in installedApplications) {
val info = FakeLocationBean(
userID,
installedApplication.loadLabel(BlackBoxCore.getPackageManager()).toString(),
installedApplication.loadIcon(BlackBoxCore.getPackageManager()),
installedApplication.packageName,
getPattern(userID, installedApplication.packageName),
getLocation(userID, installedApplication.packageName)
)
installedList.add(info)
}
Log.d(TAG, installedList.joinToString(","))
appsFakeLiveData.postValue(installedList)
}
}
@@ -1,65 +0,0 @@
package top.niunaijun.blackboxa.data
import androidx.lifecycle.MutableLiveData
import top.niunaijun.blackbox.BlackBoxCore
import top.niunaijun.blackboxa.R
import top.niunaijun.blackboxa.app.AppManager
import top.niunaijun.blackboxa.bean.GmsBean
import top.niunaijun.blackboxa.bean.GmsInstallBean
import top.niunaijun.blackboxa.util.getString
class GmsRepository {
fun getGmsInstalledList(mInstalledLiveData: MutableLiveData<List<GmsBean>>) {
val userList = arrayListOf<GmsBean>()
BlackBoxCore.get().users.forEach {
val userId = it.id
val userName =
AppManager.mRemarkSharedPreferences.getString("Remark$userId", "User $userId") ?: ""
val isInstalled = BlackBoxCore.get().isInstallGms(userId)
val bean = GmsBean(userId, userName, isInstalled)
userList.add(bean)
}
mInstalledLiveData.postValue(userList)
}
fun installGms(
userID: Int,
mUpdateInstalledLiveData: MutableLiveData<GmsInstallBean>
) {
val installResult = BlackBoxCore.get().installGms(userID)
val result = if (installResult.success) {
getString(R.string.install_success)
} else {
getString(R.string.install_fail, installResult.msg)
}
val bean = GmsInstallBean(userID,installResult.success,result)
mUpdateInstalledLiveData.postValue(bean)
}
fun uninstallGms(
userID: Int,
mUpdateInstalledLiveData: MutableLiveData<GmsInstallBean>
) {
var isSuccess = false
if (BlackBoxCore.get().isInstallGms(userID)) {
isSuccess = BlackBoxCore.get().uninstallGms(userID)
}
val result = if (isSuccess) {
getString(R.string.uninstall_success)
} else {
getString(R.string.uninstall_fail)
}
val bean = GmsInstallBean(userID,isSuccess,result)
mUpdateInstalledLiveData.postValue(bean)
}
}
@@ -1,18 +0,0 @@
package top.niunaijun.blackboxa.util
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.provider.Settings
object ContextUtil {
fun Context.openAppSystemSettings() {
startActivity(Intent().apply {
action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
data = Uri.fromParts("package", packageName, null)
})
}
}
@@ -1,40 +0,0 @@
package top.niunaijun.blackboxa.util
import top.niunaijun.blackboxa.data.AppsRepository
import top.niunaijun.blackboxa.data.FakeLocationRepository
import top.niunaijun.blackboxa.data.GmsRepository
import top.niunaijun.blackboxa.view.apps.AppsFactory
import top.niunaijun.blackboxa.view.fake.FakeLocationFactory
import top.niunaijun.blackboxa.view.gms.GmsFactory
import top.niunaijun.blackboxa.view.list.ListFactory
object InjectionUtil {
private val appsRepository = AppsRepository()
private val gmsRepository = GmsRepository()
private val fakeLocationRepository = FakeLocationRepository()
fun getAppsFactory() : AppsFactory {
return AppsFactory(appsRepository)
}
fun getListFactory(): ListFactory {
return ListFactory(appsRepository)
}
fun getGmsFactory():GmsFactory{
return GmsFactory(gmsRepository)
}
fun getFakeLocationFactory():FakeLocationFactory{
return FakeLocationFactory(fakeLocationRepository)
}
}
@@ -1,47 +0,0 @@
package top.niunaijun.blackboxa.util;
import android.graphics.Point;
import android.graphics.PointF;
public class MathUtil {
public MathUtil() {
}
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));
}
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));
}
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)));
}
public static float getRadian(Point A, Point B) {
float lenA = B.x - A.x;
float lenB = B.y - A.y;
float lenC = (float) Math.sqrt(lenA * lenA + lenB * lenB);
float radian = (float) Math.acos(lenA / lenC);
radian = radian * (B.y < A.y ? -1 : 1);
return radian;
}
public static double angle2Radian(double angle) {
return angle / 180 * Math.PI;
}
public static double radian2Angle(double radian) {
return radian / Math.PI * 180;
}
}
@@ -1,128 +0,0 @@
package top.niunaijun.blackboxa.util
import android.util.Log
import java.lang.Runtime
object MemoryManager {
private const val TAG = "MemoryManager"
private const val MEMORY_THRESHOLD = 0.8
private const val CRITICAL_MEMORY_THRESHOLD = 0.9
fun isMemorySafe(): Boolean {
return try {
val runtime = Runtime.getRuntime()
val usedMemory = runtime.totalMemory() - runtime.freeMemory()
val maxMemory = runtime.maxMemory()
val memoryUsage = usedMemory.toDouble() / maxMemory.toDouble()
memoryUsage < MEMORY_THRESHOLD
} catch (e: Exception) {
Log.e(TAG, "Error checking memory: ${e.message}")
true
}
}
fun isMemoryCritical(): Boolean {
return try {
val runtime = Runtime.getRuntime()
val usedMemory = runtime.totalMemory() - runtime.freeMemory()
val maxMemory = runtime.maxMemory()
val memoryUsage = usedMemory.toDouble() / maxMemory.toDouble()
memoryUsage > CRITICAL_MEMORY_THRESHOLD
} catch (e: Exception) {
Log.e(TAG, "Error checking critical memory: ${e.message}")
false
}
}
fun getMemoryUsagePercentage(): Int {
return try {
val runtime = Runtime.getRuntime()
val usedMemory = runtime.totalMemory() - runtime.freeMemory()
val maxMemory = runtime.maxMemory()
val memoryUsage = usedMemory.toDouble() / maxMemory.toDouble()
(memoryUsage * 100).toInt()
} catch (e: Exception) {
Log.e(TAG, "Error getting memory usage: ${e.message}")
0
}
}
fun forceGarbageCollectionIfNeeded(): Boolean {
return try {
if (isMemoryCritical()) {
Log.w(
TAG,
"Memory usage critical (${getMemoryUsagePercentage()}%), forcing garbage collection"
)
System.gc()
Thread.sleep(100)
true
} else {
false
}
} catch (e: Exception) {
Log.e(TAG, "Error during garbage collection: ${e.message}")
false
}
}
fun optimizeMemoryForRecyclerView() {
try {
val memoryUsage = getMemoryUsagePercentage()
if (memoryUsage > 70) {
Log.d(TAG, "Memory usage high (${memoryUsage}%), optimizing for RecyclerView")
System.gc()
try {
val runtime = Runtime.getRuntime()
runtime.gc()
} catch (e: Exception) {
Log.w(TAG, "Could not force runtime GC: ${e.message}")
}
}
} catch (e: Exception) {
Log.e(TAG, "Error optimizing memory: ${e.message}")
}
}
fun shouldSkipIconLoading(): Boolean {
return try {
val memoryUsage = getMemoryUsagePercentage()
memoryUsage > 75
} catch (e: Exception) {
Log.e(TAG, "Error checking if should skip icon loading: ${e.message}")
false
}
}
fun getMemoryInfo(): String {
return try {
val runtime = Runtime.getRuntime()
val totalMemory = runtime.totalMemory()
val freeMemory = runtime.freeMemory()
val usedMemory = totalMemory - freeMemory
val maxMemory = runtime.maxMemory()
"Memory: ${usedMemory / 1024 / 1024}MB used / ${maxMemory / 1024 / 1024}MB max (${getMemoryUsagePercentage()}%)"
} catch (e: Exception) {
"Memory: Unknown (${e.message})"
}
}
}
@@ -1,13 +0,0 @@
package top.niunaijun.blackboxa.util
import androidx.annotation.StringRes
import top.niunaijun.blackboxa.app.App
fun getString(@StringRes id:Int,vararg arg:String):String{
if(arg.isEmpty()){
return App.getContext().getString(id)
}
return App.getContext().getString(id,*arg)
}
@@ -1,237 +0,0 @@
package top.niunaijun.blackboxa.util;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.KeyguardManager;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Point;
import android.os.Build;
import android.os.Handler;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.View;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import java.lang.reflect.Field;
public class Resolution {
private static final String TAG = "UtilsScreen";
public static int getScreenWidth(Context context) {
return getScreenSize(context, null).x;
}
public static int getScreenHeight(Context context) {
return getScreenSize(context, null).y;
}
@SuppressLint("NewApi")
public static Point getScreenSize(Context context, Point outSize) {
WindowManager wm = (WindowManager) context
.getSystemService(Context.WINDOW_SERVICE);
Point ret = outSize == null ? new Point() : outSize;
if (Build.VERSION.SDK_INT >= 30) {
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) {
@SuppressWarnings("deprecation")
final Display defaultDisplay = wm.getDefaultDisplay();
defaultDisplay.getSize(ret);
} else {
@SuppressWarnings("deprecation")
final Display defaultDisplay = wm.getDefaultDisplay();
ret.x = defaultDisplay.getWidth();
ret.y = defaultDisplay.getHeight();
}
return ret;
}
public static float convertDpToPixel(float dp, Context context) {
Resources resources = context.getResources();
DisplayMetrics metrics = resources.getDisplayMetrics();
float px = dp * (metrics.densityDpi / 160f);
return px;
}
public static float convertPixelsToDp(float px, Context context) {
Resources resources = context.getResources();
DisplayMetrics metrics = resources.getDisplayMetrics();
float dp = px / (metrics.densityDpi / 160f);
return dp;
}
public static float getDensity(Context context) {
float density = 0f;
if (context== null) {
return density;
}
try {
density = context.getResources().getDisplayMetrics().density;
} catch (Exception e) {
}
return density;
}
public static boolean checkPix(Activity context, int width, int height) {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN) {
DisplayMetrics metrics = new DisplayMetrics();
context.getWindowManager().getDefaultDisplay().getRealMetrics(metrics);
return metrics.widthPixels == width && metrics.heightPixels == height;
} else {
return getScreenPixWidth(context) == width && getScreenPixHeight(context) == height;
}
}
public static int getScreenPixWidth(Context context) {
return context.getResources().getDisplayMetrics().widthPixels;
}
public static int getScreenPixHeight(Context context) {
return context.getResources().getDisplayMetrics().heightPixels;
}
public static int dipToPx(Context context, int dip) {
return (int) (dip * context.getResources().getDisplayMetrics().density + 0.5f);
}
public static int pxToDip(Context context, float pxValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (pxValue / scale + 0.5f);
}
public static int sp2px(Context context, float spValue) {
final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
return (int) (spValue * fontScale + 0.5f);
}
public static void hideInputMethod(View view) {
InputMethodManager imm = (InputMethodManager) view.getContext()
.getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm != null) {
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
}
public static void showInputMethod(View view) {
InputMethodManager imm = (InputMethodManager) view.getContext()
.getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm != null) {
imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT);
}
}
public static void showInputMethod(final View view, long delayMillis) {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
Resolution.showInputMethod(view);
}
}, delayMillis);
}
public static boolean isScreenLocked(Context c) {
KeyguardManager mKeyguardManager = (KeyguardManager) c
.getSystemService(Context.KEYGUARD_SERVICE);
boolean bResult = !mKeyguardManager.inKeyguardRestrictedInputMode();
return bResult;
}
public static int getBarHeight(Context context) {
Class<?> c = null;
Object obj = null;
Field field = null;
int x = 0, sbar = 38;
try {
c = Class.forName("com.android.internal.R$dimen");
obj = c.newInstance();
field = c.getField("status_bar_height");
x = Integer.parseInt(field.get(obj).toString());
sbar = context.getResources().getDimensionPixelSize(x);
} catch (Exception e1) {
e1.printStackTrace();
}
return sbar;
}
public static Point getNavigationBarSize(Context context) {
Point appUsableSize = getScreenSize(context, null);
Point realScreenSize = getRealScreenSize(context);
if (appUsableSize.y < realScreenSize.y) {
return new Point(appUsableSize.x, realScreenSize.y - appUsableSize.y);
}
return new Point();
}
public static Point getRealScreenSize(Context context) {
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Point size = new Point();
if (Build.VERSION.SDK_INT >= 30) {
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) {
@SuppressWarnings("deprecation")
Display display = windowManager.getDefaultDisplay();
display.getRealSize(size);
} else if (Build.VERSION.SDK_INT >= 14) {
@SuppressWarnings("deprecation")
Display display = windowManager.getDefaultDisplay();
try {
size.x = (Integer) Display.class.getMethod("getRawWidth").invoke(display);
size.y = (Integer) Display.class.getMethod("getRawHeight").invoke(display);
} catch (Exception e) {
}
}
return size;
}
}
@@ -1,77 +0,0 @@
package top.niunaijun.blackboxa.util
import android.content.Context
import android.content.Intent
import androidx.core.content.pm.ShortcutInfoCompat
import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.graphics.drawable.IconCompat
import androidx.core.graphics.drawable.toBitmap
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.input.input
import top.niunaijun.blackboxa.R
import top.niunaijun.blackboxa.app.App
import top.niunaijun.blackboxa.app.AppManager
import top.niunaijun.blackboxa.bean.AppInfo
import top.niunaijun.blackboxa.util.ContextUtil.openAppSystemSettings
import top.niunaijun.blackboxa.view.main.ShortcutActivity
object ShortcutUtil {
fun createShortcut(context: Context,userID: Int, info: AppInfo) {
if (ShortcutManagerCompat.isRequestPinShortcutSupported(context)) {
val labelName = info.name + userID
val intent = Intent(context, ShortcutActivity::class.java)
.setAction(Intent.ACTION_MAIN)
.putExtra("pkg", info.packageName)
.putExtra("userId", userID)
MaterialDialog(context).show {
title(res = R.string.app_shortcut)
input(
hintRes = R.string.shortcut_name,
prefill = labelName
) { _, input ->
val shortcutInfo: ShortcutInfoCompat =
ShortcutInfoCompat.Builder(context, info.packageName + userID)
.setIntent(intent)
.setShortLabel(input)
.setLongLabel(input)
.setIcon(IconCompat.createWithBitmap(info.icon!!.toBitmap()))
.build()
ShortcutManagerCompat.requestPinShortcut(context, shortcutInfo, null)
showAllowPermissionDialog(context)
}
positiveButton(R.string.done)
negativeButton(R.string.cancel)
}
} else {
toast(R.string.cannot_create_shortcut)
}
}
private fun showAllowPermissionDialog(context: Context){
if (!AppManager.mBlackBoxLoader.showShortcutPermissionDialog()){
return
}
MaterialDialog(context).show {
title(R.string.try_add_shortcut)
message(R.string.add_shortcut_fail_msg)
positiveButton(R.string.done)
negativeButton(R.string.permission_setting){
App.getContext().openAppSystemSettings()
}
neutralButton(R.string.no_reminders){
AppManager.mBlackBoxLoader.invalidShortcutPermissionDialog(false)
}
}
}
}
@@ -1,23 +0,0 @@
package top.niunaijun.blackboxa.util
import android.content.Context
import android.widget.Toast
import androidx.annotation.StringRes
import top.niunaijun.blackboxa.app.App
var toastImpl:Toast? = null
fun Context.toast(msg:String){
toastImpl?.cancel()
toastImpl = Toast.makeText(this,msg,Toast.LENGTH_SHORT)
toastImpl?.show()
}
fun toast(msg: String){
App.getContext().toast(msg)
}
fun toast(@StringRes msgID:Int){
toast(getString(msgID))
}
@@ -1,36 +0,0 @@
package top.niunaijun.blackboxa.util
import android.app.Activity
import android.app.Dialog
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.viewbinding.ViewBinding
inline fun <reified T : ViewBinding> Activity.inflate(): Lazy<T> = lazy {
inflateBinding(layoutInflater)
}
inline fun <reified T : ViewBinding> Fragment.inflate(): Lazy<T> = lazy {
inflateBinding(layoutInflater)
}
inline fun <reified T : ViewBinding> Dialog.inflate(): Lazy<T> = lazy {
inflateBinding(layoutInflater)
}
inline fun <reified T : ViewBinding> inflateBinding(layoutInflater: LayoutInflater): T {
val method = T::class.java.getMethod("inflate", LayoutInflater::class.java)
return method.invoke(null, layoutInflater) as T
}
inline fun <reified T : ViewBinding> newBindingViewHolder(viewGroup: ViewGroup, attachToParent:Boolean = false): T {
val method = T::class.java.getMethod("inflate",
LayoutInflater::class.java,
ViewGroup::class.java,
Boolean::class.java)
return method.invoke(null,LayoutInflater.from(viewGroup.context),viewGroup,attachToParent) as T
}
@@ -1,166 +0,0 @@
package top.niunaijun.blackboxa.view.apps
import android.graphics.drawable.Drawable
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import cbfg.rvadapter.RVHolder
import cbfg.rvadapter.RVHolderFactory
import top.niunaijun.blackboxa.R
import top.niunaijun.blackboxa.bean.AppInfo
import top.niunaijun.blackboxa.databinding.ItemAppBinding
import android.util.Log
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.ColorDrawable
import android.graphics.Color
import android.view.ViewTreeObserver
import androidx.recyclerview.widget.RecyclerView
class AppsAdapter : RVHolderFactory() {
companion object {
private const val TAG = "AppsAdapter"
private const val MAX_ICON_SIZE = 96
private val DEFAULT_ICON_COLOR = Color.parseColor("#CCCCCC")
}
override fun createViewHolder(parent: ViewGroup?, viewType: Int, item: Any): RVHolder<out Any> {
return try {
AppsVH(inflate(R.layout.item_app, parent))
} catch (e: Exception) {
Log.e(TAG, "Error creating ViewHolder: ${e.message}")
FallbackAppsVH(inflate(R.layout.item_app, parent))
}
}
class AppsVH(itemView: View) : RVHolder<AppInfo>(itemView) {
val binding = ItemAppBinding.bind(itemView)
private var currentIcon: Drawable? = null
private var isAttached = false
init {
try {
binding.icon.scaleType = ImageView.ScaleType.CENTER_CROP
itemView.viewTreeObserver.addOnPreDrawListener(object : ViewTreeObserver.OnPreDrawListener {
override fun onPreDraw(): Boolean {
if (isAttached) {
itemView.viewTreeObserver.removeOnPreDrawListener(this)
}
return true
}
})
} catch (e: Exception) {
Log.e(TAG, "Error initializing ViewHolder: ${e.message}")
}
}
override fun setContent(item: AppInfo, isSelected: Boolean, payload: Any?) {
try {
setIconSafely(item.icon, item.packageName)
binding.name.text = item.name ?: "Unknown App"
if (item.isXpModule) {
binding.cornerLabel.visibility = View.VISIBLE
} else {
binding.cornerLabel.visibility = View.INVISIBLE
}
isAttached = true
} catch (e: Exception) {
Log.e(TAG, "Error setting content for ${item.packageName}: ${e.message}")
setSafeDefaults()
}
}
private fun setIconSafely(icon: Drawable?, packageName: String) {
try {
if (icon != null) {
val optimizedIcon = optimizeIcon(icon)
binding.icon.setImageDrawable(optimizedIcon)
currentIcon = optimizedIcon
} else {
binding.icon.setImageDrawable(createDefaultIcon())
currentIcon = null
}
} catch (e: Exception) {
Log.w(TAG, "Failed to set icon for $packageName: ${e.message}")
binding.icon.setImageDrawable(createDefaultIcon())
currentIcon = null
}
}
private fun optimizeIcon(icon: Drawable): Drawable {
return try {
if (icon is BitmapDrawable) {
val bitmap = icon.bitmap
if (bitmap.width > MAX_ICON_SIZE || bitmap.height > MAX_ICON_SIZE) {
val scaledBitmap = Bitmap.createScaledBitmap(
bitmap, MAX_ICON_SIZE, MAX_ICON_SIZE, true
)
BitmapDrawable(itemView.resources, scaledBitmap)
} else {
icon
}
} else {
icon
}
} catch (e: Exception) {
Log.w(TAG, "Error optimizing icon: ${e.message}")
icon
}
}
private fun createDefaultIcon(): Drawable {
return try {
ColorDrawable(DEFAULT_ICON_COLOR)
} catch (e: Exception) {
Log.w(TAG, "Error creating default icon: ${e.message}")
ColorDrawable(Color.GRAY)
}
}
private fun setSafeDefaults() {
try {
binding.icon.setImageDrawable(createDefaultIcon())
binding.name.text = "Unknown App"
binding.cornerLabel.visibility = View.INVISIBLE
} catch (e: Exception) {
Log.e(TAG, "Error setting safe defaults: ${e.message}")
}
}
}
class FallbackAppsVH(itemView: View) : RVHolder<AppInfo>(itemView) {
val binding = ItemAppBinding.bind(itemView)
override fun setContent(item: AppInfo, isSelected: Boolean, payload: Any?) {
try {
binding.icon.setImageDrawable(ColorDrawable(DEFAULT_ICON_COLOR))
binding.name.text = item.name ?: "Unknown App"
binding.cornerLabel.visibility = View.INVISIBLE
} catch (e: Exception) {
Log.e(TAG, "Error in fallback ViewHolder: ${e.message}")
}
}
}
}
@@ -1,14 +0,0 @@
package top.niunaijun.blackboxa.view.apps
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import top.niunaijun.blackboxa.data.AppsRepository
@Suppress("UNCHECKED_CAST")
class AppsFactory(private val appsRepository: AppsRepository) : ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return AppsViewModel(appsRepository) as T
}
}
@@ -1,550 +0,0 @@
package top.niunaijun.blackboxa.view.apps
import android.graphics.Point
import android.os.Bundle
import android.text.TextUtils
import android.util.Log
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.PopupMenu
import androidx.core.os.bundleOf
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import cbfg.rvadapter.RVAdapter
import com.afollestad.materialdialogs.MaterialDialog
import top.niunaijun.blackbox.BlackBoxCore
import top.niunaijun.blackboxa.R
import top.niunaijun.blackboxa.bean.AppInfo
import top.niunaijun.blackboxa.databinding.FragmentAppsBinding
import top.niunaijun.blackboxa.util.InjectionUtil
import top.niunaijun.blackboxa.util.ShortcutUtil
import top.niunaijun.blackboxa.util.inflate
import top.niunaijun.blackboxa.util.MemoryManager
import top.niunaijun.blackboxa.util.toast
import top.niunaijun.blackboxa.view.base.LoadingActivity
import top.niunaijun.blackboxa.view.main.MainActivity
import java.util.*
import kotlin.math.abs
class AppsFragment : Fragment() {
var userID: Int = 0
private lateinit var viewModel: AppsViewModel
private lateinit var mAdapter: RVAdapter<AppInfo>
private val viewBinding: FragmentAppsBinding by inflate()
private var popupMenu: PopupMenu? = null
companion object {
private const val TAG = "AppsFragment"
fun newInstance(userID:Int): AppsFragment {
val fragment = AppsFragment()
val bundle = bundleOf("userID" to userID)
fragment.arguments = bundle
return fragment
}
}
override fun onCreate(savedInstanceState: Bundle?) {
try {
super.onCreate(savedInstanceState)
viewModel =
ViewModelProvider(this, InjectionUtil.getAppsFactory()).get(AppsViewModel::class.java)
userID = requireArguments().getInt("userID", 0)
} catch (e: Exception) {
Log.e(TAG, "Error in onCreate: ${e.message}")
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
try {
viewBinding.stateView.showEmpty()
mAdapter =
RVAdapter<AppInfo>(requireContext(), AppsAdapter()).bind(viewBinding.recyclerView)
viewBinding.recyclerView.adapter = mAdapter
val layoutManager = GridLayoutManager(requireContext(), 4)
layoutManager.isItemPrefetchEnabled = true
layoutManager.initialPrefetchItemCount = 8
viewBinding.recyclerView.layoutManager = layoutManager
viewBinding.recyclerView.setItemViewCacheSize(20)
viewBinding.recyclerView.setHasFixedSize(true)
viewBinding.recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
try {
super.onScrollStateChanged(recyclerView, newState)
when (newState) {
RecyclerView.SCROLL_STATE_IDLE -> {
MemoryManager.optimizeMemoryForRecyclerView()
}
RecyclerView.SCROLL_STATE_DRAGGING -> {
}
RecyclerView.SCROLL_STATE_SETTLING -> {
}
}
} catch (e: Exception) {
Log.e(TAG, "Error in scroll state change: ${e.message}")
}
}
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
try {
super.onScrolled(recyclerView, dx, dy)
if (Math.abs(dy) > 100) {
if (MemoryManager.isMemoryCritical()) {
Log.w(TAG, "Memory critical during fast scrolling, forcing GC")
MemoryManager.forceGarbageCollectionIfNeeded()
}
}
} catch (e: Exception) {
Log.e(TAG, "Error in scroll: ${e.message}")
}
}
})
val touchCallBack = AppsTouchCallBack { from, to ->
try {
onItemMove(from, to)
viewModel.updateSortLiveData.postValue(true)
} catch (e: Exception) {
Log.e(TAG, "Error in touch callback: ${e.message}")
}
}
val itemTouchHelper = ItemTouchHelper(touchCallBack)
itemTouchHelper.attachToRecyclerView(viewBinding.recyclerView)
mAdapter.setItemClickListener { _, data, _ ->
try {
showLoading()
viewModel.launchApk(data.packageName, userID)
} catch (e: Exception) {
Log.e(TAG, "Error launching app: ${e.message}")
hideLoading()
}
}
interceptTouch()
setOnLongClick()
return viewBinding.root
} catch (e: Exception) {
Log.e(TAG, "Error in onCreateView: ${e.message}")
return View(requireContext())
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
try {
super.onViewCreated(view, savedInstanceState)
initData()
} catch (e: Exception) {
Log.e(TAG, "Error in onViewCreated: ${e.message}")
}
}
override fun onStart() {
try {
super.onStart()
try {
BlackBoxCore.get().addServiceAvailableCallback {
Log.d(TAG, "Services became available, refreshing app list")
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}")
}
}
private fun interceptTouch() {
try {
val point = Point()
var isScrolling = false
var scrollStartTime = 0L
viewBinding.recyclerView.setOnTouchListener { _, e ->
try {
when (e.action) {
MotionEvent.ACTION_DOWN -> {
isScrolling = false
scrollStartTime = System.currentTimeMillis()
point.set(0, 0)
}
MotionEvent.ACTION_UP -> {
val scrollDuration = System.currentTimeMillis() - scrollStartTime
if (!isScrolling && !isMove(point, e) && scrollDuration < 500) {
try {
popupMenu?.show()
} catch (e: Exception) {
Log.e(TAG, "Error showing popup menu: ${e.message}")
}
}
popupMenu = null
point.set(0, 0)
isScrolling = false
}
MotionEvent.ACTION_MOVE -> {
if (point.x == 0 && point.y == 0) {
point.x = e.rawX.toInt()
point.y = e.rawY.toInt()
}
if (isMove(point, e)) {
isScrolling = true
popupMenu?.dismiss()
}
isDownAndUp(point, e)
}
}
} catch (e: Exception) {
Log.e(TAG, "Error in touch listener: ${e.message}")
}
return@setOnTouchListener false
}
} catch (e: Exception) {
Log.e(TAG, "Error in interceptTouch: ${e.message}")
}
}
private fun isMove(point: Point, e: MotionEvent): Boolean {
return try {
val max = 40
val x = point.x
val y = point.y
val xU = abs(x - e.rawX)
val yU = abs(y - e.rawY)
xU > max || yU > max
} catch (e: Exception) {
Log.e(TAG, "Error in isMove: ${e.message}")
false
}
}
private fun isDownAndUp(point: Point, e: MotionEvent) {
try {
val min = 10
val y = point.y
val yU = y - e.rawY
if (abs(yU) > min) {
try {
(requireActivity() as? MainActivity)?.showFloatButton(yU < 0)
} catch (e: Exception) {
Log.e(TAG, "Error showing/hiding float button: ${e.message}")
}
}
} catch (e: Exception) {
Log.e(TAG, "Error in isDownAndUp: ${e.message}")
}
}
private fun onItemMove(fromPosition: Int, toPosition: Int) {
try {
val items = mAdapter.getItems()
if (fromPosition < 0 || toPosition < 0 ||
fromPosition >= items.size || toPosition >= items.size) {
Log.w(TAG, "Invalid positions for move: from=$fromPosition, to=$toPosition, size=${items.size}")
return
}
if (fromPosition < toPosition) {
for (i in fromPosition until toPosition) {
try {
Collections.swap(items, i, i + 1)
} catch (e: Exception) {
Log.e(TAG, "Error swapping items at position $i: ${e.message}")
return
}
}
} else {
for (i in fromPosition downTo toPosition + 1) {
try {
Collections.swap(items, i, i - 1)
} catch (e: Exception) {
Log.e(TAG, "Error swapping items at position $i: ${e.message}")
return
}
}
}
try {
mAdapter.notifyItemMoved(fromPosition, toPosition)
} catch (e: Exception) {
Log.e(TAG, "Error notifying item moved: ${e.message}")
mAdapter.notifyDataSetChanged()
}
} catch (e: Exception) {
Log.e(TAG, "Error in onItemMove: ${e.message}")
}
}
private fun setOnLongClick() {
try {
mAdapter.setItemLongClickListener { view, data, _ ->
try {
popupMenu = PopupMenu(requireContext(),view).also {
it.inflate(R.menu.app_menu)
it.setOnMenuItemClickListener { item ->
try {
when (item.itemId) {
R.id.app_remove -> {
if (data.isXpModule) {
toast(R.string.uninstall_module_toast)
} else {
unInstallApk(data)
}
}
R.id.app_clear -> {
clearApk(data)
}
R.id.app_stop -> {
stopApk(data)
}
R.id.app_shortcut -> {
ShortcutUtil.createShortcut(requireContext(), userID, data)
}
}
return@setOnMenuItemClickListener true
} catch (e: Exception) {
Log.e(TAG, "Error in menu item click: ${e.message}")
return@setOnMenuItemClickListener false
}
}
it.show()
}
} catch (e: Exception) {
Log.e(TAG, "Error in long click: ${e.message}")
}
}
} catch (e: Exception) {
Log.e(TAG, "Error in setOnLongClick: ${e.message}")
}
}
private fun initData() {
try {
viewBinding.stateView.showLoading()
viewModel.getInstalledApps(userID)
viewModel.appsLiveData.observe(viewLifecycleOwner) {
try {
if (it != null) {
mAdapter.setItems(it)
if (it.isEmpty()) {
viewBinding.stateView.showEmpty()
} else {
viewBinding.stateView.showContent()
}
}
} catch (e: Exception) {
Log.e(TAG, "Error observing apps data: ${e.message}")
}
}
viewModel.resultLiveData.observe(viewLifecycleOwner) {
try {
if (!TextUtils.isEmpty(it)) {
hideLoading()
requireContext().toast(it)
viewModel.getInstalledApps(userID)
scanUser()
}
} catch (e: Exception) {
Log.e(TAG, "Error observing result data: ${e.message}")
}
}
viewModel.launchLiveData.observe(viewLifecycleOwner) {
try {
it?.run {
hideLoading()
if (!it) {
toast(R.string.start_fail)
}
}
} catch (e: Exception) {
Log.e(TAG, "Error observing launch data: ${e.message}")
}
}
viewModel.updateSortLiveData.observe(viewLifecycleOwner) {
try {
if (this::mAdapter.isInitialized) {
viewModel.updateApkOrder(userID, mAdapter.getItems())
}
} catch (e: Exception) {
Log.e(TAG, "Error observing sort data: ${e.message}")
}
}
} catch (e: Exception) {
Log.e(TAG, "Error in initData: ${e.message}")
}
}
override fun onStop() {
try {
super.onStop()
viewModel.resultLiveData.value = null
viewModel.launchLiveData.value = null
} catch (e: Exception) {
Log.e(TAG, "Error in onStop: ${e.message}")
}
}
private fun unInstallApk(info: AppInfo) {
try {
MaterialDialog(requireContext()).show {
title(R.string.uninstall_app)
message(text = getString(R.string.uninstall_app_hint, info.name))
positiveButton(R.string.done) {
try {
showLoading()
viewModel.unInstall(info.packageName, userID)
} catch (e: Exception) {
Log.e(TAG, "Error uninstalling app: ${e.message}")
hideLoading()
}
}
negativeButton(R.string.cancel)
}
} catch (e: Exception) {
Log.e(TAG, "Error showing uninstall dialog: ${e.message}")
}
}
private fun stopApk(info: AppInfo) {
try {
MaterialDialog(requireContext()).show {
title(R.string.app_stop)
message(text = getString(R.string.app_stop_hint,info.name))
positiveButton(R.string.done) {
try {
BlackBoxCore.get().stopPackage(info.packageName, userID)
toast(getString(R.string.is_stop,info.name))
} catch (e: Exception) {
Log.e(TAG, "Error stopping app: ${e.message}")
}
}
negativeButton(R.string.cancel)
}
} catch (e: Exception) {
Log.e(TAG, "Error showing stop dialog: ${e.message}")
}
}
private fun clearApk(info: AppInfo) {
try {
MaterialDialog(requireContext()).show {
title(R.string.app_clear)
message(text = getString(R.string.app_clear_hint,info.name))
positiveButton(R.string.done) {
try {
showLoading()
viewModel.clearApkData(info.packageName, userID)
} catch (e: Exception) {
Log.e(TAG, "Error clearing app data: ${e.message}")
hideLoading()
}
}
negativeButton(R.string.cancel)
}
} catch (e: Exception) {
Log.e(TAG, "Error showing clear dialog: ${e.message}")
}
}
fun installApk(source: String) {
try {
showLoading()
viewModel.install(source, userID)
} catch (e: Exception) {
Log.e(TAG, "Error installing APK: ${e.message}")
hideLoading()
}
}
private fun scanUser() {
try {
(requireActivity() as? MainActivity)?.scanUser()
} catch (e: Exception) {
Log.e(TAG, "Error scanning user: ${e.message}")
}
}
private fun showLoading() {
try {
if(requireActivity() is LoadingActivity){
(requireActivity() as LoadingActivity).showLoading()
}
} catch (e: Exception) {
Log.e(TAG, "Error showing loading: ${e.message}")
}
}
private fun hideLoading() {
try {
if(requireActivity() is LoadingActivity){
(requireActivity() as LoadingActivity).hideLoading()
}
} catch (e: Exception) {
Log.e(TAG, "Error hiding loading: ${e.message}")
}
}
}
@@ -1,105 +0,0 @@
package top.niunaijun.blackboxa.view.apps
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import android.util.Log
class AppsTouchCallBack(private val onMoveBlock: (from: Int, to: Int) -> Unit) :
ItemTouchHelper.Callback() {
companion object {
private const val TAG = "AppsTouchCallBack"
}
override fun getMovementFlags(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder
): Int {
return try {
makeMovementFlags(
ItemTouchHelper.UP or ItemTouchHelper.DOWN or
ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT,
0
)
} catch (e: Exception) {
Log.e(TAG, "Error getting movement flags: ${e.message}")
makeMovementFlags(0, 0)
}
}
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
return try {
val fromPosition = viewHolder.bindingAdapterPosition
val toPosition = target.bindingAdapterPosition
if (fromPosition == RecyclerView.NO_POSITION || toPosition == RecyclerView.NO_POSITION) {
Log.w(TAG, "Invalid positions: from=$fromPosition, to=$toPosition")
false
} else if (fromPosition == toPosition) {
false
} else {
onMoveBlock(fromPosition, toPosition)
true
}
} catch (e: Exception) {
Log.e(TAG, "Error in onMove: ${e.message}")
false
}
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
}
override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
try {
super.onSelectedChanged(viewHolder, actionState)
when (actionState) {
ItemTouchHelper.ACTION_STATE_DRAG -> {
viewHolder?.itemView?.alpha = 0.8f
}
ItemTouchHelper.ACTION_STATE_IDLE -> {
viewHolder?.itemView?.alpha = 1.0f
}
}
} catch (e: Exception) {
Log.e(TAG, "Error in onSelectedChanged: ${e.message}")
}
}
override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
try {
super.clearView(recyclerView, viewHolder)
viewHolder.itemView.alpha = 1.0f
} catch (e: Exception) {
Log.e(TAG, "Error in clearView: ${e.message}")
}
}
override fun canDropOver(
recyclerView: RecyclerView,
current: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
return try {
val targetPosition = target.bindingAdapterPosition
targetPosition != RecyclerView.NO_POSITION
} catch (e: Exception) {
Log.e(TAG, "Error in canDropOver: ${e.message}")
false
}
}
}
@@ -1,81 +0,0 @@
package top.niunaijun.blackboxa.view.apps
import androidx.lifecycle.MutableLiveData
import top.niunaijun.blackboxa.bean.AppInfo
import top.niunaijun.blackboxa.data.AppsRepository
import top.niunaijun.blackboxa.view.base.BaseViewModel
import android.util.Log
class AppsViewModel(private val repo: AppsRepository) : BaseViewModel() {
val appsLiveData = MutableLiveData<List<AppInfo>>()
val resultLiveData = MutableLiveData<String>()
val launchLiveData = MutableLiveData<Boolean>()
val updateSortLiveData = MutableLiveData<Boolean>()
fun getInstalledApps(userId: Int) {
launchOnUI {
repo.getVmInstallList(userId, appsLiveData)
}
}
fun getInstalledAppsWithRetry(userId: Int, maxRetries: Int = 3) {
var retryCount = 0
fun attemptLoad() {
launchOnUI {
repo.getVmInstallList(userId, appsLiveData)
val currentApps = appsLiveData.value
if ((currentApps == null || currentApps.isEmpty()) && retryCount < maxRetries) {
retryCount++
Log.d("AppsViewModel", "No apps loaded, retrying... (${retryCount}/${maxRetries})")
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
attemptLoad()
}, 1000)
}
}
}
attemptLoad()
}
fun install(source: String, userID: Int) {
launchOnUI {
repo.installApk(source, userID, resultLiveData)
}
}
fun unInstall(packageName: String, userID: Int) {
launchOnUI {
repo.unInstall(packageName, userID, resultLiveData)
}
}
fun clearApkData(packageName: String,userID: Int){
launchOnUI {
repo.clearApkData(packageName,userID,resultLiveData)
}
}
fun launchApk(packageName: String, userID: Int) {
launchOnUI {
repo.launchApk(packageName, userID, launchLiveData)
}
}
fun updateApkOrder(userID: Int,dataList:List<AppInfo>){
launchOnUI {
repo.updateApkOrder(userID,dataList)
}
}
}
@@ -1,28 +0,0 @@
package top.niunaijun.blackboxa.view.base
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
open class BaseActivity : AppCompatActivity() {
protected fun initToolbar(toolbar: Toolbar,title:Int, showBack: Boolean = false, onBack: (() -> Unit)? = null) {
setSupportActionBar(toolbar)
toolbar.setTitle(title)
if (showBack) {
supportActionBar?.let {
it.setDisplayHomeAsUpEnabled(true)
toolbar.setNavigationOnClickListener {
if (onBack != null) {
onBack()
}
finish()
}
}
}
}
protected fun currentUserID():Int{
return intent.getIntExtra("userID", 0)
}
}
@@ -1,29 +0,0 @@
package top.niunaijun.blackboxa.view.base
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.*
open class BaseViewModel : ViewModel() {
fun launchOnUI(block: suspend CoroutineScope.() -> Unit) {
viewModelScope.launch {
withContext(Dispatchers.IO) {
try {
block()
} catch (e: Throwable) {
e.printStackTrace()
}
}
}
}
override fun onCleared() {
super.onCleared()
viewModelScope.cancel()
}
}
@@ -1,38 +0,0 @@
package top.niunaijun.blackboxa.view.base
import android.view.KeyEvent
import com.roger.catloadinglibrary.CatLoadingView
import top.niunaijun.blackboxa.R
abstract class LoadingActivity : BaseActivity() {
private lateinit var loadingView: CatLoadingView
fun showLoading() {
if (!this::loadingView.isInitialized) {
loadingView = CatLoadingView()
}
if (!loadingView.isAdded) {
loadingView.setBackgroundColor(R.color.primary)
loadingView.show(supportFragmentManager, "")
supportFragmentManager.executePendingTransactions()
loadingView.setClickCancelAble(false)
loadingView.dialog?.setOnKeyListener { _, keyCode, _ ->
if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE) {
return@setOnKeyListener true
}
false
}
}
}
fun hideLoading() {
if (this::loadingView.isInitialized) {
loadingView.dismiss()
}
}
}
@@ -1,38 +0,0 @@
package top.niunaijun.blackboxa.view.fake
import android.view.View
import android.view.ViewGroup
import cbfg.rvadapter.RVHolder
import cbfg.rvadapter.RVHolderFactory
import top.niunaijun.blackbox.fake.frameworks.BLocationManager
import top.niunaijun.blackboxa.R
import top.niunaijun.blackboxa.bean.FakeLocationBean
import top.niunaijun.blackboxa.databinding.ItemFakeBinding
import top.niunaijun.blackboxa.util.getString
class FakeLocationAdapter : RVHolderFactory() {
override fun createViewHolder(parent: ViewGroup?, viewType: Int, item: Any): RVHolder<out Any> {
return FakeLocationVH(inflate(R.layout.item_fake,parent))
}
class FakeLocationVH(itemView:View):RVHolder<FakeLocationBean>(itemView){
private val binding = ItemFakeBinding.bind(itemView)
override fun setContent(item: FakeLocationBean, isSelected: Boolean, payload: Any?) {
binding.icon.setImageDrawable(item.icon)
binding.name.text = item.name
if (item.fakeLocation == null || item.fakeLocationPattern == BLocationManager.CLOSE_MODE) {
binding.fakeLocation.text = getString(R.string.real_location)
} else {
binding.fakeLocation.text =
String.format("%f, %f", item.fakeLocation!!.latitude, item.fakeLocation!!.longitude)
}
binding.cornerLabel.visibility = View.VISIBLE
}
}
}
@@ -1,14 +0,0 @@
package top.niunaijun.blackboxa.view.fake
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import top.niunaijun.blackboxa.data.FakeLocationRepository
class FakeLocationFactory(private val repo: FakeLocationRepository) :
ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return FakeLocationViewModel(repo) as T
}
}
@@ -1,33 +0,0 @@
package top.niunaijun.blackboxa.view.fake
import androidx.lifecycle.MutableLiveData
import top.niunaijun.blackbox.entity.location.BLocation
import top.niunaijun.blackboxa.bean.FakeLocationBean
import top.niunaijun.blackboxa.data.FakeLocationRepository
import top.niunaijun.blackboxa.view.base.BaseViewModel
class FakeLocationViewModel(private val mRepo: FakeLocationRepository) : BaseViewModel() {
val appsLiveData = MutableLiveData<List<FakeLocationBean>>()
fun getInstallAppList(userID: Int) {
launchOnUI {
mRepo.getInstalledAppList(userID, appsLiveData)
}
}
fun setPattern(userId: Int, pkg: String, pattern: Int) {
launchOnUI {
mRepo.setPattern(userId, pkg, pattern)
}
}
fun setLocation(userId: Int, pkg: String, location: BLocation) {
launchOnUI {
mRepo.setLocation(userId, pkg, location)
}
}
}
@@ -1,184 +0,0 @@
package top.niunaijun.blackboxa.view.fake
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.Menu
import android.view.inputmethod.InputMethodManager
import androidx.activity.result.contract.ActivityResultContracts
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import cbfg.rvadapter.RVAdapter
import com.afollestad.materialdialogs.MaterialDialog
import com.ferfalk.simplesearchview.SimpleSearchView
import top.niunaijun.blackbox.entity.location.BLocation
import top.niunaijun.blackbox.fake.frameworks.BLocationManager
import top.niunaijun.blackboxa.R
import top.niunaijun.blackboxa.bean.FakeLocationBean
import top.niunaijun.blackboxa.databinding.ActivityListBinding
import top.niunaijun.blackboxa.util.InjectionUtil
import top.niunaijun.blackboxa.util.inflate
import top.niunaijun.blackboxa.util.toast
import top.niunaijun.blackboxa.view.base.BaseActivity
class FakeManagerActivity : BaseActivity() {
val TAG: String = "FakeManagerActivity"
private val viewBinding: ActivityListBinding by inflate()
private lateinit var mAdapter: RVAdapter<FakeLocationBean>
private lateinit var viewModel: FakeLocationViewModel
private var appList: List<FakeLocationBean> = ArrayList()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(viewBinding.root)
initToolbar(viewBinding.toolbarLayout.toolbar, R.string.fake_location, true)
mAdapter = RVAdapter<FakeLocationBean>(this,FakeLocationAdapter()).bind(viewBinding.recyclerView)
.setItemClickListener { _, data, _ ->
val intent = Intent(this, FollowMyLocationOverlay::class.java)
intent.putExtra("location", data.fakeLocation)
intent.putExtra("pkg", data.packageName)
locationResult.launch(intent)
}.setItemLongClickListener { _, item, position ->
disableFakeLocation(item,position)
}
viewBinding.recyclerView.layoutManager = LinearLayoutManager(this)
initSearchView()
initViewModel()
}
private fun disableFakeLocation(item: FakeLocationBean,position:Int) {
MaterialDialog(this).show {
title(R.string.close_fake_location)
message(text = getString(R.string.close_app_fake_location,item.name))
negativeButton(R.string.cancel)
positiveButton(R.string.done){
BLocationManager.disableFakeLocation(currentUserID(),item.packageName)
toast(getString(R.string.close_fake_location_success,item.name))
item.fakeLocationPattern = BLocationManager.CLOSE_MODE
mAdapter.replaceAt(position,item)
}
}
}
private fun initSearchView() {
viewBinding.searchView.setOnQueryTextListener(object :
SimpleSearchView.OnQueryTextListener {
override fun onQueryTextChange(newText: String): Boolean {
filterApp(newText)
return true
}
override fun onQueryTextCleared(): Boolean {
return true
}
override fun onQueryTextSubmit(query: String): Boolean {
return true
}
})
}
private fun initViewModel() {
viewModel = ViewModelProvider(this, InjectionUtil.getFakeLocationFactory()).get(
FakeLocationViewModel::class.java
)
loadAppList()
viewBinding.toolbarLayout.toolbar.setTitle(R.string.fake_location)
viewModel.appsLiveData.observe(this) {
if (it != null) {
this.appList = it
viewBinding.searchView.setQuery("", false)
filterApp("")
if (it.isNotEmpty()) {
viewBinding.stateView.showContent()
} else {
viewBinding.stateView.showEmpty()
}
}
}
}
private fun loadAppList() {
viewBinding.stateView.showLoading()
viewModel.getInstallAppList(currentUserID())
}
private val locationResult =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == RESULT_OK) {
it.data?.let { data ->
val latitude = data.getDoubleExtra("latitude", 0.0)
val longitude = data.getDoubleExtra("longitude", 0.0)
val pkg = data.getStringExtra("pkg")
viewModel.setPattern(currentUserID(), pkg.toString(), BLocationManager.OWN_MODE)
viewModel.setLocation(currentUserID(), pkg.toString(), BLocation(latitude, longitude))
toast(getString(R.string.set_location,latitude.toString(), longitude.toString()))
loadAppList()
}
}
}
private fun filterApp(newText: String) {
val newList = this.appList.filter {
it.name.contains(newText, true) or it.packageName.contains(newText, true)
}
mAdapter.setItems(newList)
}
private fun finishWithResult(source: String) {
intent.putExtra("source", source)
setResult(Activity.RESULT_OK, intent)
val imm: InputMethodManager = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
window.peekDecorView()?.run {
imm.hideSoftInputFromWindow(windowToken, 0)
}
finish()
}
override fun onBackPressed() {
if (viewBinding.searchView.isSearchOpen) {
viewBinding.searchView.closeSearch()
} else {
super.onBackPressed()
}
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.menu_search, menu)
val item = menu!!.findItem(R.id.list_search)
viewBinding.searchView.setMenuItem(item)
return true
}
companion object {
fun start(context: Context) {
val intent = Intent(context, FakeManagerActivity::class.java)
context.startActivity(intent)
}
}
}
@@ -1,139 +0,0 @@
package top.niunaijun.blackboxa.view.fake
import android.app.Activity
import android.os.Bundle
import android.view.inputmethod.InputMethodManager
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.preference.PreferenceManager
import org.osmdroid.config.Configuration
import org.osmdroid.events.MapEventsReceiver
import org.osmdroid.tileprovider.tilesource.TileSourceFactory
import org.osmdroid.util.GeoPoint
import org.osmdroid.views.overlay.MapEventsOverlay
import org.osmdroid.views.overlay.Marker
import top.niunaijun.blackbox.entity.location.BLocation
import top.niunaijun.blackboxa.databinding.ActivityOsmdroidBinding
import top.niunaijun.blackboxa.util.inflate
import top.niunaijun.blackboxa.util.toast
class FollowMyLocationOverlay : AppCompatActivity() {
val TAG: String = "FollowMyLocationOverlay"
private val REQUEST_PERMISSIONS_REQUEST_CODE = 1
private val binding: ActivityOsmdroidBinding by inflate()
lateinit var startPoint: GeoPoint
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Configuration.getInstance().load(this, PreferenceManager.getDefaultSharedPreferences(this))
setContentView(binding.root)
val location: BLocation? = intent.getParcelableExtra("location")
startPoint = if (location == null) {
GeoPoint(30.2736, 120.1563)
} else {
GeoPoint(location.latitude, location.longitude)
}
val startMarker = Marker(binding.map)
startMarker.position = startPoint
startMarker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM)
binding.map.overlays.add(startMarker)
val mReceive: MapEventsReceiver = object : MapEventsReceiver {
override fun singleTapConfirmedHelper(p: GeoPoint): Boolean {
startPoint = p
startMarker.position = p
startMarker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM)
binding.map.overlays.add(startMarker)
toast(p.latitude.toString() + " - " + p.longitude)
return false
}
override fun longPressHelper(p: GeoPoint): Boolean {
return false
}
}
binding.map.overlays.add(MapEventsOverlay(mReceive))
val mapController = binding.map.controller
mapController.setZoom(12.5)
mapController.setCenter(startPoint)
binding.map.setTileSource(TileSourceFactory.MAPNIK)
}
override fun onBackPressed() {
finishWithResult(startPoint)
}
override fun onResume() {
super.onResume()
binding.map.onResume()
}
override fun onPause() {
super.onPause()
binding.map.onPause()
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
val permissionsToRequest = ArrayList<String>()
var i = 0
while (i < grantResults.size) {
permissionsToRequest.add(permissions[i])
i++
}
if (permissionsToRequest.size > 0) {
ActivityCompat.requestPermissions(
this,
permissionsToRequest.toTypedArray(),
REQUEST_PERMISSIONS_REQUEST_CODE
)
}
}
private fun finishWithResult(geoPoint: GeoPoint) {
intent.putExtra("latitude", geoPoint.latitude)
intent.putExtra("longitude", geoPoint.longitude)
setResult(Activity.RESULT_OK, intent)
val imm: InputMethodManager = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
window.peekDecorView()?.run {
imm.hideSoftInputFromWindow(windowToken, 0)
}
finish()
}
}
@@ -1,32 +0,0 @@
package top.niunaijun.blackboxa.view.gms
import android.view.View
import android.view.ViewGroup
import cbfg.rvadapter.RVHolder
import cbfg.rvadapter.RVHolderFactory
import top.niunaijun.blackboxa.R
import top.niunaijun.blackboxa.bean.GmsBean
import top.niunaijun.blackboxa.databinding.ItemGmsBinding
class GmsAdapter : RVHolderFactory() {
override fun createViewHolder(parent: ViewGroup?, viewType: Int, item: Any): RVHolder<out Any> {
return GmsVH(inflate(R.layout.item_gms,parent))
}
class GmsVH(itemView:View):RVHolder<GmsBean>(itemView){
private val binding = ItemGmsBinding.bind(itemView)
override fun setContent(item: GmsBean, isSelected: Boolean, payload: Any?) {
binding.tvTitle.text = item.userName
binding.checkbox.isChecked = item.isInstalledGms
binding.checkbox.setOnCheckedChangeListener { buttonView, _ ->
if(buttonView.isPressed){
binding.root.performClick()
}
}
}
}
}
@@ -1,13 +0,0 @@
package top.niunaijun.blackboxa.view.gms
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import top.niunaijun.blackboxa.data.GmsRepository
class GmsFactory(private val repo:GmsRepository): ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return GmsViewModel(repo) as T
}
}
@@ -1,128 +0,0 @@
package top.niunaijun.blackboxa.view.gms
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.widget.Switch
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import cbfg.rvadapter.RVAdapter
import com.afollestad.materialdialogs.MaterialDialog
import top.niunaijun.blackboxa.R
import top.niunaijun.blackboxa.bean.GmsBean
import top.niunaijun.blackboxa.databinding.ActivityGmsBinding
import top.niunaijun.blackboxa.util.InjectionUtil
import top.niunaijun.blackboxa.util.inflate
import top.niunaijun.blackboxa.util.toast
import top.niunaijun.blackboxa.view.base.LoadingActivity
class GmsManagerActivity : LoadingActivity() {
private lateinit var viewModel: GmsViewModel
private lateinit var mAdapter: RVAdapter<GmsBean>
private val viewBinding: ActivityGmsBinding by inflate()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(viewBinding.root)
initToolbar(viewBinding.toolbarLayout.toolbar, R.string.gms_manager, true)
initViewModel()
initRecyclerView()
}
private fun initViewModel() {
viewModel = ViewModelProvider(this, InjectionUtil.getGmsFactory())[GmsViewModel::class.java]
showLoading()
viewModel.mInstalledLiveData.observe(this) {
hideLoading()
mAdapter.setItems(it)
}
viewModel.mUpdateInstalledLiveData.observe(this) { result ->
if (result == null) {
return@observe
}
val items = mAdapter.getItems()
for (index in items.indices) {
val bean = items[index]
if (bean.userID == result.userID) {
if (result.success) {
bean.isInstalledGms = !bean.isInstalledGms
}
mAdapter.replaceAt( index,bean)
break
}
}
hideLoading()
if (result.success) {
toast(result.msg)
} else {
MaterialDialog(this).show {
title(R.string.gms_manager)
message(text = result.msg)
positiveButton(R.string.done)
}
}
}
viewModel.getInstalledUser()
}
private fun initRecyclerView() {
mAdapter = RVAdapter<GmsBean>(this, GmsAdapter()).bind(viewBinding.recyclerView)
.setItemClickListener { view, item, _ ->
val checkbox = view.findViewById<Switch>(R.id.checkbox)
if (item.isInstalledGms) {
uninstallGms(item.userID, checkbox)
} else {
installGms(item.userID, checkbox)
}
}
viewBinding.recyclerView.layoutManager = LinearLayoutManager(this)
}
private fun installGms(userID: Int, checkbox: Switch){
MaterialDialog(this).show {
title(R.string.enable_gms)
message(R.string.enable_gms_hint)
positiveButton(R.string.done){
showLoading()
viewModel.installGms(userID)
}
negativeButton(R.string.cancel){
checkbox.isChecked = !checkbox.isChecked
}
}
}
private fun uninstallGms(userID: Int, checkbox: Switch){
MaterialDialog(this).show {
title(R.string.disable_gms)
message(R.string.disable_gms_hint)
positiveButton(R.string.done){
showLoading()
viewModel.uninstallGms(userID)
}
negativeButton(R.string.cancel){
checkbox.isChecked = !checkbox.isChecked
}
}
}
companion object{
fun start(context: Context){
val intent = Intent(context,GmsManagerActivity::class.java)
context.startActivity(intent)
}
}
}
@@ -1,33 +0,0 @@
package top.niunaijun.blackboxa.view.gms
import androidx.lifecycle.MutableLiveData
import top.niunaijun.blackboxa.bean.GmsBean
import top.niunaijun.blackboxa.bean.GmsInstallBean
import top.niunaijun.blackboxa.data.GmsRepository
import top.niunaijun.blackboxa.view.base.BaseViewModel
class GmsViewModel(private val mRepo: GmsRepository) : BaseViewModel() {
val mInstalledLiveData = MutableLiveData<List<GmsBean>>()
val mUpdateInstalledLiveData = MutableLiveData<GmsInstallBean>()
fun getInstalledUser() {
launchOnUI {
mRepo.getGmsInstalledList(mInstalledLiveData)
}
}
fun installGms(userID: Int) {
launchOnUI {
mRepo.installGms(userID,mUpdateInstalledLiveData)
}
}
fun uninstallGms(userID: Int) {
launchOnUI {
mRepo.uninstallGms(userID,mUpdateInstalledLiveData)
}
}
}
@@ -1,157 +0,0 @@
package top.niunaijun.blackboxa.view.list
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import android.view.inputmethod.InputMethodManager
import androidx.activity.result.contract.ActivityResultContracts
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import cbfg.rvadapter.RVAdapter
import com.ferfalk.simplesearchview.SimpleSearchView
import top.niunaijun.blackboxa.R
import top.niunaijun.blackboxa.bean.InstalledAppBean
import top.niunaijun.blackboxa.databinding.ActivityListBinding
import top.niunaijun.blackboxa.util.InjectionUtil
import top.niunaijun.blackboxa.util.inflate
import top.niunaijun.blackboxa.view.base.BaseActivity
class ListActivity : BaseActivity() {
private val viewBinding: ActivityListBinding by inflate()
private lateinit var mAdapter: RVAdapter<InstalledAppBean>
private lateinit var viewModel: ListViewModel
private var appList: List<InstalledAppBean> = ArrayList()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(viewBinding.root)
initToolbar(viewBinding.toolbarLayout.toolbar, R.string.installed_app, true)
mAdapter =
RVAdapter<InstalledAppBean>(this, ListAdapter())
.bind(viewBinding.recyclerView)
.setItemClickListener { _, item, _ -> finishWithResult(item.packageName) }
viewBinding.recyclerView.layoutManager = LinearLayoutManager(this)
initSearchView()
initViewModel()
}
private fun initSearchView() {
viewBinding.searchView.setOnQueryTextListener(
object : SimpleSearchView.OnQueryTextListener {
override fun onQueryTextChange(newText: String): Boolean {
filterApp(newText)
return true
}
override fun onQueryTextCleared(): Boolean {
return true
}
override fun onQueryTextSubmit(query: String): Boolean {
return true
}
}
)
}
private fun initViewModel() {
viewModel =
ViewModelProvider(this, InjectionUtil.getListFactory())
.get(ListViewModel::class.java)
val userID = intent.getIntExtra("userID", 0)
viewModel.getInstallAppList(userID)
viewBinding.toolbarLayout.toolbar.setTitle(R.string.installed_app)
viewModel.loadingLiveData.observe(this) {
if (it) {
viewBinding.stateView.showLoading()
} else {
viewBinding.stateView.showContent()
}
}
viewModel.appsLiveData.observe(this) {
if (it != null) {
this.appList = it
viewBinding.searchView.setQuery("", false)
filterApp("")
if (it.isNotEmpty()) {
viewBinding.stateView.showContent()
viewModel.previewInstalledList()
} else {
viewBinding.stateView.showEmpty()
}
}
}
}
private fun filterApp(newText: String) {
val newList =
this.appList.filter {
it.name.contains(newText, true) or it.packageName.contains(newText, true)
}
mAdapter.setItems(newList)
}
private val openDocumentedResult =
registerForActivityResult(ActivityResultContracts.GetContent()) {
it?.run { finishWithResult(it.toString()) }
}
private fun finishWithResult(source: String) {
intent.putExtra("source", source)
setResult(Activity.RESULT_OK, intent)
val imm: InputMethodManager = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
window.peekDecorView()?.run { imm.hideSoftInputFromWindow(windowToken, 0) }
finish()
}
override fun onBackPressed() {
if (viewBinding.searchView.isSearchOpen) {
viewBinding.searchView.closeSearch()
} else {
super.onBackPressed()
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == R.id.list_choose) {
openDocumentedResult.launch("application/vnd.android.package-archive")
}
return true
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.menu_list, menu)
val item = menu!!.findItem(R.id.list_search)
viewBinding.searchView.setMenuItem(item)
return true
}
override fun onStop() {
super.onStop()
viewModel.loadingLiveData.postValue(true)
viewModel.loadingLiveData.removeObservers(this)
viewModel.appsLiveData.postValue(null)
viewModel.appsLiveData.removeObservers(this)
}
companion object {
fun start(context: Context) {
val intent = Intent(context, ListActivity::class.java)
context.startActivity(intent)
}
}
}
@@ -1,33 +0,0 @@
package top.niunaijun.blackboxa.view.list
import android.view.View
import android.view.ViewGroup
import cbfg.rvadapter.RVHolder
import cbfg.rvadapter.RVHolderFactory
import top.niunaijun.blackboxa.R
import top.niunaijun.blackboxa.bean.InstalledAppBean
import top.niunaijun.blackboxa.databinding.ItemPackageBinding
class ListAdapter : RVHolderFactory() {
override fun createViewHolder(parent: ViewGroup?, viewType: Int, item: Any): RVHolder<out Any> {
return ListVH(inflate(R.layout.item_package,parent))
}
class ListVH(itemView:View) :RVHolder<InstalledAppBean>(itemView){
val binding = ItemPackageBinding.bind(itemView)
override fun setContent(item: InstalledAppBean, isSelected: Boolean, payload: Any?) {
binding.icon.setImageDrawable(item.icon)
binding.name.text = item.name
binding.packageName.text = item.packageName
binding.cornerLabel.visibility = if (item.isInstall) {
View.VISIBLE
} else {
View.GONE
}
}
}
}
@@ -1,14 +0,0 @@
package top.niunaijun.blackboxa.view.list
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import top.niunaijun.blackboxa.data.AppsRepository
@Suppress("UNCHECKED_CAST")
class ListFactory(private val appsRepository: AppsRepository) : ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return ListViewModel(appsRepository) as T
}
}
@@ -1,22 +0,0 @@
package top.niunaijun.blackboxa.view.list
import androidx.lifecycle.MutableLiveData
import top.niunaijun.blackboxa.bean.InstalledAppBean
import top.niunaijun.blackboxa.data.AppsRepository
import top.niunaijun.blackboxa.view.base.BaseViewModel
class ListViewModel(private val repo: AppsRepository) : BaseViewModel() {
val appsLiveData = MutableLiveData<List<InstalledAppBean>>()
val loadingLiveData = MutableLiveData<Boolean>()
fun previewInstalledList() {
launchOnUI { repo.previewInstallList() }
}
fun getInstallAppList(userID: Int) {
launchOnUI { repo.getInstalledAppList(userID, loadingLiveData, appsLiveData) }
}
}
@@ -1,318 +0,0 @@
package top.niunaijun.blackboxa.view.main
import android.app.Application
import android.content.Context
import android.util.Log
import java.io.File
import top.niunaijun.blackbox.BlackBoxCore
import top.niunaijun.blackbox.app.BActivityThread
import top.niunaijun.blackbox.app.configuration.AppLifecycleCallback
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
class BlackBoxLoader {
private var mHideRoot by AppSharedPreferenceDelegate(App.getContext(), false)
private var mDaemonEnable by AppSharedPreferenceDelegate(App.getContext(), true)
private var mShowShortcutPermissionDialog by AppSharedPreferenceDelegate(App.getContext(), true)
private var mUseVpnNetwork by AppSharedPreferenceDelegate(App.getContext(), false)
private var mDisableFlagSecure by AppSharedPreferenceDelegate(App.getContext(), false)
fun hideRoot(): Boolean {
return try {
mHideRoot
} catch (e: Exception) {
Log.e(TAG, "Error getting hideRoot: ${e.message}")
false
}
}
fun invalidHideRoot(hideRoot: Boolean) {
try {
this.mHideRoot = hideRoot
} catch (e: Exception) {
Log.e(TAG, "Error setting hideRoot: ${e.message}")
}
}
fun 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
} catch (e: Exception) {
Log.e(TAG, "Error getting daemonEnable: ${e.message}")
false
}
}
fun invalidDaemonEnable(enable: Boolean) {
try {
this.mDaemonEnable = enable
} catch (e: Exception) {
Log.e(TAG, "Error setting daemonEnable: ${e.message}")
}
}
fun showShortcutPermissionDialog(): Boolean {
return try {
mShowShortcutPermissionDialog
} catch (e: Exception) {
Log.e(TAG, "Error getting showShortcutPermissionDialog: ${e.message}")
true
}
}
fun invalidShortcutPermissionDialog(show: Boolean) {
try {
this.mShowShortcutPermissionDialog = show
} catch (e: Exception) {
Log.e(TAG, "Error setting showShortcutPermissionDialog: ${e.message}")
}
}
fun useVpnNetwork(): Boolean {
return try {
mUseVpnNetwork
} catch (e: Exception) {
Log.e(TAG, "Error getting useVpnNetwork: ${e.message}")
false
}
}
fun invalidUseVpnNetwork(enable: Boolean) {
try {
this.mUseVpnNetwork = enable
} catch (e: Exception) {
Log.e(TAG, "Error setting useVpnNetwork: ${e.message}")
}
}
fun getBlackBoxCore(): BlackBoxCore {
return try {
BlackBoxCore.get()
} catch (e: Exception) {
Log.e(TAG, "Error getting BlackBoxCore: ${e.message}")
throw e
}
}
fun addLifecycleCallback() {
try {
BlackBoxCore.get()
.addAppLifecycleCallback(
object : AppLifecycleCallback() {
override fun beforeCreateApplication(
packageName: String?,
processName: String?,
context: Context?,
userId: Int
) {
try {
Log.d(
TAG,
"beforeCreateApplication: pkg $packageName, processName $processName,userID:${BActivityThread.getUserId()}"
)
} catch (e: Exception) {
Log.e(TAG, "Error in beforeCreateApplication: ${e.message}")
}
}
override fun beforeApplicationOnCreate(
packageName: String?,
processName: String?,
application: Application?,
userId: Int
) {
try {
Log.d(
TAG,
"beforeApplicationOnCreate: pkg $packageName, processName $processName"
)
} catch (e: Exception) {
Log.e(
TAG,
"Error in beforeApplicationOnCreate: ${e.message}"
)
}
}
override fun afterApplicationOnCreate(
packageName: String?,
processName: String?,
application: Application?,
userId: Int
) {
try {
Log.d(
TAG,
"afterApplicationOnCreate: pkg $packageName, processName $processName"
)
RockerManager.init(application, userId)
} catch (e: Exception) {
Log.e(
TAG,
"Error in afterApplicationOnCreate: ${e.message}"
)
}
}
override fun onStoragePermissionNeeded(
packageName: String?,
userId: Int
): Boolean {
try {
Log.w(
TAG,
"Storage permission needed for launching: $packageName"
)
val intent =
android.content.Intent(
"top.niunaijun.blackboxa.REQUEST_STORAGE_PERMISSION"
)
intent.putExtra("package_name", packageName)
intent.putExtra("user_id", userId)
intent.setPackage(App.getContext().packageName)
App.getContext().sendBroadcast(intent)
return false
} catch (e: Exception) {
Log.e(
TAG,
"Error in onStoragePermissionNeeded: ${e.message}"
)
return false
}
}
}
)
} catch (e: Exception) {
Log.e(TAG, "Error adding lifecycle callback: ${e.message}")
}
}
fun attachBaseContext(context: Context) {
try {
BlackBoxCore.get()
.doAttachBaseContext(
context,
object : ClientConfiguration() {
override fun getHostPackageName(): String {
return try {
context.packageName
} catch (e: Exception) {
Log.e(TAG, "Error getting package name: ${e.message}")
"unknown"
}
}
override fun isHideRoot(): Boolean {
return try {
mHideRoot
} catch (e: Exception) {
Log.e(TAG, "Error checking hideRoot: ${e.message}")
false
}
}
override fun isEnableDaemonService(): Boolean {
return true
}
override fun isUseVpnNetwork(): Boolean {
return try {
mUseVpnNetwork
} catch (e: Exception) {
Log.e(TAG, "Error checking useVpnNetwork: ${e.message}")
false
}
}
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
): Boolean {
return try {
if (file == null) {
Log.w(TAG, "requestInstallPackage: file is null")
return false
}
val packageInfo =
context.packageManager.getPackageArchiveInfo(
file.absolutePath,
0
)
false
} catch (e: Exception) {
Log.e(TAG, "Error in requestInstallPackage: ${e.message}")
false
}
}
}
)
} catch (e: Exception) {
Log.e(TAG, "Error in attachBaseContext: ${e.message}")
}
}
fun doOnCreate(context: Context) {
try {
BlackBoxCore.get().doCreate()
try {
BlackBoxCore.get().addServiceAvailableCallback {
Log.d(TAG, "Services became available, triggering app list refresh")
}
} catch (e: Exception) {
Log.e(TAG, "Error registering service available callback: ${e.message}")
}
} catch (e: Exception) {
Log.e(TAG, "Error in doOnCreate: ${e.message}")
}
}
companion object {
val TAG: String = BlackBoxLoader::class.java.simpleName
}
}
@@ -1,434 +0,0 @@
package top.niunaijun.blackboxa.view.main
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.net.VpnService
import android.os.Bundle
import android.util.Log
import android.view.Menu
import android.view.MenuItem
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.edit
import androidx.viewpager2.widget.ViewPager2
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.input.input
import top.niunaijun.blackbox.BlackBoxCore
import top.niunaijun.blackboxa.R
import top.niunaijun.blackboxa.app.App
import top.niunaijun.blackboxa.app.AppManager
import top.niunaijun.blackboxa.databinding.ActivityMainBinding
import top.niunaijun.blackboxa.util.Resolution
import top.niunaijun.blackboxa.util.inflate
import top.niunaijun.blackboxa.view.apps.AppsFragment
import top.niunaijun.blackboxa.view.base.LoadingActivity
import top.niunaijun.blackboxa.view.fake.FakeManagerActivity
import top.niunaijun.blackboxa.view.list.ListActivity
import top.niunaijun.blackboxa.view.setting.SettingActivity
class MainActivity : LoadingActivity() {
private val viewBinding: ActivityMainBinding by inflate()
private lateinit var mViewPagerAdapter: ViewPagerAdapter
private val fragmentList = mutableListOf<AppsFragment>()
private var currentUser = 0
companion object {
private const val TAG = "MainActivity"
private const val STORAGE_PERMISSION_REQUEST_CODE = 1001
private const val VPN_PERMISSION_REQUEST_CODE = 1002
fun start(context: Context) {
val intent = Intent(context, MainActivity::class.java)
context.startActivity(intent)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
try {
super.onCreate(savedInstanceState)
try {
BlackBoxCore.get().onBeforeMainActivityOnCreate(this)
} catch (e: Exception) {
Log.e(TAG, "Error in onBeforeMainActivityOnCreate: ${e.message}")
}
setContentView(viewBinding.root)
initToolbar(viewBinding.toolbarLayout.toolbar, R.string.app_name)
initViewPager()
initFab()
initToolbarSubTitle()
checkStoragePermission()
checkVpnPermission()
try {
BlackBoxCore.get().onAfterMainActivityOnCreate(this)
} catch (e: Exception) {
Log.e(TAG, "Error in onAfterMainActivityOnCreate: ${e.message}")
}
} catch (e: Exception) {
Log.e(TAG, "Critical error in onCreate: ${e.message}")
showErrorDialog("Failed to initialize app: ${e.message}")
}
}
private fun checkStoragePermission() {
try {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
if (!android.os.Environment.isExternalStorageManager()) {
Log.w(TAG, "MANAGE_EXTERNAL_STORAGE permission not granted")
showStoragePermissionDialog()
}
} else {
if (androidx.core.content.ContextCompat.checkSelfPermission(
this,
android.Manifest.permission.WRITE_EXTERNAL_STORAGE
) != android.content.pm.PackageManager.PERMISSION_GRANTED ||
androidx.core.content.ContextCompat.checkSelfPermission(
this,
android.Manifest.permission.READ_EXTERNAL_STORAGE
) != android.content.pm.PackageManager.PERMISSION_GRANTED
) {
Log.w(
TAG,
"Storage permissions not granted on Android ${android.os.Build.VERSION.SDK_INT}"
)
requestLegacyStoragePermission()
}
}
} catch (e: Exception) {
Log.e(TAG, "Error checking storage permission: ${e.message}")
}
}
private fun requestLegacyStoragePermission() {
try {
androidx.core.app.ActivityCompat.requestPermissions(
this,
arrayOf(
android.Manifest.permission.READ_EXTERNAL_STORAGE,
android.Manifest.permission.WRITE_EXTERNAL_STORAGE
),
STORAGE_PERMISSION_REQUEST_CODE
)
} catch (e: Exception) {
Log.e(TAG, "Error requesting storage permission: ${e.message}")
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == STORAGE_PERMISSION_REQUEST_CODE) {
if (grantResults.isNotEmpty() &&
grantResults.all {
it == android.content.pm.PackageManager.PERMISSION_GRANTED
}
) {
Log.d(TAG, "Storage permissions granted")
} else {
Log.w(TAG, "Storage permissions denied")
}
}
}
private fun showStoragePermissionDialog() {
try {
MaterialDialog(this).show {
title(text = "Storage Permission Required")
message(
text =
"This app needs 'All Files Access' permission to properly run sandboxed apps. Without this permission, some apps may not work correctly.\n\nPlease grant permission in the next screen."
)
positiveButton(text = "Grant Permission") { openAllFilesAccessSettings() }
negativeButton(text = "Later") { Log.w(TAG, "User postponed storage permission") }
cancelable(false)
}
} catch (e: Exception) {
Log.e(TAG, "Error showing storage permission dialog: ${e.message}")
}
}
private fun openAllFilesAccessSettings() {
try {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
val intent =
Intent(
android.provider.Settings
.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION
)
intent.data = Uri.parse("package:$packageName")
storagePermissionResult.launch(intent)
}
} catch (e: Exception) {
Log.e(TAG, "Error opening storage settings: ${e.message}")
try {
val intent =
Intent(android.provider.Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION)
storagePermissionResult.launch(intent)
} catch (e2: Exception) {
Log.e(TAG, "Error opening fallback storage settings: ${e2.message}")
}
}
}
private val storagePermissionResult =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
try {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
if (android.os.Environment.isExternalStorageManager()) {
Log.d(TAG, "Storage permission granted!")
} else {
Log.w(TAG, "Storage permission still not granted")
}
}
} catch (e: Exception) {
Log.e(TAG, "Error handling storage permission result: ${e.message}")
}
}
private fun checkVpnPermission() {
try {
val vpnIntent = VpnService.prepare(this)
if (vpnIntent != null) {
Log.d(TAG, "VPN permission not granted, requesting...")
vpnPermissionResult.launch(vpnIntent)
} else {
Log.d(TAG, "VPN permission already granted")
}
} catch (e: Exception) {
Log.e(TAG, "Error checking VPN permission: ${e.message}")
}
}
private val vpnPermissionResult =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
try {
if (result.resultCode == RESULT_OK) {
Log.d(TAG, "VPN permission granted!")
} else {
Log.w(TAG, "VPN permission denied by user")
}
} catch (e: Exception) {
Log.e(TAG, "Error handling VPN permission result: ${e.message}")
}
}
private fun showErrorDialog(message: String) {
try {
MaterialDialog(this).show {
title(text = "Error")
message(text = message)
positiveButton(text = "OK") { finish() }
}
} catch (e: Exception) {
Log.e(TAG, "Error showing error dialog: ${e.message}")
finish()
}
}
private fun initToolbarSubTitle() {
try {
updateUserRemark(0)
viewBinding.toolbarLayout.toolbar.getChildAt(1)?.setOnClickListener {
try {
MaterialDialog(this).show {
title(res = R.string.userRemark)
input(
hintRes = R.string.userRemark,
prefill = viewBinding.toolbarLayout.toolbar.subtitle
) { _, input ->
try {
AppManager.mRemarkSharedPreferences.edit {
putString("Remark$currentUser", input.toString())
viewBinding.toolbarLayout.toolbar.subtitle = input
}
} catch (e: Exception) {
Log.e(TAG, "Error saving user remark: ${e.message}")
}
}
positiveButton(res = R.string.done)
negativeButton(res = R.string.cancel)
}
} catch (e: Exception) {
Log.e(TAG, "Error showing remark dialog: ${e.message}")
}
}
} catch (e: Exception) {
Log.e(TAG, "Error in initToolbarSubTitle: ${e.message}")
}
}
private fun initViewPager() {
try {
val userList = BlackBoxCore.get().users
userList.forEach { fragmentList.add(AppsFragment.newInstance(it.id)) }
currentUser = userList.firstOrNull()?.id ?: 0
fragmentList.add(AppsFragment.newInstance(userList.size))
mViewPagerAdapter = ViewPagerAdapter(this)
mViewPagerAdapter.replaceData(fragmentList)
viewBinding.viewPager.adapter = mViewPagerAdapter
viewBinding.dotsIndicator.setViewPager2(viewBinding.viewPager)
viewBinding.viewPager.registerOnPageChangeCallback(
object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
try {
super.onPageSelected(position)
currentUser = fragmentList[position].userID
updateUserRemark(currentUser)
showFloatButton(true)
} catch (e: Exception) {
Log.e(TAG, "Error in onPageSelected: ${e.message}")
}
}
}
)
} catch (e: Exception) {
Log.e(TAG, "Error in initViewPager: ${e.message}")
}
}
private fun initFab() {
try {
viewBinding.fab.setOnClickListener {
try {
val userId = viewBinding.viewPager.currentItem
val intent = Intent(this, ListActivity::class.java)
intent.putExtra("userID", userId)
apkPathResult.launch(intent)
} catch (e: Exception) {
Log.e(TAG, "Error launching ListActivity: ${e.message}")
}
}
} catch (e: Exception) {
Log.e(TAG, "Error in initFab: ${e.message}")
}
}
fun showFloatButton(show: Boolean) {
try {
val tranY: Float = Resolution.convertDpToPixel(120F, App.getContext())
val time = 200L
if (show) {
viewBinding.fab.animate().translationY(0f).alpha(1f).setDuration(time).start()
} else {
viewBinding.fab.animate().translationY(tranY).alpha(0f).setDuration(time).start()
}
} catch (e: Exception) {
Log.e(TAG, "Error in showFloatButton: ${e.message}")
}
}
fun scanUser() {
try {
val userList = BlackBoxCore.get().users
if (fragmentList.size == userList.size) {
fragmentList.add(AppsFragment.newInstance(fragmentList.size))
} else if (fragmentList.size > userList.size + 1) {
fragmentList.removeLast()
}
mViewPagerAdapter.notifyDataSetChanged()
} catch (e: Exception) {
Log.e(TAG, "Error in scanUser: ${e.message}")
}
}
private fun updateUserRemark(userId: Int) {
try {
var remark =
AppManager.mRemarkSharedPreferences.getString("Remark$userId", "User $userId")
if (remark.isNullOrEmpty()) {
remark = "User $userId"
}
viewBinding.toolbarLayout.toolbar.subtitle = remark
} catch (e: Exception) {
Log.e(TAG, "Error updating user remark: ${e.message}")
viewBinding.toolbarLayout.toolbar.subtitle = "User $userId"
}
}
private val apkPathResult =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
try {
if (it.resultCode == RESULT_OK) {
it.data?.let { data ->
val userId = data.getIntExtra("userID", 0)
val source = data.getStringExtra("source")
if (source != null) {
fragmentList[userId].installApk(source)
}
}
}
} catch (e: Exception) {
Log.e(TAG, "Error handling APK path result: ${e.message}")
}
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
try {
menuInflater.inflate(R.menu.menu_main, menu)
return true
} catch (e: Exception) {
Log.e(TAG, "Error creating options menu: ${e.message}")
return false
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
try {
when (item.itemId) {
R.id.main_git -> {
val intent =
Intent(
Intent.ACTION_VIEW,
Uri.parse("https://github.com/ALEX5402/NewBlackbox")
)
startActivity(intent)
}
R.id.main_setting -> {
SettingActivity.start(this)
}
R.id.main_tg -> {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://t.me/newblackboxa"))
startActivity(intent)
}
R.id.fake_location -> {
val intent = Intent(this, FakeManagerActivity::class.java)
intent.putExtra("userID", 0)
startActivity(intent)
}
}
return true
} catch (e: Exception) {
Log.e(TAG, "Error handling menu item selection: ${e.message}")
return false
}
}
}
@@ -1,23 +0,0 @@
package top.niunaijun.blackboxa.view.main
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
import top.niunaijun.blackbox.BlackBoxCore
class ShortcutActivity:AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val pkg = intent.getStringExtra("pkg")
val userID = intent.getIntExtra("userId",0)
lifecycleScope.launch {
BlackBoxCore.get().launchApk(pkg,userID)
finish()
}
}
}
@@ -1,27 +0,0 @@
package top.niunaijun.blackboxa.view.main
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.viewpager2.adapter.FragmentStateAdapter
import top.niunaijun.blackboxa.view.apps.AppsFragment
class ViewPagerAdapter(appCompatActivity: AppCompatActivity) : FragmentStateAdapter(appCompatActivity) {
private var fragmentList = mutableListOf<AppsFragment>()
fun replaceData(list: MutableList<AppsFragment>){
this.fragmentList = list
notifyDataSetChanged()
}
override fun getItemCount(): Int {
return fragmentList.size
}
override fun createFragment(position: Int): Fragment {
return fragmentList[position]
}
}
@@ -1,34 +0,0 @@
package top.niunaijun.blackboxa.view.main
import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModelProvider
import top.niunaijun.blackbox.BlackBoxCore
import top.niunaijun.blackboxa.util.InjectionUtil
import top.niunaijun.blackboxa.view.list.ListViewModel
import top.niunaijun.blackboxa.fridabox.FridaBoxActivity
class WelcomeActivity : AppCompatActivity() {
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
jump()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
previewInstalledAppList()
jump()
}
private fun jump() {
startActivity(Intent(this, FridaBoxActivity::class.java))
finish()
}
private fun previewInstalledAppList(){
val viewModel = ViewModelProvider(this,InjectionUtil.getListFactory()).get(ListViewModel::class.java)
viewModel.previewInstalledList()
}
}
@@ -1,32 +0,0 @@
package top.niunaijun.blackboxa.view.setting
import android.content.Context
import android.content.Intent
import android.os.Bundle
import top.niunaijun.blackboxa.R
import top.niunaijun.blackboxa.databinding.ActivitySettingBinding
import top.niunaijun.blackboxa.util.inflate
import top.niunaijun.blackboxa.view.base.BaseActivity
class SettingActivity : BaseActivity() {
private val viewBinding: ActivitySettingBinding by inflate()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(viewBinding.root)
initToolbar(viewBinding.toolbarLayout.toolbar, R.string.setting, true)
supportFragmentManager.beginTransaction()
.replace(R.id.fragment, SettingFragment())
.commit()
}
companion object{
fun start(context: Context){
val intent = Intent(context,SettingActivity::class.java)
intent.action = Intent.ACTION_OPEN_DOCUMENT
context.startActivity(intent)
}
}
}
@@ -1,111 +0,0 @@
package top.niunaijun.blackboxa.view.setting
import android.os.Bundle
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import top.niunaijun.blackbox.BlackBoxCore
import top.niunaijun.blackboxa.R
import top.niunaijun.blackboxa.app.AppManager
import top.niunaijun.blackboxa.util.toast
import top.niunaijun.blackboxa.view.gms.GmsManagerActivity
class SettingFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.setting, rootKey)
initGms()
invalidHideState {
val rootHidePreference: Preference = (findPreference("root_hide")!!)
val hideRoot = AppManager.mBlackBoxLoader.hideRoot()
rootHidePreference.setDefaultValue(hideRoot)
rootHidePreference
}
invalidHideState {
val daemonPreference: Preference = (findPreference("daemon_enable")!!)
val mDaemonEnable = AppManager.mBlackBoxLoader.daemonEnable()
daemonPreference.setDefaultValue(mDaemonEnable)
daemonPreference
}
invalidHideState {
val vpnPreference: Preference = (findPreference("use_vpn_network")!!)
val mUseVpnNetwork = AppManager.mBlackBoxLoader.useVpnNetwork()
vpnPreference.setDefaultValue(mUseVpnNetwork)
vpnPreference
}
invalidHideState {
val disableFlagSecurePreference: Preference = (findPreference("disable_flag_secure")!!)
val mDisableFlagSecure = AppManager.mBlackBoxLoader.disableFlagSecure()
disableFlagSecurePreference.setDefaultValue(mDisableFlagSecure)
disableFlagSecurePreference
}
initSendLogs()
}
private fun initGms() {
val gmsManagerPreference: Preference = (findPreference("gms_manager")!!)
if (BlackBoxCore.get().isSupportGms) {
gmsManagerPreference.setOnPreferenceClickListener {
GmsManagerActivity.start(requireContext())
true
}
} else {
gmsManagerPreference.summary = getString(R.string.no_gms)
gmsManagerPreference.isEnabled = false
}
}
private fun invalidHideState(block: () -> Preference) {
val pref = block()
pref.setOnPreferenceChangeListener { preference, newValue ->
val tmpHide = (newValue == true)
when (preference.key) {
"root_hide" -> {
AppManager.mBlackBoxLoader.invalidHideRoot(tmpHide)
}
"daemon_enable" -> {
AppManager.mBlackBoxLoader.invalidDaemonEnable(tmpHide)
}
"use_vpn_network" -> {
AppManager.mBlackBoxLoader.invalidUseVpnNetwork(tmpHide)
}
"disable_flag_secure" -> {
AppManager.mBlackBoxLoader.invalidDisableFlagSecure(tmpHide)
}
}
toast(R.string.restart_module)
return@setOnPreferenceChangeListener true
}
}
private fun initSendLogs() {
val sendLogsPreference: Preference? = findPreference("send_logs")
sendLogsPreference?.setOnPreferenceClickListener {
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
}
}
}
@@ -1,52 +0,0 @@
package top.niunaijun.blackboxa.widget
import android.content.Context
import android.view.MotionEvent
import com.imuxuan.floatingview.FloatingMagnetView
import top.niunaijun.blackboxa.R
class EnFloatView(mContext: Context) : FloatingMagnetView(mContext) {
private val TAG = "RockerManager"
private var rockerView: RockerView? = null
private var mListener: LocationListener? = null
init {
inflate(mContext, R.layout.view_float_rocker, this)
initRockerView()
}
private fun initRockerView() {
rockerView = findViewById(R.id.rocker)
rockerView?.setListener { type, currentAngle, currentDistance ->
if (type == RockerView.EVENT_CLOCK && currentAngle != -1F) {
val realAngle = currentAngle
val realDistance = currentDistance * 0.001F
mListener?.invoke(realAngle, realDistance)
}
}
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
if (event?.action == MotionEvent.ACTION_DOWN) {
rockerView?.setCanMove(false)
} else if (event?.action == MotionEvent.ACTION_UP) {
rockerView?.setCanMove(true)
}
return super.onTouchEvent(event)
}
fun setListener(listener: LocationListener) {
this.mListener = listener
}
}
typealias LocationListener = (angle: Float, distance: Float) -> Unit
@@ -1,441 +0,0 @@
package top.niunaijun.blackboxa.widget;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import androidx.annotation.NonNull;
import top.niunaijun.blackboxa.util.MathUtil;
public class RockerView extends SurfaceView implements Runnable, SurfaceHolder.Callback {
private static final int DEFAULT_AREA_RADIUS = 100;
private static final int DEFAULT_ROCKER_RADIUS = 35;
private static final int DEFAULT_AREA_COLOR = Color.argb(128,0,0,0);
private static final int DEFAULT_ROCKER_COLOR = Color.argb(128,0,0,0);
private static final int DEFAULT_REFRESH_CYCLE = 30;
private static final int DEFAULT_CALLBACK_CYCLE = 300;
private SurfaceHolder mHolder;
private static Thread mDrawThread;
private static Thread mCallbackThread;
private static boolean mDrawOk = true;
private static boolean mCallbackOk = true;
private Paint mPaint;
private Point mAreaPosition;
private Point mRockerPosition;
private int mAreaRadius = -1;
private int mRockerRadius = -1;
private int mAreaColor;
private int mRockerColor;
private Bitmap mAreaBitmap;
private Bitmap mRockerBitmap;
private boolean canMove = true;
private RockerListener mListener;
public static final int EVENT_ACTION = 1;
public static final int EVENT_CLOCK = 2;
private int mRefreshCycle = DEFAULT_REFRESH_CYCLE;
private int mCallbackCycle = DEFAULT_CALLBACK_CYCLE;
public RockerView(Context context) {
this(context, null);
}
public RockerView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public RockerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initAttrs(context, attrs);
setPaint();
if (isInEditMode()) {
return;
}
configSurfaceView();
configSurfaceHolder();
}
private void initAttrs(Context context, AttributeSet attrs) {
mAreaColor = DEFAULT_AREA_COLOR;
mRockerColor = DEFAULT_ROCKER_COLOR;
mAreaRadius = DEFAULT_AREA_RADIUS;
mRockerRadius = DEFAULT_ROCKER_RADIUS;
}
private void setPaint() {
mPaint = new Paint();
mPaint.setAntiAlias(true);
}
private void configSurfaceView() {
setKeepScreenOn(true);
setFocusable(true);
setFocusableInTouchMode(true);
setZOrderOnTop(true);
}
private void configSurfaceHolder() {
mHolder = getHolder();
mHolder.addCallback(this);
mHolder.setFormat(PixelFormat.TRANSPARENT);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int measureWidth = 0, measureHeight = 0;
int defaultWidth = (mAreaRadius + mRockerRadius) * 2;
int defalutHeight = defaultWidth;
int widthsize = MeasureSpec.getSize(widthMeasureSpec);
int widthmode = MeasureSpec.getMode(widthMeasureSpec);
int heightsize = MeasureSpec.getSize(heightMeasureSpec);
int heightmode = MeasureSpec.getMode(heightMeasureSpec);
if (widthmode == MeasureSpec.AT_MOST || widthmode == MeasureSpec.UNSPECIFIED || widthsize < 0) {
measureWidth = defaultWidth;
} else {
measureWidth = widthsize;
}
if (heightmode == MeasureSpec.AT_MOST || heightmode == MeasureSpec.UNSPECIFIED || heightsize < 0) {
measureHeight = defalutHeight;
} else {
measureHeight = heightsize;
}
setMeasuredDimension(measureWidth, measureHeight);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mAreaPosition = new Point(w / 2, h / 2);
mRockerPosition = new Point(mAreaPosition);
int tempRadius = Math.min(w - getPaddingLeft() - getPaddingRight(), h - getPaddingTop() - getPaddingBottom());
tempRadius /= 2;
if (mAreaRadius == -1)
mAreaRadius = (int) (tempRadius * 0.75);
if (mRockerRadius == -1)
mRockerRadius = (int) (tempRadius * 0.25);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
try {
mDrawThread = new Thread(this);
mDrawThread.start();
mCallbackThread = new Thread(() -> {
while (mCallbackOk) {
listenerCallback();
try {
Thread.sleep(mCallbackCycle);
} catch (Exception e) {
e.printStackTrace();
}
}
});
mCallbackThread.start();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
mDrawOk = false;
mCallbackOk = false;
}
@Override
protected void onVisibilityChanged(@NonNull View changedView, int visibility) {
super.onVisibilityChanged(changedView, visibility);
if (visibility == VISIBLE) {
mDrawOk = true;
mCallbackOk = true;
} else {
mDrawOk = false;
mCallbackOk = false;
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
try {
int len = MathUtil.getDistance(mAreaPosition.x, mAreaPosition.y, event.getX(), event.getY());
if (event.getAction() == MotionEvent.ACTION_DOWN) {
if (len > mAreaRadius) {
return true;
}
}
if (event.getAction() == MotionEvent.ACTION_MOVE) {
if (len <= mAreaRadius) {
mRockerPosition.set((int) event.getX(), (int) event.getY());
} else {
mRockerPosition = MathUtil.getPointByCutLength(mAreaPosition,
new Point((int) event.getX(), (int) event.getY()), mAreaRadius);
}
if (mListener != null) {
float radian = MathUtil.getRadian(mAreaPosition, new Point((int) event.getX(), (int) event.getY()));
float angle = RockerView.this.getAngleConvert(radian);
float distance = MathUtil.getDistance(mAreaPosition.x, mAreaPosition.y, event.getX(), event.getY());
mListener.callback(EVENT_ACTION, angle, distance);
}
}
if (event.getAction() == MotionEvent.ACTION_UP) {
mRockerPosition = new Point(mAreaPosition);
if (mListener != null) {
mListener.callback(EVENT_ACTION, -1, 0);
}
}
} catch (Exception e) {
e.printStackTrace();
}
return true;
}
@Override
public void run() {
if (isInEditMode()) {
return;
}
Canvas canvas = null;
while (mDrawOk) {
boolean canMove = this.canMove;
try {
if (canMove) {
canvas = mHolder.lockCanvas();
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
drawArea(canvas);
drawRocker(canvas);
}
Thread.sleep(mRefreshCycle);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (canvas != null && canMove) {
mHolder.unlockCanvasAndPost(canvas);
}
}
}
}
private void drawArea(Canvas canvas) {
if (null != mAreaBitmap) {
mPaint.setColor(Color.BLACK);
Rect src = new Rect(0, 0, mAreaBitmap.getWidth(), mAreaBitmap.getHeight());
Rect dst = new Rect(
mAreaPosition.x - mAreaRadius,
mAreaPosition.y - mAreaRadius,
mAreaPosition.x + mAreaRadius,
mAreaPosition.y + mAreaRadius);
canvas.drawBitmap(mAreaBitmap, src, dst, mPaint);
} else {
mPaint.setColor(mAreaColor);
canvas.drawCircle(mAreaPosition.x, mAreaPosition.y, mAreaRadius, mPaint);
}
}
private void drawRocker(Canvas canvas) {
if (null != mRockerBitmap) {
mPaint.setColor(Color.BLACK);
Rect src = new Rect(0, 0, mRockerBitmap.getWidth(), mRockerBitmap.getHeight());
Rect dst = new Rect(
mRockerPosition.x - mRockerRadius,
mRockerPosition.y - mRockerRadius,
mRockerPosition.x + mRockerRadius,
mRockerPosition.y + mRockerRadius);
canvas.drawBitmap(mRockerBitmap, src, dst, mPaint);
} else {
mPaint.setColor(mRockerColor);
canvas.drawCircle(mRockerPosition.x, mRockerPosition.y, mRockerRadius, mPaint);
}
}
private void listenerCallback() {
if (mListener != null) {
if (mRockerPosition.x == mAreaPosition.x && mRockerPosition.y == mAreaPosition.y) {
mListener.callback(EVENT_CLOCK, -1, 0);
} else {
float radian = MathUtil.getRadian(mAreaPosition, new Point(mRockerPosition.x, mRockerPosition.y));
float angle = RockerView.this.getAngleConvert(radian);
float distance = MathUtil.getDistance(mAreaPosition.x, mAreaPosition.y, mRockerPosition.x, mRockerPosition.y);
mListener.callback(EVENT_CLOCK, angle, distance);
}
}
}
private float getAngleConvert(float radian) {
return 90 + Math.round(radian / Math.PI * 180);
}
@Override
protected void onDraw(Canvas canvas) {
if (isInEditMode()) {
canvas.drawColor(Color.WHITE);
drawArea(canvas);
drawRocker(canvas);
}
}
public void setCanMove(boolean isMove) {
this.canMove = isMove;
}
public int getAreaRadius() {
return mAreaRadius;
}
public void setAreaRadius(int areaRadius) {
mAreaRadius = areaRadius;
}
public int getRockerRadius() {
return mRockerRadius;
}
public void setRockerRadius(int rockerRadius) {
mRockerRadius = rockerRadius;
}
public Bitmap getAreaBitmap() {
return mAreaBitmap;
}
public void setAreaBitmap(Bitmap areaBitmap) {
mAreaBitmap = areaBitmap;
}
public Bitmap getRockerBitmap() {
return mRockerBitmap;
}
public void setRockerBitmap(Bitmap rockerBitmap) {
mRockerBitmap = rockerBitmap;
}
public int getRefreshCycle() {
return mRefreshCycle;
}
public void setRefreshCycle(int refreshCycle) {
mRefreshCycle = refreshCycle;
}
public int getCallbackCycle() {
return mCallbackCycle;
}
public void setCallbackCycle(int callbackCycle) {
mCallbackCycle = callbackCycle;
}
public int getAreaColor() {
return mAreaColor;
}
public void setAreaColor(int areaColor) {
mAreaColor = areaColor;
mAreaBitmap = null;
}
public int getRockerColor() {
return mRockerColor;
}
public void setRockerColor(int rockerColor) {
mRockerColor = rockerColor;
mRockerBitmap = null;
}
public void setListener(@NonNull RockerListener listener) {
mListener = listener;
}
public interface RockerListener {
void callback(int eventType, float currentAngle, float currentDistance);
}
}
@@ -1,11 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#FFFFFF"
android:alpha="0.8">
<path
android:fillColor="@android:color/white"
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
</vector>
@@ -1,11 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#333333"
android:alpha="0.6">
<path
android:fillColor="@android:color/white"
android:pathData="M20.94,11c-0.46,-4.17 -3.77,-7.48 -7.94,-7.94V1h-2v2.06C6.83,3.52 3.52,6.83 3.06,11H1v2h2.06c0.46,4.17 3.77,7.48 7.94,7.94V23h2v-2.06c4.17,-0.46 7.48,-3.77 7.94,-7.94H23v-2h-2.06zM12,19c-3.87,0 -7,-3.13 -7,-7s3.13,-7 7,-7 7,3.13 7,7 -3.13,7 -7,7z"/>
</vector>
@@ -1,11 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#FFFFFF"
android:alpha="0.8">
<path
android:fillColor="@android:color/white"
android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z"/>
</vector>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 500 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 347 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 325 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 201 B

@@ -1,30 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 127 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 635 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 368 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 164 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 999 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 579 B

-180
View File
@@ -1,180 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="480dp"
android:height="480dp"
android:viewportWidth="480"
android:viewportHeight="480">
<path
android:pathData="M240,240m-184,0a184,184 0,1 1,368 0a184,184 0,1 1,-368 0"
android:fillColor="#f3f3fa"/>
<path
android:pathData="M152,376c-10.838,-4.087 -16.3,0 -32,0 -26.51,0 -48,-8.954 -48,-20s21.49,-20 48,-20h256a16,16 0,1 1,0 32s-15.5,0 -27.5,3S328,382.714 328,382.714 290.392,424 244,424c-38.041,0 -70.176,-13.246 -80.647,-31.653C161.219,388.6 162.838,380.087 152,376Z"
android:fillColor="#d1d6e2"/>
<path
android:pathData="M88,351.5a58,12.5 0,1 0,116 0a58,12.5 0,1 0,-116 0z"
android:fillColor="#c0c9db"/>
<path
android:pathData="M154,348.5a32.5,12.5 0,1 0,65 0a32.5,12.5 0,1 0,-65 0z"
android:fillColor="#c0c9db"/>
<path
android:pathData="M167,373.5a16.5,6.5 0,1 0,33 0a16.5,6.5 0,1 0,-33 0z"
android:fillColor="#c0c9db"/>
<path
android:pathData="M191,369.5a8.5,6.5 0,1 0,17 0a8.5,6.5 0,1 0,-17 0z"
android:fillColor="#c0c9db"/>
<path
android:pathData="M182,380a9,4 0,1 0,18 0a9,4 0,1 0,-18 0z"
android:fillColor="#c0c9db"/>
<path
android:pathData="M208,388a8,4 0,1 0,16 0a8,4 0,1 0,-16 0z"
android:fillColor="#c0c9db"/>
<path
android:pathData="M352.222,156.14 L346.176,157.428L346.281,154.431Z"
android:fillColor="#8f9aa9"/>
<path
android:pathData="M350.93,152.445 L346.637,156.897L345.047,154.353Z"
android:fillColor="#8f9aa9"/>
<path
android:pathData="M301.165,165.165c-1.474,-0.755 -3.846,-3.467 -3.093,-4.943s4.341,-1.155 5.817,-0.403l1.528,0.779l-0.234,-0.347c-0.927,-1.374 -1.682,-4.897 -0.309,-5.823s4.357,1.095 5.283,2.468l6.151,9.119l-4.918,3.317l-0.422,0.829Z"
android:fillColor="#8f9aa9"/>
<path
android:pathData="M301.226,183.428c-1.417,-1.125 -3.542,-1.958 -2.167,-4.5s5.25,-2.667 8.667,-4.667a22.194,22.194 0,0 1,2 -1.042c0,-0.1 0,-0.194 0,-0.292 0,-5.522 5.373,-10 12,-10 2.744,0 6.565,5.071 9.5,5a5.012,5.012 0,0 0,2.79 -1.1c-5.818,-0.692 -10.29,-4.86 -10.29,-9.9 0,-5.523 5.372,-10 12,-10s12,4.477 12,10c0,5.254 -4.863,9.562 -11.043,9.969 -0.652,5.721 -8.606,16.031 -14.957,16.031a12.547,12.547 0,0 1,-10.483 -5.13,23.54 23.54,0 0,1 -4.516,4.3c-1.887,1.23 -3.056,1.9 -4.037,1.9A2.286,2.286 0,0 1,301.226 183.428Z"
android:fillColor="#747f95"/>
<path
android:pathData="M267.725,170a6,6 0,1 1,6 6A6,6 0,0 1,267.725 170ZM239.725,170a6,6 0,1 1,6 6A6,6 0,0 1,239.725 170ZM211.725,170a6,6 0,1 1,6 6A6,6 0,0 1,211.725 170ZM183.725,170a6,6 0,1 1,6 6A6,6 0,0 1,183.725 170ZM155.725,170a6,6 0,1 1,6 6A6,6 0,0 1,155.725 170ZM127.725,170a6,6 0,1 1,6 6A6,6 0,0 1,127.725 170Z">
<aapt:attr name="android:fillColor">
<gradient
android:startY="164"
android:startX="124.685"
android:endY="164"
android:endX="283.677"
android:type="linear">
<item android:offset="0" android:color="#FFEBEDF5"/>
<item android:offset="1" android:color="#FF909AA9"/>
</gradient>
</aapt:attr>
</path>
<path
android:pathData="M339.725,154m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0"
android:fillColor="#fff"/>
<path
android:pathData="M160,272 L188,312L132,312Z"
android:fillColor="#e1e6ef"/>
<path
android:pathData="M159.4,272 L188,312L162.417,312l9.167,-13.5L164.417,298.5l4.917,-8.25Z"
android:fillColor="#cdd3df"/>
<path
android:pathData="M160,256 L180,288L140,288Z"
android:fillColor="#e1e6ef"/>
<path
android:pathData="M160,256 L180,288L172.417,288Z"
android:fillColor="#cdd3df"/>
<path
android:pathData="M160,248 L172,264L148,264Z"
android:fillColor="#e1e6ef"/>
<path
android:pathData="M159.969,248 L172,264L168,264Z"
android:fillColor="#cdd3df"/>
<path
android:pathData="M156,312h8v32h-8z"
android:fillColor="#e1e6ef"/>
<path
android:pathData="M162.5,312L166,312L166,344L162,344L162,321.75L156.167,321.75Z"
android:fillColor="#cdd3df"/>
<path
android:pathData="M114.5,304 L133,331L96,331Z"
android:fillColor="#e1e6ef"/>
<path
android:pathData="M114.267,304 L133.067,331.094L122.75,331.094Z"
android:fillColor="#cdd3df"/>
<path
android:pathData="M114.5,293 L128,315L101,315Z"
android:fillColor="#e1e6ef"/>
<path
android:pathData="M114.666,293.333 L127.999,315.125h-8Z"
android:fillColor="#cdd3df"/>
<path
android:pathData="M115,288l8,11L107,299Z"
android:fillColor="#e1e6ef"/>
<path
android:pathData="M115.031,288l8.031,11.094L117.437,299.094Z"
android:fillColor="#cdd3df"/>
<path
android:pathData="M115,331h5v21h-5z"
android:fillColor="#cdd3df"/>
<path
android:pathData="M112,331h5v21h-5z"
android:fillColor="#e1e6ef"/>
<path
android:pathData="M149.938,301.125 L138.969,312h5.625l8.094,-4.125Z"
android:fillColor="#cdd3df"/>
<path
android:pathData="M149.438,317 L167.938,344L130.938,344Z"
android:fillColor="#e1e6ef"/>
<path
android:pathData="M149.205,317 L168.005,344.094L157.688,344.094Z"
android:fillColor="#cdd3df"/>
<path
android:pathData="M149.438,306 L162.938,328L135.938,328Z"
android:fillColor="#e1e6ef"/>
<path
android:pathData="M149.604,306.333 L162.937,328.125h-8Z"
android:fillColor="#cdd3df"/>
<path
android:pathData="M149.938,344h5v21h-5z"
android:fillColor="#cdd3df"/>
<path
android:pathData="M146.938,344h5v21h-5z"
android:fillColor="#e1e6ef"/>
<path
android:pathData="M149.938,301l8,11L141.938,312Z"
android:fillColor="#e1e6ef"/>
<path
android:pathData="M149.969,301l8.031,11.094h-4.25a43.253,43.253 0,0 1,-2.643 -5.848A23.947,23.947 0,0 1,149.969 301Z"
android:fillColor="#cdd3df"/>
<path
android:pathData="M188,280 L216,320L160,320Z"
android:fillColor="#e1e6ef"/>
<path
android:pathData="M187.4,280 L216,320L200,320Z"
android:fillColor="#cdd3df"/>
<path
android:pathData="M188,264 L208,296L168,296Z"
android:fillColor="#e1e6ef"/>
<path
android:pathData="M188,264 L208,296L196.625,296Z"
android:fillColor="#cdd3df"/>
<path
android:pathData="M188,256 L200,272L176,272Z"
android:fillColor="#e1e6ef"/>
<path
android:pathData="M187.969,256 L200,272L192,272Z"
android:fillColor="#cdd3df"/>
<path
android:pathData="M188,320h8v32h-8z"
android:fillColor="#cdd3df"/>
<path
android:pathData="M184,320h8v32h-8z"
android:fillColor="#e1e6ef"/>
<path
android:pathData="M102.928,140.042a21.752,21.752 0,0 1,-9.116 1.905c-8.733,0 -15.812,-4.666 -15.812,-10.421 0,-5.512 6.495,-10.025 14.716,-10.4C96.006,115.217 104.6,111 114.684,111A30.025,30.025 0,0 1,131.7 115.848a23.511,23.511 0,0 1,4.493 -0.427c8.733,0 15.812,4.666 15.812,10.421s-7.079,10.421 -15.812,10.421a24.011,24.011 0,0 1,-2.544 -0.134c0.01,0.149 0.015,0.3 0.015,0.45 0,5.755 -7.08,10.421 -15.812,10.421C110.955,147 105.094,144.1 102.928,140.042Z"
android:fillColor="#fff"/>
<path
android:pathData="M214.811,264.135a12.024,12.024 0,0 1,-5.051 1.058C204.922,265.193 201,262.6 201,259.4c0,-3.062 3.6,-5.57 8.153,-5.776C210.976,250.343 215.738,248 221.325,248a16.6,16.6 0,0 1,9.425 2.693,12.993 12.993,0 0,1 2.489,-0.237c4.839,0 8.761,2.592 8.761,5.79s-3.922,5.789 -8.761,5.789a13.267,13.267 0,0 1,-1.41 -0.075c0.005,0.083 0.008,0.166 0.008,0.25 0,3.2 -3.922,5.789 -8.761,5.789C219.259,268 216.012,266.386 214.811,264.135Z"
android:fillColor="#fff"/>
<path
android:pathData="M118.432,234.294a8.042,8.042 0,0 1,-3.449 0.741c-3.3,0 -5.983,-1.814 -5.983,-4.052 0,-2.144 2.458,-3.9 5.568,-4.043 1.245,-2.3 4.5,-3.939 8.312,-3.939a11.139,11.139 0,0 1,6.437 1.885,8.664 8.664,0 0,1 1.7,-0.166c3.3,0 5.983,1.815 5.983,4.053s-2.679,4.052 -5.983,4.052a8.841,8.841 0,0 1,-0.963 -0.052c0,0.058 0.005,0.116 0.005,0.175 0,2.238 -2.679,4.052 -5.983,4.052C121.47,237 119.252,235.87 118.432,234.294Z"
android:fillColor="#fff"/>
<path
android:pathData="M118.432,234.294a8.042,8.042 0,0 1,-3.449 0.741c-3.3,0 -5.983,-1.814 -5.983,-4.052 0,-2.144 2.458,-3.9 5.568,-4.043 1.245,-2.3 4.5,-3.939 8.312,-3.939a11.139,11.139 0,0 1,6.437 1.885,8.664 8.664,0 0,1 1.7,-0.166c3.3,0 5.983,1.815 5.983,4.053s-2.679,4.052 -5.983,4.052a8.841,8.841 0,0 1,-0.963 -0.052c0,0.058 0.005,0.116 0.005,0.175 0,2.238 -2.679,4.052 -5.983,4.052C121.47,237 119.252,235.87 118.432,234.294Z"
android:fillColor="#fff"/>
<path
android:pathData="M374.928,185.042a21.752,21.752 0,0 1,-9.116 1.905c-8.733,0 -15.812,-4.666 -15.812,-10.421 0,-5.512 6.495,-10.025 14.716,-10.4C368.006,160.217 376.6,156 386.684,156A30.025,30.025 0,0 1,403.7 160.848a23.511,23.511 0,0 1,4.493 -0.427c8.733,0 15.812,4.666 15.812,10.421s-7.079,10.421 -15.812,10.421a24.011,24.011 0,0 1,-2.544 -0.134c0.01,0.149 0.015,0.3 0.015,0.45 0,5.755 -7.08,10.421 -15.812,10.421C382.955,192 377.094,189.1 374.928,185.042Z"
android:fillColor="#fff"/>
<path
android:pathData="M256.928,125.042a21.752,21.752 0,0 1,-9.116 1.905c-8.733,0 -15.812,-4.666 -15.812,-10.421 0,-5.512 6.495,-10.025 14.716,-10.4C250.006,100.217 258.6,96 268.684,96A30.025,30.025 0,0 1,285.7 100.848a23.511,23.511 0,0 1,4.493 -0.427c8.733,0 15.812,4.666 15.812,10.421s-7.079,10.421 -15.812,10.421a24.011,24.011 0,0 1,-2.544 -0.134c0.01,0.149 0.015,0.3 0.015,0.45 0,5.755 -7.08,10.421 -15.812,10.421C264.955,132 259.094,129.1 256.928,125.042Z"
android:fillColor="#fff"/>
<path
android:pathData="M278.811,226.135a12.024,12.024 0,0 1,-5.051 1.058C268.922,227.193 265,224.6 265,221.4c0,-3.062 3.6,-5.57 8.153,-5.776C274.976,212.343 279.738,210 285.325,210a16.6,16.6 0,0 1,9.425 2.693,12.993 12.993,0 0,1 2.489,-0.237c4.839,0 8.761,2.592 8.761,5.79s-3.922,5.789 -8.761,5.789a13.267,13.267 0,0 1,-1.41 -0.075c0.005,0.083 0.008,0.166 0.008,0.25 0,3.2 -3.922,5.789 -8.761,5.789C283.259,230 280.012,228.386 278.811,226.135Z"
android:fillColor="#fff"/>
</vector>
@@ -1,170 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>
-14
View File
@@ -1,14 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="@android:color/white" />
</shape>
</item>
<item>
<bitmap
android:gravity="center"
android:src="@mipmap/ic_launcher" />
</item>
</layer-list>
-22
View File
@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include
android:id="@+id/toolbar_layout"
layout="@layout/view_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/toolbar_layout" />
</androidx.constraintlayout.widget.ConstraintLayout>
-44
View File
@@ -1,44 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".view.list.ListActivity">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<include
android:id="@+id/toolbar_layout"
layout="@layout/view_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent" />
<com.ferfalk.simplesearchview.SimpleSearchView
android:id="@+id/searchView"
app:type="card"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/primary" />
</FrameLayout>
<com.github.nukc.stateview.StateView
android:id="@+id/stateView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintTop_toBottomOf="@id/toolbar" />
</LinearLayout>
-60
View File
@@ -1,60 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".view.main.MainActivity">
<include
android:id="@+id/toolbar_layout"
layout="@layout/view_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent" />
<com.github.nukc.stateview.StateView
android:id="@+id/stateView"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/toolbar_layout" />
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@id/dots_indicator"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/toolbar_layout" />
<com.tbuonomo.viewpagerdotsindicator.WormDotsIndicator
android:id="@+id/dots_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
android:paddingBottom="12dp"
app:dotsCornerRadius="8dp"
app:dotsSize="8dp"
app:dotsSpacing="4dp"
app:dotsWidthFactor="2.5"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/viewPager"
app:progressMode="true" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="24dp"
android:layout_marginBottom="24dp"
android:contentDescription="TODO"
android:src="@drawable/ic_add"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".view.fake.FollowMyLocationOverlay">
<org.osmdroid.views.MapView android:id="@+id/map"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".view.setting.SettingActivity">
<include
android:id="@+id/toolbar_layout"
layout="@layout/view_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent" />
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fragment"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/toolbar_layout" />
</androidx.constraintlayout.widget.ConstraintLayout>

Some files were not shown because too many files have changed in this diff Show More