commit e7d342644aea4c5c4cb3e03a4cc55448519ddbb3 Author: tongtongstudio Date: Tue Aug 4 16:19:58 2026 +0800 init: mdm版本初始化 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ecb1d3c --- /dev/null +++ b/.gitignore @@ -0,0 +1,90 @@ +# Built application files +*.apk +*.aar +*.ap_ +*.aab + +# Files for the ART/Dalvik VM +*.dex + +# Java class files +*.class + +# Generated files +bin/ +gen/ +out/ +# Uncomment the following line in case you need and you don't have the release build type files in your app +# release/ + +# Gradle files +.gradle/ +build/ + +# Local configuration file (sdk path, etc) +local.properties + +# Proguard folder generated by Eclipse +proguard/ + +# Log Files +*.log + +# Android Studio Navigation editor temp files +.navigation/ + +# Android Studio captures folder +captures/ + +# IntelliJ +*.iml +.idea/workspace.xml +.idea/tasks.xml +.idea/gradle.xml +.idea/assetWizardSettings.xml +.idea/dictionaries +.idea/libraries +# Android Studio 3 in .gitignore file. +.idea/caches +.idea/modules.xml +# Comment next line if keeping position of elements in Navigation Editor is relevant for you +.idea/navEditor.xml + +# Keystore files +# Uncomment the following lines if you do not want to check your keystore files in. +#*.jks +#*.keystore + +# External native build folder generated in Android Studio 2.2 and later +.externalNativeBuild +.cxx/ + +# Google Services (e.g. APIs or Firebase) +# google-services.json + +# Freeline +freeline.py +freeline/ +freeline_project_description.json + +# fastlane +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots +fastlane/test_output +fastlane/readme.md + +# Version control +vcs.xml + +# lint +lint/intermediates/ +lint/generated/ +lint/outputs/ +lint/tmp/ +# lint/reports/ +/.idea/ +/app/src/test/ +/app/src/androidTest/java/com/jiaoguanyi/appstore/ +/adb +/app/proguard-rules.pro diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..ec4c59d --- /dev/null +++ b/app/.gitignore @@ -0,0 +1,85 @@ +# Built application files +*.apk +*.aar +*.ap_ +*.aab + +# Files for the ART/Dalvik VM +*.dex + +# Java class files +*.class + +# Generated files +bin/ +gen/ +out/ +# Uncomment the following line in case you need and you don't have the release build type files in your app +# release/ + +# Gradle files +.gradle/ +build/ + +# Local configuration file (sdk path, etc) +local.properties + +# Proguard folder generated by Eclipse +proguard/ + +# Log Files +*.log + +# Android Studio Navigation editor temp files +.navigation/ + +# Android Studio captures folder +captures/ + +# IntelliJ +*.iml +.idea/workspace.xml +.idea/tasks.xml +.idea/gradle.xml +.idea/assetWizardSettings.xml +.idea/dictionaries +.idea/libraries +# Android Studio 3 in .gitignore file. +.idea/caches +.idea/modules.xml +# Comment next line if keeping position of elements in Navigation Editor is relevant for you +.idea/navEditor.xml + +# Keystore files +# Uncomment the following lines if you do not want to check your keystore files in. +#*.jks +#*.keystore + +# External native build folder generated in Android Studio 2.2 and later +.externalNativeBuild +.cxx/ + +# Google Services (e.g. APIs or Firebase) +# google-services.json + +# Freeline +freeline.py +freeline/ +freeline_project_description.json + +# fastlane +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots +fastlane/test_output +fastlane/readme.md + +# Version control +vcs.xml + +# lint +lint/intermediates/ +lint/generated/ +lint/outputs/ +lint/tmp/ +# lint/reports/ \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..bf65523 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,125 @@ +apply plugin: 'com.android.application' + +static def appName() { + return "ElderlyDialerMdm" +} + +static def releaseTime() { + return new Date().format("yyyyMMdd_HHmmss", TimeZone.getDefault()) +} + +android { + namespace "com.ttstd.sn" + + compileSdkVersion 33 + + defaultConfig { + applicationId "com.ttstd.sn" + //There are no CERT files because If the mini sdk version is 23+, the AGP will ignore the V1 scheme signature. + minSdkVersion 23 + targetSdkVersion 33 + + versionCode 1 + versionName "1.0.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + + multiDexEnabled true + + lintOptions { + checkReleaseBuilds false + // Or, if you prefer, you can continue to check for errors in release builds, + // but continue the build even when errors are found: + abortOnError false + } + + ndk { + //选择要添加的对应 cpu 类型的 .so 库。 + abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86' + } + + buildConfigField "String", "platform", '"honor"' + + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + buildFeatures { + dataBinding true + buildConfig true + aidl true + } + + + buildTypes { + debug { + debuggable true + versionNameSuffix "_debug" + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + + release { + //混淆 + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + + // Add application variant configuration here instead + applicationVariants.all { variant -> + variant.outputs.each { output -> + def buildType = variant.buildType.name + if (buildType.contains("debug")) { + output.outputFileName = "app_debug.apk" + } else { + output.outputFileName = "${appName()}_${variant.versionCode}_V${variant.versionName}_${releaseTime()}_${buildType}.apk" + } + } + } +} + +dependencies { + compileOnly files('libs/magic-android-34.38.1.200.jar') + + implementation 'androidx.appcompat:appcompat:1.3.1' + implementation 'androidx.constraintlayout:constraintlayout:2.0.4' + implementation 'androidx.multidex:multidex:2.0.1' + implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.1.0' + + //Annotations(BaseRxService 使用 @NotNull) + implementation 'org.jetbrains:annotations:24.0.1' + + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test.ext:junit:1.1.3' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' + + //RxJava + implementation 'io.reactivex.rxjava3:rxjava:3.0.0' + implementation 'io.reactivex.rxjava3:rxandroid:3.0.0' + //生命周期管理 + implementation 'com.trello.rxlifecycle4:rxlifecycle:4.0.2' + implementation 'com.trello.rxlifecycle4:rxlifecycle-android:4.0.2' + implementation 'com.trello.rxlifecycle4:rxlifecycle-components:4.0.2' + implementation 'com.trello.rxlifecycle4:rxlifecycle-android-lifecycle:4.0.2' + + //图片加载框架 + implementation 'com.github.bumptech.glide:glide:4.13.2' + annotationProcessor 'com.github.bumptech.glide:compiler:4.13.2' + + //状态栏透明 + implementation 'com.gitee.zackratos:UltimateBarX:0.8.0' +} + +// 在 dependencies 之后添加 +project.afterEvaluate { + android.applicationVariants.all { variant -> + variant.javaCompileProvider.get().options.bootstrapClasspath = files( + file('libs/framework.jar'), + android.getBootClasspath() + ) + } +} diff --git a/app/libs/framework.jar b/app/libs/framework.jar new file mode 100644 index 0000000..fefe7ec Binary files /dev/null and b/app/libs/framework.jar differ diff --git a/app/libs/magic-android-34.38.1.200.jar b/app/libs/magic-android-34.38.1.200.jar new file mode 100644 index 0000000..049285e Binary files /dev/null and b/app/libs/magic-android-34.38.1.200.jar differ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..0012da1 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/java/com/ttstd/sn/App.java b/app/src/main/java/com/ttstd/sn/App.java new file mode 100644 index 0000000..115948e --- /dev/null +++ b/app/src/main/java/com/ttstd/sn/App.java @@ -0,0 +1,72 @@ +package com.ttstd.sn; + +import android.app.Application; +import android.content.Context; +import android.text.TextUtils; + +import androidx.multidex.MultiDex; + +import com.ttstd.sn.mdm.AdminManager; +import com.ttstd.sn.ui.base.GlobalCrashHandler; + +public class App extends Application { + private static final String TAG = "App"; + + private static App sInstance; + + public static App getInstance() { + return sInstance; + } + + @Override + protected void attachBaseContext(Context base) { + super.attachBaseContext(base); + MultiDex.install(this); + sInstance = this; + } + + @Override + public void onCreate() { + super.onCreate(); + //非主进程不初始化 + if (isMainProcessName(this, android.os.Process.myPid())) { + init(); + } + } + + private void init() { + // 全局异常捕获 + GlobalCrashHandler.getInstance().init(this, null); + + // 初始化 MDM(设备激活后才生效) + try { + AdminManager.init(this); + } catch (Throwable e) { + e.printStackTrace(); + } + } + + /** + * 判断当前进程是否为主进程 + */ + private boolean isMainProcessName(Context context, int pid) { + String processName = null; + try { + android.app.ActivityManager am = + (android.app.ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + if (am == null) return true; + java.util.List list = am.getRunningAppProcesses(); + if (list == null || list.size() == 0) return true; + for (android.app.ActivityManager.RunningAppProcessInfo info : list) { + if (info.pid == pid) { + processName = info.processName; + break; + } + } + } catch (Exception e) { + e.printStackTrace(); + } + if (TextUtils.isEmpty(processName)) return true; + return processName.equalsIgnoreCase(getPackageName()); + } +} diff --git a/app/src/main/java/com/ttstd/sn/mdm/AdminManager.java b/app/src/main/java/com/ttstd/sn/mdm/AdminManager.java new file mode 100644 index 0000000..89757f1 --- /dev/null +++ b/app/src/main/java/com/ttstd/sn/mdm/AdminManager.java @@ -0,0 +1,473 @@ +package com.ttstd.sn.mdm; + +import android.annotation.SuppressLint; +import android.app.admin.DevicePolicyManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.Build; +import android.text.TextUtils; +import android.util.Log; + +import com.ttstd.sn.receiver.AoleDeviceAdminReceiver; +import com.hihonor.android.app.admin.DeviceApplicationManager; +import com.hihonor.android.app.admin.DeviceBluetoothManager; +import com.hihonor.android.app.admin.DeviceCameraManager; +import com.hihonor.android.app.admin.DeviceControlManager; +import com.hihonor.android.app.admin.DeviceEmailManager; +import com.hihonor.android.app.admin.DeviceFirewallManager; +import com.hihonor.android.app.admin.DeviceHwSystemManager; +import com.hihonor.android.app.admin.DeviceInfraredManager; +import com.hihonor.android.app.admin.DeviceLocationManager; +import com.hihonor.android.app.admin.DeviceNetworkManager; +import com.hihonor.android.app.admin.DeviceP2PManager; +import com.hihonor.android.app.admin.DevicePackageManager; +import com.hihonor.android.app.admin.DevicePasswordManager; +import com.hihonor.android.app.admin.DevicePhoneManager; +import com.hihonor.android.app.admin.DeviceRestrictionManager; +import com.hihonor.android.app.admin.DeviceSettingsManager; +import com.hihonor.android.app.admin.DeviceStorageManagerEx; +import com.hihonor.android.app.admin.DeviceTelephonyManager; +import com.hihonor.android.app.admin.DeviceVpnManager; +import com.hihonor.android.app.admin.DeviceWifiPolicyManager; + +import java.util.ArrayList; +import java.util.List; + +public class AdminManager { + private static final String TAG = "AdminManager"; + + public static final List mDeleteMenus = new ArrayList() {{ + this.add("honor_id"); + this.add("super_device"); + this.add("vpn_settings"); + this.add("apps_clone"); + this.add("apps_startup_management"); + this.add("apps_management"); + this.add("apps_honor_tips"); + this.add("pengine_settings"); + this.add("screen_wallpaper"); + }}; + + private static final int STATUS_DISABLE = 0; + private static final int STATUS_ENABLE = 1; + + @SuppressLint("StaticFieldLeak") + private static AdminManager sInstance; + + private Context mContext; + private ComponentName mAdminName; + private DevicePolicyManager mDevicePolicyManager; + + /*设备应用程序管理类*/ + private DeviceApplicationManager mDeviceApplicationManager; + /*蓝牙相关的管理类*/ + private DeviceBluetoothManager mDeviceBluetoothManager; + /*相机相关的类*/ + private DeviceCameraManager mDeviceCameraManager; + /*设备控制相关的类*/ + private DeviceControlManager mDeviceControlManager; + /*邮箱相关的管理类*/ + private DeviceEmailManager mDeviceEmailManager; + /*防火墙相关管理类*/ + private DeviceFirewallManager mDeviceFirewallManager; + /*系统相关类*/ + private DeviceHwSystemManager mDeviceHwSystemManager; + /*设备红外管理类*/ + private DeviceInfraredManager mDeviceInfraredManager; + /*地理位置相关的类*/ + private DeviceLocationManager mDeviceLocationManager; + /*网络管理相关的类*/ + private DeviceNetworkManager mDeviceNetworkManager; + /*Wifi 直连权限管理授权*/ + private DeviceP2PManager mDeviceP2PManager; + /*设备包管理类*/ + private DevicePackageManager mDevicePackageManager; + /*密码相关类*/ + private DevicePasswordManager mDevicePasswordManager; + /*设备通话管理类*/ + private DevicePhoneManager mDevicePhoneManager; + /*禁用/启用管理类*/ + private DeviceRestrictionManager mDeviceRestrictionManager; + /*设置相关类*/ + private DeviceSettingsManager mDeviceSettingsManager; + /*设备存储管理类*/ + private DeviceStorageManagerEx mDeviceStorageManagerEx; + /*通讯服务管理类*/ + private DeviceTelephonyManager mDeviceTelephonyManager; + /*VPN 相关管理类*/ + private DeviceVpnManager mDeviceVpnManager; + /*WIFI 管理相关类*/ + private DeviceWifiPolicyManager mDeviceWifiPolicyManager; + + + private AdminManager(Context context) { + if (context == null) { + throw new RuntimeException("Context is NULL"); + } + this.mContext = context; + this.mAdminName = new ComponentName(context, AoleDeviceAdminReceiver.class); + this.mDevicePolicyManager = (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE); + this.mDeviceApplicationManager = new DeviceApplicationManager(); + this.mDeviceBluetoothManager = new DeviceBluetoothManager(); + this.mDeviceCameraManager = new DeviceCameraManager(); + this.mDeviceControlManager = new DeviceControlManager(); + this.mDeviceEmailManager = new DeviceEmailManager(); + this.mDeviceFirewallManager = new DeviceFirewallManager(); + this.mDeviceHwSystemManager = new DeviceHwSystemManager(); + this.mDeviceInfraredManager = new DeviceInfraredManager(); + this.mDeviceLocationManager = new DeviceLocationManager(); + this.mDeviceNetworkManager = new DeviceNetworkManager(); + this.mDeviceP2PManager = new DeviceP2PManager(); + this.mDevicePackageManager = new DevicePackageManager(); + this.mDevicePasswordManager = new DevicePasswordManager(); + this.mDevicePhoneManager = new DevicePhoneManager(); + this.mDeviceRestrictionManager = new DeviceRestrictionManager(); + this.mDeviceSettingsManager = new DeviceSettingsManager(); + this.mDeviceStorageManagerEx = new DeviceStorageManagerEx(); + this.mDeviceTelephonyManager = new DeviceTelephonyManager(); + this.mDeviceVpnManager = new DeviceVpnManager(); + this.mDeviceWifiPolicyManager = new DeviceWifiPolicyManager(); + + Log.e(TAG, "AdminManager: isActiveMe = " + isActiveMe()); + if (isActiveMe()) { + setYoyoDisabled(true); + } + } + + public static void init(Context context) { + if (sInstance == null) { + Log.e(TAG, "init: "); + sInstance = new AdminManager(context); + } + } + + public static AdminManager getInstance() { + if (sInstance == null) { + throw new IllegalStateException("You must be init AdminManager first"); + } + return sInstance; + } + + public ComponentName getAdminName() { + return mAdminName; + } + + public DeviceApplicationManager getDeviceApplicationManager() { + return mDeviceApplicationManager; + } + + public DeviceBluetoothManager getDeviceBluetoothManager() { + return mDeviceBluetoothManager; + } + + public DeviceCameraManager getDeviceCameraManager() { + return mDeviceCameraManager; + } + + public DeviceControlManager getDeviceControlManager() { + return mDeviceControlManager; + } + + public DeviceEmailManager getDeviceEmailManager() { + return mDeviceEmailManager; + } + + public DeviceFirewallManager getDeviceFirewallManager() { + return mDeviceFirewallManager; + } + + public DeviceHwSystemManager getDeviceHwSystemManager() { + return mDeviceHwSystemManager; + } + + public DeviceInfraredManager getDeviceInfraredManager() { + return mDeviceInfraredManager; + } + + public DeviceLocationManager getDeviceLocationManager() { + return mDeviceLocationManager; + } + + public DeviceNetworkManager getDeviceNetworkManager() { + return mDeviceNetworkManager; + } + + public DeviceP2PManager getDeviceP2PManager() { + return mDeviceP2PManager; + } + + public DevicePackageManager getDevicePackageManager() { + return mDevicePackageManager; + } + + public DevicePasswordManager getDevicePasswordManager() { + return mDevicePasswordManager; + } + + public DevicePhoneManager getDevicePhoneManager() { + return mDevicePhoneManager; + } + + public DeviceRestrictionManager getDeviceRestrictionManager() { + return mDeviceRestrictionManager; + } + + public DeviceSettingsManager getDeviceSettingsManager() { + return mDeviceSettingsManager; + } + + public DeviceStorageManagerEx getDeviceStorageManagerEx() { + return mDeviceStorageManagerEx; + } + + public DeviceTelephonyManager getDeviceTelephonyManager() { + return mDeviceTelephonyManager; + } + + public DeviceVpnManager getDeviceVpnManager() { + return mDeviceVpnManager; + } + + public DeviceWifiPolicyManager getDeviceWifiPolicyManager() { + return mDeviceWifiPolicyManager; + } + + public String getSerial() { + String sn = ""; + DevicePolicyManager dpm = (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + try { + dpm.setPermissionPolicy(mAdminName, DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED); + } catch (Exception e) { + Log.e(TAG, "getSerial: " + e.getMessage()); + } + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + try { + sn = Build.getSerial(); + } catch (Exception e) { + Log.e(TAG, "getSerial: " + e.getMessage()); + } + } + // 未获取到 MDM 权限时,Build.getSerial() 可能返回 "unknown", + // 此时传 "" 给接口,避免获取到不属于本设备的配置 + if ("unknown".equalsIgnoreCase(sn)) { + sn = ""; + } + return sn; + } + + public boolean isActiveMe() { + if (mDevicePolicyManager == null) { + return false; + } else { + return mDevicePolicyManager.isAdminActive(mAdminName); + } + } + + /** + * 移除白名单 + */ + public void removeTrustList() { + try { + List trustList = mDevicePackageManager.getInstallPackageTrustList(mAdminName); + Log.e(TAG, "removeTrustList: " + trustList); + if (trustList != null && trustList.size() != 0) { + mDevicePackageManager.removeInstallPackageTrustList(mAdminName, trustList); + } + } catch (Exception e) { + Log.e(TAG, "removeTrustList: " + e.getMessage()); + } + } + + /** + * 添加保持某应用始终运行名单 + * + * @param pkg + */ + public void addPersistentApp(String pkg) { + Log.e(TAG, "addPersistentApp: " + pkg); + List forceAppList = new ArrayList<>(); + forceAppList.add(pkg); + try { + // 添加保持某应用始终运行名单 + mDeviceApplicationManager.addPersistentApp(mAdminName, forceAppList); + } catch (Exception e) { + Log.e(TAG, "addPersistentApp: " + e.getMessage()); + } + } + + /** + * 添加保持某应用始终运行名单 + * + * @param packageNames + */ + public void addPersistentApp(List packageNames) { + Log.e(TAG, "addPersistentApp: " + packageNames); + try { + // 添加保持某应用始终运行名单 + mDeviceApplicationManager.addPersistentApp(mAdminName, packageNames); + } catch (Exception e) { + Log.e(TAG, "addPersistentApp: Exception " + e.getMessage()); + } + Log.e(TAG, "setDisallowUninstall: getPersistentApp: " + mDeviceApplicationManager.getPersistentApp(mAdminName)); + } + + /** + * @param pkg 添加保持某应用始终运行名单 + */ + public void removePersistentApp(String pkg) { + Log.e(TAG, "removePersistentApp: " + pkg); + List packageNames = new ArrayList<>(); + packageNames.add(pkg); + try { + // 添加保持某应用始终运行名单 + mDeviceApplicationManager.removePersistentApp(mAdminName, packageNames); + } catch (Exception e) { + Log.e(TAG, "removePersistentApp: " + e.getMessage()); + } + } + + public void removePersistentApp(List packageNames) { + Log.e(TAG, "removePersistentApp: " + packageNames); + try { + // 添加保持某应用始终运行名单 + mDeviceApplicationManager.removePersistentApp(mAdminName, packageNames); + } catch (Exception e) { + Log.e(TAG, "removePersistentApp: " + e.getMessage()); + } + } + + /** + * 添加阻止某应用启动运行名单 + * + * @param packageNames + */ + public void addDisallowedRunningApp(List packageNames) { + if (packageNames == null || packageNames.size() == 0) return; + Log.e(TAG, "addDisallowedRunningApp: packageNames = " + packageNames); + List packageList = mDeviceApplicationManager.getDisallowedRunningApp(mAdminName); + Log.e(TAG, "addDisallowedRunningApp: getDisallowedRunningApp = " + packageList); + try { + mDeviceApplicationManager.addDisallowedRunningApp(mAdminName, packageNames); + } catch (Exception e) { + Log.e(TAG, "addDisallowedRunningApp: " + e.getMessage()); + } + Log.e(TAG, "addDisallowedRunningApp: getDisallowedRunningApp = " + mDeviceApplicationManager.getDisallowedRunningApp(mAdminName)); + } + + /** + * 添加阻止某应用启动运行名单 + * + * @param pkg 添加后,黑名单中的应用不能打开,正在使用的应用也会被停止运行。 + */ + public void addDisallowedRunningApp(String pkg) { + Log.e(TAG, "addDisallowedRunningApp: pkg = " + pkg); + if (TextUtils.isEmpty(pkg)) return; + if (!isAvailable(mContext, pkg)) return; + List packageList = new ArrayList<>(); + packageList.add(pkg); + try { + mDeviceApplicationManager.addDisallowedRunningApp(mAdminName, packageList); + } catch (Exception e) { + Log.e(TAG, "addDisallowedRunningApp: " + e.getMessage()); + } + Log.e(TAG, "addDisallowedRunningApp: getDisallowedRunningApp = " + mDeviceApplicationManager.getDisallowedRunningApp(mAdminName)); + } + + + /** + * 删除阻止某应用启动运行名单 + * + * @param packageNames + */ + public void removeDisallowedRunningApp(List packageNames) { + if (packageNames == null || packageNames.size() == 0) return; + mDeviceApplicationManager.removeDisallowedRunningApp(mAdminName, packageNames); + Log.e(TAG, "removeDisallowedRunningApp: getDisallowedRunningApp = " + mDeviceApplicationManager.getDisallowedRunningApp(mAdminName)); + } + + + /** + * 删除阻止某应用启动运行名单 + * + * @param pkg 待删除的应用包名列表 + */ + public void removeDisallowedRunningApp(String pkg) { + Log.e(TAG, "removeDisallowedRunningApp: pkg = " + pkg); + if (TextUtils.isEmpty(pkg)) return; + List stringList = new ArrayList<>(); + stringList.add(pkg); + mDeviceApplicationManager.removeDisallowedRunningApp(mAdminName, stringList); + Log.e(TAG, "removeDisallowedRunningApp: getDisallowedRunningApp = " + mDeviceApplicationManager.getDisallowedRunningApp(mAdminName)); + } + + /** + * @param pkg + * @return 是否阻止某应用启动 + */ + public boolean isDisallowedRunningApp(String pkg) { + List packageList = mDeviceApplicationManager.getDisallowedRunningApp(mAdminName); + Log.e(TAG, "isDisallowedRunningApp: packageList = " + packageList); + return packageList.contains(pkg); + } + + /** + * 设置 yoyo 助手 + * + * @param disable true: 禁用 yoyo 助手 + */ + public void setYoyoDisable(boolean disable) { + if (disable) { + setYoyoDisabled(true); + } else { + setYoyoDisabled(false); + } + } + + public void setYoyoDisabled(boolean disable) { + Log.e(TAG, "setYoyoDisabled: " + disable); + try { + if (disable) { + addDisallowedRunningApp("com.hihonor.servicecenter"); + } else { + removeDisallowedRunningApp("com.hihonor.servicecenter"); + } + } catch (Exception e) { + Log.e(TAG, "setYoyoDisabled: " + e.getMessage()); + } + } + + /** + * 添加忽略频繁重新启动的应用 + * + * @param packageNames + */ + public void addIgnoreFrequentRelaunchAppList(ArrayList packageNames) { + Log.e(TAG, "addIgnoreFrequentRelaunchAppList: " + packageNames); + try { + if (packageNames != null && packageNames.size() > 0) { + mDeviceApplicationManager.addIgnoreFrequentRelaunchAppList(mAdminName, packageNames); + } + } catch (Exception e) { + Log.e(TAG, "addIgnoreFrequentRelaunchAppList: " + e.getMessage()); + } + } + + /** + * 判断应用是否已安装 + */ + private boolean isAvailable(Context context, String pkg) { + if (TextUtils.isEmpty(pkg)) return false; + try { + PackageManager pm = context.getPackageManager(); + pm.getPackageInfo(pkg, 0); + return true; + } catch (PackageManager.NameNotFoundException e) { + return false; + } catch (Exception e) { + return false; + } + } +} diff --git a/app/src/main/java/com/ttstd/sn/receiver/AoleDeviceAdminReceiver.java b/app/src/main/java/com/ttstd/sn/receiver/AoleDeviceAdminReceiver.java new file mode 100644 index 0000000..f1ef4d6 --- /dev/null +++ b/app/src/main/java/com/ttstd/sn/receiver/AoleDeviceAdminReceiver.java @@ -0,0 +1,20 @@ +package com.ttstd.sn.receiver; + +import android.app.admin.DeviceAdminReceiver; +import android.content.Context; +import android.content.Intent; + +import com.ttstd.sn.R; + +public class AoleDeviceAdminReceiver extends DeviceAdminReceiver { + + @Override + public CharSequence onDisableRequested(Context context, Intent intent) { + return context.getString(R.string.disable_warning); + } + + @Override + public void onEnabled(Context context, Intent intent) { + super.onEnabled(context, intent); + } +} diff --git a/app/src/main/java/com/ttstd/sn/ui/base/BaseActivity.java b/app/src/main/java/com/ttstd/sn/ui/base/BaseActivity.java new file mode 100644 index 0000000..b4220ca --- /dev/null +++ b/app/src/main/java/com/ttstd/sn/ui/base/BaseActivity.java @@ -0,0 +1,55 @@ +package com.ttstd.sn.ui.base; + +import android.os.Bundle; + +import androidx.annotation.CallSuper; +import androidx.annotation.Nullable; + +import com.ttstd.sn.R; +import com.ttstd.sn.ui.base.rx.BaseRxActivity; +import com.bumptech.glide.Glide; +import com.zackratos.ultimatebarx.ultimatebarx.java.UltimateBarX; + +public abstract class BaseActivity extends BaseRxActivity { + + public BaseActivity() { + super(); + } + + @Override + @CallSuper + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); +// StatusBarUtil.init(this); + UltimateBarX.statusBar(this) + .transparent() + .colorRes(R.color.colorPrimaryDark) + .light(true) + .apply(); + UltimateBarX.navigationBar(this) + .transparent() + .colorRes(R.color.colorPrimaryDark) + .light(true) + .apply(); + } + + /** + * 设置布局 + */ + protected abstract int getLayoutId(); + + @Override + public void onTrimMemory(int level) { + super.onTrimMemory(level); + if (level == TRIM_MEMORY_UI_HIDDEN) { + Glide.get(this).clearMemory(); + } + Glide.get(this).trimMemory(level); + } + + @Override + public void onLowMemory() { + super.onLowMemory(); + Glide.get(this).clearMemory(); + } +} diff --git a/app/src/main/java/com/ttstd/sn/ui/base/BaseDataBindingActivity.java b/app/src/main/java/com/ttstd/sn/ui/base/BaseDataBindingActivity.java new file mode 100644 index 0000000..38aea3c --- /dev/null +++ b/app/src/main/java/com/ttstd/sn/ui/base/BaseDataBindingActivity.java @@ -0,0 +1,74 @@ +package com.ttstd.sn.ui.base; + +import android.os.Bundle; + +import androidx.annotation.CallSuper; +import androidx.annotation.Nullable; + +import com.ttstd.sn.R; +import com.ttstd.sn.ui.base.rx.BaseRxActivity; +import com.zackratos.ultimatebarx.ultimatebarx.java.UltimateBarX; + +public abstract class BaseDataBindingActivity extends BaseRxActivity { + + public BaseDataBindingActivity() { + super(); + } + + @Override + @CallSuper + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); +// StatusBarUtil.init(this); + if (initBar()) { + UltimateBarX.statusBar(this) + .transparent() + .colorRes(R.color.colorPrimaryDark) + .light(setNightMode()) + .fitWindow(setfitWindow()) + .apply(); + UltimateBarX.navigationBar(this) + .transparent() + .colorRes(R.color.colorPrimaryDark) + .light(setNightMode()) + .fitWindow(setfitWindow()) + .apply(); + } + initDataBinding(); + initView(); + initData(); + } + + public boolean initBar() { + return true; + } + + /** + * @return 是否是黑色状态栏 + */ +// protected abstract boolean setNightMode(); + public boolean setNightMode() { + return false; + } + + /** + * @return 是否是入侵 + */ +// protected abstract boolean setNightMode(); + public boolean setfitWindow() { + return false; + } + + + protected abstract void initDataBinding(); + + /** + * 初始化视图 + */ + protected abstract void initView(); + + /** + * 初始化数据 + */ + protected abstract void initData(); +} diff --git a/app/src/main/java/com/ttstd/sn/ui/base/BaseTransparentActivity.java b/app/src/main/java/com/ttstd/sn/ui/base/BaseTransparentActivity.java new file mode 100644 index 0000000..fe28802 --- /dev/null +++ b/app/src/main/java/com/ttstd/sn/ui/base/BaseTransparentActivity.java @@ -0,0 +1,51 @@ +package com.ttstd.sn.ui.base; + +import android.os.Bundle; + +import androidx.annotation.CallSuper; +import androidx.annotation.Nullable; + +import com.ttstd.sn.R; +import com.ttstd.sn.ui.base.rx.BaseRxActivity; +import com.zackratos.ultimatebarx.ultimatebarx.java.UltimateBarX; + +public abstract class BaseTransparentActivity extends BaseRxActivity { + + public BaseTransparentActivity() { + super(); + } + + @Override + @CallSuper + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); +// StatusBarUtil.init(this); + UltimateBarX.statusBar(this) + .transparent() + .colorRes(R.color.colorPrimaryDark) + .light(setNightMode()) + .fitWindow(setfitWindow()) + .apply(); + UltimateBarX.navigationBar(this) + .transparent() + .colorRes(R.color.colorPrimaryDark) + .light(setNightMode()) + .fitWindow(setfitWindow()) + .apply(); + } + + /** + * 设置布局 + */ + protected abstract int getLayoutId(); + + /** + * @return 是否是黑色状态栏 + */ + protected abstract boolean setNightMode(); + + /** + * @return 是否是入侵 + */ + protected abstract boolean setfitWindow(); +} diff --git a/app/src/main/java/com/ttstd/sn/ui/base/GlobalCrashHandler.java b/app/src/main/java/com/ttstd/sn/ui/base/GlobalCrashHandler.java new file mode 100644 index 0000000..9d4da54 --- /dev/null +++ b/app/src/main/java/com/ttstd/sn/ui/base/GlobalCrashHandler.java @@ -0,0 +1,192 @@ +package com.ttstd.sn.ui.base; + +import android.content.Context; +import android.content.Intent; +import android.os.Build; +import android.os.Process; +import android.util.Log; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.Thread.UncaughtExceptionHandler; + +public class GlobalCrashHandler implements UncaughtExceptionHandler { + + private static final String TAG = "CrashHandler"; + + private static GlobalCrashHandler instance; + + private Context context; + private UncaughtExceptionHandler defaultHandler; + private Class restartActivityClass; + + private GlobalCrashHandler() { + } + + public static GlobalCrashHandler getInstance() { + if (instance == null) { + synchronized (GlobalCrashHandler.class) { + if (instance == null) { + instance = new GlobalCrashHandler(); + } + } + } + return instance; + } + + /** + * 初始化全局异常捕获 + * + * @param context App Context + * @param restartActivityClass 崩溃后重新打开的 Activity,可传 MainActivity.class + */ + public void init(Context context, Class restartActivityClass) { + this.context = context.getApplicationContext(); + this.restartActivityClass = restartActivityClass; + + // 保存系统默认异常处理器 + defaultHandler = Thread.getDefaultUncaughtExceptionHandler(); + + // 设置当前类为全局异常处理器 + Thread.setDefaultUncaughtExceptionHandler(this); + } + + @Override + public void uncaughtException(Thread thread, Throwable throwable) { + try { + handleException(thread, throwable); + } catch (Exception e) { + Log.e(TAG, "handle crash failed", e); + } + + // 稍微等待一下,确保日志写入完成 + try { + Thread.sleep(800); + } catch (InterruptedException ignored) { + } + + // 重启 App + restartAppByAlarm(); + + // 杀死当前进程 + Process.killProcess(Process.myPid()); + System.exit(10); + } + + private void handleException(Thread thread, Throwable throwable) { + if (throwable == null) { + return; + } + + String crashInfo = getCrashInfo(thread, throwable); + + Log.e(TAG, crashInfo); + + // 这里可以做本地保存 + saveCrashToLocal(crashInfo); + + // 这里可以做服务器上报 + uploadCrashToServer(crashInfo); + } + + private String getCrashInfo(Thread thread, Throwable throwable) { + StringBuilder sb = new StringBuilder(); + + sb.append("========== Crash Start ==========\n"); + sb.append("Thread: ").append(thread.getName()).append("\n\n"); + + sb.append("Device Info:\n"); + sb.append("Brand: ").append(Build.BRAND).append("\n"); + sb.append("Model: ").append(Build.MODEL).append("\n"); + sb.append("Manufacturer: ").append(Build.MANUFACTURER).append("\n"); + sb.append("Android Version: ").append(Build.VERSION.RELEASE).append("\n"); + sb.append("SDK: ").append(Build.VERSION.SDK_INT).append("\n\n"); + + sb.append("Exception:\n"); + sb.append(getStackTrace(throwable)); + sb.append("\n========== Crash End ==========\n"); + + return sb.toString(); + } + + private String getStackTrace(Throwable throwable) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + throwable.printStackTrace(pw); + pw.close(); + return sw.toString(); + } + + private void saveCrashToLocal(String crashInfo) { + // 你可以写入文件,例如: + // /Android/data/你的包名/files/crash/crash_xxx.log + + try { + File dir = new File(context.getExternalFilesDir(null), "crash"); + if (!dir.exists()) { + dir.mkdirs(); + } + + String fileName = "crash_" + System.currentTimeMillis() + ".log"; + File file = new File(dir, fileName); + + FileOutputStream fos = new FileOutputStream(file); + fos.write(crashInfo.getBytes("UTF-8")); + fos.flush(); + fos.close(); + + Log.e(TAG, "crash log saved: " + file.getAbsolutePath()); + + } catch (Exception e) { + Log.e(TAG, "save crash log failed", e); + } + } + + private void uploadCrashToServer(String crashInfo) { + // 这里可以异步上传到服务器 + // 注意:崩溃时进程即将退出,不建议做太耗时操作 + Log.e(TAG, "uploadCrashToServer"); + } + + private void restartApp() { + if (context == null || restartActivityClass == null) { + return; + } + + Intent intent = new Intent(context, restartActivityClass); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); + + context.startActivity(intent); + } + + private void restartAppByAlarm() { + if (context == null || restartActivityClass == null) { + return; + } + + Intent intent = new Intent(context, restartActivityClass); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); + + android.app.PendingIntent pendingIntent = android.app.PendingIntent.getActivity( + context, + 10086, + intent, + android.app.PendingIntent.FLAG_ONE_SHOT | android.app.PendingIntent.FLAG_IMMUTABLE + ); + + android.app.AlarmManager alarmManager = + (android.app.AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + + if (alarmManager != null) { + alarmManager.set( + android.app.AlarmManager.RTC, + System.currentTimeMillis() + 1000, + pendingIntent + ); + } + } +} diff --git a/app/src/main/java/com/ttstd/sn/ui/base/mvvm/BaseMvvmActivity.java b/app/src/main/java/com/ttstd/sn/ui/base/mvvm/BaseMvvmActivity.java new file mode 100644 index 0000000..5c1eb51 --- /dev/null +++ b/app/src/main/java/com/ttstd/sn/ui/base/mvvm/BaseMvvmActivity.java @@ -0,0 +1,75 @@ +package com.ttstd.sn.ui.base.mvvm; + +import android.os.Bundle; + +import androidx.annotation.Nullable; +import androidx.databinding.DataBindingUtil; +import androidx.databinding.ViewDataBinding; +import androidx.lifecycle.ViewModelProvider; + +import com.ttstd.sn.ui.base.BaseTransparentActivity; + +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; + +/** + * MVVM 基类 Activity。 + *

+ * 采用 Android 标准 MVVM 架构: + * - ViewModel 通过 {@link ViewModelProvider} 创建,生命周期由系统统一管理; + * - 通过 DataBinding 将数据与 UI 绑定,UI 变化自动反映到绑定数据上; + * - ViewModel 不持有 Activity / Context 引用,避免内存泄漏。 + * + * @param ViewModel 类型,需继承 {@link BaseViewModel} + * @param 页面数据绑定类型,需继承 {@link ViewDataBinding} + */ +public abstract class BaseMvvmActivity, VDB extends ViewDataBinding> extends BaseTransparentActivity { + + protected VM mViewModel; + protected VDB mViewDataBinding; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + // ViewDataBinding + mViewDataBinding = DataBindingUtil.setContentView(this, getLayoutId()); + mViewDataBinding.setLifecycleOwner(this); + // ViewModel(Android 标准:通过 ViewModelProvider 创建) + mViewModel = createViewModel(); + if (mViewModel != null) { + mViewModel.setVDBinding(mViewDataBinding); + } + initDataBinding(); + initView(); + initData(); + } + + /** + * 创建 ViewModel 实例,默认根据泛型参数通过 {@link ViewModelProvider} 反射创建。 + * 子类如需自定义 ViewModel 创建方式,可重写此方法。 + * + * @return ViewModel 实例;若泛型类型为抽象类则返回 null + */ + protected VM createViewModel() { + Class vmClass = (Class) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0]; + if (Modifier.isAbstract(vmClass.getModifiers())) { + return null; + } + return new ViewModelProvider(this).get(vmClass); + } + + /** + * 初始化数据绑定(在 ViewModel 创建并注入 binding 后调用) + */ + protected abstract void initDataBinding(); + + /** + * 初始化视图 + */ + protected abstract void initView(); + + /** + * 初始化数据 + */ + protected abstract void initData(); +} diff --git a/app/src/main/java/com/ttstd/sn/ui/base/mvvm/BaseViewModel.java b/app/src/main/java/com/ttstd/sn/ui/base/mvvm/BaseViewModel.java new file mode 100644 index 0000000..c0da118 --- /dev/null +++ b/app/src/main/java/com/ttstd/sn/ui/base/mvvm/BaseViewModel.java @@ -0,0 +1,48 @@ +package com.ttstd.sn.ui.base.mvvm; + +import androidx.databinding.ViewDataBinding; +import androidx.lifecycle.ViewModel; + +/** + * 所有 ViewModel 的基类。 + *

+ * 采用 Android 标准 MVVM 架构: + * 1. ViewModel 继承自 {@link ViewModel},生命周期由系统(ViewModelStore)统一管理; + * 2. ViewModel 不持有 Context / Activity 引用,避免内存泄漏; + * 3. 通过数据绑定(DataBinding)将 UI 与 ViewModel 中的数据建立绑定关系。 + * + * @param 当前 ViewModel 对应的页面数据绑定类型 + */ +public abstract class BaseViewModel extends ViewModel { + + /** + * 当前 ViewModel 对应的页面 binding + */ + protected VDB binding; + + /** + * 注入数据绑定实例(由 View 层在初始化时调用) + * + * @param binding 页面数据绑定实例 + */ + public void setVDBinding(VDB binding) { + this.binding = binding; + } + + /** + * 获取数据绑定实例 + * + * @return 页面数据绑定实例 + */ + public VDB getVDBinding() { + if (binding == null) { + throw new NullPointerException("BaseViewModel >> getVDBinding >> null!!!"); + } + return binding; + } + + /** + * 页面销毁时清理资源(由 View 层在 onDestroy 时调用) + */ + public abstract void onDestroy(); +} diff --git a/app/src/main/java/com/ttstd/sn/ui/base/rx/BaseRxActivity.java b/app/src/main/java/com/ttstd/sn/ui/base/rx/BaseRxActivity.java new file mode 100644 index 0000000..09f6884 --- /dev/null +++ b/app/src/main/java/com/ttstd/sn/ui/base/rx/BaseRxActivity.java @@ -0,0 +1,93 @@ +package com.ttstd.sn.ui.base.rx; + +import android.os.Bundle; + +import androidx.annotation.CallSuper; +import androidx.annotation.CheckResult; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; + +import com.trello.rxlifecycle4.LifecycleProvider; +import com.trello.rxlifecycle4.LifecycleTransformer; +import com.trello.rxlifecycle4.RxLifecycle; +import com.trello.rxlifecycle4.android.ActivityEvent; +import com.trello.rxlifecycle4.android.RxLifecycleAndroid; + +import io.reactivex.rxjava3.core.Observable; +import io.reactivex.rxjava3.subjects.BehaviorSubject; + +/** + * {@link com.trello.rxlifecycle4.components.RxActivity} + * copied form RxActivity} + */ +public abstract class BaseRxActivity extends AppCompatActivity implements LifecycleProvider { + private final BehaviorSubject lifecycleSubject = BehaviorSubject.create(); + + public BehaviorSubject getLifecycleSubject() { + return lifecycleSubject; + } + + @Override + @NonNull + @CheckResult + public final Observable lifecycle() { + return lifecycleSubject.hide(); + } + + @Override + @NonNull + @CheckResult + public final LifecycleTransformer bindUntilEvent(@NonNull ActivityEvent event) { + return RxLifecycle.bindUntilEvent(lifecycleSubject, event); + } + + @Override + @NonNull + @CheckResult + public final LifecycleTransformer bindToLifecycle() { + return RxLifecycleAndroid.bindActivity(lifecycleSubject); + } + + @Override + @CallSuper + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + lifecycleSubject.onNext(ActivityEvent.CREATE); + } + + @Override + @CallSuper + protected void onStart() { + super.onStart(); + lifecycleSubject.onNext(ActivityEvent.START); + } + + @Override + @CallSuper + protected void onResume() { + super.onResume(); + lifecycleSubject.onNext(ActivityEvent.RESUME); + } + + @Override + @CallSuper + protected void onPause() { + lifecycleSubject.onNext(ActivityEvent.PAUSE); + super.onPause(); + } + + @Override + @CallSuper + protected void onStop() { + lifecycleSubject.onNext(ActivityEvent.STOP); + super.onStop(); + } + + @Override + @CallSuper + protected void onDestroy() { + lifecycleSubject.onNext(ActivityEvent.DESTROY); + super.onDestroy(); + } +} diff --git a/app/src/main/java/com/ttstd/sn/ui/base/rx/BaseRxService.java b/app/src/main/java/com/ttstd/sn/ui/base/rx/BaseRxService.java new file mode 100644 index 0000000..9e43ff9 --- /dev/null +++ b/app/src/main/java/com/ttstd/sn/ui/base/rx/BaseRxService.java @@ -0,0 +1,52 @@ +package com.ttstd.sn.ui.base.rx; + +import android.app.Service; + +import com.trello.rxlifecycle4.LifecycleProvider; +import com.trello.rxlifecycle4.LifecycleTransformer; +import com.trello.rxlifecycle4.RxLifecycle; +import com.trello.rxlifecycle4.android.ActivityEvent; +import com.trello.rxlifecycle4.android.RxLifecycleAndroid; + +import org.jetbrains.annotations.NotNull; + +import io.reactivex.rxjava3.core.Observable; +import io.reactivex.rxjava3.subjects.BehaviorSubject; + +public abstract class BaseRxService extends Service implements LifecycleProvider { + private final BehaviorSubject lifecycleSubject = BehaviorSubject.create(); + + public BehaviorSubject getLifecycleSubject() { + return lifecycleSubject; + } + + @NotNull + @Override + public Observable lifecycle() { + return lifecycleSubject.hide(); + } + + @NotNull + @Override + public LifecycleTransformer bindUntilEvent(@NotNull ActivityEvent event) { + return RxLifecycle.bindUntilEvent(lifecycleSubject, event); + } + + @NotNull + @Override + public LifecycleTransformer bindToLifecycle() { + return RxLifecycleAndroid.bindActivity(lifecycleSubject); + } + + @Override + public void onCreate() { + super.onCreate(); + lifecycleSubject.onNext(ActivityEvent.CREATE); + } + + @Override + public void onDestroy() { + super.onDestroy(); + lifecycleSubject.onNext(ActivityEvent.DESTROY); + } +} diff --git a/app/src/main/java/com/ttstd/sn/ui/main/MainActivity.java b/app/src/main/java/com/ttstd/sn/ui/main/MainActivity.java new file mode 100644 index 0000000..5cd8901 --- /dev/null +++ b/app/src/main/java/com/ttstd/sn/ui/main/MainActivity.java @@ -0,0 +1,179 @@ +package com.ttstd.sn.ui.main; + +import android.app.admin.DevicePolicyManager; +import android.content.ComponentName; +import android.content.Context; +import android.util.Log; +import android.view.View; +import android.widget.CompoundButton; + +import com.ttstd.sn.R; +import com.ttstd.sn.mdm.AdminManager; +import com.ttstd.sn.databinding.ActivityMainBinding; +import com.ttstd.sn.receiver.AoleDeviceAdminReceiver; +import com.ttstd.sn.ui.base.mvvm.BaseMvvmActivity; +import com.hihonor.android.app.admin.DeviceApplicationManager; +import com.hihonor.android.app.admin.DeviceControlManager; +import com.hihonor.android.app.admin.DeviceHwSystemManager; +import com.hihonor.android.app.admin.DevicePackageManager; +import com.hihonor.android.app.admin.DevicePasswordManager; +import com.hihonor.android.app.admin.DeviceRestrictionManager; +import com.hihonor.android.app.admin.DeviceSettingsManager; + + +public class MainActivity extends BaseMvvmActivity { + private static final String TAG = "DebugActivity"; + + private DevicePolicyManager mDevicePolicyManager; + private DeviceRestrictionManager mDeviceRestrictionManager; + private DeviceControlManager mDeviceControlManager; + private DeviceApplicationManager mDeviceApplicationManager; + private DeviceHwSystemManager mDeviceHwSystemManager; + private DeviceSettingsManager mDeviceSettingsManager; + private DevicePasswordManager mDevicePasswordManager; + + /*设备包管理类*/ + private DevicePackageManager mDevicePackageManager; + + private ComponentName mAdminName; + + @Override + protected int getLayoutId() { + return R.layout.activity_main; + } + + @Override + protected boolean setNightMode() { + return true; + } + + @Override + protected boolean setfitWindow() { + return false; + } + + @Override + protected void initDataBinding() { + mViewDataBinding.setClick(new BtnClick()); + } + + @Override + protected void initView() { + mDevicePolicyManager = (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE); + mAdminName = new ComponentName(this, AoleDeviceAdminReceiver.class); + + mDeviceRestrictionManager = new DeviceRestrictionManager(); + mDeviceControlManager = new DeviceControlManager(); + mDeviceApplicationManager = new DeviceApplicationManager(); + mDeviceHwSystemManager = new DeviceHwSystemManager(); + mDeviceSettingsManager = new DeviceSettingsManager(); + mDevicePackageManager = new DevicePackageManager(); + mDevicePasswordManager = new DevicePasswordManager(); + + mViewDataBinding.swWindow.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + try { + mDeviceRestrictionManager.setMultiWindowDisabled(mAdminName, isChecked); + } catch (Exception e) { + Log.e(TAG, "onCheckedChanged: " + e.getMessage()); + } + } + }); + mViewDataBinding.swYoyo.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + mDeviceRestrictionManager.setVoiceAssistantButtonDisabled(mAdminName, isChecked); + if (isChecked) { + AdminManager.getInstance().removeDisallowedRunningApp("com.hihonor.servicecenter"); + } else { + AdminManager.getInstance().addDisallowedRunningApp("com.hihonor.servicecenter"); + } + } + }); + mViewDataBinding.swRestore.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + mDeviceSettingsManager.setRestoreFactoryDisabled(mAdminName, isChecked); + } + }); + mViewDataBinding.swUsbDebug.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + mDeviceRestrictionManager.setUSBDataDisabled(mAdminName, isChecked); + mDeviceControlManager.turnOnUsbDebugMode(mAdminName, !isChecked); + } + }); + mViewDataBinding.swRemoveAdmin.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (isChecked) { + mDeviceControlManager.setForcedActiveDeviceAdmin(mAdminName, MainActivity.this); + } else { + mDeviceControlManager.removeActiveDeviceAdmin(mAdminName); + mDeviceControlManager.clearDeviceOwnerApp(); + } + } + }); + mViewDataBinding.swQuickTools.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + try { + mDevicePasswordManager.setQuickToolsDisabled(mAdminName, isChecked); + } catch (Exception e) { + Log.e(TAG, "onCheckedChanged: " + e.getMessage()); + } + } + }); + } + + @Override + protected void initData() { + + } + + @Override + protected void onResume() { + super.onResume(); + getAdminStatus(); + } + + private void getAdminStatus() { + if (isActiveMe()) { + mViewDataBinding.tvAdmin.setText("已激活"); + mViewDataBinding.swWindow.setChecked(mDeviceRestrictionManager.isMultiWindowDisabled(mAdminName)); + mViewDataBinding.swYoyo.setChecked(mDeviceRestrictionManager.isVoiceAssistantButtonDisabled(mAdminName)); + mViewDataBinding.swRestore.setChecked(mDeviceSettingsManager.isRestoreFactoryDisabled(mAdminName)); + mViewDataBinding.swUsbDebug.setChecked(mDeviceRestrictionManager.isUSBDataDisabled(mAdminName)); + mViewDataBinding.swRemoveAdmin.setChecked(mDeviceControlManager.isForcedActiveDeviceAdmin(mAdminName)); + mViewDataBinding.swQuickTools.setChecked(mDevicePasswordManager.isQuickToolsDisabled(mAdminName)); + } else { + mViewDataBinding.tvAdmin.setText("未激活"); + } + } + + /** + * 判断是否激活 + * + * @return + */ + private boolean isActiveMe() { + boolean active; + if (mDevicePolicyManager == null) { + active = false; + } else { + active = mDevicePolicyManager.isAdminActive(mAdminName); + } + Log.e(TAG, "isActiveMe: active = " + active); + return active; + } + + public class BtnClick { + + public void refresh(View view) { + getAdminStatus(); + } + } + + +} diff --git a/app/src/main/java/com/ttstd/sn/ui/main/MainUiState.java b/app/src/main/java/com/ttstd/sn/ui/main/MainUiState.java new file mode 100644 index 0000000..41e94e7 --- /dev/null +++ b/app/src/main/java/com/ttstd/sn/ui/main/MainUiState.java @@ -0,0 +1,109 @@ +package com.ttstd.sn.ui.main; + +/** + * 主界面 UI 状态。 + *

+ * 采用不可变数据类保存页面渲染所需的状态, + * 由 {@link MainViewModel} 持有并通过观察者(如 LiveData)下发给 View 层。 + */ +public class MainUiState { + + private final boolean deviceAdminActive; + private final boolean multiWindowDisabled; + private final boolean voiceAssistantDisabled; + private final boolean restoreFactoryDisabled; + private final boolean usbDebugDisabled; + private final boolean forcedDeviceAdmin; + private final boolean quickToolsDisabled; + + private MainUiState(Builder builder) { + this.deviceAdminActive = builder.deviceAdminActive; + this.multiWindowDisabled = builder.multiWindowDisabled; + this.voiceAssistantDisabled = builder.voiceAssistantDisabled; + this.restoreFactoryDisabled = builder.restoreFactoryDisabled; + this.usbDebugDisabled = builder.usbDebugDisabled; + this.forcedDeviceAdmin = builder.forcedDeviceAdmin; + this.quickToolsDisabled = builder.quickToolsDisabled; + } + + public boolean isDeviceAdminActive() { + return deviceAdminActive; + } + + public boolean isMultiWindowDisabled() { + return multiWindowDisabled; + } + + public boolean isVoiceAssistantDisabled() { + return voiceAssistantDisabled; + } + + public boolean isRestoreFactoryDisabled() { + return restoreFactoryDisabled; + } + + public boolean isUsbDebugDisabled() { + return usbDebugDisabled; + } + + public boolean isForcedDeviceAdmin() { + return forcedDeviceAdmin; + } + + public boolean isQuickToolsDisabled() { + return quickToolsDisabled; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private boolean deviceAdminActive; + private boolean multiWindowDisabled; + private boolean voiceAssistantDisabled; + private boolean restoreFactoryDisabled; + private boolean usbDebugDisabled; + private boolean forcedDeviceAdmin; + private boolean quickToolsDisabled; + + public Builder deviceAdminActive(boolean deviceAdminActive) { + this.deviceAdminActive = deviceAdminActive; + return this; + } + + public Builder multiWindowDisabled(boolean multiWindowDisabled) { + this.multiWindowDisabled = multiWindowDisabled; + return this; + } + + public Builder voiceAssistantDisabled(boolean voiceAssistantDisabled) { + this.voiceAssistantDisabled = voiceAssistantDisabled; + return this; + } + + public Builder restoreFactoryDisabled(boolean restoreFactoryDisabled) { + this.restoreFactoryDisabled = restoreFactoryDisabled; + return this; + } + + public Builder usbDebugDisabled(boolean usbDebugDisabled) { + this.usbDebugDisabled = usbDebugDisabled; + return this; + } + + public Builder forcedDeviceAdmin(boolean forcedDeviceAdmin) { + this.forcedDeviceAdmin = forcedDeviceAdmin; + return this; + } + + public Builder quickToolsDisabled(boolean quickToolsDisabled) { + this.quickToolsDisabled = quickToolsDisabled; + return this; + } + + public MainUiState build() { + return new MainUiState(this); + } + } +} diff --git a/app/src/main/java/com/ttstd/sn/ui/main/MainViewModel.java b/app/src/main/java/com/ttstd/sn/ui/main/MainViewModel.java new file mode 100644 index 0000000..8fd33f2 --- /dev/null +++ b/app/src/main/java/com/ttstd/sn/ui/main/MainViewModel.java @@ -0,0 +1,17 @@ +package com.ttstd.sn.ui.main; + +import com.ttstd.sn.databinding.ActivityMainBinding; +import com.ttstd.sn.ui.base.mvvm.BaseViewModel; + +public class MainViewModel extends BaseViewModel { + + @Override + public ActivityMainBinding getVDBinding() { + return binding; + } + + @Override + public void onDestroy() { + + } +} diff --git a/app/src/main/java/com/ttstd/sn/utils/DateUtils.java b/app/src/main/java/com/ttstd/sn/utils/DateUtils.java new file mode 100644 index 0000000..3f13b4b --- /dev/null +++ b/app/src/main/java/com/ttstd/sn/utils/DateUtils.java @@ -0,0 +1,73 @@ +package com.ttstd.sn.utils; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; + +/** + * 日期时间工具类 + */ +public class DateUtils { + + public static final String PATTERN_DATE = "yyyy-MM-dd"; + public static final String PATTERN_TIME = "HH:mm:ss"; + public static final String PATTERN_DATETIME = "yyyy-MM-dd HH:mm:ss"; + public static final String PATTERN_DATETIME_COMPACT = "yyyyMMdd_HHmmss"; + + private DateUtils() { + } + + /** + * 将时间戳格式化为指定格式的字符串 + * + * @param timestamp 毫秒时间戳 + * @param pattern 日期格式 + */ + public static String format(long timestamp, String pattern) { + return new SimpleDateFormat(pattern, Locale.getDefault()).format(new Date(timestamp)); + } + + /** + * 将 Date 格式化为指定格式的字符串 + */ + public static String format(Date date, String pattern) { + if (date == null) return ""; + return new SimpleDateFormat(pattern, Locale.getDefault()).format(date); + } + + /** + * 解析字符串为时间戳 + * + * @param time 日期字符串 + * @param pattern 日期格式 + * @return 毫秒时间戳;解析失败返回 -1 + */ + public static long parse(String time, String pattern) { + if (time == null || time.trim().isEmpty()) return -1; + SimpleDateFormat sdf = new SimpleDateFormat(pattern, Locale.getDefault()); + try { + Date date = sdf.parse(time); + return date == null ? -1 : date.getTime(); + } catch (ParseException e) { + return -1; + } + } + + /** + * 当前毫秒时间戳 + */ + public static long now() { + return System.currentTimeMillis(); + } + + /** + * 获取指定时区的当前时间 + */ + public static String now(String pattern, String timeZoneId) { + SimpleDateFormat sdf = new SimpleDateFormat(pattern, Locale.getDefault()); + sdf.setTimeZone(TimeZone.getTimeZone(timeZoneId)); + return sdf.format(new Date()); + } +} diff --git a/app/src/main/java/com/ttstd/sn/utils/NetworkUtils.java b/app/src/main/java/com/ttstd/sn/utils/NetworkUtils.java new file mode 100644 index 0000000..e4829d9 --- /dev/null +++ b/app/src/main/java/com/ttstd/sn/utils/NetworkUtils.java @@ -0,0 +1,84 @@ +package com.ttstd.sn.utils; + +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.net.NetworkInfo; + +/** + * 网络工具类 + */ +public class NetworkUtils { + + private NetworkUtils() { + } + + /** + * 判断当前是否有可用网络连接 + */ + public static boolean isNetworkAvailable(Context context) { + if (context == null) return false; + ConnectivityManager cm = + (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + if (cm == null) return false; + + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { + Network network = cm.getActiveNetwork(); + if (network == null) return false; + NetworkCapabilities capabilities = cm.getNetworkCapabilities(network); + return capabilities != null + && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); + } + + @SuppressWarnings("deprecation") + NetworkInfo activeInfo = cm.getActiveNetworkInfo(); + return activeInfo != null && activeInfo.isConnected(); + } + + /** + * 判断当前是否为 WIFI 网络 + */ + public static boolean isWifi(Context context) { + if (context == null) return false; + ConnectivityManager cm = + (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + if (cm == null) return false; + + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { + Network network = cm.getActiveNetwork(); + if (network == null) return false; + NetworkCapabilities capabilities = cm.getNetworkCapabilities(network); + return capabilities != null + && capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI); + } + + @SuppressWarnings("deprecation") + NetworkInfo activeInfo = cm.getActiveNetworkInfo(); + return activeInfo != null + && activeInfo.getType() == ConnectivityManager.TYPE_WIFI; + } + + /** + * 判断当前是否为移动网络 + */ + public static boolean isMobileData(Context context) { + if (context == null) return false; + ConnectivityManager cm = + (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + if (cm == null) return false; + + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { + Network network = cm.getActiveNetwork(); + if (network == null) return false; + NetworkCapabilities capabilities = cm.getNetworkCapabilities(network); + return capabilities != null + && capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR); + } + + @SuppressWarnings("deprecation") + NetworkInfo activeInfo = cm.getActiveNetworkInfo(); + return activeInfo != null + && activeInfo.getType() == ConnectivityManager.TYPE_MOBILE; + } +} diff --git a/app/src/main/res/drawable/bt_blue_background.xml b/app/src/main/res/drawable/bt_blue_background.xml new file mode 100644 index 0000000..36f3af7 --- /dev/null +++ b/app/src/main/res/drawable/bt_blue_background.xml @@ -0,0 +1,18 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/main_card_bg.xml b/app/src/main/res/drawable/main_card_bg.xml new file mode 100644 index 0000000..d2a29ef --- /dev/null +++ b/app/src/main/res/drawable/main_card_bg.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..b102a38 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,338 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..cde69bc Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..c133a0c Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..bfa42f0 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..324e72c Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..aee44e1 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..447a5af --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,66 @@ + + + #efefef + #303F9F + #4880ff + + + + #ff797979 + #878686 + #ffffff + #d3d1d1 + #383749 + #E6000000 + #80000000 + #00ffffff + #FFF45C + #80808080 + + #00EE76 + + #B44600 + #80000000 + + #C55C00 + #BF5D13 + #F19149 + + #626C4C + + #023E6C + #A3D568 + #F69200 + + #18B6B1 + #07BA63 + + #BEBDBF + + + #999999 + #68cc21 + + #797979 + #B3B1B1 + #00a0e9 + + @color/white + #ff797979 + #55b68a + #8f82bc + #4880ff + #3e76f6 + #D3D3D3 + #494949 + + #CD000000 + #4880ff + #4880ff + #D1D1D1 + #F19149 + #9a9a9a + #1f1f1f + #989898 + + \ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml new file mode 100644 index 0000000..8c2f534 --- /dev/null +++ b/app/src/main/res/values/dimens.xml @@ -0,0 +1,451 @@ + + + + + @dimen/dp_15 + + 16dp + 16dp + + + + -60dp + -30dp + -20dp + -12dp + -10dp + -8dp + -5dp + -2dp + -1dp + 0dp + 0.1dp + 0.5dp + 1dp + 1.5dp + 2dp + 2.5dp + 3dp + 3.5dp + 4dp + 4.5dp + 5dp + 6dp + 7dp + 8dp + 9dp + 10dp + 11dp + 12dp + 13dp + 14dp + 15dp + 16dp + 17dp + 18dp + 19dp + 20dp + 21dp + 22dp + 23dp + 24dp + 25dp + 26dp + 27dp + 28dp + 29dp + 30dp + 31dp + 32dp + 33dp + 34dp + 35dp + 36dp + 37dp + 38dp + 39dp + 40dp + 41dp + 42dp + 43dp + 44dp + 45dp + 46dp + 47dp + 48dp + 49dp + 50dp + 51dp + 52dp + 53dp + 54dp + 55dp + 56dp + 57dp + 58dp + 59dp + 60dp + 61dp + 62dp + 63dp + 64dp + 65dp + 66dp + 67dp + 68dp + 69dp + 70dp + 71dp + 72dp + 73dp + 74dp + 75dp + 76dp + 77dp + 78dp + 79dp + 80dp + 81dp + 82dp + 83dp + 84dp + 85dp + 86dp + 87dp + 88dp + 89dp + 90dp + 91dp + 92dp + 93dp + 94dp + 95dp + 96dp + 97dp + 98dp + 99dp + 100dp + 101dp + 102dp + 103dp + 104dp + 105dp + 106dp + 107dp + 108dp + 109dp + 110dp + 111dp + 112dp + 113dp + 114dp + 115dp + 116dp + 117dp + 118dp + 119dp + 120dp + 121dp + 122dp + 123dp + 124dp + 125dp + 126dp + 127dp + 128dp + 129dp + 130dp + 131dp + 132dp + 133dp + 134dp + 135dp + 136dp + 137dp + 138dp + 139dp + 140dp + 141dp + 142dp + 143dp + 144dp + 145dp + 146dp + 147dp + 148dp + 149dp + 150dp + 151dp + 152dp + 153dp + 154dp + 155dp + 156dp + 157dp + 158dp + 159dp + 160dp + 161dp + 162dp + 163dp + 164dp + 165dp + 166dp + 167dp + 168dp + 169dp + 170dp + 171dp + 172dp + 173dp + 174dp + 175dp + 176dp + 177dp + 178dp + 179dp + 180dp + 181dp + 182dp + 183dp + 184dp + 185dp + 186dp + 187dp + 188dp + 189dp + 190dp + 191dp + 192dp + 193dp + 194dp + 195dp + 196dp + 197dp + 198dp + 199dp + 200dp + 201dp + 202dp + 203dp + 204dp + 205dp + 206dp + 207dp + 208dp + 209dp + 210dp + 211dp + 212dp + 213dp + 214dp + 215dp + 216dp + 217dp + 218dp + 219dp + 220dp + 221dp + 222dp + 223dp + 224dp + 225dp + 226dp + 227dp + 228dp + 229dp + 230dp + 231dp + 232dp + 233dp + 234dp + 235dp + 236dp + 237dp + 238dp + 239dp + 240dp + 241dp + 242dp + 243dp + 244dp + 245dp + 246dp + 247dp + 248dp + 249dp + 250dp + 251dp + 252dp + 253dp + 254dp + 255dp + 256dp + 257dp + 258dp + 259dp + 260dp + 261dp + 262dp + 263dp + 264dp + 265dp + 266dp + 267dp + 268dp + 269dp + 270dp + 271dp + 272dp + 273dp + 274dp + 275dp + 276dp + 277dp + 278dp + 279dp + 280dp + 281dp + 282dp + 283dp + 284dp + 285dp + 286dp + 287dp + 288dp + 289dp + 290dp + 291dp + 292dp + 293dp + 294dp + 295dp + 296dp + 297dp + 298dp + 299dp + 300dp + 301dp + 302dp + 303dp + 304dp + 305dp + 306dp + 307dp + 308dp + 309dp + 310dp + 311dp + 312dp + 313dp + 314dp + 315dp + 316dp + 317dp + 318dp + 319dp + 320dp + 321dp + 322dp + 323dp + 324dp + 325dp + 326dp + 327dp + 328dp + 329dp + 330dp + 331dp + 332dp + 333dp + 334dp + 335dp + 336dp + 337dp + 338dp + 339dp + 340dp + 341dp + 342dp + 343dp + 344dp + 345dp + 346dp + 347dp + 348dp + 349dp + 350dp + 351dp + 352dp + 353dp + 354dp + 355dp + 356dp + 357dp + 358dp + 359dp + 360dp + 365dp + 370dp + 400dp + 410dp + 422dp + 472dp + 500dp + 600dp + 640dp + 720dp + + + 1sp + 2sp + 3sp + 4sp + 5sp + 6sp + 7sp + 8sp + 9sp + 10sp + 11sp + 12sp + 13sp + 14sp + 15sp + 16sp + 17sp + 18sp + 19sp + 20sp + 21sp + 22sp + 23sp + 24sp + 25sp + 26sp + 27sp + 28sp + 29sp + 30sp + 31sp + 32sp + 33sp + 34sp + 35sp + 36sp + 37sp + 38sp + 39sp + 40sp + 41sp + 42sp + 43sp + 44sp + 45sp + 46sp + 47sp + 48sp + 49sp + 50sp + + diff --git a/app/src/main/res/values/string_magic_os.xml b/app/src/main/res/values/string_magic_os.xml new file mode 100644 index 0000000..71264f7 --- /dev/null +++ b/app/src/main/res/values/string_magic_os.xml @@ -0,0 +1,40 @@ + + + + "当前状态:" + "当前状态:" + "当前状态:" + 已禁用 + 未禁用 + 已保活 + 未保活 + 禁用WiFi + 启用WiFi + 将该应用保活 + 取消该应用保活 + 启用截屏 + 禁用截屏 + 启用数据连接 + 该应用未激活 + 无操作权限 + 本应用只支持MAGIC4及以上的版本 + 取消激活后将导致本软件的部分功能无法正常工作,您确定要取消激活么? + 同意 + 退出 + 已阅读 + 不再提示 + 免责声明与软件许可协议 + 权限使用声明 + 若您点击同意并开始使用本软件,代表您已阅读并接受《免责声明与软件许可协议》 + 请选择以下模式中的一种进行设备管理器的激活。 + 用户确认激活模式即普通的激活方式,激活后用户可随时解除激活。 + 强制激活模式只在企业定制版本(通过荣耀商城企业定制服务进行定制的终端版本)上生效,用户只能点击激活,激活成功后无法手动解除激活。 + 延时解除激活模式请先输入延迟时间(范围在1~72的数字),激活成功后用户在设定的延迟时间(单位:小时)过后才能解除激活设备管理器。 + 用户确认激活模式 + 强制激活模式 + 延时解除激活模式 + 请输入延迟时间,范围:1~72 + 解除激活设备管理器 + setForcedActiveDeviceAdmin error + setDelayDeactiveDeviceAdmin error + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..5973aad --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,9 @@ + + + 设备管理 + - + + 为关怀系统提供管控功能,请激活后使用 + 开启我吧 + + \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..8f85611 --- /dev/null +++ b/app/src/main/res/values/styles.xml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/xml/device_admin_sample.xml b/app/src/main/res/xml/device_admin_sample.xml new file mode 100644 index 0000000..b676b50 --- /dev/null +++ b/app/src/main/res/xml/device_admin_sample.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/file_paths.xml b/app/src/main/res/xml/file_paths.xml new file mode 100644 index 0000000..8131b84 --- /dev/null +++ b/app/src/main/res/xml/file_paths.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/network.xml b/app/src/main/res/xml/network.xml new file mode 100644 index 0000000..dca93c0 --- /dev/null +++ b/app/src/main/res/xml/network.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..c52ea2c --- /dev/null +++ b/build.gradle @@ -0,0 +1,64 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + repositories { + google() + mavenCentral() + maven { url 'https://jitpack.io' } + maven { url 'https://maven.google.com' } + maven { url 'https://developer.huawei.com/repo/' } + maven { url 'https://developer.hihonor.com/repo' } + maven { url 'https://maven.aliyun.com/repository/central' } + maven { url 'https://maven.aliyun.com/repository/jcenter' } + maven { url 'https://maven.aliyun.com/repository/public' } + maven { url 'https://maven.aliyun.com/repository/google' } + maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } + } + dependencies { + classpath 'com.android.tools.build:gradle:8.13.2' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + configurations.all { + resolutionStrategy { + force 'androidx.constraintlayout:constraintlayout:2.0.4' + force 'androidx.core:core:1.5.0' + + //for targetSdkVersion 29 + force 'androidx.activity:activity:1.2.2' + force 'androidx.lifecycle:lifecycle-livedata:2.3.1' + force 'androidx.lifecycle:lifecycle-livedata-core:2.3.1' + force 'androidx.lifecycle:lifecycle-runtime:2.3.1' + force 'androidx.lifecycle:lifecycle-viewmodel:2.3.1' + force 'androidx.lifecycle:lifecycle-viewmodel-savedstate:2.3.1' + force 'androidx.lifecycle:lifecycle-service:2.3.1' + force 'androidx.lifecycle:lifecycle-process:2.3.1' + force 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1' + force 'androidx.savedstate:savedstate:1.1.0' + force 'androidx.profileinstaller:profileinstaller:1.1.0' + force 'androidx.startup:startup-runtime:1.0.0' + force 'androidx.arch.core:core-runtime:2.1.0' + force 'androidx.sqlite:sqlite:2.1.0' + force 'androidx.sqlite:sqlite-framework:2.1.0' + force 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0' + } + } + + repositories { + google() + mavenCentral() + maven { url 'https://jitpack.io' } + maven { url 'https://maven.google.com' } + maven { url 'https://developer.huawei.com/repo/' } + maven { url 'https://developer.hihonor.com/repo' } + maven { url 'https://maven.aliyun.com/repository/central' } + maven { url 'https://maven.aliyun.com/repository/jcenter' } + maven { url 'https://maven.aliyun.com/repository/public' } + maven { url 'https://maven.aliyun.com/repository/google' } + maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } + } +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..47e6c74 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,16 @@ +## For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +# Default value: -Xmx1024m -XX:MaxPermSize=256m +# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 +# +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +#Thu Apr 15 10:50:40 CST 2021 +android.enableJetifier=true +org.gradle.jvmargs=-Xmx2048M -Dkotlin.daemon.jvm.options\="-Xmx2048M" +android.useAndroidX=true diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..13372ae Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..3f63540 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Tue Mar 16 15:27:10 CST 2021 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-all.zip diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..cccdd3d --- /dev/null +++ b/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..f955316 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +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% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..ca378e7 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,2 @@ +include ':app' +rootProject.name='ElderlyDialerMdm'