diff --git a/Bcore/black-fake/build.gradle b/Bcore/black-fake/build.gradle index d11b89f1..8ce9fd9b 100644 --- a/Bcore/black-fake/build.gradle +++ b/Bcore/black-fake/build.gradle @@ -1,15 +1,15 @@ plugins { id 'com.android.library' + id 'org.jetbrains.kotlin.android' } -apply plugin: 'kotlin-android' android { - compileSdkVersion 30 - buildToolsVersion "30.0.3" + compileSdk rootProject.ext.compileSdk + buildToolsVersion rootProject.ext.buildToolsVersion defaultConfig { - minSdkVersion 21 - targetSdkVersion rootProject.ext.targetSdkVersion + minSdkVersion rootProject.ext.minSdkVersion + targetSdkVersion rootProject.ext.targetSdk consumerProguardFiles "consumer-rules.pro" } @@ -25,23 +25,18 @@ android { targetCompatibility JavaVersion.VERSION_1_8 } - aaptOptions { - cruncherEnabled = false - useNewCruncher = false - } - testOptions { unitTests.returnDefaultValues = true } - lintOptions { - checkReleaseBuilds false + lint { abortOnError false - warningsAsErrors false - disable "UnusedResources", 'RestrictedApi' - textOutput "stdout" - textReport false checkOnly 'NewApi', 'InlinedApi' + checkReleaseBuilds false + disable 'UnusedResources', 'RestrictedApi' + textOutput file('stdout') + textReport false + warningsAsErrors false } } @@ -56,7 +51,3 @@ dependencies { implementation "androidx.core:core-ktx:$ktx_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" } - -repositories { - mavenCentral() -} \ No newline at end of file diff --git a/Bcore/black-fake/src/main/java/android/location/LocationRequest.java b/Bcore/black-fake/src/main/java/android/location/LocationRequest.java index ad683321..83e8a84b 100644 --- a/Bcore/black-fake/src/main/java/android/location/LocationRequest.java +++ b/Bcore/black-fake/src/main/java/android/location/LocationRequest.java @@ -26,6 +26,5 @@ public int describeContents() { } @Override - public void writeToParcel(Parcel dest, int flags) { - } + public void writeToParcel(Parcel dest, int flags) { } } diff --git a/Bcore/black-hook/build.gradle b/Bcore/black-hook/build.gradle index e67b7592..8cce8b16 100644 --- a/Bcore/black-hook/build.gradle +++ b/Bcore/black-hook/build.gradle @@ -1,15 +1,15 @@ plugins { id 'com.android.library' + id 'org.jetbrains.kotlin.android' } -apply plugin: 'kotlin-android' android { - compileSdkVersion 30 - buildToolsVersion "30.0.3" + compileSdk rootProject.ext.compileSdk + buildToolsVersion rootProject.ext.buildToolsVersion defaultConfig { - minSdkVersion 21 - targetSdkVersion rootProject.ext.targetSdkVersion + minSdkVersion rootProject.ext.minSdkVersion + targetSdk rootProject.ext.targetSdk consumerProguardFiles "consumer-rules.pro" } @@ -26,23 +26,18 @@ android { targetCompatibility JavaVersion.VERSION_1_8 } - aaptOptions { - cruncherEnabled = false - useNewCruncher = false - } - testOptions { unitTests.returnDefaultValues = true } - lintOptions { - checkReleaseBuilds false + lint { abortOnError false - warningsAsErrors false - disable "UnusedResources", 'RestrictedApi' - textOutput "stdout" - textReport false checkOnly 'NewApi', 'InlinedApi' + checkReleaseBuilds false + disable 'UnusedResources', 'RestrictedApi' + textOutput file('stdout') + textReport false + warningsAsErrors false } } @@ -53,12 +48,8 @@ tasks.withType(Javadoc) { } dependencies { - implementation 'androidx.appcompat:appcompat:1.2.0' - implementation 'com.google.android.material:material:1.3.0' + implementation 'androidx.appcompat:appcompat:1.5.0' + implementation 'com.google.android.material:material:1.6.1' implementation "androidx.core:core-ktx:$ktx_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" } - -repositories { - mavenCentral() -} \ No newline at end of file diff --git a/Bcore/build.gradle b/Bcore/build.gradle index 66e5dff8..0fe92141 100644 --- a/Bcore/build.gradle +++ b/Bcore/build.gradle @@ -1,12 +1,14 @@ -apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' +plugins { + id 'com.android.library' + id 'org.jetbrains.kotlin.android' +} android { - compileSdkVersion 32 + compileSdk rootProject.ext.compileSdk defaultConfig { - minSdkVersion 21 - targetSdkVersion rootProject.ext.targetSdkVersion + minSdkVersion rootProject.ext.minSdkVersion + targetSdk rootProject.ext.targetSdk consumerProguardFiles "consumer-rules.pro" ndk { @@ -37,38 +39,29 @@ android { targetCompatibility JavaVersion.VERSION_1_8 } - aaptOptions { - cruncherEnabled = false - useNewCruncher = false - } - testOptions { unitTests.returnDefaultValues = true } + lint { + abortOnError false + checkOnly 'NewApi', 'InlinedApi' + checkReleaseBuilds false + disable 'UnusedResources', 'RestrictedApi' + textOutput file('stdout') + textReport false + warningsAsErrors false + } + defaultConfig { ndk { abiFilters 'armeabi-v7a', 'arm64-v8a' } } - lintOptions { - checkReleaseBuilds false - abortOnError false - warningsAsErrors false - disable "UnusedResources", 'RestrictedApi' - textOutput "stdout" - textReport false - checkOnly 'NewApi', 'InlinedApi' - } - buildFeatures { prefab true } - - packagingOptions { - exclude '**/libshadowhook.so' - } } tasks.withType(Javadoc) { @@ -79,12 +72,11 @@ tasks.withType(Javadoc) { dependencies { implementation fileTree(dir: "libs", include: ["*.jar", "*.aar"]) - implementation 'androidx.appcompat:appcompat:1.2.0' + implementation 'androidx.appcompat:appcompat:1.5.0' implementation "com.github.CodingGay.BlackReflection:core:$rootProject.ext.blackReflection" implementation 'org.lsposed.hiddenapibypass:hiddenapibypass:4.3' - implementation 'com.bytedance.android:shadowhook:1.0.3' - implementation 'com.iqiyi.xcrash:xcrash-android-lib:3.0.0' + implementation 'com.iqiyi.xcrash:xcrash-android-lib:3.1.0' implementation project(':Bcore:black-fake') implementation project(':Bcore:black-hook') @@ -96,7 +88,3 @@ dependencies { implementation "androidx.core:core-ktx:$ktx_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" } - -repositories { - mavenCentral() -} \ No newline at end of file diff --git a/Bcore/pine-core/build.gradle b/Bcore/pine-core/build.gradle index 27ed57cb..9ab4af3f 100644 --- a/Bcore/pine-core/build.gradle +++ b/Bcore/pine-core/build.gradle @@ -1,16 +1,16 @@ -apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' +plugins { + id 'com.android.library' + id 'org.jetbrains.kotlin.android' +} android { - compileSdkVersion rootProject.ext.compileSdkVersion + compileSdk rootProject.ext.compileSdk buildToolsVersion rootProject.ext.buildToolsVersion ndkVersion rootProject.ext.ndkVersion defaultConfig { - minSdkVersion 19 - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" + minSdkVersion rootProject.ext.minSdkVersion + targetSdk rootProject.ext.targetSdk testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles 'consumer-rules.pro' @@ -49,7 +49,3 @@ dependencies { implementation "androidx.core:core-ktx:$ktx_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" } - -repositories { - mavenCentral() -} \ No newline at end of file diff --git a/Bcore/pine-xposed-res/build.gradle b/Bcore/pine-xposed-res/build.gradle index d1c0ccad..1137ecc7 100644 --- a/Bcore/pine-xposed-res/build.gradle +++ b/Bcore/pine-xposed-res/build.gradle @@ -1,12 +1,14 @@ -apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' +plugins { + id 'com.android.library' + id 'org.jetbrains.kotlin.android' +} android { - compileSdkVersion rootProject.ext.compileSdkVersion + compileSdk rootProject.ext.compileSdk defaultConfig { - minSdkVersion 19 - targetSdkVersion rootProject.ext.targetSdkVersion + minSdkVersion rootProject.ext.minSdkVersion + targetSdk rootProject.ext.targetSdk testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles 'consumer-rules.pro' @@ -33,11 +35,11 @@ android { cmake { path "src/main/cpp/CMakeLists.txt" } - }*/ + } buildFeatures { - // prefab true - } + prefab true + }*/ } dependencies { @@ -48,7 +50,3 @@ dependencies { implementation "androidx.core:core-ktx:$ktx_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" } - -repositories { - mavenCentral() -} diff --git a/Bcore/pine-xposed/build.gradle b/Bcore/pine-xposed/build.gradle index a6c81c14..d04a5164 100644 --- a/Bcore/pine-xposed/build.gradle +++ b/Bcore/pine-xposed/build.gradle @@ -1,15 +1,15 @@ -apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' +plugins { + id 'com.android.library' + id 'org.jetbrains.kotlin.android' +} android { - compileSdkVersion rootProject.ext.compileSdkVersion + compileSdk rootProject.ext.compileSdk buildToolsVersion rootProject.ext.buildToolsVersion defaultConfig { - minSdkVersion 19 - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" + minSdkVersion rootProject.ext.minSdkVersion + targetSdk rootProject.ext.targetSdk testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles 'consumer-rules.pro' @@ -35,7 +35,3 @@ dependencies { implementation "androidx.core:core-ktx:$ktx_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" } - -repositories { - mavenCentral() -} \ No newline at end of file diff --git a/Bcore/pine-xposed/src/main/apacheCommonsLang/external/org/apache/commons/lang3/ArrayUtils.java b/Bcore/pine-xposed/src/main/apacheCommonsLang/external/org/apache/commons/lang3/ArrayUtils.java index 8ca830e1..c8a65089 100644 --- a/Bcore/pine-xposed/src/main/apacheCommonsLang/external/org/apache/commons/lang3/ArrayUtils.java +++ b/Bcore/pine-xposed/src/main/apacheCommonsLang/external/org/apache/commons/lang3/ArrayUtils.java @@ -3439,7 +3439,7 @@ public static T[] addAll(T[] array1, T... array2) { * - safer, in case check turns out to be too strict */ final Class type2 = array2.getClass().getComponentType(); - if (!type1.isAssignableFrom(type2)){ + if (!type1.isAssignableFrom(type2)) { throw new IllegalArgumentException("Cannot store "+type2.getName()+" in an array of " +type1.getName(), ase); } @@ -3711,7 +3711,7 @@ public static double[] addAll(double[] array1, double... array2) { */ public static T[] add(T[] array, T element) { Class type; - if (array != null){ + if (array != null) { type = array.getClass(); } else if (element != null) { type = element.getClass(); diff --git a/Bcore/pine-xposed/src/main/apacheCommonsLang/external/org/apache/commons/lang3/StringUtils.java b/Bcore/pine-xposed/src/main/apacheCommonsLang/external/org/apache/commons/lang3/StringUtils.java index e4b2fadd..a7830ce6 100644 --- a/Bcore/pine-xposed/src/main/apacheCommonsLang/external/org/apache/commons/lang3/StringUtils.java +++ b/Bcore/pine-xposed/src/main/apacheCommonsLang/external/org/apache/commons/lang3/StringUtils.java @@ -3598,7 +3598,7 @@ public static String removeStart(String str, String remove) { if (isEmpty(str) || isEmpty(remove)) { return str; } - if (str.startsWith(remove)){ + if (str.startsWith(remove)) { return str.substring(remove.length()); } return str; diff --git a/Bcore/src/main/aidl/top/niunaijun/blackbox/core/system/am/IBActivityManagerService.aidl b/Bcore/src/main/aidl/top/niunaijun/blackbox/core/system/am/IBActivityManagerService.aidl index 75600cd0..4159dff8 100644 --- a/Bcore/src/main/aidl/top/niunaijun/blackbox/core/system/am/IBActivityManagerService.aidl +++ b/Bcore/src/main/aidl/top/niunaijun/blackbox/core/system/am/IBActivityManagerService.aidl @@ -57,4 +57,6 @@ interface IBActivityManagerService { void getIntentSender(in IBinder target, String packageName, int uid, int userId); String getPackageForIntentSender(in IBinder target, int userId); int getUidForIntentSender(in IBinder target, int userId); + + void setServiceForeground(in ComponentName className, in IBinder token, int id, in Notification notification, int flags, int foregroundServiceType, int userId); } diff --git a/Bcore/src/main/cpp/Dobby/external/misc-helper/pthread_helper.cc b/Bcore/src/main/cpp/Dobby/external/misc-helper/pthread_helper.cc index a539fc1d..99a48c5c 100644 --- a/Bcore/src/main/cpp/Dobby/external/misc-helper/pthread_helper.cc +++ b/Bcore/src/main/cpp/Dobby/external/misc-helper/pthread_helper.cc @@ -9,9 +9,9 @@ int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void { uintptr_t handle = _beginthread((windows_thread)start_routine,0,arg); thread->handle = (HANDLE)handle; - if(thread->handle == (HANDLE)-1){ + if (thread->handle == (HANDLE)-1) { return 1; - }else{ + } else { return 0; } } @@ -30,9 +30,9 @@ void pthread_exit(void *value_ptr) int pthread_join(pthread_t thread, void **value_ptr) { DWORD retvalue = WaitForSingleObject(thread.handle,INFINITE); - if(retvalue == WAIT_OBJECT_0){ + if (retvalue == WAIT_OBJECT_0) { return 0; - }else{ + } else { return EINVAL; } } @@ -73,10 +73,10 @@ int pthread_mutex_destroy(pthread_mutex_t *mutex) int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr) { HANDLE handle = CreateMutex(NULL,FALSE,NULL); - if(handle != NULL){ + if (handle != NULL) { mutex->handle = handle; return 0; - }else{ + } else { return 1; } } @@ -84,9 +84,9 @@ int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr) int pthread_mutex_lock(pthread_mutex_t *mutex) { DWORD retvalue = WaitForSingleObject(mutex->handle,INFINITE); - if(retvalue == WAIT_OBJECT_0){ + if (retvalue == WAIT_OBJECT_0) { return 0; - }else{ + } else { return EINVAL; } } @@ -94,11 +94,11 @@ int pthread_mutex_lock(pthread_mutex_t *mutex) int pthread_mutex_trylock(pthread_mutex_t *mutex) { DWORD retvalue = WaitForSingleObject(mutex->handle,0); - if(retvalue == WAIT_OBJECT_0){ + if (retvalue == WAIT_OBJECT_0) { return 0; - }else if(retvalue == WAIT_TIMEOUT){ + } else if (retvalue == WAIT_TIMEOUT) { return EBUSY; - }else{ + } else { return EINVAL; } } @@ -113,28 +113,28 @@ int pthread_mutex_unlock(pthread_mutex_t *mutex) int pthread_key_create(pthread_key_t *key, void (*destr_function) (void *)) { DWORD dkey = TlsAlloc(); - if(dkey != 0xFFFFFFFF){ + if (dkey != 0xFFFFFFFF) { *key = dkey; return 0; - }else{ + } else { return EAGAIN; } } int pthread_key_delete(pthread_key_t key) { - if(TlsFree(key)){ + if (TlsFree(key)) { return 0; - }else{ + } else { return EINVAL; } } int pthread_setspecific(pthread_key_t key, const void *pointer) { - if(TlsSetValue(key,(LPVOID)pointer)){ + if (TlsSetValue(key,(LPVOID)pointer)) { return 0; - }else{ + } else { return EINVAL; } } diff --git a/Bcore/src/main/cpp/Dobby/source/core/modules/assembler/assembler-arm64.h b/Bcore/src/main/cpp/Dobby/source/core/modules/assembler/assembler-arm64.h index 4aeb4c01..f3faa470 100644 --- a/Bcore/src/main/cpp/Dobby/source/core/modules/assembler/assembler-arm64.h +++ b/Bcore/src/main/cpp/Dobby/source/core/modules/assembler/assembler-arm64.h @@ -104,7 +104,7 @@ class PseudoLabel : public Label { private: #if 0 // From a design perspective, these fix-function write as callback, maybe beeter. - void FixLdr(PseudoLabelInstruction *instruction){ + void FixLdr(PseudoLabelInstruction *instruction) { // dummy }; #endif diff --git a/Bcore/src/main/cpp/IO.cpp b/Bcore/src/main/cpp/IO.cpp index 105e1614..bc32ece0 100644 --- a/Bcore/src/main/cpp/IO.cpp +++ b/Bcore/src/main/cpp/IO.cpp @@ -44,7 +44,7 @@ const char *IO::redirectPath(const char *__path) { IO::RelocateInfo info = *iterator; if (strstr(__path, info.targetPath) && !strstr(__path, "/blackbox/")) { char *ret = replace(__path, info.targetPath, info.relocatePath); - // ALOGD("redirectPath %s => %s", __path, ret); + //ALOGD("redirectPath %s => %s", __path, ret); return ret; } } @@ -52,7 +52,7 @@ const char *IO::redirectPath(const char *__path) { } //#ifdef __arm__ https://developer.android.com/games/optimize/64-bit?hl=zh-cn -HOOK_JNI(void *, openat, int fd, const char *pathname, int flags, int mode){ +HOOK_JNI(void *, openat, int fd, const char *pathname, int flags, int mode) { // 执行 stack 清理(不可省略),只需调用一次 // SHADOWHOOK_STACK_SCOPE(); list::iterator white_iterator; @@ -75,7 +75,7 @@ HOOK_JNI(void *, openat, int fd, const char *pathname, int flags, int mode){ return orig_openat(fd, pathname, flags, mode); } -HOOK_JNI(FILE *, popen, const char* cmd, const char* mode){ +HOOK_JNI(FILE *, popen, const char* cmd, const char* mode) { // 执行 stack 清理(不可省略),只需调用一次 // SHADOWHOOK_STACK_SCOPE(); diff --git a/Bcore/src/main/java/top/niunaijun/blackbox/app/dispatcher/AppJobServiceDispatcher.java b/Bcore/src/main/java/top/niunaijun/blackbox/app/dispatcher/AppJobServiceDispatcher.java index 55268c98..9034bcc7 100644 --- a/Bcore/src/main/java/top/niunaijun/blackbox/app/dispatcher/AppJobServiceDispatcher.java +++ b/Bcore/src/main/java/top/niunaijun/blackbox/app/dispatcher/AppJobServiceDispatcher.java @@ -91,7 +91,7 @@ JobService getJobService(int jobId) { } try { JobRecord record = BlackBoxCore.getBJobManager().queryJobRecord(BActivityThread.getAppProcessName(), jobId); - if (record == null){ + if (record == null) { return null; } record.mJobService = BActivityThread.currentActivityThread().createJobService(record.mServiceInfo); diff --git a/Bcore/src/main/java/top/niunaijun/blackbox/core/NativeCore.java b/Bcore/src/main/java/top/niunaijun/blackbox/core/NativeCore.java index daa05166..c11a9d4a 100644 --- a/Bcore/src/main/java/top/niunaijun/blackbox/core/NativeCore.java +++ b/Bcore/src/main/java/top/niunaijun/blackbox/core/NativeCore.java @@ -16,8 +16,6 @@ import static top.niunaijun.blackbox.core.env.BEnvironment.EMPTY_JAR; -import com.bytedance.shadowhook.ShadowHook; - /** * Created by Milk on 4/9/21. * * ∧_∧ diff --git a/Bcore/src/main/java/top/niunaijun/blackbox/core/env/AppSystemEnv.java b/Bcore/src/main/java/top/niunaijun/blackbox/core/env/AppSystemEnv.java index d68a76f7..e85dde51 100644 --- a/Bcore/src/main/java/top/niunaijun/blackbox/core/env/AppSystemEnv.java +++ b/Bcore/src/main/java/top/niunaijun/blackbox/core/env/AppSystemEnv.java @@ -59,7 +59,7 @@ public class AppSystemEnv { sXposedPackages.add("de.robv.android.xposed.installer"); // sPreInstallPackages.add("com.huawei.hwid"); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && Build.VERSION.SDK_INT < 29){ + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && Build.VERSION.SDK_INT < 29) { //解决Android 9三星浏览器闪退问题 } else { diff --git a/Bcore/src/main/java/top/niunaijun/blackbox/core/system/BProcessManagerService.java b/Bcore/src/main/java/top/niunaijun/blackbox/core/system/BProcessManagerService.java index c8954dda..deccf9c0 100644 --- a/Bcore/src/main/java/top/niunaijun/blackbox/core/system/BProcessManagerService.java +++ b/Bcore/src/main/java/top/niunaijun/blackbox/core/system/BProcessManagerService.java @@ -108,17 +108,17 @@ public ProcessRecord startProcessLocked(String packageName, String processName, return app; } - private void killProcess(final ProcessRecord app){ + private void killProcess(final ProcessRecord app) { // make sure process be killed - if(app.pid > 0 ){ + if(app.pid > 0) { Process.killProcess(app.pid); - }else{ + } else { try { ActivityManager manager = (ActivityManager) BlackBoxCore.getContext().getSystemService(Context.ACTIVITY_SERVICE); List runningAppProcesses = manager.getRunningAppProcesses(); for (ActivityManager.RunningAppProcessInfo runningAppProcess : runningAppProcesses) { int bpid = parseBPid(runningAppProcess.processName); - if(bpid != -1 && app.bpid == bpid){ + if (bpid != -1 && app.bpid == bpid) { Slog.d(TAG, "force kill process: "+app.processName+", pid: "+runningAppProcess.pid+", bpid: "+bpid); Process.killProcess(runningAppProcess.pid); } diff --git a/Bcore/src/main/java/top/niunaijun/blackbox/core/system/accounts/BAccountManagerService.java b/Bcore/src/main/java/top/niunaijun/blackbox/core/system/accounts/BAccountManagerService.java index 15da59af..15227843 100644 --- a/Bcore/src/main/java/top/niunaijun/blackbox/core/system/accounts/BAccountManagerService.java +++ b/Bcore/src/main/java/top/niunaijun/blackbox/core/system/accounts/BAccountManagerService.java @@ -862,7 +862,7 @@ protected String readAuthTokenInternal(BUserAccounts accounts, Account account, private void completeCloningAccount(IAccountManagerResponse response, final Bundle accountCredentials, final Account account, final BUserAccounts targetUser, - final int parentUserId){ + final int parentUserId) { new Session(targetUser, response, account.type, false, false /* stripAuthTokenFromResult */, account.name, false /* authDetailsRequired */) { diff --git a/Bcore/src/main/java/top/niunaijun/blackbox/core/system/am/ActiveServices.java b/Bcore/src/main/java/top/niunaijun/blackbox/core/system/am/ActiveServices.java index 354383b9..2d049383 100644 --- a/Bcore/src/main/java/top/niunaijun/blackbox/core/system/am/ActiveServices.java +++ b/Bcore/src/main/java/top/niunaijun/blackbox/core/system/am/ActiveServices.java @@ -2,6 +2,7 @@ import android.annotation.SuppressLint; import android.app.ActivityManager; +import android.app.Notification; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -18,11 +19,13 @@ import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; +import black.android.os.UserHandle; import top.niunaijun.blackbox.BlackBoxCore; import top.niunaijun.blackbox.core.IEmpty; import top.niunaijun.blackbox.core.system.BProcessManagerService; import top.niunaijun.blackbox.core.system.ProcessRecord; import top.niunaijun.blackbox.core.system.pm.BPackageManagerService; +import top.niunaijun.blackbox.entity.ServiceRecord; import top.niunaijun.blackbox.entity.UnbindRecord; import top.niunaijun.blackbox.entity.am.RunningServiceInfo; import top.niunaijun.blackbox.proxy.ProxyManifest; @@ -168,6 +171,10 @@ public void stopServiceToken(ComponentName className, IBinder token, int userId) } } + public void setServiceForeground(ComponentName className, IBinder token, int id, Notification notification, int flags, int foregroundServiceType, int userId) { + + } + public void onStartCommand(Intent proxyIntent, int userId) { } diff --git a/Bcore/src/main/java/top/niunaijun/blackbox/core/system/am/BActivityManagerService.java b/Bcore/src/main/java/top/niunaijun/blackbox/core/system/am/BActivityManagerService.java index 14413ff1..8971813c 100644 --- a/Bcore/src/main/java/top/niunaijun/blackbox/core/system/am/BActivityManagerService.java +++ b/Bcore/src/main/java/top/niunaijun/blackbox/core/system/am/BActivityManagerService.java @@ -1,6 +1,7 @@ package top.niunaijun.blackbox.core.system.am; import android.app.ActivityManager; +import android.app.Notification; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -272,6 +273,14 @@ public int getUidForIntentSender(IBinder target, int userId) { return -1; } + @Override + public void setServiceForeground(ComponentName className, IBinder token, int id, Notification notification, int flags, int foregroundServiceType, int userId) { + UserSpace userSpace = getOrCreateSpaceLocked(userId); + synchronized (userSpace.mActiveServices) { + userSpace.mActiveServices.setServiceForeground(className, token, id, notification, flags, foregroundServiceType, userId); + } + } + @Override public void onStartCommand(Intent intent, int userId) { UserSpace userSpace = getOrCreateSpaceLocked(userId); diff --git a/Bcore/src/main/java/top/niunaijun/blackbox/core/system/pm/BPackageManagerService.java b/Bcore/src/main/java/top/niunaijun/blackbox/core/system/pm/BPackageManagerService.java index 6a986c0e..a03b336f 100644 --- a/Bcore/src/main/java/top/niunaijun/blackbox/core/system/pm/BPackageManagerService.java +++ b/Bcore/src/main/java/top/niunaijun/blackbox/core/system/pm/BPackageManagerService.java @@ -15,6 +15,7 @@ import android.content.pm.ServiceInfo; import android.net.Uri; import android.os.Binder; +import android.os.Build; import android.os.RemoteException; import android.text.TextUtils; import android.util.Log; @@ -66,7 +67,6 @@ public class BPackageManagerService extends IBPackageManagerService.Stub impleme private static final BUserManagerService sUserManager = BUserManagerService.get(); private final List mPackageMonitors = new ArrayList<>(); - final Map mPackages = mSettings.mPackages; final Object mInstallLock = new Object(); @@ -80,22 +80,21 @@ public BPackageManagerService() { filter.addAction("android.intent.action.PACKAGE_ADDED"); filter.addAction("android.intent.action.PACKAGE_REMOVED"); filter.addDataScheme("package"); + BroadcastReceiver mPackageChangedHandler = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (!TextUtils.isEmpty(action)) { + if ("android.intent.action.PACKAGE_ADDED".equals(action) || "android.intent.action.PACKAGE_REMOVED".equals(action)) { + mSettings.scanPackage(); + } + } + } + }; BlackBoxCore.getContext() .registerReceiver(mPackageChangedHandler, filter); } - private final BroadcastReceiver mPackageChangedHandler = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (!TextUtils.isEmpty(action)) { - if ("android.intent.action.PACKAGE_ADDED".equals(action) || "android.intent.action.PACKAGE_REMOVED".equals(action)) { - mSettings.scanPackage(); - } - } - } - }; - @Override public ApplicationInfo getApplicationInfo(String packageName, int flags, int userId) { if (!sUserManager.exists(userId)) return null; @@ -771,14 +770,16 @@ static String fixProcessName(String defProcessName, String processName) { * Update given flags based on encryption status of current user. */ private int updateFlags(int flags, int userId) { - if ((flags & (PackageManager.MATCH_DIRECT_BOOT_UNAWARE + if ((flags & (MATCH_DIRECT_BOOT_UNAWARE | PackageManager.MATCH_DIRECT_BOOT_AWARE)) != 0) { // Caller expressed an explicit opinion about what encryption // aware/unaware components they want to see, so fall through and // give them what they want } else { // Caller expressed no opinion, so match based on user state - flags |= PackageManager.MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + flags |= PackageManager.MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE; + } } return flags; } diff --git a/Bcore/src/main/java/top/niunaijun/blackbox/core/system/pm/PackageManagerCompat.java b/Bcore/src/main/java/top/niunaijun/blackbox/core/system/pm/PackageManagerCompat.java index 71a9570a..7af8d7d7 100644 --- a/Bcore/src/main/java/top/niunaijun/blackbox/core/system/pm/PackageManagerCompat.java +++ b/Bcore/src/main/java/top/niunaijun/blackbox/core/system/pm/PackageManagerCompat.java @@ -192,8 +192,8 @@ public static PackageInfo generatePackageInfo(BPackage p, int flags, long firstI base = BlackBoxCore.getPackageManager().getPackageInfo(p.packageName, flags); } catch (PackageManager.NameNotFoundException ignored) { } - if(base != null){ - if(base.splitNames != null){ + if (base != null) { + if (base.splitNames != null) { pi.splitNames = base.splitNames; } } diff --git a/Bcore/src/main/java/top/niunaijun/blackbox/entity/location/BCell.java b/Bcore/src/main/java/top/niunaijun/blackbox/entity/location/BCell.java index 38b247d8..55df5bc3 100644 --- a/Bcore/src/main/java/top/niunaijun/blackbox/entity/location/BCell.java +++ b/Bcore/src/main/java/top/niunaijun/blackbox/entity/location/BCell.java @@ -67,7 +67,7 @@ public void writeToParcel(Parcel dest, int flags) { dest.writeInt(this.TYPE); } - public BCell(){} + public BCell() {} public BCell(int MCC, int MNC, int LAC, int CID) { this.TYPE = PHONE_TYPE_GSM; this.MCC = MCC; diff --git a/Bcore/src/main/java/top/niunaijun/blackbox/fake/delegate/BaseInstrumentationDelegate.java b/Bcore/src/main/java/top/niunaijun/blackbox/fake/delegate/BaseInstrumentationDelegate.java index 38b693e5..a6e368c3 100644 --- a/Bcore/src/main/java/top/niunaijun/blackbox/fake/delegate/BaseInstrumentationDelegate.java +++ b/Bcore/src/main/java/top/niunaijun/blackbox/fake/delegate/BaseInstrumentationDelegate.java @@ -20,6 +20,8 @@ import android.view.KeyEvent; import android.view.MotionEvent; +import androidx.annotation.RequiresApi; + import top.niunaijun.blackbox.BlackBoxCore; import top.niunaijun.blackbox.app.BActivityThread; import top.niunaijun.blackbox.app.configuration.AppLifecycleCallback; @@ -55,6 +57,7 @@ public void sendStatus(int resultCode, Bundle results) { mBaseInstrumentation.sendStatus(resultCode, results); } + @RequiresApi(api = Build.VERSION_CODES.O) @Override public void addResults(Bundle results) { mBaseInstrumentation.addResults(results); diff --git a/Bcore/src/main/java/top/niunaijun/blackbox/fake/frameworks/BActivityManager.java b/Bcore/src/main/java/top/niunaijun/blackbox/fake/frameworks/BActivityManager.java index a75e2ec0..7a5f2227 100644 --- a/Bcore/src/main/java/top/niunaijun/blackbox/fake/frameworks/BActivityManager.java +++ b/Bcore/src/main/java/top/niunaijun/blackbox/fake/frameworks/BActivityManager.java @@ -1,6 +1,7 @@ package top.niunaijun.blackbox.fake.frameworks; import android.app.Activity; +import android.app.Notification; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ProviderInfo; @@ -290,4 +291,12 @@ public int getUidForIntentSender(IBinder target) { } return -1; } + + public void setServiceForeground(ComponentName className, IBinder token, int id, Notification notification, int flags, int foregroundServiceType) { + try { + getService().setServiceForeground(className, token, id, notification, flags, foregroundServiceType, BActivityThread.getUserId()); + } catch (RemoteException e) { + e.printStackTrace(); + } + } } diff --git a/Bcore/src/main/java/top/niunaijun/blackbox/fake/service/ActivityManagerCommonProxy.java b/Bcore/src/main/java/top/niunaijun/blackbox/fake/service/ActivityManagerCommonProxy.java index 22ba0be9..e53d9cda 100644 --- a/Bcore/src/main/java/top/niunaijun/blackbox/fake/service/ActivityManagerCommonProxy.java +++ b/Bcore/src/main/java/top/niunaijun/blackbox/fake/service/ActivityManagerCommonProxy.java @@ -123,7 +123,7 @@ protected Object hook(Object who, Method method, Object[] args) throws Throwable String[] resolvedTypes = (String[]) args[index++]; IBinder resultTo = (IBinder) args[index++]; Bundle options = (Bundle) args[index]; - // todo ?? + if (!ComponentUtils.isSelf(intents)) { return method.invoke(who, args); } diff --git a/Bcore/src/main/java/top/niunaijun/blackbox/fake/service/IActivityManagerProxy.java b/Bcore/src/main/java/top/niunaijun/blackbox/fake/service/IActivityManagerProxy.java index 1887728a..bb7c54e9 100644 --- a/Bcore/src/main/java/top/niunaijun/blackbox/fake/service/IActivityManagerProxy.java +++ b/Bcore/src/main/java/top/niunaijun/blackbox/fake/service/IActivityManagerProxy.java @@ -1,8 +1,12 @@ package top.niunaijun.blackbox.fake.service; +import static android.content.pm.PackageManager.GET_META_DATA; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; + import android.Manifest; import android.app.ActivityManager; import android.app.IServiceConnection; +import android.app.Notification; import android.content.ComponentName; import android.content.IIntentReceiver; import android.content.Intent; @@ -30,6 +34,7 @@ import top.niunaijun.blackbox.BlackBoxCore; import top.niunaijun.blackbox.app.BActivityThread; import top.niunaijun.blackbox.core.env.AppSystemEnv; +import top.niunaijun.blackbox.core.system.DaemonService; import top.niunaijun.blackbox.entity.AppConfig; import top.niunaijun.blackbox.entity.am.RunningAppProcessInfo; import top.niunaijun.blackbox.entity.am.RunningServiceInfo; @@ -54,9 +59,6 @@ import top.niunaijun.blackbox.utils.compat.ParceledListSliceCompat; import top.niunaijun.blackbox.utils.compat.TaskDescriptionCompat; -import static android.content.pm.PackageManager.GET_META_DATA; -import static android.content.pm.PackageManager.PERMISSION_GRANTED; - /** * Created by Milk on 3/30/21. * * ∧_∧ @@ -115,7 +117,7 @@ protected Object hook(Object who, Method method, Object[] args) throws Exception /*try { BlackBoxCore.getBPackageManager().resolveContentProvider((String) auth, GET_META_DATA, BActivityThread.getUserId()); - } catch (Exception e){ + } catch (Exception e) { Log.e(TAG, "BlackBoxCore.getBPackageManager().resolveContentProvider Failed"); e.printStackTrace(); } @@ -395,18 +397,6 @@ protected Object hook(Object who, Method method, Object[] args) throws Throwable } } - @ProxyMethod("getIntentSenderWithSourceToken") - public static class GetIntentSenderWithSourceToken extends GetIntentSender { - } - - @ProxyMethod("getIntentSenderWithFeature") - public static class GetIntentSenderWithFeature extends GetIntentSender { - } - - @ProxyMethod("broadcastIntentWithFeature") - public static class BroadcastIntentWithFeature extends BroadcastIntent { - } - @ProxyMethod("broadcastIntent") public static class BroadcastIntent extends MethodHook { @Override @@ -441,30 +431,6 @@ int getIntentIndex(Object[] args) { } } - @ProxyMethod("unregisterReceiver") - public static class unregisterReceiver extends MethodHook { - @Override - protected Object hook(Object who, Method method, Object[] args) throws Throwable { - return method.invoke(who, args); - } - } - - @ProxyMethod("finishReceiver") - public static class finishReceiver extends MethodHook { - @Override - protected Object hook(Object who, Method method, Object[] args) throws Throwable { - return method.invoke(who, args); - } - } - - @ProxyMethod("publishService") - public static class PublishService extends MethodHook { - @Override - protected Object hook(Object who, Method method, Object[] args) throws Throwable { - return method.invoke(who, args); - } - } - @ProxyMethod("peekService") public static class PeekService extends MethodHook { @Override @@ -472,8 +438,7 @@ protected Object hook(Object who, Method method, Object[] args) throws Throwable MethodParameterUtils.replaceLastAppPkg(args); Intent intent = (Intent) args[0]; String resolvedType = (String) args[1]; - IBinder peek = BlackBoxCore.getBActivityManager().peekService(intent, resolvedType, BActivityThread.getUserId()); - return peek; + return BlackBoxCore.getBActivityManager().peekService(intent, resolvedType, BActivityThread.getUserId()); } } @@ -486,11 +451,6 @@ protected Object hook(Object who, Method method, Object[] args) throws Throwable } } - // android 10 - @ProxyMethod("registerReceiverWithFeature") - public static class RegisterReceiverWithFeature extends RegisterReceiver { - } - @ProxyMethod("registerReceiver") public static class RegisterReceiver extends MethodHook { @Override @@ -544,14 +504,22 @@ protected Object hook(Object who, Method method, Object[] args) throws Throwable } @ProxyMethod("setServiceForeground") - public static class setServiceForeground extends MethodHook { + public static class SetServiceForeground extends MethodHook { @Override protected Object hook(Object who, Method method, Object[] args) throws Throwable { - /*if (args[0] instanceof ComponentName) { - args[0] = new ComponentName(BlackBoxCore.getHostPkg(), ProxyManifest.getProxyService(BActivityThread.getAppPid())); + Notification notification = (Notification) args[3]; + + Intent intent = new Intent(BlackBoxCore.getContext(), DaemonService.class); + if (notification != null) { + if (BuildCompat.isOreo()) { + BlackBoxCore.getContext().startForegroundService(intent); + } else { + BlackBoxCore.getContext().startService(intent); + } + } else { + BlackBoxCore.getContext().stopService(intent); } - return method.invoke(who, args);*/ - return 0; + return method.invoke(who, args); } } @@ -603,42 +571,4 @@ protected Object hook(Object who, Method method, Object[] args) throws Throwable return method.invoke(who, args); } } - - @ProxyMethod("setRequestedOrientation") - public static class setRequestedOrientation extends MethodHook { - - @Override - protected Object hook(Object who, Method method, Object[] args) throws Throwable { - try { - return method.invoke(who, args); - } catch (Throwable e) { - e.printStackTrace(); - } - return 0; - } - } - - @ProxyMethod("registerUidObserver") - public static class registerUidObserver extends MethodHook { - @Override - protected Object hook(Object who, Method method, Object[] args) throws Throwable { - return 0; - } - } - - @ProxyMethod("unregisterUidObserver") - public static class unregisterUidObserver extends MethodHook { - @Override - protected Object hook(Object who, Method method, Object[] args) throws Throwable { - return 0; - } - } - - @ProxyMethod("updateConfiguration") - public static class updateConfiguration extends MethodHook { - @Override - protected Object hook(Object who, Method method, Object[] args) throws Throwable { - return 0; - } - } } diff --git a/Bcore/src/main/java/top/niunaijun/blackbox/fake/service/IFingerprintManagerProxy.java b/Bcore/src/main/java/top/niunaijun/blackbox/fake/service/IFingerprintManagerProxy.java index 4bfa0ebc..2865c583 100644 --- a/Bcore/src/main/java/top/niunaijun/blackbox/fake/service/IFingerprintManagerProxy.java +++ b/Bcore/src/main/java/top/niunaijun/blackbox/fake/service/IFingerprintManagerProxy.java @@ -1,8 +1,11 @@ package top.niunaijun.blackbox.fake.service; import android.content.Context; +import android.os.Build; import android.os.IBinder; +import androidx.annotation.RequiresApi; + import black.android.os.BRServiceManager; import black.android.view.BRIGraphicsStatsStub; import top.niunaijun.blackbox.fake.hook.BinderInvocationStub; diff --git a/Bcore/src/main/java/top/niunaijun/blackbox/fake/service/IPackageManagerProxy.java b/Bcore/src/main/java/top/niunaijun/blackbox/fake/service/IPackageManagerProxy.java index 2469877d..6fb7d169 100644 --- a/Bcore/src/main/java/top/niunaijun/blackbox/fake/service/IPackageManagerProxy.java +++ b/Bcore/src/main/java/top/niunaijun/blackbox/fake/service/IPackageManagerProxy.java @@ -20,6 +20,8 @@ import top.niunaijun.blackbox.BlackBoxCore; import top.niunaijun.blackbox.app.BActivityThread; import top.niunaijun.blackbox.core.env.AppSystemEnv; +import top.niunaijun.blackbox.core.system.pm.BPackageManagerService; +import top.niunaijun.blackbox.core.system.pm.BPackageSettings; import top.niunaijun.blackbox.fake.hook.BinderInvocationStub; import top.niunaijun.blackbox.fake.hook.MethodHook; import top.niunaijun.blackbox.fake.hook.ProxyMethod; @@ -347,8 +349,20 @@ protected Object hook(Object who, Method method, Object[] args) throws Throwable public static class GetSharedLibraries extends MethodHook { @Override protected Object hook(Object who, Method method, Object[] args) throws Throwable { - // todo - return ParceledListSliceCompat.create(new ArrayList<>()); + String packageName = (String) args[0]; + BPackageSettings packageSettings = BPackageManagerService.get().getBPackageSetting(packageName); + if (packageSettings != null) { + ArrayList packageLibraries = new ArrayList<>(); + if (packageSettings.pkg.usesLibraries != null) { + packageLibraries.addAll(packageSettings.pkg.usesLibraries); + } + + if (packageSettings.pkg.usesOptionalLibraries != null) { + packageLibraries.addAll(packageSettings.pkg.usesOptionalLibraries); + } + return ParceledListSliceCompat.create(packageLibraries); + } + return method.invoke(who, args); } } diff --git a/Bcore/src/main/java/top/niunaijun/blackbox/fake/service/IShortcutManagerProxy.java b/Bcore/src/main/java/top/niunaijun/blackbox/fake/service/IShortcutManagerProxy.java index 5d823158..42f30765 100644 --- a/Bcore/src/main/java/top/niunaijun/blackbox/fake/service/IShortcutManagerProxy.java +++ b/Bcore/src/main/java/top/niunaijun/blackbox/fake/service/IShortcutManagerProxy.java @@ -3,6 +3,9 @@ import android.content.Context; import android.content.Intent; import android.content.pm.ShortcutInfo; +import android.os.Build; + +import androidx.annotation.RequiresApi; import java.lang.reflect.Method; import java.util.ArrayList; @@ -61,7 +64,7 @@ protected void onBindMethod() { addMethodHook(new PkgMethodProxy("removeAllDynamicShortcuts")); addMethodHook(new PkgMethodProxy("removeDynamicShortcuts")); addMethodHook(new PkgMethodProxy("removeLongLivedShortcuts")); - addMethodHook(new PkgMethodProxy("getManifestShortcuts"){ + addMethodHook(new PkgMethodProxy("getManifestShortcuts") { @Override protected Object hook(Object who, Method method, Object[] args) throws Throwable { return ParceledListSliceCompat.create(new ArrayList()); diff --git a/Bcore/src/main/java/top/niunaijun/blackbox/fake/service/IStorageStatsManagerProxy.java b/Bcore/src/main/java/top/niunaijun/blackbox/fake/service/IStorageStatsManagerProxy.java index 980d310f..bb3c294b 100644 --- a/Bcore/src/main/java/top/niunaijun/blackbox/fake/service/IStorageStatsManagerProxy.java +++ b/Bcore/src/main/java/top/niunaijun/blackbox/fake/service/IStorageStatsManagerProxy.java @@ -12,7 +12,6 @@ /** * Created by BlackBox on 2022/3/3. */ -//@TargetApi(Build.VERSION_CODES.O) public class IStorageStatsManagerProxy extends BinderInvocationStub { public IStorageStatsManagerProxy() { super(BRServiceManager.get().getService(Context.STORAGE_STATS_SERVICE)); diff --git a/Bcore/src/main/java/top/niunaijun/blackbox/fake/service/context/LocationListenerStub.java b/Bcore/src/main/java/top/niunaijun/blackbox/fake/service/context/LocationListenerStub.java index 44d2af81..dbc6cf66 100644 --- a/Bcore/src/main/java/top/niunaijun/blackbox/fake/service/context/LocationListenerStub.java +++ b/Bcore/src/main/java/top/niunaijun/blackbox/fake/service/context/LocationListenerStub.java @@ -48,12 +48,12 @@ public static class OnLocationChanged extends MethodHook { @Override protected Object hook(Object who, Method method, Object[] args) throws Throwable { - if(args[0] instanceof List){ + if (args[0] instanceof List) { List locations = (List) args[0]; locations.clear(); locations.add(BLocationManager.get().getLocation(BActivityThread.getUserId(), BActivityThread.getAppPackageName()).convert2SystemLocation()); args[0] = locations; - }else if(args[0] instanceof Location){ + } else if(args[0] instanceof Location) { args[0] = BLocationManager.get().getLocation(BActivityThread.getUserId(), BActivityThread.getAppPackageName()).convert2SystemLocation(); } return method.invoke(who, args); diff --git a/Bcore/src/main/java/top/niunaijun/blackbox/utils/HackAppUtils.java b/Bcore/src/main/java/top/niunaijun/blackbox/utils/HackAppUtils.java index b23ef67d..997097db 100644 --- a/Bcore/src/main/java/top/niunaijun/blackbox/utils/HackAppUtils.java +++ b/Bcore/src/main/java/top/niunaijun/blackbox/utils/HackAppUtils.java @@ -46,13 +46,13 @@ private static void injectLine(ClassLoader classLoader) { } } - public static void hackApp(String packageName, ClassLoader classLoader){ + public static void hackApp(String packageName, ClassLoader classLoader) { /*if ("jp.naver.line.android".equals(packageName)) { injectLine(classLoader); }*/ } - public static void hackActivity(String packageName, ClassLoader classLoader){ + public static void hackActivity(String packageName, ClassLoader classLoader) { /*if ("com.tencent.mobileqq".equals(packageName)) { enableQQLogOutput(classLoader); }*/ diff --git a/Bcore/src/main/java/top/niunaijun/blackbox/utils/Reflector.java b/Bcore/src/main/java/top/niunaijun/blackbox/utils/Reflector.java index d3b7cf29..e52894a6 100644 --- a/Bcore/src/main/java/top/niunaijun/blackbox/utils/Reflector.java +++ b/Bcore/src/main/java/top/niunaijun/blackbox/utils/Reflector.java @@ -2,6 +2,9 @@ import android.annotation.SuppressLint; +import android.os.Build; + +import androidx.annotation.RequiresApi; import org.lsposed.hiddenapibypass.HiddenApiBypass; @@ -143,16 +146,17 @@ protected Field findInstanceField(String name) throws NoSuchFieldException { @SuppressWarnings("unchecked") protected Field findStaticField(String name) throws NoSuchFieldException { - List allStaticFields = HiddenApiBypass.getStaticFields(mType); - for (Field f : allStaticFields) { - if (f.getName().equals(name)) { - return f; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + List allStaticFields = HiddenApiBypass.getStaticFields(mType); + for (Field f : allStaticFields) { + if (f.getName().equals(name)) { + return f; + } } } throw new NoSuchFieldException(); } - @SuppressWarnings("unchecked") public R get() throws Exception { return get(mCaller); } diff --git a/Bcore/src/main/java/top/niunaijun/blackbox/utils/compat/ActivityManagerCompat.java b/Bcore/src/main/java/top/niunaijun/blackbox/utils/compat/ActivityManagerCompat.java index 4062df7c..410a7299 100644 --- a/Bcore/src/main/java/top/niunaijun/blackbox/utils/compat/ActivityManagerCompat.java +++ b/Bcore/src/main/java/top/niunaijun/blackbox/utils/compat/ActivityManagerCompat.java @@ -102,7 +102,7 @@ public static void setActivityOrientation(Activity activity, int orientation) { IBinder token = BRActivity.get(parent).mToken(); try { BRIActivityManager.get(BRActivityManagerNative.get().getDefault()).setRequestedOrientation(token, orientation); - } catch (Throwable ex){ + } catch (Throwable ex) { ex.printStackTrace(); } } diff --git a/Bcore/src/main/java/top/niunaijun/blackbox/utils/compat/StrictModeCompat.java b/Bcore/src/main/java/top/niunaijun/blackbox/utils/compat/StrictModeCompat.java index c9fcafda..d2ab8931 100644 --- a/Bcore/src/main/java/top/niunaijun/blackbox/utils/compat/StrictModeCompat.java +++ b/Bcore/src/main/java/top/niunaijun/blackbox/utils/compat/StrictModeCompat.java @@ -9,7 +9,7 @@ public class StrictModeCompat { public static int PENALTY_DEATH_ON_FILE_URI_EXPOSURE = BRStrictMode.get().PENALTY_DEATH_ON_FILE_URI_EXPOSURE() == null ? (0x04 << 24) : BRStrictMode.get().PENALTY_DEATH_ON_FILE_URI_EXPOSURE(); - public static void disableDeathOnFileUriExposure(){ + public static void disableDeathOnFileUriExposure() { try { BRStrictMode.get().disableDeathOnFileUriExposure(); } catch (Throwable e) { diff --git a/Bcore/src/main/res/xml/filepath.xml b/Bcore/src/main/res/xml/filepath.xml index 6e11ec24..080bee98 100644 --- a/Bcore/src/main/res/xml/filepath.xml +++ b/Bcore/src/main/res/xml/filepath.xml @@ -8,6 +8,4 @@ - - diff --git a/android-mirror/build.gradle b/android-mirror/build.gradle index 1ac45a5a..dcf87d4f 100644 --- a/android-mirror/build.gradle +++ b/android-mirror/build.gradle @@ -1,13 +1,12 @@ plugins { id 'com.android.library' + id 'org.jetbrains.kotlin.android' } -apply plugin: 'kotlin-android' android { - compileSdkVersion rootProject.ext.compileSdkVersion - - aidlPackageWhiteList "android/app/IServiceConnection.aidl" - aidlPackageWhiteList "android/accounts/IAccountManagerResponse.aidl" + compileSdk rootProject.ext.compileSdk + aidlPackagedList "android/app/IServiceConnection.aidl" + aidlPackagedList "android/accounts/IAccountManagerResponse.aidl" defaultConfig { testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -28,14 +27,10 @@ android { } dependencies { - implementation 'androidx.appcompat:appcompat:1.2.0' + implementation 'androidx.appcompat:appcompat:1.5.0' implementation project(':Bcore:black-fake') implementation "com.github.CodingGay.BlackReflection:core:$rootProject.ext.blackReflection" annotationProcessor "com.github.CodingGay.BlackReflection:compiler:$rootProject.ext.blackReflection" implementation "androidx.core:core-ktx:$ktx_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" } - -repositories { - mavenCentral() -} \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 73b03568..bdfc4c60 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,25 +1,25 @@ plugins { id 'com.android.application' - id 'kotlin-android' + id 'org.jetbrains.kotlin.android' } -apply plugin: 'kotlin-android' android { - compileSdkVersion 32 + compileSdk rootProject.ext.compileSdk defaultConfig { - applicationId "top.niunaijun.blackboxa" - minSdkVersion 21 - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 12 - versionName "2.1.4" + applicationId "top.niunaijun.blackbox" + minSdk rootProject.ext.minSdkVersion + targetSdk rootProject.ext.targetSdk + versionCode 7 + versionName "1.6.0" flavorDimensions "BlackBox32" + multiDexEnabled true } productFlavors { BlackBox32 { - applicationId "top.niunaijun.blackboxa32" + applicationId "top.niunaijun.blackbox32" ndk { abiFilters "armeabi-v7a" } @@ -29,7 +29,7 @@ android { } BlackBox64 { - applicationId "top.niunaijun.blackboxa64" + applicationId "top.niunaijun.blackbox64" ndk { abiFilters "arm64-v8a" } @@ -39,7 +39,7 @@ android { } BlackBox32Beta { - applicationId "top.niunaijun.blackboxa32_beta" + applicationId "top.niunaijun.blackbox32_beta" ndk { abiFilters "armeabi-v7a" } @@ -49,7 +49,7 @@ android { } BlackBox64Beta { - applicationId "top.niunaijun.blackboxa64_beta" + applicationId "top.niunaijun.blackbox64_beta" ndk { abiFilters "arm64-v8a" } @@ -86,30 +86,26 @@ android { targetCompatibility JavaVersion.VERSION_1_8 } - aaptOptions { - cruncherEnabled = false - useNewCruncher = false - } - testOptions { unitTests.returnDefaultValues = true } - lintOptions { - checkReleaseBuilds false - abortOnError false - warningsAsErrors false - disable "UnusedResources", 'RestrictedApi' - textOutput "stdout" - textReport false - checkOnly 'NewApi', 'InlinedApi' - } sourceSets { main { jniLibs.srcDirs = ['libs'] } } + + lint { + abortOnError false + checkOnly 'NewApi', 'InlinedApi' + checkReleaseBuilds false + disable 'UnusedResources', 'RestrictedApi' + textOutput file('stdout') + textReport false + warningsAsErrors false + } } tasks.withType(Javadoc) { @@ -118,34 +114,29 @@ tasks.withType(Javadoc) { options.addStringOption('charSet', 'UTF-8') } -repositories { - mavenCentral() -} - dependencies { - implementation fileTree(dir: "libs", include: ["*.jar", "*.aar"]) implementation project(':Bcore') //android - implementation 'com.google.android.material:material:1.3.0' - implementation 'androidx.appcompat:appcompat:1.3.0-rc01' - implementation 'androidx.constraintlayout:constraintlayout:2.0.4' - implementation "androidx.recyclerview:recyclerview:1.2.0" + implementation 'com.google.android.material:material:1.6.1' + implementation 'androidx.appcompat:appcompat:1.6.0-beta01' + implementation 'androidx.constraintlayout:constraintlayout:2.1.4' + implementation 'androidx.recyclerview:recyclerview:1.2.1' implementation 'androidx.viewpager2:viewpager2:1.0.0' - implementation "androidx.activity:activity-ktx:1.3.1" - implementation "androidx.fragment:fragment-ktx:1.3.3" - implementation "androidx.preference:preference-ktx:1.1.1" + implementation 'androidx.activity:activity-ktx:1.5.1' + implementation 'androidx.fragment:fragment-ktx:1.5.2' + implementation 'androidx.preference:preference-ktx:1.2.0' //coroutines - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.2" + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4' //viewModel liveData lifecycle - 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.lifecycle:lifecycle-viewmodel-ktx:2.5.1' + implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.5.1' + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1' //third - implementation 'com.tbuonomo:dotsindicator:4.2' + implementation 'com.tbuonomo:dotsindicator:4.3' //viewPager2 指示点 implementation 'com.afollestad.material-dialogs:core:3.3.0' implementation 'com.afollestad.material-dialogs:input:3.3.0' @@ -160,7 +151,7 @@ dependencies { //图标角标 // osmdroid - implementation 'org.osmdroid:osmdroid-android:6.1.11' + implementation 'org.osmdroid:osmdroid-android:6.1.14' //adapter implementation 'com.gitee.cbfg5210:RVAdapter:0.3.7' @@ -168,4 +159,4 @@ dependencies { implementation 'com.imuxuan:floatingview:1.6' implementation "androidx.core:core-ktx:$ktx_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" -} +} \ No newline at end of file diff --git a/app/src/androidTest/java/top/niunaijun/blackbox/ExampleInstrumentedTest.kt b/app/src/androidTest/java/top/niunaijun/blackbox/ExampleInstrumentedTest.kt new file mode 100644 index 00000000..457b0d26 --- /dev/null +++ b/app/src/androidTest/java/top/niunaijun/blackbox/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package top.niunaijun.blackbox + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("top.niunaijun.blackbox", appContext.packageName) + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 9f536b04..011676bb 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,7 +1,8 @@ + package="top.niunaijun.blackbox"> + @@ -23,6 +24,7 @@ @@ -37,10 +39,10 @@ android:name=".view.main.ShortcutActivity" android:excludeFromRecents="true" android:exported="true" /> - + - + \ No newline at end of file diff --git a/app/src/main/java/top/niunaijun/blackbox/app/App.kt b/app/src/main/java/top/niunaijun/blackbox/app/App.kt new file mode 100644 index 00000000..5200fa54 --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackbox/app/App.kt @@ -0,0 +1,35 @@ +package top.niunaijun.blackbox.app + +import android.annotation.SuppressLint +import android.app.Application +import android.content.Context + +/** + * + * @Description: + * @Author: wukaicheng + * @CreateDate: 2021/4/29 21:21 + */ +class App : Application() { + companion object { + @SuppressLint("StaticFieldLeak") + @Volatile + private lateinit var mContext: Context + + @JvmStatic + fun getContext(): Context { + return mContext + } + } + + override fun attachBaseContext(base: Context?) { + super.attachBaseContext(base) + mContext = base!! + AppManager.doAttachBaseContext(base) + } + + override fun onCreate() { + super.onCreate() + AppManager.doOnCreate() + } +} diff --git a/app/src/main/java/top/niunaijun/blackbox/app/AppManager.kt b/app/src/main/java/top/niunaijun/blackbox/app/AppManager.kt new file mode 100644 index 00000000..f75a6dd4 --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackbox/app/AppManager.kt @@ -0,0 +1,33 @@ +package top.niunaijun.blackbox.app + +import android.content.Context +import android.content.SharedPreferences +import top.niunaijun.blackbox.view.main.BlackBoxLoader + +object AppManager { + @JvmStatic + val mBlackBoxLoader by lazy { + BlackBoxLoader() + } + + @JvmStatic + val mRemarkSharedPreferences: SharedPreferences by lazy { + App.getContext().getSharedPreferences("UserRemark",Context.MODE_PRIVATE) + } + + fun doAttachBaseContext(context: Context) { + try { + mBlackBoxLoader.attachBaseContext(context) + mBlackBoxLoader.addLifecycleCallback() + } catch (e: Exception) { + e.printStackTrace() + } + } + + fun doOnCreate() { + mBlackBoxLoader.doOnCreate() + initThirdService() + } + + private fun initThirdService() { } +} diff --git a/app/src/main/java/top/niunaijun/blackbox/app/rocker/BaseActivityLifecycleCallback.kt b/app/src/main/java/top/niunaijun/blackbox/app/rocker/BaseActivityLifecycleCallback.kt new file mode 100644 index 00000000..b4bf71ec --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackbox/app/rocker/BaseActivityLifecycleCallback.kt @@ -0,0 +1,27 @@ +package top.niunaijun.blackbox.app.rocker + +import android.app.Activity +import android.app.Application +import android.os.Bundle + +/** + * + * @Description: + * @Author: kotlinMiku + * @CreateDate: 2022/3/19 20:08 + */ +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) { } +} diff --git a/app/src/main/java/top/niunaijun/blackbox/app/rocker/RockerManager.kt b/app/src/main/java/top/niunaijun/blackbox/app/rocker/RockerManager.kt new file mode 100644 index 00000000..f6a27c28 --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackbox/app/rocker/RockerManager.kt @@ -0,0 +1,81 @@ +package top.niunaijun.blackbox.app.rocker + +import android.app.Activity +import android.app.Application +import android.view.Gravity +import android.widget.FrameLayout +import android.widget.RelativeLayout +import com.imuxuan.floatingview.FloatingMagnetView +import com.imuxuan.floatingview.FloatingView +import top.niunaijun.blackbox.app.App +import top.niunaijun.blackbox.entity.location.BLocation +import top.niunaijun.blackbox.fake.frameworks.BLocationManager +import top.niunaijun.blackbox.widget.EnFloatView +import kotlin.math.cos +import kotlin.math.sin + +/** + * + * @Description: + * @Author: kotlinMiku + * @CreateDate: 2022/3/19 19:37 + */ +object RockerManager { + private const val TAG = "RockerManager" + private const val Ea = 6378137 //赤道半径 + private const val Eb = 6356725 //极半径 + + fun init(application: Application?, userId: Int) { + if (application == null || !BLocationManager.isFakeLocationEnable()) { + return + } + + val enFloatView = initFloatView() + if (enFloatView is EnFloatView) { + enFloatView.setListener { angle: Float, distance: Float -> + changeLocation(distance, angle, application.packageName,userId) + } + } + + application.registerActivityLifecycleCallbacks(object : BaseActivityLifecycleCallback { + override fun onActivityStarted(activity: Activity) { + super.onActivityStarted(activity) + FloatingView.get().attach(activity) + } + + override fun onActivityStopped(activity: Activity) { + super.onActivityStopped(activity) + FloatingView.get().detach(activity) + } + }) + } + + private fun initFloatView(): FloatingMagnetView? { + 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) + return FloatingView.get().view + } + + private fun changeLocation(distance: Float, angle: Float, packageName: String, userId: Int) { + val location = BLocationManager.get().getLocation(userId, packageName) + + 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) + } +} diff --git a/app/src/main/java/top/niunaijun/blackbox/bean/AppInfo.kt b/app/src/main/java/top/niunaijun/blackbox/bean/AppInfo.kt new file mode 100644 index 00000000..9fb0347b --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackbox/bean/AppInfo.kt @@ -0,0 +1,11 @@ +package top.niunaijun.blackbox.bean + +import android.graphics.drawable.Drawable + +/** + * + * @Description: + * @Author: wukaicheng + * @CreateDate: 2021/4/29 21:57 + */ +data class AppInfo(val name: String, val icon: Drawable, val packageName: String, val sourceDir: String, val isXpModule: Boolean) diff --git a/app/src/main/java/top/niunaijun/blackbox/bean/FakeLocationBean.kt b/app/src/main/java/top/niunaijun/blackbox/bean/FakeLocationBean.kt new file mode 100644 index 00000000..f722a63b --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackbox/bean/FakeLocationBean.kt @@ -0,0 +1,13 @@ +package top.niunaijun.blackbox.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? +) diff --git a/app/src/main/java/top/niunaijun/blackbox/bean/GmsBean.kt b/app/src/main/java/top/niunaijun/blackbox/bean/GmsBean.kt new file mode 100644 index 00000000..730468c3 --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackbox/bean/GmsBean.kt @@ -0,0 +1,11 @@ +package top.niunaijun.blackbox.bean + +/** + * + * @Description: + * @Author: BlackBox + * @CreateDate: 2022/3/2 21:30 + */ +data class GmsBean(val userID: Int, val userName: String, var isInstalledGms: Boolean) + +data class GmsInstallBean(val userID: Int, val success: Boolean, val msg: String) diff --git a/app/src/main/java/top/niunaijun/blackbox/bean/InstalledAppBean.kt b/app/src/main/java/top/niunaijun/blackbox/bean/InstalledAppBean.kt new file mode 100644 index 00000000..6967c41a --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackbox/bean/InstalledAppBean.kt @@ -0,0 +1,11 @@ +package top.niunaijun.blackbox.bean + +import android.graphics.drawable.Drawable + +/** + * + * @Description: + * @Author: wukaicheng + * @CreateDate: 2022/3/6 19:26 + */ +data class InstalledAppBean(val name: String, val icon: Drawable, val packageName: String, val sourceDir: String, val isInstall: Boolean) diff --git a/app/src/main/java/top/niunaijun/blackbox/bean/XpModuleInfo.kt b/app/src/main/java/top/niunaijun/blackbox/bean/XpModuleInfo.kt new file mode 100644 index 00000000..9380d6cc --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackbox/bean/XpModuleInfo.kt @@ -0,0 +1,18 @@ +package top.niunaijun.blackbox.bean + +import android.graphics.drawable.Drawable + +/** + * + * @Description: + * @Author: wukaicheng + * @CreateDate: 2021/5/2 21:03 + */ +data class XpModuleInfo( + val name: String, + val desc: String, + val packageName: String, + val version: String, + var enable:Boolean, + val icon: Drawable +) diff --git a/app/src/main/java/top/niunaijun/blackbox/biz/cache/AppSharedPreferenceDelegate.kt b/app/src/main/java/top/niunaijun/blackbox/biz/cache/AppSharedPreferenceDelegate.kt new file mode 100644 index 00000000..5edfbd39 --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackbox/biz/cache/AppSharedPreferenceDelegate.kt @@ -0,0 +1,65 @@ +package top.niunaijun.blackbox.biz.cache + +import android.content.Context +import android.text.TextUtils +import androidx.core.content.edit +import kotlin.properties.ReadWriteProperty +import kotlin.reflect.KProperty + +/** + * + * @desc:目前只支持 5种基本数据类型,如果要支持obj,请继承该类并重写他的相关方法 findData/putData + * + * @author: mini + * @created by 2021/5/10 + */ +open class AppSharedPreferenceDelegate(context: Context, private val default: Data, spName: String? = null) : ReadWriteProperty { + 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) + } + + @Suppress("UNCHECKED_CAST") + private 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 + } + } + + private 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") + } + } + } + } +} diff --git a/app/src/main/java/top/niunaijun/blackbox/data/AppsRepository.kt b/app/src/main/java/top/niunaijun/blackbox/data/AppsRepository.kt new file mode 100644 index 00000000..900e2eb6 --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackbox/data/AppsRepository.kt @@ -0,0 +1,251 @@ +package top.niunaijun.blackbox.data + +import android.content.pm.ApplicationInfo +import android.content.pm.PackageManager +import android.net.Uri +import android.os.Build +import android.util.Log +import android.webkit.URLUtil +import androidx.core.content.edit +import androidx.lifecycle.MutableLiveData +import top.niunaijun.blackbox.BlackBoxCore +import top.niunaijun.blackbox.BlackBoxCore.getPackageManager +import top.niunaijun.blackbox.utils.AbiUtils +import top.niunaijun.blackbox.R +import top.niunaijun.blackbox.app.AppManager +import top.niunaijun.blackbox.bean.AppInfo +import top.niunaijun.blackbox.bean.InstalledAppBean +import top.niunaijun.blackbox.util.getString +import java.io.File + +/** + * + * @Description: + * @Author: wukaicheng + * @CreateDate: 2021/4/29 23:05 + */ +class AppsRepository { + private val TAG: String = "AppsRepository" + private var mInstalledList = mutableListOf() + + fun previewInstallList() { + synchronized(mInstalledList) { + val installedList = mutableListOf() + val installedApplications: List = if (Build.VERSION.SDK_INT >= 33) { + getPackageManager().getInstalledApplications( + PackageManager.ApplicationInfoFlags.of(0)) + } else { + getPackageManager().getInstalledApplications(0) + } + + for (installedApplication in installedApplications) { + val file = File(installedApplication.sourceDir) + if ((installedApplication.flags and ApplicationInfo.FLAG_SYSTEM) != 0) { + continue + } + + if (!AbiUtils.isSupport(file)) { + continue + } + + val isXpModule = BlackBoxCore.get().isXposedModule(file) + + val info = AppInfo( + installedApplication.loadLabel(getPackageManager()).toString(), + installedApplication.loadIcon(getPackageManager()), + installedApplication.packageName, + installedApplication.sourceDir, + isXpModule + ) + installedList.add(info) + } + + installedList.sortWith { a, b -> + if (a.name > b.name) { + 1 + } else { + -1 + } + } + + this.mInstalledList.clear() + this.mInstalledList.addAll(installedList) + } + } + + fun getInstalledAppList( + userID: Int, + loadingLiveData: MutableLiveData, + appsLiveData: MutableLiveData> + ) { + 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) + } + } + + fun getInstalledModuleList( + loadingLiveData: MutableLiveData, + appsLiveData: MutableLiveData> + ) { + loadingLiveData.postValue(true) + synchronized(mInstalledList) { + val blackBoxCore = BlackBoxCore.get() + val moduleList = mInstalledList.filter { + it.isXpModule + }.map { + InstalledAppBean( + it.name, + it.icon, + it.packageName, + it.sourceDir, + blackBoxCore.isInstalledXposedModule(it.packageName) + ) + } + appsLiveData.postValue(moduleList) + loadingLiveData.postValue(false) + } + } + + fun getVmInstallList(userId: Int, appsLiveData: MutableLiveData>) { + val sortListData = + AppManager.mRemarkSharedPreferences.getString("AppList$userId", "") + val sortList = sortListData?.split(",") + + val applicationList = BlackBoxCore.get().getInstalledApplications(0, userId) + val appInfoList = mutableListOf() + applicationList.also { + if (sortList.isNullOrEmpty()) { + return@also + } + it.sortWith(AppsSortComparator(sortList)) + }.forEach { + val info = AppInfo( + it.loadLabel(getPackageManager()).toString(), + it.loadIcon(getPackageManager()), + it.packageName, + it.sourceDir, + isInstalledXpModule(it.packageName) + ) + + appInfoList.add(info) + } + + appsLiveData.postValue(appInfoList) + } + + private fun isInstalledXpModule(packageName: String): Boolean { + BlackBoxCore.get().installedXPModules.forEach { + if (packageName == it.packageName) { + return@isInstalledXpModule true + } + } + return false + } + + fun installApk(source: String, userId: Int, resultLiveData: MutableLiveData) { + 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() + } + + fun unInstall(packageName: String, userID: Int, resultLiveData: MutableLiveData) { + BlackBoxCore.get().uninstallPackageAsUser(packageName, userID) + updateAppSortList(userID, packageName, false) + scanUser() + resultLiveData.postValue(getString(R.string.uninstall_success)) + } + + fun launchApk(packageName: String, userId: Int, launchLiveData: MutableLiveData) { + val result = BlackBoxCore.get().launchApk(packageName, userId) + launchLiveData.postValue(result) + } + + fun clearApkData(packageName: String, userID: Int, resultLiveData: MutableLiveData) { + BlackBoxCore.get().clearPackage(packageName, userID) + resultLiveData.postValue(getString(R.string.clear_success)) + } + + /** + * 倒序递归扫描用户, + * 如果用户是空的,就删除用户,删除用户备注,删除应用排序列表 + */ + private fun scanUser() { + 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 { + remove("Remark$id") + remove("AppList$id") + } + scanUser() + } + } + + /** + * 更新排序列表 + * @param userID Int + * @param pkg String + * @param isAdd Boolean true是添加,false是移除 + */ + private fun updateAppSortList(userID: Int, pkg: String, isAdd: Boolean) { + val savedSortList = + AppManager.mRemarkSharedPreferences.getString("AppList$userID", "") + + val sortList = linkedSetOf() + if (savedSortList != null) { + sortList.addAll(savedSortList.split(",")) + } + + if (isAdd) { + sortList.add(pkg) + } else { + sortList.remove(pkg) + } + + AppManager.mRemarkSharedPreferences.edit { + putString("AppList$userID", sortList.joinToString(",")) + } + } + + /** + * 保存排序后的apk顺序 + */ + fun updateApkOrder(userID: Int, dataList: List) { + AppManager.mRemarkSharedPreferences.edit { + putString("AppList$userID", + dataList.joinToString(",") { it.packageName }) + } + } +} diff --git a/app/src/main/java/top/niunaijun/blackbox/data/AppsSortCompon.kt b/app/src/main/java/top/niunaijun/blackbox/data/AppsSortCompon.kt new file mode 100644 index 00000000..1508a1ee --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackbox/data/AppsSortCompon.kt @@ -0,0 +1,21 @@ +package top.niunaijun.blackbox.data + +import android.content.pm.ApplicationInfo + +/** + * + * @Description: app sort + * @Author: BlackBox + * @CreateDate: 2022/2/27 23:21 + */ +class AppsSortComparator(private val sortedList: List) : Comparator { + 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 + } +} diff --git a/app/src/main/java/top/niunaijun/blackbox/data/FakeLocationRepository.kt b/app/src/main/java/top/niunaijun/blackbox/data/FakeLocationRepository.kt new file mode 100644 index 00000000..bdced0bb --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackbox/data/FakeLocationRepository.kt @@ -0,0 +1,74 @@ +package top.niunaijun.blackbox.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.blackbox.bean.FakeLocationBean + +/** + * getInstalledApplications and query fake location of each of them. + * mode and location configuration are respectively concept. + * Location config just store the location but mode decides whether turns on it + * each application has three pattern.Global: use global fake location, + * self: use own config, close: use real config + * @Description: + * @Author: BlackBoxing + * @CreateDate: 2022/3/12 21:14 + */ +class FakeLocationRepository { + private 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> + ) { + val installedList = mutableListOf() + val installedApplications: List = + BlackBoxCore.get().getInstalledApplications(0, userID) + //List -> List + for (installedApplication in installedApplications) { + /*val file = File(installedApplication.sourceDir) + if ((installedApplication.flags and ApplicationInfo.FLAG_SYSTEM) != 0) { + continue + } + + if (!AbiUtils.isSupport(file)) { + continue + } + + val isXpModule = BlackBoxCore.get().isXposedModule(file)*/ + val info = FakeLocationBean( + userID, + 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) + } +} diff --git a/app/src/main/java/top/niunaijun/blackbox/data/GmsRepository.kt b/app/src/main/java/top/niunaijun/blackbox/data/GmsRepository.kt new file mode 100644 index 00000000..ebf03d7b --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackbox/data/GmsRepository.kt @@ -0,0 +1,65 @@ +package top.niunaijun.blackbox.data + +import androidx.lifecycle.MutableLiveData +import top.niunaijun.blackbox.BlackBoxCore +import top.niunaijun.blackbox.R +import top.niunaijun.blackbox.app.AppManager +import top.niunaijun.blackbox.bean.GmsBean +import top.niunaijun.blackbox.bean.GmsInstallBean +import top.niunaijun.blackbox.util.getString + +/** + * + * @Description: + * @Author: BlackBox + * @CreateDate: 2022/3/2 21:14 + */ +class GmsRepository { + fun getGmsInstalledList(mInstalledLiveData: MutableLiveData>) { + val userList = arrayListOf() + 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 + ) { + 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 + ) { + 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) + } +} diff --git a/app/src/main/java/top/niunaijun/blackbox/data/XpRepository.kt b/app/src/main/java/top/niunaijun/blackbox/data/XpRepository.kt new file mode 100644 index 00000000..74a010d5 --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackbox/data/XpRepository.kt @@ -0,0 +1,58 @@ +package top.niunaijun.blackbox.data + +import android.net.Uri +import android.webkit.URLUtil +import androidx.lifecycle.MutableLiveData +import top.niunaijun.blackbox.BlackBoxCore +import top.niunaijun.blackbox.BlackBoxCore.getPackageManager +import top.niunaijun.blackbox.R +import top.niunaijun.blackbox.bean.XpModuleInfo +import top.niunaijun.blackbox.util.getString + +/** + * + * @Description: + * @Author: wukaicheng + * @CreateDate: 2021/5/2 20:55 + */ +class XpRepository { + fun getInstallModules(modulesLiveData: MutableLiveData>) { + val moduleList = BlackBoxCore.get().installedXPModules + val result = mutableListOf() + moduleList.forEach { + val info = XpModuleInfo( + it.name, + it.desc, + it.packageName, + it.packageInfo.versionName, + it.enable, + it.application.loadIcon(getPackageManager()) + ) + result.add(info) + } + + modulesLiveData.postValue(result) + } + + fun installModule(source: String, resultLiveData: MutableLiveData) { + val blackBoxCore = BlackBoxCore.get() + val installResult = if (URLUtil.isValidUrl(source)) { + val uri = Uri.parse(source) + blackBoxCore.installXPModule(uri) + } else { + //source == packageName + blackBoxCore.installXPModule(source) + } + + if (installResult.success) { + resultLiveData.postValue(getString(R.string.install_success)) + } else { + resultLiveData.postValue(getString(R.string.install_fail, installResult.msg)) + } + } + + fun unInstallModule(packageName: String, resultLiveData: MutableLiveData) { + BlackBoxCore.get().uninstallXPModule(packageName) + resultLiveData.postValue(getString(R.string.remove_success)) + } +} diff --git a/app/src/main/java/top/niunaijun/blackbox/util/ContextUtil.kt b/app/src/main/java/top/niunaijun/blackbox/util/ContextUtil.kt new file mode 100644 index 00000000..4bc7299b --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackbox/util/ContextUtil.kt @@ -0,0 +1,22 @@ +package top.niunaijun.blackbox.util + +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.provider.Settings + +/** + * + * @Description: + * @Author: kotlinMiku + * @CreateDate: 2022/4/17 16:32 + */ +object ContextUtil { + fun Context.openAppSystemSettings() { + startActivity(Intent().apply { + action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + data = Uri.fromParts("package", packageName, null) + }) + } +} diff --git a/app/src/main/java/top/niunaijun/blackbox/util/InjectionUtil.kt b/app/src/main/java/top/niunaijun/blackbox/util/InjectionUtil.kt new file mode 100644 index 00000000..cc71d02a --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackbox/util/InjectionUtil.kt @@ -0,0 +1,44 @@ +package top.niunaijun.blackbox.util + +import top.niunaijun.blackbox.data.AppsRepository +import top.niunaijun.blackbox.data.FakeLocationRepository +import top.niunaijun.blackbox.data.GmsRepository +import top.niunaijun.blackbox.data.XpRepository +import top.niunaijun.blackbox.view.apps.AppsFactory +import top.niunaijun.blackbox.view.fake.FakeLocationFactory +import top.niunaijun.blackbox.view.gms.GmsFactory +import top.niunaijun.blackbox.view.list.ListFactory +import top.niunaijun.blackbox.view.xp.XpFactory + +/** + * + * @Description: + * @Author: wukaicheng + * @CreateDate: 2021/4/29 22:38 + */ +object InjectionUtil { + private val appsRepository = AppsRepository() + private val xpRepository = XpRepository() + private val gmsRepository = GmsRepository() + private val fakeLocationRepository = FakeLocationRepository() + + fun getAppsFactory() : AppsFactory { + return AppsFactory(appsRepository) + } + + fun getListFactory(): ListFactory { + return ListFactory(appsRepository) + } + + fun getXpFactory():XpFactory{ + return XpFactory(xpRepository) + } + + fun getGmsFactory():GmsFactory{ + return GmsFactory(gmsRepository) + } + + fun getFakeLocationFactory():FakeLocationFactory{ + return FakeLocationFactory(fakeLocationRepository) + } +} diff --git a/app/src/main/java/top/niunaijun/blackbox/util/MathUtil.java b/app/src/main/java/top/niunaijun/blackbox/util/MathUtil.java new file mode 100644 index 00000000..dbd3a589 --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackbox/util/MathUtil.java @@ -0,0 +1,77 @@ +package top.niunaijun.blackbox.util; + +import android.graphics.Point; +import android.graphics.PointF; + +public class MathUtil { + public MathUtil() { + } + + /** + * Get the distance between two points. + * + * @param A Point A + * @param B Point B + * @return the distance between point A and point B. + */ + public static int getDistance(PointF A, PointF B) { + return (int) Math.sqrt(Math.pow(A.x - B.x, 2) + Math.pow(A.y - B.y, 2)); + } + + /** + * Get the distance between two points. + */ + public static int getDistance(float x1, float y1, float x2, float y2) { + return (int) Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2)); + } + + /** + * Get the coordinates of a point on the line by cut length. + * + * @param A Point A + * @param B Point B + * @param cutLength cut length + * @return the point. + */ + public static Point getPointByCutLength(Point A, Point B, int cutLength) { + float radian = getRadian(A, B); + return new Point(A.x + (int) (cutLength * Math.cos(radian)), A.y + (int) (cutLength * Math.sin(radian))); + } + + /** + * Get the radian between current line(determined by point A and B) and horizontal line. + * + * @param A point A + * @param B point B + * @return the radian + */ + public static float getRadian(Point A, Point B) { + float lenA = B.x - A.x; + float lenB = B.y - A.y; + 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; + } + + + /** + * angle to radian + * + * @param angle angle + * @return radian + */ + public static double angle2Radian(double angle) { + return angle / 180 * Math.PI; + } + + /** + * radian to angle + * + * @param radian radian + * @return angle + */ + public static double radian2Angle(double radian) { + return radian / Math.PI * 180; + } +} diff --git a/app/src/main/java/top/niunaijun/blackbox/util/ResUtil.kt b/app/src/main/java/top/niunaijun/blackbox/util/ResUtil.kt new file mode 100644 index 00000000..be2cfec1 --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackbox/util/ResUtil.kt @@ -0,0 +1,11 @@ +package top.niunaijun.blackbox.util + +import androidx.annotation.StringRes +import top.niunaijun.blackbox.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) +} diff --git a/app/src/main/java/top/niunaijun/blackbox/util/Resolution.kt b/app/src/main/java/top/niunaijun/blackbox/util/Resolution.kt new file mode 100644 index 00000000..4a445472 --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackbox/util/Resolution.kt @@ -0,0 +1,291 @@ +package top.niunaijun.blackbox.util + +import android.annotation.SuppressLint +import android.app.Activity +import android.app.KeyguardManager +import android.content.Context +import android.graphics.Point +import android.os.Build +import android.os.Handler +import android.os.Looper +import android.util.DisplayMetrics +import android.view.Display +import android.view.View +import android.view.WindowManager +import android.view.inputmethod.InputMethodManager +import androidx.core.hardware.display.DisplayManagerCompat +import java.lang.Exception +import java.lang.reflect.Field + +object Resolution { + private const val TAG = "UtilsScreen" + + /** + * Gets the width of the display, in pixels. + * + * + * Note that this value should not be used for computing layouts, since a + * device will typically have screen decoration (such as a status bar) along + * the edges of the display that reduce the amount of application space + * available from the size returned here. Layouts should instead use the + * window size. + * + * + * The size is adjusted based on the current rotation of the display. + * + * + * The size returned by this method does not necessarily represent the + * actual raw size (native resolution) of the display. The returned size may + * be adjusted to exclude certain system decoration elements that are always + * visible. It may also be scaled to provide compatibility with older + * applications that were originally designed for smaller displays. + * + * @return Screen width in pixels. + */ + fun getScreenWidth(context: Context): Int { + return getScreenSize(context, null).x + } + + /** + * Gets the height of the display, in pixels. + * + * + * Note that this value should not be used for computing layouts, since a + * device will typically have screen decoration (such as a status bar) along + * the edges of the display that reduce the amount of application space + * available from the size returned here. Layouts should instead use the + * window size. + * + * + * The size is adjusted based on the current rotation of the display. + * + * + * The size returned by this method does not necessarily represent the + * actual raw size (native resolution) of the display. The returned size may + * be adjusted to exclude certain system decoration elements that are always + * visible. It may also be scaled to provide compatibility with older + * applications that were originally designed for smaller displays. + * + * @return Screen height in pixels. + */ + fun getScreenHeight(context: Context): Int { + return getScreenSize(context, null).y + } + + /** + * Gets the size of the display, in pixels. + * + * + * Note that this value should not be used for computing layouts, since a + * device will typically have screen decoration (such as a status bar) along + * the edges of the display that reduce the amount of application space + * available from the size returned here. Layouts should instead use the + * window size. + * + * + * The size is adjusted based on the current rotation of the display. + * + * + * The size returned by this method does not necessarily represent the + * actual raw size (native resolution) of the display. The returned size may + * be adjusted to exclude certain system decoration elements that are always + * visible. It may also be scaled to provide compatibility with older + * applications that were originally designed for smaller displays. + * + * @param outSize null-ok. If it is null, will create a Point instance inside, + * otherwise use it to fill the output. NOTE if it is not null, + * it will be the returned value. + * @return Screen size in pixels, the x is the width, the y is the height. + */ + @SuppressLint("NewApi") + fun getScreenSize(context: Context, outSize: Point?): Point { + val wm = context + .getSystemService(Context.WINDOW_SERVICE) as WindowManager + val ret = outSize ?: Point() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + val defaultDisplay = DisplayManagerCompat.getInstance(context).getDisplay(Display.DEFAULT_DISPLAY) + val displayContext = context.createDisplayContext(defaultDisplay!!) + ret.x = displayContext.resources.displayMetrics.widthPixels + ret.y = displayContext.resources.displayMetrics.heightPixels + } else { + val defaultDisplay = wm.defaultDisplay + val displayMetrics = DisplayMetrics() + defaultDisplay.getMetrics(displayMetrics) + ret.x = displayMetrics.widthPixels + ret.y = displayMetrics.heightPixels + } + return ret + } + + /** + * This method converts dp unit to equivalent pixels, depending on device density. + * + * @param dp A value in dp (density independent pixels) unit. Which we need to convert into pixels + * @param context Context to get resources and device specific display metrics + * @return A float value to represent px equivalent to dp depending on device density + */ + fun convertDpToPixel(dp: Float, context: Context): Float { + val resources = context.resources + val metrics = resources.displayMetrics + return dp * (metrics.densityDpi / 160f) + } + + /** + * This method converts device specific pixels to density independent pixels. + * + * @param px A value in px (pixels) unit. Which we need to convert into db + * @param context Context to get resources and device specific display metrics + * @return A float value to represent dp equivalent to px value + */ + fun convertPixelsToDp(px: Float, context: Context): Float { + val resources = context.resources + val metrics = resources.displayMetrics + return px / (metrics.densityDpi / 160f) + } + /////////////////////////////////////////////////////////////////////// + /** + * 获取屏幕密度 + */ + fun getDensity(context: Context?): Float { + var density = 0f + if (context == null) { + return density + } + try { + density = context.resources.displayMetrics.density + } catch (ignored: Exception) { + } + return density + } + + /** + * 检查分辨率是否为本机 + */ + fun checkPix(context: Activity, width: Int, height: Int): Boolean { + return if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN) { + val metrics = DisplayMetrics() + context.windowManager.defaultDisplay.getRealMetrics(metrics) + metrics.widthPixels == width && metrics.heightPixels == height + } else { + getScreenPixWidth(context) == width && getScreenPixHeight( + context + ) == height + } + } + + /** + * 获取屏幕分辨率:宽 + */ + private fun getScreenPixWidth(context: Context): Int { + return context.resources.displayMetrics.widthPixels + } + + /** + * 获取屏幕分辨率:高 + */ + private fun getScreenPixHeight(context: Context): Int { + return context.resources.displayMetrics.heightPixels + } + + /** + * dipתpx + */ + fun dipToPx(context: Context, dip: Int): Int { + return (dip * context.resources.displayMetrics.density + 0.5f).toInt() + } + + /** + * pxתdip + */ + fun pxToDip(context: Context, pxValue: Float): Int { + val scale = context.resources.displayMetrics.density + return (pxValue / scale + 0.5f).toInt() + } + + /** + * 将sp值转换为px值,保证文字大小不变 + * + * @param context + * @param spValue + * @return + */ + fun sp2px(context: Context, spValue: Float): Int { + val fontScale = context.resources.displayMetrics.scaledDensity + return (spValue * fontScale + 0.5f).toInt() + } + + /** + * 隐藏软键盘 + */ + fun hideInputMethod(view: View) { + val imm = view.context + .getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + imm.hideSoftInputFromWindow(view.windowToken, 0) + } + + /** + * 显示软键盘 + */ + private fun showInputMethod(view: View) { + val imm = view.context + .getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT) + } + + /** + * 多少时间后显示软键盘 + */ + fun showInputMethod(view: View, delayMillis: Long) { + // 显示输入法 + Handler(Looper.getMainLooper()!!).postDelayed({ showInputMethod(view) }, delayMillis) + } + + /** + * 判断手机是否在锁屏状态 true锁屏 false未锁屏 + */ + fun isScreenLocked(c: Context): Boolean { + val mKeyguardManager = c + .getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + !mKeyguardManager.isKeyguardLocked + } else { + !mKeyguardManager.inKeyguardRestrictedInputMode() + } + } + + fun getBarHeight(context: Context): Int { + val c: Class<*>? + val obj: Any + val field: Field? + val x: Int + var sbar = 38 //默认为38,貌似大部分是这样的 + try { + c = Class.forName("com.android.internal.R\$dimen") + obj = c.newInstance() + field = c.getField("status_bar_height") + x = field?.get(obj).toString().toInt() + sbar = context.resources.getDimensionPixelSize(x) + } catch (e1: Exception) { + e1.printStackTrace() + } + return sbar + } + + //http://stackoverflow.com/questions/20264268/how-to-get-height-and-width-of-navigation-bar-programmatically + //获取屏幕下方导航栏高度 + fun getNavigationBarSize(context: Context): Point { + val appUsableSize = getScreenSize(context, null) + + // navigation bar on the right + /*if (appUsableSize.x < realScreenSize.x) { + return new Point(realScreenSize.x - appUsableSize.x, appUsableSize.y); + }*/ + + // navigation bar at the bottom + return if (appUsableSize.y < appUsableSize.y) { + Point(appUsableSize.x, appUsableSize.y - appUsableSize.y) + } else Point() + + // navigation bar is not present + } +} diff --git a/app/src/main/java/top/niunaijun/blackbox/util/ShortcutUtil.kt b/app/src/main/java/top/niunaijun/blackbox/util/ShortcutUtil.kt new file mode 100644 index 00000000..413f55f1 --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackbox/util/ShortcutUtil.kt @@ -0,0 +1,80 @@ +package top.niunaijun.blackbox.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.blackbox.R +import top.niunaijun.blackbox.app.App +import top.niunaijun.blackbox.app.AppManager +import top.niunaijun.blackbox.bean.AppInfo +import top.niunaijun.blackbox.util.ContextUtil.openAppSystemSettings +import top.niunaijun.blackbox.view.main.ShortcutActivity + +/** + * + * @Description: 桌面快捷方式 工具类 + * @Author: BlackBox + * @CreateDate: 2022/2/27 22:56 + */ +object ShortcutUtil { + /** + * 创建桌面快捷方式 + * @param userID Int userID + * @param info AppInfo + */ + fun createShortcut(context: Context,userID: Int, info: AppInfo) { + if (ShortcutManagerCompat.isRequestPinShortcutSupported(context)) { + 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) + } + } + } +} diff --git a/app/src/main/java/top/niunaijun/blackbox/util/ToastEx.kt b/app/src/main/java/top/niunaijun/blackbox/util/ToastEx.kt new file mode 100644 index 00000000..1869c9dd --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackbox/util/ToastEx.kt @@ -0,0 +1,28 @@ +package top.niunaijun.blackbox.util + +import android.content.Context +import android.widget.Toast +import androidx.annotation.StringRes +import top.niunaijun.blackbox.app.App + +/** + * + * @Description: + * @Author: wukaicheng + * @CreateDate: 2021/5/2 0:13 + */ +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)) +} diff --git a/app/src/main/java/top/niunaijun/blackbox/util/ViewBindingEx.kt b/app/src/main/java/top/niunaijun/blackbox/util/ViewBindingEx.kt new file mode 100644 index 00000000..5bcd17ed --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackbox/util/ViewBindingEx.kt @@ -0,0 +1,31 @@ +package top.niunaijun.blackbox.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 + +/** + * + * @Description: viewBinding 扩展类 + * @Author: wukaicheng + * @CreateDate: 2021/4/29 21:23 + */ +inline fun Activity.inflate(): Lazy = lazy { + inflateBinding(layoutInflater) +} + +inline fun Fragment.inflate(): Lazy = lazy { + inflateBinding(layoutInflater) +} + +inline fun Dialog.inflate(): Lazy = lazy { + inflateBinding(layoutInflater) +} + +inline fun inflateBinding(layoutInflater: LayoutInflater): T { + val method = T::class.java.getMethod("inflate", LayoutInflater::class.java) + return method.invoke(null, layoutInflater) as T +} diff --git a/app/src/main/java/top/niunaijun/blackbox/view/apps/AppsAdapter.kt b/app/src/main/java/top/niunaijun/blackbox/view/apps/AppsAdapter.kt new file mode 100644 index 00000000..f318409e --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackbox/view/apps/AppsAdapter.kt @@ -0,0 +1,35 @@ +package top.niunaijun.blackbox.view.apps + +import android.view.View +import android.view.ViewGroup +import cbfg.rvadapter.RVHolder +import cbfg.rvadapter.RVHolderFactory +import top.niunaijun.blackbox.R +import top.niunaijun.blackbox.bean.AppInfo +import top.niunaijun.blackbox.databinding.ItemAppBinding + +/** + * + * @Description: 软件显示界面适配器 + * @Author: wukaicheng + * @CreateDate: 2021/4/29 21:52 + */ +class AppsAdapter : RVHolderFactory() { + override fun createViewHolder(parent: ViewGroup?, viewType: Int, item: Any): RVHolder { + return AppsVH(inflate(R.layout.item_app,parent)) + } + + class AppsVH(itemView:View):RVHolder(itemView) { + private val binding = ItemAppBinding.bind(itemView) + + override fun setContent(item: AppInfo, isSelected: Boolean, payload: Any?) { + binding.icon.setImageDrawable(item.icon) + binding.name.text = item.name + if (item.isXpModule) { + binding.cornerLabel.visibility = View.VISIBLE + } else { + binding.cornerLabel.visibility = View.INVISIBLE + } + } + } +} diff --git a/app/src/main/java/top/niunaijun/blackbox/view/apps/AppsFactory.kt b/app/src/main/java/top/niunaijun/blackbox/view/apps/AppsFactory.kt new file mode 100644 index 00000000..8485205d --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackbox/view/apps/AppsFactory.kt @@ -0,0 +1,18 @@ +package top.niunaijun.blackbox.view.apps + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import top.niunaijun.blackbox.data.AppsRepository + +/** + * + * @Description: + * @Author: wukaicheng + * @CreateDate: 2021/4/29 22:36 + */ +@Suppress("UNCHECKED_CAST") +class AppsFactory(private val appsRepository: AppsRepository) : ViewModelProvider.NewInstanceFactory() { + override fun create(modelClass: Class): T { + return AppsViewModel(appsRepository) as T + } +} diff --git a/app/src/main/java/top/niunaijun/blackbox/view/apps/AppsFragment.kt b/app/src/main/java/top/niunaijun/blackbox/view/apps/AppsFragment.kt new file mode 100644 index 00000000..152e85c6 --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackbox/view/apps/AppsFragment.kt @@ -0,0 +1,341 @@ +package top.niunaijun.blackbox.view.apps + +import android.os.Bundle +import android.text.TextUtils +import android.view.LayoutInflater +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 cbfg.rvadapter.RVAdapter +import com.afollestad.materialdialogs.MaterialDialog +import top.niunaijun.blackbox.BlackBoxCore +import top.niunaijun.blackbox.R +import top.niunaijun.blackbox.bean.AppInfo +import top.niunaijun.blackbox.databinding.FragmentAppsBinding +import top.niunaijun.blackbox.util.InjectionUtil +import top.niunaijun.blackbox.util.ShortcutUtil +import top.niunaijun.blackbox.util.inflate +import top.niunaijun.blackbox.util.toast +import top.niunaijun.blackbox.view.base.LoadingActivity +import top.niunaijun.blackbox.view.main.MainActivity +import java.util.* +import kotlin.math.abs +import android.view.MotionEvent +import android.graphics.Point + +/** + * + * @Description: + * @Author: wukaicheng + * @CreateDate: 2021/4/29 22:21 + */ +class AppsFragment : Fragment() { + var userID: Int = 0 + private lateinit var viewModel: AppsViewModel + private lateinit var mAdapter: RVAdapter + private val viewBinding: FragmentAppsBinding by inflate() + private var popupMenu: PopupMenu? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + viewModel = + ViewModelProvider(this, InjectionUtil.getAppsFactory()).get(AppsViewModel::class.java) + userID = requireArguments().getInt("userID", 0) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + viewBinding.stateView.showEmpty() + + mAdapter = + RVAdapter(requireContext(), AppsAdapter()).bind(viewBinding.recyclerView) + + viewBinding.recyclerView.adapter = mAdapter + viewBinding.recyclerView.layoutManager = GridLayoutManager(requireContext(), 4) + + val touchCallBack = AppsTouchCallBack { from, to -> + onItemMove(from, to) + viewModel.updateSortLiveData.postValue(true) + } + + val itemTouchHelper = ItemTouchHelper(touchCallBack) + itemTouchHelper.attachToRecyclerView(viewBinding.recyclerView) + mAdapter.setItemClickListener { _, data, _ -> + showLoading() + viewModel.launchApk(data.packageName, userID) + } + + interceptTouch() + setOnLongClick() + return viewBinding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + initData() + } + + override fun onStart() { + super.onStart() + viewModel.getInstalledApps(userID) + } + + /** + * 拖拽优化 + */ + private fun interceptTouch() { + val point = Point() + viewBinding.recyclerView.setOnTouchListener { _, e -> + when (e.action) { + MotionEvent.ACTION_UP -> { + if (!isMove(point, e)) { + popupMenu?.show() + } + popupMenu = null + point.set(0, 0) + } + + MotionEvent.ACTION_MOVE -> { + if (point.x == 0 && point.y == 0) { + point.x = e.rawX.toInt() + point.y = e.rawY.toInt() + } + isDownAndUp(point, e) + + if (isMove(point, e)) { + popupMenu?.dismiss() + } + } + } + return@setOnTouchListener false + } + } + + private fun isMove(point: Point, e: MotionEvent): Boolean { + val max = 40 + val x = point.x + val y = point.y + + val xU = abs(x - e.rawX) + val yU = abs(y - e.rawY) + return xU > max || yU > max + } + + private fun isDownAndUp(point: Point, e: MotionEvent) { + val min = 10 + val y = point.y + val yU = y - e.rawY + + if (abs(yU) > min) { + (requireActivity() as MainActivity).showFloatButton(yU < 0) + } + } + + private fun onItemMove(fromPosition:Int, toPosition:Int) { + if (fromPosition < toPosition) { + for (i in fromPosition until toPosition) { + Collections.swap(mAdapter.getItems(), i, i + 1) + } + } else { + for (i in fromPosition downTo toPosition + 1) { + Collections.swap(mAdapter.getItems(), i, i - 1) + } + } + mAdapter.notifyItemMoved(fromPosition, toPosition) + } + + private fun setOnLongClick() { + mAdapter.setItemLongClickListener { view, data, _ -> + popupMenu = PopupMenu(requireContext(), view).also { +// mAdapter.setItemClickListener { view, data, _ -> +// PopupMenu(requireContext(),view).also { + it.inflate(R.menu.app_menu) + it.setOnMenuItemClickListener { item -> + when (item.itemId) { + R.id.app_remove -> { + if (data.isXpModule) { + toast(R.string.uninstall_module_toast) + } else { + unInstallApk(data) + } + } + + R.id.app_open -> { + showLoading() + viewModel.launchApk(data.packageName, userID) + } + + R.id.app_clear -> { + clearApk(data) + } + + R.id.app_stop -> { + stopApk(data) + } + + R.id.app_shortcut -> { + ShortcutUtil.createShortcut(requireContext(), userID, data) + } + } + return@setOnMenuItemClickListener true + } + it.show() + } + } + } + + /*interceptTouch() + setOnLongClick() + return viewBinding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + initData() + } + + override fun onStart() { + super.onStart() + viewModel.getInstalledApps(userID) + } + + private fun onItemMove(fromPosition:Int, toPosition:Int) { + if (fromPosition < toPosition) { + for (i in fromPosition until toPosition) { + Collections.swap(mAdapter.getItems(), i, i + 1) + } + } else { + for (i in fromPosition downTo toPosition + 1) { + Collections.swap(mAdapter.getItems(), i, i - 1) + } + } + mAdapter.notifyItemMoved(fromPosition, toPosition) + }*/ + + private fun initData() { + viewBinding.stateView.showLoading() + viewModel.getInstalledApps(userID) + viewModel.appsLiveData.observe(viewLifecycleOwner) { + if (it != null) { + mAdapter.setItems(it) + if (it.isEmpty()) { + viewBinding.stateView.showEmpty() + } else { + viewBinding.stateView.showContent() + } + } + } + + viewModel.resultLiveData.observe(viewLifecycleOwner) { + if (!TextUtils.isEmpty(it)) { + hideLoading() + requireContext().toast(it) + viewModel.getInstalledApps(userID) + scanUser() + } + } + + viewModel.launchLiveData.observe(viewLifecycleOwner) { + it?.run { + hideLoading() + if (!it) { + toast(R.string.start_fail) + } + } + } + + viewModel.updateSortLiveData.observe(viewLifecycleOwner) { + if (this::mAdapter.isInitialized) { + viewModel.updateApkOrder(userID, mAdapter.getItems()) + } + } + } + + override fun onStop() { + super.onStop() + viewModel.resultLiveData.value = null + viewModel.launchLiveData.value = null + } + + private fun unInstallApk(info: AppInfo) { + MaterialDialog(requireContext()).show { + title(R.string.uninstall_app) + message(text = getString(R.string.uninstall_app_hint, info.name)) + positiveButton(R.string.done) { + showLoading() + viewModel.unInstall(info.packageName, userID) + } + negativeButton(R.string.cancel) + } + } + + /** + * 强行停止软件 + * @param info AppInfo + */ + private fun stopApk(info: AppInfo) { + MaterialDialog(requireContext()).show { + title(R.string.app_stop) + message(text = getString(R.string.app_stop_hint,info.name)) + positiveButton(R.string.done) { + BlackBoxCore.get().stopPackage(info.packageName, userID) + toast(getString(R.string.is_stop,info.name)) + } + negativeButton(R.string.cancel) + } + } + + /** + * 清除软件数据 + * @param info AppInfo + */ + private fun clearApk(info: AppInfo) { + MaterialDialog(requireContext()).show { + title(R.string.app_clear) + message(text = getString(R.string.app_clear_hint,info.name)) + positiveButton(R.string.done) { + showLoading() + viewModel.clearApkData(info.packageName, userID) + } + negativeButton(R.string.cancel) + } + } + + fun installApk(source: String) { + showLoading() + viewModel.install(source, userID) + } + + private fun scanUser() { + (requireActivity() as MainActivity).scanUser() + } + + private fun showLoading() { + if (requireActivity() is LoadingActivity) { + (requireActivity() as LoadingActivity).showLoading() + } + } + + private fun hideLoading() { + if (requireActivity() is LoadingActivity) { + (requireActivity() as LoadingActivity).hideLoading() + } + } + + companion object { + fun newInstance(userID:Int): AppsFragment { + val fragment = AppsFragment() + val bundle = bundleOf("userID" to userID) + fragment.arguments = bundle + return fragment + } + } +} diff --git a/app/src/main/java/top/niunaijun/blackbox/view/apps/AppsTouchCallBack.kt b/app/src/main/java/top/niunaijun/blackbox/view/apps/AppsTouchCallBack.kt new file mode 100644 index 00000000..3b1987ac --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackbox/view/apps/AppsTouchCallBack.kt @@ -0,0 +1,28 @@ +package top.niunaijun.blackbox.view.apps + +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.RecyclerView + +class AppsTouchCallBack(private val onMoveBlock: (from: Int, to: Int) -> Unit) : + ItemTouchHelper.Callback() { + + override fun getMovementFlags( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder + ): Int { + return makeMovementFlags(ItemTouchHelper.UP or ItemTouchHelper.DOWN or ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT, 0) + } + + override fun onMove( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + target: RecyclerView.ViewHolder + ): Boolean { + val fromPosition = viewHolder.bindingAdapterPosition + val toPosition = target.bindingAdapterPosition + onMoveBlock(fromPosition, toPosition) + return true + } + + override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { } +} diff --git a/app/src/main/java/top/niunaijun/blackbox/view/apps/AppsViewModel.kt b/app/src/main/java/top/niunaijun/blackbox/view/apps/AppsViewModel.kt new file mode 100644 index 00000000..b1fc585f --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackbox/view/apps/AppsViewModel.kt @@ -0,0 +1,56 @@ +package top.niunaijun.blackbox.view.apps + +import androidx.lifecycle.MutableLiveData +import top.niunaijun.blackbox.bean.AppInfo +import top.niunaijun.blackbox.data.AppsRepository +import top.niunaijun.blackbox.view.base.BaseViewModel + +/** + * + * @Description: + * @Author: wukaicheng + * @CreateDate: 2021/4/29 22:36 + */ +class AppsViewModel(private val repo: AppsRepository) : BaseViewModel() { + val appsLiveData = MutableLiveData>() + val resultLiveData = MutableLiveData() + val launchLiveData = MutableLiveData() + //利用LiveData只更新最后一次的特性,用来保存app顺序 + val updateSortLiveData = MutableLiveData() + + fun getInstalledApps(userId: Int) { + launchOnUI { + repo.getVmInstallList(userId, appsLiveData) + } + } + + 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) { + launchOnUI { + repo.updateApkOrder(userID,dataList) + } + } +} diff --git a/app/src/main/java/top/niunaijun/blackbox/view/base/BaseActivity.kt b/app/src/main/java/top/niunaijun/blackbox/view/base/BaseActivity.kt new file mode 100644 index 00000000..a2407ba3 --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackbox/view/base/BaseActivity.kt @@ -0,0 +1,32 @@ +package top.niunaijun.blackbox.view.base + +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.widget.Toolbar + +/** + * + * @Description:BaseActivity + * @Author: wukaicheng + * @CreateDate: 2021/5/4 15:58 + */ +open class BaseActivity : AppCompatActivity() { + protected fun initToolbar(toolbar: Toolbar,title:Int, showBack: Boolean = false, onBack: (() -> Unit)? = null) { + 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) + } +} diff --git a/app/src/main/java/top/niunaijun/blackbox/view/base/BaseViewModel.kt b/app/src/main/java/top/niunaijun/blackbox/view/base/BaseViewModel.kt new file mode 100644 index 00000000..d0fd2d8f --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackbox/view/base/BaseViewModel.kt @@ -0,0 +1,30 @@ +package top.niunaijun.blackbox.view.base + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.* + +/** + * + * @Description: + * @Author: wukaicheng + * @CreateDate: 2021/4/29 23:33 + */ +open class BaseViewModel : ViewModel() { + fun launchOnUI(block: suspend CoroutineScope.() -> Unit) { + viewModelScope.launch { + withContext(Dispatchers.IO) { + try { + block() + } catch (e: Throwable) { + e.printStackTrace() + } + } + } + } + + override fun onCleared() { + super.onCleared() + viewModelScope.cancel() + } +} diff --git a/app/src/main/java/top/niunaijun/blackbox/view/base/LoadingActivity.kt b/app/src/main/java/top/niunaijun/blackbox/view/base/LoadingActivity.kt new file mode 100644 index 00000000..4f9ab258 --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackbox/view/base/LoadingActivity.kt @@ -0,0 +1,40 @@ +package top.niunaijun.blackbox.view.base + +import android.view.KeyEvent +import com.roger.catloadinglibrary.CatLoadingView +import top.niunaijun.blackbox.R + +/** + * + * @Description: loading activity + * @Author: BlackBox + * @CreateDate: 2022/3/2 21:49 + */ +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() + } + } +} diff --git a/app/src/main/java/top/niunaijun/blackbox/view/fake/FakeLocationAdapter.kt b/app/src/main/java/top/niunaijun/blackbox/view/fake/FakeLocationAdapter.kt new file mode 100644 index 00000000..aa2709ec --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackbox/view/fake/FakeLocationAdapter.kt @@ -0,0 +1,38 @@ +package top.niunaijun.blackbox.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.blackbox.R +import top.niunaijun.blackbox.bean.FakeLocationBean +import top.niunaijun.blackbox.databinding.ItemFakeBinding +import top.niunaijun.blackbox.util.getString + +/** + * + * @Description: 软件显示界面适配器 + * @Author: BlackBoxing + * @CreateDate: 2022/3/14 + */ +class FakeLocationAdapter : RVHolderFactory() { + override fun createViewHolder(parent: ViewGroup?, viewType: Int, item: Any): RVHolder { + return FakeLocationVH(inflate(R.layout.item_fake,parent)) + } + + class FakeLocationVH(itemView:View):RVHolder(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 + } + } +} diff --git a/app/src/main/java/top/niunaijun/blackbox/view/fake/FakeLocationFactory.kt b/app/src/main/java/top/niunaijun/blackbox/view/fake/FakeLocationFactory.kt new file mode 100644 index 00000000..7329a080 --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackbox/view/fake/FakeLocationFactory.kt @@ -0,0 +1,19 @@ +package top.niunaijun.blackbox.view.fake + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import top.niunaijun.blackbox.data.FakeLocationRepository + +/** + * + * @Author: BlackBoxing + * @CreateDate: 2022/3/14 + */ +class FakeLocationFactory(private val repo: FakeLocationRepository) : + ViewModelProvider.NewInstanceFactory() { + + @Suppress("UNCHECKED_CAST") + override fun create(modelClass: Class): T { + return FakeLocationViewModel(repo) as T + } +} diff --git a/app/src/main/java/top/niunaijun/blackbox/view/fake/FakeLocationViewModel.kt b/app/src/main/java/top/niunaijun/blackbox/view/fake/FakeLocationViewModel.kt new file mode 100644 index 00000000..6f9f151a --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackbox/view/fake/FakeLocationViewModel.kt @@ -0,0 +1,34 @@ +package top.niunaijun.blackbox.view.fake + +import androidx.lifecycle.MutableLiveData +import top.niunaijun.blackbox.entity.location.BLocation +import top.niunaijun.blackbox.bean.FakeLocationBean +import top.niunaijun.blackbox.data.FakeLocationRepository +import top.niunaijun.blackbox.view.base.BaseViewModel + +/** + * + * @Author: BlackBoxing + * @CreateDate: 2022/3/14 + */ +class FakeLocationViewModel(private val mRepo: FakeLocationRepository) : BaseViewModel() { + val appsLiveData = MutableLiveData>() + + 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) + } + } +} diff --git a/app/src/main/java/top/niunaijun/blackbox/view/fake/FakeManagerActivity.kt b/app/src/main/java/top/niunaijun/blackbox/view/fake/FakeManagerActivity.kt new file mode 100644 index 00000000..8b03dc8a --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackbox/view/fake/FakeManagerActivity.kt @@ -0,0 +1,168 @@ +package top.niunaijun.blackbox.view.fake + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.view.Menu +import androidx.activity.OnBackPressedCallback +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.R +import top.niunaijun.blackbox.bean.FakeLocationBean +import top.niunaijun.blackbox.databinding.ActivityListBinding +import top.niunaijun.blackbox.entity.location.BLocation +import top.niunaijun.blackbox.fake.frameworks.BLocationManager +import top.niunaijun.blackbox.util.InjectionUtil +import top.niunaijun.blackbox.util.inflate +import top.niunaijun.blackbox.util.toast +import top.niunaijun.blackbox.view.base.BaseActivity + +/** + * + * @Author: BlackBoxing + * @CreateDate: 2022/3/14 + */ +class FakeManagerActivity : BaseActivity() { + val TAG: String = "FakeManagerActivity" + private val viewBinding: ActivityListBinding by inflate() + //private lateinit var mAdapter: ListAdapter + private lateinit var mAdapter: RVAdapter + private lateinit var viewModel: FakeLocationViewModel + private var appList: List = ArrayList() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(viewBinding.root) + + initToolbar(viewBinding.toolbarLayout.toolbar, R.string.fake_location, true) + + mAdapter = RVAdapter(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() + onBackPressedDispatcher + .addCallback(onBackPressedCallback) + } + + 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, BLocationManager.OWN_MODE) + viewModel.setLocation(currentUserID(), pkg, BLocation(latitude, longitude)) + + toast(getString(R.string.set_location,latitude.toString(), longitude.toString())) + + loadAppList() + } + } + } + + private val onBackPressedCallback = + object: OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + if (viewBinding.searchView.isSearchOpen) { + viewBinding.searchView.closeSearch() + } + finish() + } + } + + private fun filterApp(newText: String) { + val newList = this.appList.filter { + it.name.contains(newText, true) or it.packageName.contains(newText, true) + } + mAdapter.setItems(newList) + } + + 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) + } + } +} diff --git a/app/src/main/java/top/niunaijun/blackbox/view/fake/FollowMyLocationOverlay.kt b/app/src/main/java/top/niunaijun/blackbox/view/fake/FollowMyLocationOverlay.kt new file mode 100644 index 00000000..188c82cd --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackbox/view/fake/FollowMyLocationOverlay.kt @@ -0,0 +1,145 @@ +package top.niunaijun.blackbox.view.fake + +import android.app.Activity +import android.os.Build +import android.os.Bundle +import android.view.inputmethod.InputMethodManager +import androidx.activity.OnBackPressedCallback +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.blackbox.databinding.ActivityOsmdroidBinding +import top.niunaijun.blackbox.util.inflate +import top.niunaijun.blackbox.util.toast + +/** + * + * @Author: BlackBoxing + * @CreateDate: 2022/3/14 + */ +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) + //handle permissions first, before map is created. not depicted here + + //load/initialize the osmdroid configuration, this can be done + //This won't work unless you have imported this: org.osmdroid.config.Configuration.* + Configuration.getInstance().load(this, PreferenceManager.getDefaultSharedPreferences(this)) + //setting this before the layout is inflated is a good idea + //it 'should' ensure that the map has a writable location for the map cache, even without permissions + //if no tiles are displayed, you can try overriding the cache path using Configuration.getInstance().setCachePath + //see also StorageUtils + //note, the load method also sets the HTTP User Agent to your application's package name, if you abuse osm's + //tile servers will get you banned based on this string. + + //inflate and create the map + setContentView(binding.root) + + val location: BLocation? = if (Build.VERSION.SDK_INT >= 33) { + intent.getParcelableExtra("location", BLocation::class.java) + } else { + 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) + //val startPoint = GeoPoint(30.2736, 120.1563) + mapController.setCenter(startPoint) + binding.map.setTileSource(TileSourceFactory.MAPNIK) + onBackPressedDispatcher + .addCallback(onBackPressedCallback) + } + + private val onBackPressedCallback = + object: OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + finishWithResult(startPoint) + } + } + + override fun onResume() { + super.onResume() + //this will refresh the osmdroid configuration on resuming. + //if you make changes to the configuration, use + //SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this) + //Configuration.getInstance().load(this, PreferenceManager.getDefaultSharedPreferences(this)) + binding.map.onResume() //needed for compass, my location overlays, v6.0.0 and up + } + + override fun onPause() { + super.onPause() + //this will refresh the osmdroid configuration on resuming. + //if you make changes to the configuration, use + //SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this) + //Configuration.getInstance().save(this, prefs) + binding.map.onPause() //needed for compass, my location overlays, v6.0.0 and up + } + + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { + val permissionsToRequest = ArrayList() + 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() + } +} diff --git a/app/src/main/java/top/niunaijun/blackbox/view/gms/GmsAdapter.kt b/app/src/main/java/top/niunaijun/blackbox/view/gms/GmsAdapter.kt new file mode 100644 index 00000000..d93f03a9 --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackbox/view/gms/GmsAdapter.kt @@ -0,0 +1,35 @@ +package top.niunaijun.blackbox.view.gms + +import android.view.View +import android.view.ViewGroup +import cbfg.rvadapter.RVHolder +import cbfg.rvadapter.RVHolderFactory +import top.niunaijun.blackbox.R +import top.niunaijun.blackbox.bean.GmsBean +import top.niunaijun.blackbox.databinding.ItemGmsBinding + +/** + * + * @Description: + * @Author: BlackBox + * @CreateDate: 2022/3/2 21:13 + */ +class GmsAdapter : RVHolderFactory() { + override fun createViewHolder(parent: ViewGroup?, viewType: Int, item: Any): RVHolder { + return GmsVH(inflate(R.layout.item_gms,parent)) + } + + class GmsVH(itemView:View):RVHolder(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() + } + } + } + } +} diff --git a/app/src/main/java/top/niunaijun/blackbox/view/gms/GmsFactory.kt b/app/src/main/java/top/niunaijun/blackbox/view/gms/GmsFactory.kt new file mode 100644 index 00000000..2d0d1ed4 --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackbox/view/gms/GmsFactory.kt @@ -0,0 +1,18 @@ +package top.niunaijun.blackbox.view.gms + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import top.niunaijun.blackbox.data.GmsRepository + +/** + * + * @Description: + * @Author: BlackBox + * @CreateDate: 2022/3/2 21:15 + */ +class GmsFactory(private val repo:GmsRepository): ViewModelProvider.NewInstanceFactory() { + @Suppress("UNCHECKED_CAST") + override fun create(modelClass: Class): T { + return GmsViewModel(repo) as T + } +} diff --git a/app/src/main/java/top/niunaijun/blackbox/view/gms/GmsManagerActivity.kt b/app/src/main/java/top/niunaijun/blackbox/view/gms/GmsManagerActivity.kt new file mode 100644 index 00000000..ef1b169f --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackbox/view/gms/GmsManagerActivity.kt @@ -0,0 +1,127 @@ +package top.niunaijun.blackbox.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.blackbox.R +import top.niunaijun.blackbox.bean.GmsBean +import top.niunaijun.blackbox.databinding.ActivityGmsBinding +import top.niunaijun.blackbox.util.InjectionUtil +import top.niunaijun.blackbox.util.inflate +import top.niunaijun.blackbox.util.toast +import top.niunaijun.blackbox.view.base.LoadingActivity + +/** + * + * @Description: gms manager activity + * @Author: BlackBox + * @CreateDate: 2022/3/2 21:06 + */ +class GmsManagerActivity : LoadingActivity() { + private lateinit var viewModel: GmsViewModel + private lateinit var mAdapter: RVAdapter + 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(this, GmsAdapter()).bind(viewBinding.recyclerView) + .setItemClickListener { view, item, _ -> + val checkbox = view.findViewById(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) + } + } +} diff --git a/app/src/main/java/top/niunaijun/blackbox/view/gms/GmsViewModel.kt b/app/src/main/java/top/niunaijun/blackbox/view/gms/GmsViewModel.kt new file mode 100644 index 00000000..28205318 --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackbox/view/gms/GmsViewModel.kt @@ -0,0 +1,36 @@ +package top.niunaijun.blackbox.view.gms + +import androidx.lifecycle.MutableLiveData +import top.niunaijun.blackbox.bean.GmsBean +import top.niunaijun.blackbox.bean.GmsInstallBean +import top.niunaijun.blackbox.data.GmsRepository +import top.niunaijun.blackbox.view.base.BaseViewModel + +/** + * + * @Description: gms viewModel + * @Author: BlackBox + * @CreateDate: 2022/3/2 21:11 + */ +class GmsViewModel(private val mRepo: GmsRepository) : BaseViewModel() { + val mInstalledLiveData = MutableLiveData>() + val mUpdateInstalledLiveData = MutableLiveData() + + fun getInstalledUser() { + launchOnUI { + mRepo.getGmsInstalledList(mInstalledLiveData) + } + } + + fun installGms(userID: Int) { + launchOnUI { + mRepo.installGms(userID,mUpdateInstalledLiveData) + } + } + + fun uninstallGms(userID: Int) { + launchOnUI { + mRepo.uninstallGms(userID,mUpdateInstalledLiveData) + } + } +} diff --git a/app/src/main/java/top/niunaijun/blackbox/view/list/ListActivity.kt b/app/src/main/java/top/niunaijun/blackbox/view/list/ListActivity.kt new file mode 100644 index 00000000..5d326b25 --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackbox/view/list/ListActivity.kt @@ -0,0 +1,163 @@ +package top.niunaijun.blackbox.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.OnBackPressedCallback +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.blackbox.R +import top.niunaijun.blackbox.bean.InstalledAppBean +import top.niunaijun.blackbox.databinding.ActivityListBinding +import top.niunaijun.blackbox.util.InjectionUtil +import top.niunaijun.blackbox.util.inflate +import top.niunaijun.blackbox.view.base.BaseActivity + +class ListActivity : BaseActivity() { + private val viewBinding: ActivityListBinding by inflate() + private lateinit var mAdapter: RVAdapter + private lateinit var viewModel: ListViewModel + private var appList: List = ArrayList() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(viewBinding.root) + + initToolbar(viewBinding.toolbarLayout.toolbar, R.string.installed_app, true) + + mAdapter = RVAdapter(this,ListAdapter()).bind(viewBinding.recyclerView).setItemClickListener { _, item, _ -> + finishWithResult(item.packageName) + } + + viewBinding.recyclerView.layoutManager = LinearLayoutManager(this) + + initSearchView() + initViewModel() + onBackPressedDispatcher + .addCallback(onBackPressedCallback) + } + + 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 onlyShowXp = intent.getBooleanExtra("onlyShowXp", false) + val userID = intent.getIntExtra("userID",0) + + if (onlyShowXp) { + viewModel.getInstalledModules() + viewBinding.toolbarLayout.toolbar.setTitle(R.string.installed_module) + } else { + 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 val onBackPressedCallback = + object: OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + if (viewBinding.searchView.isSearchOpen) { + viewBinding.searchView.closeSearch() + } + finish() + } + } + + 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 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, onlyShowXp: Boolean) { + val intent = Intent(context, ListActivity::class.java) + intent.putExtra("onlyShowXp", onlyShowXp) + context.startActivity(intent) + } + } +} diff --git a/app/src/main/java/top/niunaijun/blackbox/view/list/ListAdapter.kt b/app/src/main/java/top/niunaijun/blackbox/view/list/ListAdapter.kt new file mode 100644 index 00000000..34b90ab2 --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackbox/view/list/ListAdapter.kt @@ -0,0 +1,36 @@ +package top.niunaijun.blackbox.view.list + +import android.view.View +import android.view.ViewGroup +import cbfg.rvadapter.RVHolder +import cbfg.rvadapter.RVHolderFactory +import top.niunaijun.blackbox.R +import top.niunaijun.blackbox.bean.InstalledAppBean +import top.niunaijun.blackbox.databinding.ItemPackageBinding + +/** + * + * @Description: 软件显示界面适配器 + * @Author: wukaicheng + * @CreateDate: 2021/4/29 21:52 + */ +class ListAdapter : RVHolderFactory() { + override fun createViewHolder(parent: ViewGroup?, viewType: Int, item: Any): RVHolder { + return ListVH(inflate(R.layout.item_package,parent)) + } + + class ListVH(itemView:View) :RVHolder(itemView) { + private 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 + } + } + } +} diff --git a/app/src/main/java/top/niunaijun/blackbox/view/list/ListFactory.kt b/app/src/main/java/top/niunaijun/blackbox/view/list/ListFactory.kt new file mode 100644 index 00000000..0558ca0b --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackbox/view/list/ListFactory.kt @@ -0,0 +1,19 @@ +package top.niunaijun.blackbox.view.list + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import top.niunaijun.blackbox.data.AppsRepository + +/** + * + * @Description: + * @Author: wukaicheng + * @CreateDate: 2021/4/29 22:36 + */ +@Suppress("UNCHECKED_CAST") +class ListFactory(private val appsRepository: AppsRepository) : ViewModelProvider.NewInstanceFactory() { + @Suppress("UNCHECKED_CAST") + override fun create(modelClass: Class): T { + return ListViewModel(appsRepository) as T + } +} diff --git a/app/src/main/java/top/niunaijun/blackbox/view/list/ListViewModel.kt b/app/src/main/java/top/niunaijun/blackbox/view/list/ListViewModel.kt new file mode 100644 index 00000000..5f38ff52 --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackbox/view/list/ListViewModel.kt @@ -0,0 +1,35 @@ +package top.niunaijun.blackbox.view.list + +import androidx.lifecycle.MutableLiveData +import top.niunaijun.blackbox.bean.InstalledAppBean +import top.niunaijun.blackbox.data.AppsRepository +import top.niunaijun.blackbox.view.base.BaseViewModel + +/** + * + * @Description: + * @Author: wukaicheng + * @CreateDate: 2021/4/29 22:36 + */ +class ListViewModel(private val repo: AppsRepository) : BaseViewModel() { + val appsLiveData = MutableLiveData>() + val loadingLiveData = MutableLiveData() + + fun previewInstalledList() { + launchOnUI{ + repo.previewInstallList() + } + } + + fun getInstallAppList(userID:Int) { + launchOnUI { + repo.getInstalledAppList(userID, loadingLiveData, appsLiveData) + } + } + + fun getInstalledModules() { + launchOnUI { + repo.getInstalledModuleList(loadingLiveData, appsLiveData) + } + } +} diff --git a/app/src/main/java/top/niunaijun/blackbox/view/main/BlackBoxLoader.kt b/app/src/main/java/top/niunaijun/blackbox/view/main/BlackBoxLoader.kt new file mode 100644 index 00000000..dcab1fd5 --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackbox/view/main/BlackBoxLoader.kt @@ -0,0 +1,124 @@ +package top.niunaijun.blackbox.view.main + +import android.app.Application +import android.content.Context +import android.util.Log +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.blackbox.app.App +import top.niunaijun.blackbox.biz.cache.AppSharedPreferenceDelegate +import java.io.File + +/** + * + * @Description: + * @Author: wukaicheng + * @CreateDate: 2021/5/6 23:38 + */ +class BlackBoxLoader { + private var mHideRoot by AppSharedPreferenceDelegate(App.getContext(), false) + private var mHideXposed by AppSharedPreferenceDelegate(App.getContext(), false) + private var mDaemonEnable by AppSharedPreferenceDelegate(App.getContext(), false) + private var mShowShortcutPermissionDialog by AppSharedPreferenceDelegate(App.getContext(), true) + + fun hideRoot(): Boolean { + return mHideRoot + } + + fun invalidHideRoot(hideRoot: Boolean) { + this.mHideRoot = hideRoot + } + + fun hideXposed(): Boolean { + return mHideXposed + } + + fun invalidHideXposed(hideXposed: Boolean) { + this.mHideXposed = hideXposed + } + + fun daemonEnable(): Boolean { + return mDaemonEnable + } + + fun invalidDaemonEnable(enable: Boolean) { + this.mDaemonEnable = enable + } + + fun showShortcutPermissionDialog(): Boolean { + return mShowShortcutPermissionDialog + } + + fun invalidShortcutPermissionDialog(show: Boolean) { + this.mShowShortcutPermissionDialog = show + } + + fun addLifecycleCallback() { + BlackBoxCore.get().addAppLifecycleCallback(object : AppLifecycleCallback() { + override fun beforeCreateApplication( + packageName: String?, + processName: String?, + context: Context?, + userId: Int + ) { + Log.d( + TAG, + "beforeCreateApplication: pkg $packageName, processName $processName,userID:${BActivityThread.getUserId()}" + ) + } + + override fun beforeApplicationOnCreate( + packageName: String?, + processName: String?, + application: Application?, + userId: Int + ) { + Log.d(TAG, "beforeApplicationOnCreate: pkg $packageName, processName $processName") + } + + override fun afterApplicationOnCreate( + packageName: String?, + processName: String?, + application: Application?, + userId: Int + ) { + Log.d(TAG, "afterApplicationOnCreate: pkg $packageName, processName $processName") + //RockerManager.init(application, userId) + } + }) + } + + fun attachBaseContext(context: Context) { + BlackBoxCore.get().doAttachBaseContext(context, object : ClientConfiguration() { + override fun getHostPackageName(): String { + return context.packageName + } + + override fun isHideRoot(): Boolean { + return mHideRoot + } + + override fun isHideXposed(): Boolean { + return mHideXposed + } + + override fun isEnableDaemonService(): Boolean { + return mDaemonEnable + } + + override fun requestInstallPackage(file: File?, userId: Int): Boolean { + return false + } + }) + } + + fun doOnCreate() { + BlackBoxCore.get().doCreate() + } + + companion object { + val TAG: String = BlackBoxLoader::class.java.simpleName + } +} diff --git a/app/src/main/java/top/niunaijun/blackbox/view/main/MainActivity.kt b/app/src/main/java/top/niunaijun/blackbox/view/main/MainActivity.kt new file mode 100644 index 00000000..49ee3b22 --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackbox/view/main/MainActivity.kt @@ -0,0 +1,181 @@ +package top.niunaijun.blackbox.view.main + +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Bundle +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.blackbox.R +import top.niunaijun.blackbox.app.App +import top.niunaijun.blackbox.app.AppManager +import top.niunaijun.blackbox.databinding.ActivityMainBinding +import top.niunaijun.blackbox.util.Resolution +import top.niunaijun.blackbox.util.inflate +import top.niunaijun.blackbox.view.apps.AppsFragment +import top.niunaijun.blackbox.view.base.LoadingActivity +import top.niunaijun.blackbox.view.fake.FakeManagerActivity +import top.niunaijun.blackbox.view.list.ListActivity +import top.niunaijun.blackbox.view.setting.SettingActivity + +class MainActivity : LoadingActivity() { + private val viewBinding: ActivityMainBinding by inflate() + private lateinit var mViewPagerAdapter: ViewPagerAdapter + private val fragmentList = mutableListOf() + private var currentUser = 0 + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(viewBinding.root) + initToolbar(viewBinding.toolbarLayout.toolbar, R.string.app_name) + initViewPager() + initFab() + initToolbarSubTitle() + } + + private fun initToolbarSubTitle() { + updateUserRemark(0) + //hack code + viewBinding.toolbarLayout.toolbar.getChildAt(1).setOnClickListener { + MaterialDialog(this).show { + title(res = R.string.userRemark) + input( + hintRes = R.string.userRemark, + prefill = viewBinding.toolbarLayout.toolbar.subtitle + ) { _, input -> + AppManager.mRemarkSharedPreferences.edit { + putString("Remark$currentUser", input.toString()) + viewBinding.toolbarLayout.toolbar.subtitle = input + } + } + positiveButton(res = R.string.done) + negativeButton(res = R.string.cancel) + } + } + } + + private fun initViewPager() { + 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.attachTo(viewBinding.viewPager) + viewBinding.viewPager.registerOnPageChangeCallback(object : + ViewPager2.OnPageChangeCallback() { + override fun onPageSelected(position: Int) { + super.onPageSelected(position) + currentUser = fragmentList[position].userID + updateUserRemark(currentUser) + showFloatButton(true) + } + }) + } + + private fun initFab() { + viewBinding.fab.setOnClickListener { + val userId = viewBinding.viewPager.currentItem + val intent = Intent(this, ListActivity::class.java) + intent.putExtra("userID", userId) + apkPathResult.launch(intent) + } + } + + fun showFloatButton(show: Boolean) { + 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() + } + } + + fun scanUser() { + 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() + } + + private fun updateUserRemark(userId: Int) { + var remark = AppManager.mRemarkSharedPreferences.getString("Remark$userId", "User $userId") + if (remark.isNullOrEmpty()) { + remark = "User $userId" + } + + viewBinding.toolbarLayout.toolbar.subtitle = remark + } + + private val apkPathResult = + registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { + 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) + } + } + } + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.menu_main, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.main_git -> { + val intent = + Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/BlackBoxing/BlackBox")) + startActivity(intent) + } + + R.id.main_setting -> { + SettingActivity.start(this) + } + + R.id.main_tg -> { + val intent = + Intent(Intent.ACTION_VIEW, Uri.parse("https://t.me/blackboxing")) + startActivity(intent) + } + + R.id.fake_location -> { + //toast("Still Developing") + val intent = Intent(this, FakeManagerActivity::class.java) + intent.putExtra("userID", currentUser) + startActivity(intent) + } + } + return true + } + + companion object { + fun start(context: Context) { + val intent = Intent(context, MainActivity::class.java) + context.startActivity(intent) + } + } +} diff --git a/app/src/main/java/top/niunaijun/blackbox/view/main/ShortcutActivity.kt b/app/src/main/java/top/niunaijun/blackbox/view/main/ShortcutActivity.kt new file mode 100644 index 00000000..90eaa322 --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackbox/view/main/ShortcutActivity.kt @@ -0,0 +1,26 @@ +package top.niunaijun.blackbox.view.main + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.lifecycleScope +import kotlinx.coroutines.launch +import top.niunaijun.blackbox.BlackBoxCore + +/** + * + * @Description: 快捷方式跳转activity + * @Author: wukaicheng + * @CreateDate: 2022/2/11 23:13 + */ +class ShortcutActivity:AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val pkg = intent.getStringExtra("pkg") + val userID = intent.getIntExtra("userId",0) + + lifecycleScope.launch { + BlackBoxCore.get().launchApk(pkg,userID) + finish() + } + } +} diff --git a/app/src/main/java/top/niunaijun/blackbox/view/main/ViewPagerAdapter.kt b/app/src/main/java/top/niunaijun/blackbox/view/main/ViewPagerAdapter.kt new file mode 100644 index 00000000..f7fa6fed --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackbox/view/main/ViewPagerAdapter.kt @@ -0,0 +1,29 @@ +package top.niunaijun.blackbox.view.main + +import androidx.appcompat.app.AppCompatActivity +import androidx.fragment.app.Fragment +import androidx.viewpager2.adapter.FragmentStateAdapter +import top.niunaijun.blackbox.view.apps.AppsFragment + +/** + * + * @Description: + * @Author: wukaicheng + * @CreateDate: 2021/4/29 22:00 + */ +class ViewPagerAdapter(appCompatActivity: AppCompatActivity) : FragmentStateAdapter(appCompatActivity) { + private var fragmentList = mutableListOf() + + fun replaceData(list: MutableList) { + this.fragmentList = list + notifyDataSetChanged() + } + + override fun getItemCount(): Int { + return fragmentList.size + } + + override fun createFragment(position: Int): Fragment { + return fragmentList[position] + } +} diff --git a/app/src/main/java/top/niunaijun/blackbox/view/main/WelcomeActivity.kt b/app/src/main/java/top/niunaijun/blackbox/view/main/WelcomeActivity.kt new file mode 100644 index 00000000..0d793801 --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackbox/view/main/WelcomeActivity.kt @@ -0,0 +1,31 @@ +package top.niunaijun.blackbox.view.main + +import android.content.Intent +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.ViewModelProvider +import top.niunaijun.blackbox.util.InjectionUtil +import top.niunaijun.blackbox.view.list.ListViewModel + +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() { + MainActivity.start(this) + finish() + } + + private fun previewInstalledAppList() { + val viewModel = ViewModelProvider(this,InjectionUtil.getListFactory()).get(ListViewModel::class.java) + viewModel.previewInstalledList() + } +} diff --git a/app/src/main/java/top/niunaijun/blackbox/view/setting/SettingActivity.kt b/app/src/main/java/top/niunaijun/blackbox/view/setting/SettingActivity.kt new file mode 100644 index 00000000..fdea635d --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackbox/view/setting/SettingActivity.kt @@ -0,0 +1,30 @@ +package top.niunaijun.blackbox.view.setting + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import top.niunaijun.blackbox.R +import top.niunaijun.blackbox.databinding.ActivitySettingBinding +import top.niunaijun.blackbox.util.inflate +import top.niunaijun.blackbox.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) + } + } +} diff --git a/app/src/main/java/top/niunaijun/blackbox/view/setting/SettingFragment.kt b/app/src/main/java/top/niunaijun/blackbox/view/setting/SettingFragment.kt new file mode 100644 index 00000000..dee072b0 --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackbox/view/setting/SettingFragment.kt @@ -0,0 +1,102 @@ +package top.niunaijun.blackbox.view.setting + +import android.content.Intent +import android.os.Bundle +import androidx.preference.Preference +import androidx.preference.PreferenceFragmentCompat +import androidx.preference.SwitchPreferenceCompat +import top.niunaijun.blackbox.BlackBoxCore +import top.niunaijun.blackbox.R +import top.niunaijun.blackbox.app.AppManager +import top.niunaijun.blackbox.util.toast +import top.niunaijun.blackbox.view.gms.GmsManagerActivity +import top.niunaijun.blackbox.view.xp.XpActivity + +/** + * + * @Description: + * @Author: wukaicheng + * @CreateDate: 2021/5/6 22:13 + */ +class SettingFragment : PreferenceFragmentCompat() { + private lateinit var xpEnable: SwitchPreferenceCompat + private lateinit var xpModule: Preference + + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + setPreferencesFromResource(R.xml.setting, rootKey) + + xpEnable = findPreference("xp_enable")!! + xpEnable.isChecked = BlackBoxCore.get().isXPEnable + + xpEnable.setOnPreferenceChangeListener { _, newValue -> + BlackBoxCore.get().isXPEnable = (newValue == true) + true + } + //xp模块跳转 + xpModule = findPreference("xp_module")!! + xpModule.setOnPreferenceClickListener { + val intent = Intent(requireActivity(), XpActivity::class.java) + requireContext().startActivity(intent) + true + } + initGms() + + invalidHideState{ + val xpHidePreference: Preference = (findPreference("xp_hide")!!) + val hideXposed = AppManager.mBlackBoxLoader.hideXposed() + xpHidePreference.setDefaultValue(hideXposed) + xpHidePreference + } + + 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 + } + } + + 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) { + "xp_hide" -> { + AppManager.mBlackBoxLoader.invalidHideXposed(tmpHide) + } + + "root_hide" -> { + AppManager.mBlackBoxLoader.invalidHideRoot(tmpHide) + } + + "daemon_enable" -> { + AppManager.mBlackBoxLoader.invalidDaemonEnable(tmpHide) + } + } + + toast(R.string.restart_module) + return@setOnPreferenceChangeListener true + } + } +} diff --git a/app/src/main/java/top/niunaijun/blackbox/view/xp/XpActivity.kt b/app/src/main/java/top/niunaijun/blackbox/view/xp/XpActivity.kt new file mode 100644 index 00000000..8fc31ed2 --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackbox/view/xp/XpActivity.kt @@ -0,0 +1,135 @@ +package top.niunaijun.blackbox.view.xp + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.text.TextUtils +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 top.niunaijun.blackbox.BlackBoxCore +import top.niunaijun.blackbox.R +import top.niunaijun.blackbox.bean.XpModuleInfo +import top.niunaijun.blackbox.databinding.ActivityXpBinding +import top.niunaijun.blackbox.util.InjectionUtil +import top.niunaijun.blackbox.util.inflate +import top.niunaijun.blackbox.util.toast +import top.niunaijun.blackbox.view.base.LoadingActivity +import top.niunaijun.blackbox.view.list.ListActivity + +/** + * + * @Description: xposed模块管理界面 + * @Author: wukaicheng + * @CreateDate: 2021/5/2 20:25 + */ +class XpActivity : LoadingActivity() { + private val viewBinding: ActivityXpBinding by inflate() + private lateinit var viewModel: XpViewModel + private lateinit var mAdapter: RVAdapter + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(viewBinding.root) + initToolbar(viewBinding.toolbarLayout.toolbar, R.string.xp_setting, true) + + viewModel = ViewModelProvider(this, InjectionUtil.getXpFactory()).get(XpViewModel::class.java) + + initRecyclerView() + initFab() + } + + private fun observeLiveData() { + viewBinding.stateView.showLoading() + viewModel.getInstalledModule() + viewModel.appsLiveData.observe(this) { + if (it.isNullOrEmpty()) { + viewBinding.stateView.showEmpty() + } else { + mAdapter.setItems(it) + viewBinding.stateView.showContent() + } + } + + viewModel.resultLiveData.observe(this) { + if (!TextUtils.isEmpty(it)) { + hideLoading() + toast(it) + viewModel.getInstalledModule() + } + } + } + + private fun initRecyclerView() { + mAdapter = RVAdapter(this, XpAdapter()).bind(viewBinding.recyclerView) + .setItemClickListener { _, item, position -> + item.enable = !item.enable + BlackBoxCore.get().setModuleEnable(item.packageName, item.enable) + mAdapter.replaceAt(position, item) + toast(R.string.restart_module) + }.setItemLongClickListener { _, item, _ -> + unInstallModule(item.packageName) + } + + viewBinding.recyclerView.layoutManager = LinearLayoutManager(this) + viewBinding.stateView.showEmpty() + } + + private fun initFab() { + viewBinding.fab.setOnClickListener { + val intent = Intent(this, ListActivity::class.java) + intent.putExtra("onlyShowXp", true) + apkPathResult.launch(intent) + } + } + + override fun onStart() { + super.onStart() + observeLiveData() + } + + override fun onStop() { + super.onStop() + viewModel.appsLiveData.value = null + viewModel.appsLiveData.removeObservers(this) + viewModel.resultLiveData.value = null + viewModel.resultLiveData.removeObservers(this) + } + + private fun unInstallModule(packageName: String) { + MaterialDialog(this).show { + title(R.string.uninstall_module) + message(R.string.uninstall_module_hint) + positiveButton(R.string.done) { + showLoading() + viewModel.unInstallModule(packageName) + } + negativeButton(R.string.cancel) + } + } + + private fun installModule(source: String) { + showLoading() + viewModel.installModule(source) + } + + private val apkPathResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { + if (it.resultCode == RESULT_OK) { + it.data?.let { data -> + val source = data.getStringExtra("source") + if (source != null) { + installModule(source) + } + } + } + } + + companion object { + fun start(context: Context) { + val intent = Intent(context, XpActivity::class.java) + context.startActivity(intent) + } + } +} diff --git a/app/src/main/java/top/niunaijun/blackbox/view/xp/XpAdapter.kt b/app/src/main/java/top/niunaijun/blackbox/view/xp/XpAdapter.kt new file mode 100644 index 00000000..a1a36233 --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackbox/view/xp/XpAdapter.kt @@ -0,0 +1,37 @@ +package top.niunaijun.blackbox.view.xp + +import android.view.View +import android.view.ViewGroup +import cbfg.rvadapter.RVHolder +import cbfg.rvadapter.RVHolderFactory +import top.niunaijun.blackbox.R +import top.niunaijun.blackbox.bean.XpModuleInfo +import top.niunaijun.blackbox.databinding.ItemXpBinding + +/** + * + * @Description: + * @Author: wukaicheng + * @CreateDate: 2021/5/2 21:32 + */ +class XpAdapter : RVHolderFactory() { + override fun createViewHolder(parent: ViewGroup?, viewType: Int, item: Any): RVHolder { + return XpVH(inflate(R.layout.item_xp, parent)) + } + + class XpVH(itemView: View) : RVHolder(itemView) { + private val binding = ItemXpBinding.bind(itemView) + + override fun setContent(item: XpModuleInfo, isSelected: Boolean, payload: Any?) { + binding.icon.setImageDrawable(item.icon) + binding.name.text = item.name + binding.desc.text = item.desc + binding.enable.isChecked = item.enable + binding.enable.setOnCheckedChangeListener { buttonView, _ -> + if (buttonView.isPressed) { + binding.root.performClick() + } + } + } + } +} diff --git a/app/src/main/java/top/niunaijun/blackbox/view/xp/XpFactory.kt b/app/src/main/java/top/niunaijun/blackbox/view/xp/XpFactory.kt new file mode 100644 index 00000000..fe18ce3f --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackbox/view/xp/XpFactory.kt @@ -0,0 +1,19 @@ +package top.niunaijun.blackbox.view.xp + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import top.niunaijun.blackbox.data.XpRepository + +/** + * + * @Description: + * @Author: wukaicheng + * @CreateDate: 2021/5/2 20:56 + */ +@Suppress("UNCHECKED_CAST") +class XpFactory(private val repo:XpRepository): ViewModelProvider.NewInstanceFactory() { + @Suppress("UNCHECKED_CAST") + override fun create(modelClass: Class): T { + return XpViewModel(repo) as T + } +} diff --git a/app/src/main/java/top/niunaijun/blackbox/view/xp/XpViewModel.kt b/app/src/main/java/top/niunaijun/blackbox/view/xp/XpViewModel.kt new file mode 100644 index 00000000..b9d3692f --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackbox/view/xp/XpViewModel.kt @@ -0,0 +1,35 @@ +package top.niunaijun.blackbox.view.xp + +import androidx.lifecycle.MutableLiveData +import top.niunaijun.blackbox.bean.XpModuleInfo +import top.niunaijun.blackbox.data.XpRepository +import top.niunaijun.blackbox.view.base.BaseViewModel + +/** + * + * @Description: + * @Author: wukaicheng + * @CreateDate: 2021/5/2 20:55 + */ +class XpViewModel(private val repo: XpRepository) : BaseViewModel() { + val appsLiveData = MutableLiveData>() + val resultLiveData = MutableLiveData() + + fun getInstalledModule() { + launchOnUI { + repo.getInstallModules(appsLiveData) + } + } + + fun installModule(source:String) { + launchOnUI { + repo.installModule(source, resultLiveData) + } + } + + fun unInstallModule(packageName: String) { + launchOnUI { + repo.unInstallModule(packageName, resultLiveData) + } + } +} diff --git a/app/src/main/java/top/niunaijun/blackbox/widget/EnFloatView.kt b/app/src/main/java/top/niunaijun/blackbox/widget/EnFloatView.kt new file mode 100644 index 00000000..3b7a2a8d --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackbox/widget/EnFloatView.kt @@ -0,0 +1,50 @@ +package top.niunaijun.blackbox.widget + +import android.content.Context +import android.view.MotionEvent +import com.imuxuan.floatingview.FloatingMagnetView +import top.niunaijun.blackbox.R + +/** + * + * @Description: rocker parent + * @Author: kotlinMiku + * @CreateDate: 2022/3/20 16:58 + */ +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 \ No newline at end of file diff --git a/app/src/main/java/top/niunaijun/blackbox/widget/RockerView.java b/app/src/main/java/top/niunaijun/blackbox/widget/RockerView.java new file mode 100644 index 00000000..08a98942 --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackbox/widget/RockerView.java @@ -0,0 +1,434 @@ +package top.niunaijun.blackbox.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.view.MotionEvent; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.View; + +import androidx.annotation.NonNull; + +import top.niunaijun.blackbox.util.MathUtil; + +/** + * A custom view for game or others. + *

+ * Author: GcsSloop + * Created Date: 16/5/24 + * Copyright (C) 2016 GcsSloop. + * GitHub: https://github.com/GcsSloop + */ +public class RockerView extends SurfaceView implements Runnable, SurfaceHolder.Callback { + private static final int DEFAULT_AREA_RADIUS = 100; + 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 boolean mDrawOk = true; + private static boolean mCallbackOk = true; + + private Paint mPaint; + + /** + * The rocker active area center position. + * usually, it is the center of this view. + */ + private Point mAreaPosition; + + /** + * The Rocker position. + * usually, it as same asmAreaPosition . + * if this view touched, it will follow the touch position. + *

+ * we get position information from this. + */ + 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; + + /*Life 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); + // init attrs + initAttrs(); + + // set paint + setPaint(); + + if (isInEditMode()) { + return; + } + + // config surfaceView + configSurfaceView(); + + // config surfaceHolder + configSurfaceHolder(); + } + + private void initAttrs() { + 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); // do not lock screen when surfaceView is running. + setFocusable(true); // make sure this surfaceView can get focus from keyboard. + setFocusableInTouchMode(true); // make sure this surfaceView can get focus from touch. + setZOrderOnTop(true); // make sure this surface is placed on top of the window + } + + private void configSurfaceHolder() { + mHolder = getHolder(); + mHolder.addCallback(this); + mHolder.setFormat(PixelFormat.TRANSPARENT); //设置背景透明 + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int measureWidth, measureHeight; + int defaultWidth = (mAreaRadius + mRockerRadius) * 2; + + 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 = defaultWidth; + } 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); + + // this need subtract the view padding + 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 { + Thread mDrawThread = new Thread(this); + mDrawThread.start(); + // listener callback + Thread mCallbackThread = new Thread(() -> { + while (mCallbackOk) { + // listener callback + 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; + } + } + + /*Event Response*******************************************************************************/ + @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; + } + + /*Thread - draw view***************************************************************************/ + @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 (mAreaBitmap != null) { + 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 (mRockerBitmap != null) { + 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); + } + } + } + + //获取摇杆偏移角度 上方中间为0,左为负,右为正 + private float getAngleConvert(float radian) { + return 90 + Math.round(radian / Math.PI * 180); + } + + // for preview + @Override + protected void onDraw(Canvas canvas) { + if (isInEditMode()) { + canvas.drawColor(Color.WHITE); + drawArea(canvas); + drawRocker(canvas); + } + } + + /*Getter Setter********************************************************************************/ + 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; + } + + /*Rocker Listener******************************************************************************/ + /** + * rocker listener + */ + public interface RockerListener { + /** + * you can get some event from this method + * + * @param eventType The event type, EVENT_ACTION or EVENT_CLOCK + * @param currentAngle The current angle + * @param currentDistance The current distance (px) + */ + void callback(int eventType, float currentAngle, float currentDistance); + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/view_float_rocker.xml b/app/src/main/res/layout/view_float_rocker.xml index a2f7a905..fea49cb6 100644 --- a/app/src/main/res/layout/view_float_rocker.xml +++ b/app/src/main/res/layout/view_float_rocker.xml @@ -3,7 +3,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="wrap_content" android:layout_height="wrap_content"> - NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if "%ERRORLEVEL%" == "0" goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -35,7 +54,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -45,28 +64,14 @@ echo location of your Java installation. goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell diff --git a/settings.gradle b/settings.gradle index f4f85485..faddd618 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,10 +1,25 @@ -include ':Bcore' -include ':app' -include ':Bcore:black-fake' -include ':Bcore:black-hook' -include ':Bcore:pine-xposed' -include ':Bcore:pine-core' -include ':Bcore:pine-xposed-res' -include ':android-mirror' +pluginManagement { + repositories { + gradlePluginPortal() + google() + mavenCentral() + jcenter() + maven { + url 'https://jitpack.io' + } + } +} +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + jcenter() + maven { + url 'https://jitpack.io' + } + } +} rootProject.name = "BlackBox" +include ':Bcore', ':app', ':Bcore:black-fake', ':Bcore:black-hook', ':Bcore:pine-xposed', ':Bcore:pine-core', ':Bcore:pine-xposed-res', ':android-mirror'