refactor: remove legacy BlackBox app shell
@@ -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");
|
||||
|
||||
@@ -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! 🚀✨
|
||||
@@ -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
|
||||
|
||||
@@ -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 |
|
||||
@@ -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'
|
||||
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
Before Width: | Height: | Size: 148 B |
|
Before Width: | Height: | Size: 500 B |
|
Before Width: | Height: | Size: 347 B |
|
Before Width: | Height: | Size: 101 B |
|
Before Width: | Height: | Size: 325 B |
|
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>
|
||||
|
Before Width: | Height: | Size: 127 B |
|
Before Width: | Height: | Size: 635 B |
|
Before Width: | Height: | Size: 368 B |
|
Before Width: | Height: | Size: 164 B |
|
Before Width: | Height: | Size: 999 B |
|
Before Width: | Height: | Size: 579 B |
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||