From e3bf43caeb6c1ea044e61cb3d72ba4b328fae1aa Mon Sep 17 00:00:00 2001 From: tongtongstudio Date: Tue, 4 Aug 2026 18:01:18 +0800 Subject: [PATCH] =?UTF-8?q?refactor(mdm):=20=E6=8A=BD=E8=B1=A1IMdmManager?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E5=B9=B6=E6=94=AF=E6=8C=81=E5=A4=9A=E5=B9=B3?= =?UTF-8?q?=E5=8F=B0=E6=9E=84=E5=BB=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 移除AdminManager具体实现,引入IMdmManager接口以实现平台解耦。 重构MainActivity,通过接口调用MDM功能。 新增honor、huawei、emui产品变体,支持多平台编译。 添加AGENTS.md开发规范文件。 --- AGENTS.md | 5 + app/build.gradle | 38 +- app/libs/hw_emui_11.0.1_20210408.jar | Bin 0 -> 41454 bytes app/src/emui/AndroidManifest.xml | 27 + .../java/com/ttstd/sn/mdm/AdminManager.java | 410 +++++++++++++++ app/src/honor/AndroidManifest.xml | 28 ++ .../java/com/ttstd/sn/mdm/AdminManager.java | 339 +++++++++++++ app/src/huawei/AndroidManifest.xml | 26 + .../java/com/ttstd/sn/mdm/AdminManager.java | 339 +++++++++++++ app/src/main/AndroidManifest.xml | 23 - .../java/com/ttstd/sn/mdm/AdminManager.java | 473 ------------------ .../java/com/ttstd/sn/mdm/IMdmManager.java | 124 +++++ .../com/ttstd/sn/ui/main/MainActivity.java | 92 +--- 13 files changed, 1353 insertions(+), 571 deletions(-) create mode 100644 AGENTS.md create mode 100644 app/libs/hw_emui_11.0.1_20210408.jar create mode 100644 app/src/emui/AndroidManifest.xml create mode 100644 app/src/emui/java/com/ttstd/sn/mdm/AdminManager.java create mode 100644 app/src/honor/AndroidManifest.xml create mode 100644 app/src/honor/java/com/ttstd/sn/mdm/AdminManager.java create mode 100644 app/src/huawei/AndroidManifest.xml create mode 100644 app/src/huawei/java/com/ttstd/sn/mdm/AdminManager.java delete mode 100644 app/src/main/java/com/ttstd/sn/mdm/AdminManager.java create mode 100644 app/src/main/java/com/ttstd/sn/mdm/IMdmManager.java diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..6aff598 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,5 @@ +# AGENTS.md — ElderlyDialerMdm + +本文件是 `ElderlyDialerMdm/` 工程的 **AI 协作规则与开发规范** +## 1.所有ai生成的临时文件全部放在app/build/temp 目录下 +## 2.所有实现IMdmManager接口的类,在调用以Manager结尾的类的方法时,都要加入try-catch块,防止异常 diff --git a/app/build.gradle b/app/build.gradle index bf65523..e6fa155 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -37,9 +37,6 @@ android { //选择要添加的对应 cpu 类型的 .so 库。 abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86' } - - buildConfigField "String", "platform", '"honor"' - } compileOptions { @@ -53,6 +50,25 @@ android { aidl true } + // 平台维度:honor(荣耀 MagicOS)/ huawei(华为 MDM Kit)/ emui(华为 EMUI) + flavorDimensions "platform" + productFlavors { + honor { + dimension "platform" + versionNameSuffix "_honor" + buildConfigField "String", "PLATFORM", '"honor"' + } + huawei { + dimension "platform" + versionNameSuffix "_huawei" + buildConfigField "String", "PLATFORM", '"huawei"' + } + emui { + dimension "platform" + versionNameSuffix "_emui" + buildConfigField "String", "PLATFORM", '"emui"' + } + } buildTypes { debug { @@ -73,17 +89,27 @@ android { applicationVariants.all { variant -> variant.outputs.each { output -> def buildType = variant.buildType.name + def flavorName = variant.flavorName if (buildType.contains("debug")) { - output.outputFileName = "app_debug.apk" + output.outputFileName = "app_${flavorName}_debug.apk" } else { - output.outputFileName = "${appName()}_${variant.versionCode}_V${variant.versionName}_${releaseTime()}_${buildType}.apk" + output.outputFileName = "${appName()}_${flavorName}_${variant.versionCode}_V${variant.versionName}_${releaseTime()}_${buildType}.apk" } } } } dependencies { - compileOnly files('libs/magic-android-34.38.1.200.jar') + // 荣耀 MDM SDK(compileOnly,仅编译期) + honorCompileOnly files('libs/magic-android-34.38.1.200.jar') + + // 华为 MDM Kit(compileOnly,仅编译期) + huaweiCompileOnly 'com.huawei.mdm:mdmkit:11.1.0.300' + // 华为 HEM License SDK(可选,需要授权校验时放开) + //huaweiImplementation 'com.huawei.hms:hemsdk:1.0.4.303' + + // 华为 EMUI MDM SDK(compileOnly,仅编译期) + emuiCompileOnly files('libs/hw_emui_11.0.1_20210408.jar') implementation 'androidx.appcompat:appcompat:1.3.1' implementation 'androidx.constraintlayout:constraintlayout:2.0.4' diff --git a/app/libs/hw_emui_11.0.1_20210408.jar b/app/libs/hw_emui_11.0.1_20210408.jar new file mode 100644 index 0000000000000000000000000000000000000000..22637d7d2434f54748404cb1fd30c3dd67e3889b GIT binary patch literal 41454 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 literal 0 HcmV?d00001 diff --git a/app/src/emui/AndroidManifest.xml b/app/src/emui/AndroidManifest.xml new file mode 100644 index 0000000..5792e1a --- /dev/null +++ b/app/src/emui/AndroidManifest.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/emui/java/com/ttstd/sn/mdm/AdminManager.java b/app/src/emui/java/com/ttstd/sn/mdm/AdminManager.java new file mode 100644 index 0000000..665f602 --- /dev/null +++ b/app/src/emui/java/com/ttstd/sn/mdm/AdminManager.java @@ -0,0 +1,410 @@ +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.huawei.android.app.admin.DeviceApplicationManager; +import com.huawei.android.app.admin.DeviceControlManager; +import com.huawei.android.app.admin.DevicePackageManager; +import com.huawei.android.app.admin.DevicePasswordManager; +import com.huawei.android.app.admin.DeviceRestrictionManager; +import com.huawei.android.app.admin.DeviceSettingsManager; +import com.ttstd.sn.receiver.AoleDeviceAdminReceiver; + +import java.util.ArrayList; +import java.util.List; + +/** + * 华为 EMUI MDM 实现。 + * + *

依赖 hw_emui_11.0.1_20210408.jar(com.huawei.android.app.admin.*),仅存在于 emui flavor。

+ */ +public class AdminManager implements IMdmManager { + private static final String TAG = "AdminManager[emui]"; + + @SuppressLint("StaticFieldLeak") + private static AdminManager sInstance; + + private Context mContext; + private ComponentName mAdminName; + private DevicePolicyManager mDevicePolicyManager; + /*设备应用程序管理类*/ + private DeviceApplicationManager mDeviceApplicationManager; + /*设备控制相关的类*/ + private DeviceControlManager mDeviceControlManager; + /*设备包管理类*/ + private DevicePackageManager mDevicePackageManager; + /*密码相关类*/ + private DevicePasswordManager mDevicePasswordManager; + /*禁用/启用管理类*/ + private DeviceRestrictionManager mDeviceRestrictionManager; + /*设置相关类*/ + private DeviceSettingsManager mDeviceSettingsManager; + + 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.mDeviceControlManager = new DeviceControlManager(); + this.mDevicePackageManager = new DevicePackageManager(); + this.mDevicePasswordManager = new DevicePasswordManager(); + this.mDeviceRestrictionManager = new DeviceRestrictionManager(); + this.mDeviceSettingsManager = new DeviceSettingsManager(); + removeTrustList(); + } + + 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; + } + + /*============================== 基础能力 ==============================*/ + + @Override + public ComponentName getAdminName() { + return mAdminName; + } + + @Override + public boolean isActiveMe() { + if (mDevicePolicyManager == null) { + return false; + } else { + return mDevicePolicyManager.isAdminActive(mAdminName); + } + } + + @Override + 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; + } + + /*============================== 应用管理 ==============================*/ + + /** + * 添加阻止某应用启动运行名单(DeviceApplicationManager) + */ + @Override + public void addDisallowedRunningApp(String pkg) { + if (TextUtils.isEmpty(pkg)) return; + List packageList = new ArrayList<>(); + packageList.add(pkg); + try { + mDeviceApplicationManager.addDisallowedRunningApp(mAdminName, packageList); + } catch (Exception e) { + Log.e(TAG, "addDisallowedRunningApp: " + e.getMessage()); + } + } + + /** + * 删除阻止某应用启动运行名单(DeviceApplicationManager) + */ + @Override + public void removeDisallowedRunningApp(String pkg) { + if (TextUtils.isEmpty(pkg)) return; + List stringList = new ArrayList<>(); + stringList.add(pkg); + try { + mDeviceApplicationManager.removeDisallowedRunningApp(mAdminName, stringList); + } catch (Exception e) { + Log.e(TAG, "removeDisallowedRunningApp: " + e.getMessage()); + } + } + + /*============================== 系统限制 ==============================*/ + + @Override + public void setMultiWindowDisabled(boolean disabled) { + try { + mDeviceRestrictionManager.setMultiWindowDisabled(mAdminName, disabled); + } catch (Exception e) { + Log.e(TAG, "setMultiWindowDisabled: " + e.getMessage()); + } + } + + @Override + public boolean isMultiWindowDisabled() { + try { + return mDeviceRestrictionManager.isMultiWindowDisabled(mAdminName); + } catch (Exception e) { + Log.e(TAG, "isMultiWindowDisabled: " + e.getMessage()); + } + return false; + } + + @Override + public void setVoiceAssistantButtonDisabled(boolean disabled) { + try { + mDeviceRestrictionManager.setVoiceAssistantButtonDisabled(mAdminName, disabled); + } catch (Exception e) { + Log.e(TAG, "setVoiceAssistantButtonDisabled: " + e.getMessage()); + } + } + + @Override + public boolean isVoiceAssistantButtonDisabled() { + try { + return mDeviceRestrictionManager.isVoiceAssistantButtonDisabled(mAdminName); + } catch (Exception e) { + Log.e(TAG, "isVoiceAssistantButtonDisabled: " + e.getMessage()); + } + return false; + } + + @Override + public void setUSBDataDisabled(boolean disabled) { + try { + mDeviceRestrictionManager.setUSBDataDisabled(mAdminName, disabled); + } catch (Exception e) { + Log.e(TAG, "setUSBDataDisabled: " + e.getMessage()); + } + } + + @Override + public boolean isUSBDataDisabled() { + try { + return mDeviceRestrictionManager.isUSBDataDisabled(mAdminName); + } catch (Exception e) { + Log.e(TAG, "isUSBDataDisabled: " + e.getMessage()); + return false; + } + } + + /*============================== 设备设置 ==============================*/ + + @Override + public boolean setRestoreFactoryDisabled(boolean disabled) { + try { + return mDeviceSettingsManager.setRestoreFactoryDisabled(mAdminName, disabled); + } catch (Exception e) { + Log.e(TAG, "setRestoreFactoryDisabled: " + e.getMessage()); + } + return false; + } + + @Override + public boolean isRestoreFactoryDisabled() { + try { + return mDeviceSettingsManager.isRestoreFactoryDisabled(mAdminName); + } catch (Exception e) { + Log.e(TAG, "isRestoreFactoryDisabled: " + e.getMessage()); + } + return false; + } + + /*============================== 快捷工具 ==============================*/ + + @Override + public void setQuickToolsDisabled(boolean disabled) { + try { + mDevicePasswordManager.setQuickToolsDisabled(mAdminName, disabled); + } catch (Exception e) { + Log.e(TAG, "setQuickToolsDisabled: " + e.getMessage()); + } + } + + @Override + public boolean isQuickToolsDisabled() { + try { + return mDevicePasswordManager.isQuickToolsDisabled(mAdminName); + } catch (Exception e) { + Log.e(TAG, "isQuickToolsDisabled: " + e.getMessage()); + } + return false; + } + + /*============================== 设备控制 ==============================*/ + + @Override + public void turnOnUsbDebugMode(boolean on) { + try { + mDeviceControlManager.turnOnUsbDebugMode(mAdminName, on); + } catch (Exception e) { + Log.e(TAG, "turnOnUsbDebugMode: " + e.getMessage()); + } + } + + @Override + public void setForcedActiveDeviceAdmin(Context activity) { + try { + mDeviceControlManager.setForcedActiveDeviceAdmin(mAdminName, activity); + } catch (Exception e) { + Log.e(TAG, "setForcedActiveDeviceAdmin: " + e.getMessage()); + } + } + + @Override + public void removeActiveDeviceAdmin() { + try { + mDeviceControlManager.removeActiveDeviceAdmin(mAdminName); + } catch (Exception e) { + Log.e(TAG, "removeActiveDeviceAdmin: " + e.getMessage()); + } + } + + @Override + public void clearDeviceOwnerApp() { + try { + mDeviceControlManager.clearDeviceOwnerApp(); + } catch (Exception e) { + Log.e(TAG, "clearDeviceOwnerApp: " + e.getMessage()); + } + } + + @Override + public boolean isForcedActiveDeviceAdmin() { + try { + return mDeviceControlManager.isForcedActiveDeviceAdmin(mAdminName); + } catch (Exception e) { + Log.e(TAG, "isForcedActiveDeviceAdmin: " + e.getMessage()); + } + return false; + } + + /*============================== 平台差异 ==============================*/ + + /** + * 禁用/启用 YOYO 助手(EMUI 平台映射为 HiVoice 语音助手) + */ + @Override + public void setYoyoDisabled(boolean disable) { + Log.e(TAG, "setYoyoDisabled: " + disable + " (emui use voice assistant)"); + setVoiceAssistantButtonDisabled(disable); + } + + /*============================== EMUI 特有能力 ==============================*/ + + /** + * 静默激活设备管理员(DeviceControlManager) + */ + public void setSilentActiveAdmin() { + try { + mDeviceControlManager.setSilentActiveAdmin(mAdminName); + } catch (Exception e) { + Log.e(TAG, "setSilentActiveAdmin: " + e.getMessage()); + } + } + + /** + * 设置管控模式(DeviceControlManager) + * + * @param mode {@link DeviceControlManager#CONTROL_MODE} 或 {@link DeviceControlManager#RELEASE_MODE} + */ + public boolean setControlMode(int mode) { + try { + return mDeviceControlManager.setControlMode(mAdminName, mode); + } catch (Exception e) { + Log.e(TAG, "setControlMode: " + e.getMessage()); + } + return false; + } + + /** + * 获取当前管控模式(DeviceControlManager) + */ + public int getControlMode() { + try { + return mDeviceControlManager.getControlMode(mAdminName); + } catch (Exception e) { + Log.e(TAG, "getControlMode: " + e.getMessage()); + } + return DeviceControlManager.NORMAL_MODE; + } + + /** + * 开启/关闭护眼模式(DeviceControlManager) + */ + public boolean turnOnEyeComfort(boolean on) { + try { + return mDeviceControlManager.turnOnEyeComfort(mAdminName, on); + } catch (Exception e) { + Log.e(TAG, "turnOnEyeComfort: " + e.getMessage()); + } + return false; + } + + /** + * 查询护眼模式是否开启(DeviceControlManager) + */ + public boolean isEyeComfortTurnedOn() { + try { + return mDeviceControlManager.isEyeComfortTurnedOn(mAdminName); + } catch (Exception e) { + Log.e(TAG, "isEyeComfortTurnedOn: " + e.getMessage()); + } + return false; + } + + /*============================== 初始化辅助 ==============================*/ + + /** + * 移除安装白名单(构造时调用,清理历史白名单配置) + */ + public void removeTrustList() { + try { + List trustList = mDevicePackageManager.getInstallPackageWhiteList(mAdminName); + Log.e(TAG, "removeTrustList: " + trustList); + if (trustList != null && trustList.size() != 0) { + mDevicePackageManager.removeInstallPackageWhiteList(mAdminName, trustList); + } + } catch (Exception e) { + Log.e(TAG, "removeTrustList: " + 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/honor/AndroidManifest.xml b/app/src/honor/AndroidManifest.xml new file mode 100644 index 0000000..1e775a6 --- /dev/null +++ b/app/src/honor/AndroidManifest.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/honor/java/com/ttstd/sn/mdm/AdminManager.java b/app/src/honor/java/com/ttstd/sn/mdm/AdminManager.java new file mode 100644 index 0000000..a7c4665 --- /dev/null +++ b/app/src/honor/java/com/ttstd/sn/mdm/AdminManager.java @@ -0,0 +1,339 @@ +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.hihonor.android.app.admin.DeviceApplicationManager; +import com.hihonor.android.app.admin.DeviceControlManager; +import com.hihonor.android.app.admin.DevicePasswordManager; +import com.hihonor.android.app.admin.DeviceRestrictionManager; +import com.hihonor.android.app.admin.DeviceSettingsManager; +import com.ttstd.sn.receiver.AoleDeviceAdminReceiver; + +import java.util.ArrayList; +import java.util.List; + +/** + * 荣耀 MagicOS MDM 实现。 + * + *

依赖 magic-android SDK(com.hihonor.android.app.admin.*),仅存在于 honor flavor。

+ */ +public class AdminManager implements IMdmManager { + private static final String TAG = "AdminManager[honor]"; + + @SuppressLint("StaticFieldLeak") + private static AdminManager sInstance; + + private Context mContext; + private ComponentName mAdminName; + private DevicePolicyManager mDevicePolicyManager; + /*设备应用程序管理类*/ + private DeviceApplicationManager mDeviceApplicationManager; + /*设备控制相关的类*/ + private DeviceControlManager mDeviceControlManager; + /*密码相关类*/ + private DevicePasswordManager mDevicePasswordManager; + /*禁用/启用管理类*/ + private DeviceRestrictionManager mDeviceRestrictionManager; + /*设置相关类*/ + private DeviceSettingsManager mDeviceSettingsManager; + + 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.mDeviceControlManager = new DeviceControlManager(); + this.mDevicePasswordManager = new DevicePasswordManager(); + this.mDeviceRestrictionManager = new DeviceRestrictionManager(); + this.mDeviceSettingsManager = new DeviceSettingsManager(); + + 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; + } + + /*============================== 基础能力 ==============================*/ + + @Override + public ComponentName getAdminName() { + return mAdminName; + } + + @Override + public boolean isActiveMe() { + if (mDevicePolicyManager == null) { + return false; + } else { + return mDevicePolicyManager.isAdminActive(mAdminName); + } + } + + @Override + 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; + } + + /*============================== 应用管理 ==============================*/ + + /** + * 添加阻止某应用启动运行名单(DeviceApplicationManager) + */ + @Override + 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)); + } + + /** + * 删除阻止某应用启动运行名单(DeviceApplicationManager) + */ + @Override + 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)); + } + + /*============================== 系统限制 ==============================*/ + + @Override + public void setMultiWindowDisabled(boolean disabled) { + try { + mDeviceRestrictionManager.setMultiWindowDisabled(mAdminName, disabled); + } catch (Exception e) { + Log.e(TAG, "setMultiWindowDisabled: " + e.getMessage()); + } + } + + @Override + public boolean isMultiWindowDisabled() { + try { + return mDeviceRestrictionManager.isMultiWindowDisabled(mAdminName); + } catch (Exception e) { + Log.e(TAG, "isMultiWindowDisabled: " + e.getMessage()); + } + return false; + } + + @Override + public void setVoiceAssistantButtonDisabled(boolean disabled) { + try { + mDeviceRestrictionManager.setVoiceAssistantButtonDisabled(mAdminName, disabled); + } catch (Exception e) { + Log.e(TAG, "setVoiceAssistantButtonDisabled: " + e.getMessage()); + } + } + + @Override + public boolean isVoiceAssistantButtonDisabled() { + try { + return mDeviceRestrictionManager.isVoiceAssistantButtonDisabled(mAdminName); + } catch (Exception e) { + Log.e(TAG, "isVoiceAssistantButtonDisabled: " + e.getMessage()); + } + return false; + } + + @Override + public void setUSBDataDisabled(boolean disabled) { + try { + mDeviceRestrictionManager.setUSBDataDisabled(mAdminName, disabled); + } catch (Exception e) { + Log.e(TAG, "setUSBDataDisabled: " + e.getMessage()); + } + } + + @Override + public boolean isUSBDataDisabled() { + try { + return mDeviceRestrictionManager.isUSBDataDisabled(mAdminName); + } catch (Exception e) { + Log.e(TAG, "isUSBDataDisabled: " + e.getMessage()); + } + return false; + } + + /*============================== 设备设置 ==============================*/ + + @Override + public boolean setRestoreFactoryDisabled(boolean disabled) { + try { + return mDeviceSettingsManager.setRestoreFactoryDisabled(mAdminName, disabled); + } catch (Exception e) { + Log.e(TAG, "setRestoreFactoryDisabled: " + e.getMessage()); + } + return false; + } + + @Override + public boolean isRestoreFactoryDisabled() { + try { + return mDeviceSettingsManager.isRestoreFactoryDisabled(mAdminName); + } catch (Exception e) { + Log.e(TAG, "isRestoreFactoryDisabled: " + e.getMessage()); + } + return false; + } + + /*============================== 快捷工具 ==============================*/ + + @Override + public void setQuickToolsDisabled(boolean disabled) { + try { + mDevicePasswordManager.setQuickToolsDisabled(mAdminName, disabled); + } catch (Exception e) { + Log.e(TAG, "setQuickToolsDisabled: " + e.getMessage()); + } + } + + @Override + public boolean isQuickToolsDisabled() { + try { + return mDevicePasswordManager.isQuickToolsDisabled(mAdminName); + } catch (Exception e) { + Log.e(TAG, "isQuickToolsDisabled: " + e.getMessage()); + } + return false; + } + + /*============================== 设备控制 ==============================*/ + + @Override + public void turnOnUsbDebugMode(boolean on) { + try { + mDeviceControlManager.turnOnUsbDebugMode(mAdminName, on); + } catch (Exception e) { + Log.e(TAG, "turnOnUsbDebugMode: " + e.getMessage()); + } + } + + @Override + public void setForcedActiveDeviceAdmin(Context activity) { + try { + mDeviceControlManager.setForcedActiveDeviceAdmin(mAdminName, activity); + } catch (Exception e) { + Log.e(TAG, "setForcedActiveDeviceAdmin: " + e.getMessage()); + } + } + + @Override + public void removeActiveDeviceAdmin() { + try { + mDeviceControlManager.removeActiveDeviceAdmin(mAdminName); + } catch (Exception e) { + Log.e(TAG, "removeActiveDeviceAdmin: " + e.getMessage()); + } + } + + @Override + public void clearDeviceOwnerApp() { + try { + mDeviceControlManager.clearDeviceOwnerApp(); + } catch (Exception e) { + Log.e(TAG, "clearDeviceOwnerApp: " + e.getMessage()); + } + } + + @Override + public boolean isForcedActiveDeviceAdmin() { + try { + return mDeviceControlManager.isForcedActiveDeviceAdmin(mAdminName); + } catch (Exception e) { + Log.e(TAG, "isForcedActiveDeviceAdmin: " + e.getMessage()); + } + return false; + } + + /*============================== 平台差异 ==============================*/ + + /** + * 禁用/启用 YOYO 助手(通过黑名单禁用 com.hihonor.servicecenter) + */ + @Override + 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()); + } + } + + /** + * 判断应用是否已安装 + */ + 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/huawei/AndroidManifest.xml b/app/src/huawei/AndroidManifest.xml new file mode 100644 index 0000000..b675c94 --- /dev/null +++ b/app/src/huawei/AndroidManifest.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/huawei/java/com/ttstd/sn/mdm/AdminManager.java b/app/src/huawei/java/com/ttstd/sn/mdm/AdminManager.java new file mode 100644 index 0000000..1c7cf64 --- /dev/null +++ b/app/src/huawei/java/com/ttstd/sn/mdm/AdminManager.java @@ -0,0 +1,339 @@ +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.huawei.android.app.admin.DeviceApplicationManager; +import com.huawei.android.app.admin.DeviceControlManager; +import com.huawei.android.app.admin.DevicePackageManager; +import com.huawei.android.app.admin.DevicePasswordManager; +import com.huawei.android.app.admin.DeviceRestrictionManager; +import com.huawei.android.app.admin.DeviceSettingsManager; +import com.ttstd.sn.receiver.AoleDeviceAdminReceiver; + +import java.util.ArrayList; +import java.util.List; + +/** + * 华为 MDM Kit 实现。 + * + *

依赖 com.huawei.mdm:mdmkit(com.huawei.android.app.admin.*),仅存在于 huawei flavor。

+ */ +public class AdminManager implements IMdmManager { + private static final String TAG = "AdminManager[huawei]"; + + @SuppressLint("StaticFieldLeak") + private static AdminManager sInstance; + + private Context mContext; + private ComponentName mAdminName; + private DevicePolicyManager mDevicePolicyManager; + /*设备应用程序管理类*/ + private DeviceApplicationManager mDeviceApplicationManager; + /*设备控制相关的类*/ + private DeviceControlManager mDeviceControlManager; + /*设备包管理类*/ + private DevicePackageManager mDevicePackageManager; + /*密码相关类*/ + private DevicePasswordManager mDevicePasswordManager; + /*禁用/启用管理类*/ + private DeviceRestrictionManager mDeviceRestrictionManager; + /*设置相关类*/ + private DeviceSettingsManager mDeviceSettingsManager; + + 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.mDeviceControlManager = new DeviceControlManager(); + this.mDevicePackageManager = new DevicePackageManager(); + this.mDevicePasswordManager = new DevicePasswordManager(); + this.mDeviceRestrictionManager = new DeviceRestrictionManager(); + this.mDeviceSettingsManager = new DeviceSettingsManager(); + removeTrustList(); + } + + 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; + } + + /*============================== 基础能力 ==============================*/ + + @Override + public ComponentName getAdminName() { + return mAdminName; + } + + @Override + public boolean isActiveMe() { + if (mDevicePolicyManager == null) { + return false; + } else { + return mDevicePolicyManager.isAdminActive(mAdminName); + } + } + + @Override + 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; + } + + /*============================== 应用管理 ==============================*/ + + /** + * 添加阻止某应用启动运行名单(DeviceApplicationManager) + */ + @Override + public void addDisallowedRunningApp(String pkg) { + if (TextUtils.isEmpty(pkg)) return; + List packageList = new ArrayList<>(); + packageList.add(pkg); + try { + mDeviceApplicationManager.addDisallowedRunningApp(mAdminName, packageList); + } catch (Exception e) { + Log.e(TAG, "addDisallowedRunningApp: " + e.getMessage()); + } + } + + /** + * 删除阻止某应用启动运行名单(DeviceApplicationManager) + */ + @Override + public void removeDisallowedRunningApp(String pkg) { + if (TextUtils.isEmpty(pkg)) return; + List stringList = new ArrayList<>(); + stringList.add(pkg); + try { + mDeviceApplicationManager.removeDisallowedRunningApp(mAdminName, stringList); + } catch (Exception e) { + Log.e(TAG, "removeDisallowedRunningApp: " + e.getMessage()); + } + } + + /*============================== 系统限制 ==============================*/ + + @Override + public void setMultiWindowDisabled(boolean disabled) { + try { + mDeviceRestrictionManager.setMultiWindowDisabled(mAdminName, disabled); + } catch (Exception e) { + Log.e(TAG, "setMultiWindowDisabled: " + e.getMessage()); + } + } + + @Override + public boolean isMultiWindowDisabled() { + try { + return mDeviceRestrictionManager.isMultiWindowDisabled(mAdminName); + } catch (Exception e) { + Log.e(TAG, "isMultiWindowDisabled: " + e.getMessage()); + } + return false; + } + + @Override + public void setVoiceAssistantButtonDisabled(boolean disabled) { + try { + mDeviceRestrictionManager.setVoiceAssistantButtonDisabled(mAdminName, disabled); + } catch (Exception e) { + Log.e(TAG, "setVoiceAssistantButtonDisabled: " + e.getMessage()); + } + } + + @Override + public boolean isVoiceAssistantButtonDisabled() { + try { + return mDeviceRestrictionManager.isVoiceAssistantButtonDisabled(mAdminName); + } catch (Exception e) { + Log.e(TAG, "isVoiceAssistantButtonDisabled: " + e.getMessage()); + } + return false; + } + + @Override + public void setUSBDataDisabled(boolean disabled) { + try { + mDeviceRestrictionManager.setUSBDataDisabled(mAdminName, disabled); + } catch (Exception e) { + Log.e(TAG, "setUSBDataDisabled: " + e.getMessage()); + } + } + + @Override + public boolean isUSBDataDisabled() { + try { + return mDeviceRestrictionManager.isUSBDataDisabled(mAdminName); + } catch (Exception e) { + Log.e(TAG, "isUSBDataDisabled: " + e.getMessage()); + return false; + } + } + + /*============================== 设备设置 ==============================*/ + + @Override + public boolean setRestoreFactoryDisabled(boolean disabled) { + try { + return mDeviceSettingsManager.setRestoreFactoryDisabled(mAdminName, disabled); + } catch (Exception e) { + Log.e(TAG, "setRestoreFactoryDisabled: " + e.getMessage()); + } + return false; + } + + @Override + public boolean isRestoreFactoryDisabled() { + try { + return mDeviceSettingsManager.isRestoreFactoryDisabled(mAdminName); + } catch (Exception e) { + Log.e(TAG, "isRestoreFactoryDisabled: " + e.getMessage()); + } + return false; + } + + /*============================== 快捷工具 ==============================*/ + + @Override + public void setQuickToolsDisabled(boolean disabled) { + Log.w(TAG, "setQuickToolsDisabled is not supported on huawei platform: " + disabled); + } + + @Override + public boolean isQuickToolsDisabled() { + Log.w(TAG, "isQuickToolsDisabled is not supported on huawei platform"); + return false; + } + + /*============================== 设备控制 ==============================*/ + + @Override + public void turnOnUsbDebugMode(boolean on) { + try { + mDeviceControlManager.turnOnUsbDebugMode(mAdminName, on); + } catch (Exception e) { + Log.e(TAG, "turnOnUsbDebugMode: " + e.getMessage()); + } + } + + @Override + public void setForcedActiveDeviceAdmin(Context activity) { + try { + mDeviceControlManager.setForcedActiveDeviceAdmin(mAdminName, activity); + } catch (Exception e) { + Log.e(TAG, "setForcedActiveDeviceAdmin: " + e.getMessage()); + } + } + + @Override + public void removeActiveDeviceAdmin() { + try { + mDeviceControlManager.removeActiveDeviceAdmin(mAdminName); + } catch (Exception e) { + Log.e(TAG, "removeActiveDeviceAdmin: " + e.getMessage()); + } + } + + @Override + public void clearDeviceOwnerApp() { + try { + mDevicePolicyManager.clearDeviceOwnerApp(mContext.getPackageName()); + } catch (Exception e) { + Log.e(TAG, "clearDeviceOwnerApp: " + e.getMessage()); + } + } + + @Override + public boolean isForcedActiveDeviceAdmin() { + try { + return mDeviceControlManager.isForcedActiveDeviceAdmin(mAdminName); + } catch (Exception e) { + Log.e(TAG, "isForcedActiveDeviceAdmin: " + e.getMessage()); + } + return false; + } + + /*============================== 平台差异 ==============================*/ + + /** + * 禁用/启用 YOYO 助手(华为平台映射为 HiVoice 语音助手) + */ + @Override + public void setYoyoDisabled(boolean disable) { + Log.e(TAG, "setYoyoDisabled: " + disable + " (huawei use voice assistant)"); + setVoiceAssistantButtonDisabled(disable); + } + + /*============================== 初始化辅助 ==============================*/ + + /** + * 移除安装白名单(构造时调用,清理历史白名单配置) + */ + public void removeTrustList() { + try { + List trustList = mDevicePackageManager.getInstallPackageWhiteList(mAdminName); + Log.e(TAG, "removeTrustList: " + trustList); + if (trustList != null && trustList.size() != 0) { + mDevicePackageManager.removeInstallPackageWhiteList(mAdminName, trustList); + } + } catch (Exception e) { + Log.e(TAG, "removeTrustList: " + 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/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 0012da1..0bca3de 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,29 +2,6 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/java/com/ttstd/sn/mdm/AdminManager.java b/app/src/main/java/com/ttstd/sn/mdm/AdminManager.java deleted file mode 100644 index 89757f1..0000000 --- a/app/src/main/java/com/ttstd/sn/mdm/AdminManager.java +++ /dev/null @@ -1,473 +0,0 @@ -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/mdm/IMdmManager.java b/app/src/main/java/com/ttstd/sn/mdm/IMdmManager.java new file mode 100644 index 0000000..92b7ec0 --- /dev/null +++ b/app/src/main/java/com/ttstd/sn/mdm/IMdmManager.java @@ -0,0 +1,124 @@ +package com.ttstd.sn.mdm; + +import android.content.ComponentName; +import android.content.Context; + +/** + * MDM 能力抽象接口。 + * + *

荣耀(com.hihonor.android.app.admin.*)、华为(com.huawei.android.app.admin.*) + * 与 EMUI(com.huawei.android.app.admin.*)三套平台 SDK 的 MDM 能力在此统一抽象, + * 业务层(App / Activity)只依赖本接口,平台差异由各 flavor 中的 {@code AdminManager} 实现。

+ * + *

方法按底层使用的 SDK Manager 分组排列,并在注释中标注对应 Manager;

+ */ +public interface IMdmManager { + + /*============================== 基础能力(DevicePolicyManager / Build) ==============================*/ + + /** + * 设备管理员组件(DevicePolicyManager) + */ + ComponentName getAdminName(); + + /** + * 设备管理员是否已激活(DevicePolicyManager) + */ + boolean isActiveMe(); + + /** + * 获取设备序列号(Build.getSerial,依赖 MDM 权限) + */ + String getSerial(); + + /*============================== 应用管理(DeviceApplicationManager) ==============================*/ + + /** + * 添加阻止某应用启动运行名单(黑名单) + */ + void addDisallowedRunningApp(String pkg); + + /** + * 删除阻止某应用启动运行名单 + */ + void removeDisallowedRunningApp(String pkg); + + /*============================== 系统限制(DeviceRestrictionManager) ==============================*/ + + /** + * 禁用/启用多窗口(DeviceRestrictionManager) + */ + void setMultiWindowDisabled(boolean disabled); + + boolean isMultiWindowDisabled(); + + /** + * 禁用/启用语音助手按键(DeviceRestrictionManager) + */ + void setVoiceAssistantButtonDisabled(boolean disabled); + + boolean isVoiceAssistantButtonDisabled(); + + /** + * 禁用/启用 USB 数据传输(DeviceRestrictionManager) + */ + void setUSBDataDisabled(boolean disabled); + + boolean isUSBDataDisabled(); + + /*============================== 设备设置(DeviceSettingsManager) ==============================*/ + + /** + * 禁用/启用恢复出厂设置(DeviceSettingsManager) + * + * @return true:配置成功;false:配置失败 + */ + boolean setRestoreFactoryDisabled(boolean disabled); + + boolean isRestoreFactoryDisabled(); + + /*============================== 快捷工具(DevicePasswordManager) ==============================*/ + + /** + * 禁用/启用快捷工具(DevicePasswordManager) + */ + void setQuickToolsDisabled(boolean disabled); + + boolean isQuickToolsDisabled(); + + /*============================== 设备控制(DeviceControlManager) ==============================*/ + + /** + * 开启/关闭 USB 调试模式(DeviceControlManager) + */ + void turnOnUsbDebugMode(boolean on); + + /** + * 强制激活设备管理员,静默激活,需系统签名(DeviceControlManager) + */ + void setForcedActiveDeviceAdmin(Context activity); + + /** + * 移除设备管理员(DeviceControlManager) + */ + void removeActiveDeviceAdmin(); + + /** + * 清除 Device Owner(DeviceControlManager) + */ + void clearDeviceOwnerApp(); + + /** + * 是否已强制激活设备管理员(DeviceControlManager) + */ + boolean isForcedActiveDeviceAdmin(); + + /*============================== 平台差异(无统一 Manager) ==============================*/ + + /** + * 禁用/启用 YOYO 助手。 + * 荣耀平台通过 {@link #addDisallowedRunningApp(String)} 禁用 com.hihonor.servicecenter; + * 华为 / EMUI 平台映射为 {@link #setVoiceAssistantButtonDisabled(boolean)}(HiVoice)。 + */ + void setYoyoDisabled(boolean disable); +} 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 index 5cd8901..7eb95e7 100644 --- a/app/src/main/java/com/ttstd/sn/ui/main/MainActivity.java +++ b/app/src/main/java/com/ttstd/sn/ui/main/MainActivity.java @@ -1,41 +1,20 @@ package com.ttstd.sn.ui.main; -import android.app.admin.DevicePolicyManager; -import android.content.ComponentName; -import android.content.Context; +import android.os.Bundle; 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.mdm.IMdmManager; 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 static final String TAG = "MainActivity"; - 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; + private IMdmManager mMdmManager; @Override protected int getLayoutId() { @@ -59,22 +38,13 @@ public class MainActivity extends BaseMvvmActivity