扩展程序应用和本地命令

借助 Android Management API (AMAPI) SDK,EMM 指定的扩展应用可以直接与 Android Device Policy (ADP) 通信,并在设备上执行 Commands。

与 AMAPI SDK 集成提供了有关此库以及如何将其添加到应用的更多信息。

集成 SDK 后,您的扩展程序应用可以与 ADP 通信,以便:

发出命令

扩展程序应用可以请求使用 ADP 发出命令。 IssueCommandRequest 包含请求对象,该对象将包含要发出的命令和特定参数的详细信息。

清除应用数据

以下代码段展示了如何发出清除软件包数据的请求:

import android.util.Log;
...
import com.google.android.managementapi.commands.LocalCommandClientFactory;
import com.google.android.managementapi.commands.model.Command;
import com.google.android.managementapi.commands.model.GetCommandRequest;
import com.google.android.managementapi.commands.model.IssueCommandRequest;
import com.google.android.managementapi.commands.model.IssueCommandRequest.ClearAppsData;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.MoreExecutors;

...
  void issueClearAppDataCommand(ImmutableList<String> packageNames) {
    Futures.addCallback(
        LocalCommandClientFactory.create(getContext())
            .issueCommand(createClearAppRequest(packageNames)),
        new FutureCallback<Command>() {
          @Override
          public void onSuccess(Command result) {
            // Process the returned command result here
            Log.i(TAG, "Successfully issued command");
          }

          @Override
          public void onFailure(Throwable t) {
            Log.e(TAG, "Failed to issue command", t);
          }
        },
        MoreExecutors.directExecutor());
  }

  IssueCommandRequest createClearAppRequest(ImmutableList<String> packageNames) {
    return IssueCommandRequest.builder()
        .setClearAppsData(
            ClearAppsData.builder()
                .setPackageNames(packageNames)
                .build()
        )
        .build();
  }
...

前面的示例展示了如何针对指定软件包发出清除应用数据请求,并等待命令成功发出。如果成功发出,系统将返回一个 Command 对象,其中包含当前命令状态和命令 ID,后者稍后可用于查询任何长时间运行的命令的状态。

擦除设备或工作资料

当 IT 管理员分配具有 LOCAL_DEVICE_WIPE_COMMAND_ALLOWED 额外功能的 COMPANION_APP 角色时,配套应用可以发出本地 Wipe 命令。在公司自有设备上,此操作会将设备恢复出厂设置;在具有工作资料的个人设备上,此操作会移除工作资料。如果应用不具备此角色和额外功能,请求会失败。如需了解详情,请参阅政策配置。

两个 Wipe 参数都是可选的。如果您不想指定值,请省略构建器 setter 或传递空集合或空字符串(不要传递 null)。

  • WipeDataFlag 值用于控制要擦除的数据:
    • PRESERVE_RESET_PROTECTION_DATA:保留恢复出厂设置保护机制数据(默认情况下会被清除)。仅适用于公司自有设备。
    • WIPE_EXTERNAL_STORAGE:同时擦除外部存储空间,例如 SD 卡。仅适用于公司自有设备。
    • WIPE_ESIMS:从公司自有设备中移除所有 eSIM 卡。在个人拥有的设备上,仅移除受管理的 eSIM 卡(在 Android 16 或更高版本中,无论是否设置此标志,受管理的 eSIM 卡始终会随工作资料一起移除)。
  • wipeReason:从个人设备中移除工作资料时向用户显示的简短消息,最多可包含 200 个字符。此设置对公司自有设备没有影响。

以下代码段展示了如何发出擦除设备或工作资料的请求:

Kotlin

import android.content.Context
import android.util.Log
...
import com.google.android.managementapi.commands.CommandException
import com.google.android.managementapi.commands.LocalCommandClientFactory
import com.google.android.managementapi.commands.model.IssueCommandRequest
import com.google.android.managementapi.commands.model.IssueCommandRequest.Wipe
import com.google.android.managementapi.commands.model.IssueCommandRequest.Wipe.WipeDataFlag

...
  suspend fun issueWipeCommand(
      context: Context,
      wipeDataFlags: List<WipeDataFlag>,
      wipeReason: String,
  ) {
    try {
      val command =
          LocalCommandClientFactory.create(context)
              .issueCommandAwait(createWipeRequest(wipeDataFlags, wipeReason))
      Log.i(TAG, "Successfully issued wipe command: ${command.commandId}")
    } catch (e: CommandException) {
      Log.e(TAG, "Failed to issue wipe command", e)
    }
  }

  fun createWipeRequest(
      wipeDataFlags: List<WipeDataFlag>,
      wipeReason: String,
  ): IssueCommandRequest =
      IssueCommandRequest.builder()
          .setWipe(
              Wipe.builder()
                  .setWipeDataFlags(wipeDataFlags)
                  .setWipeReason(wipeReason)
                  .build()
          )
          .build()
...

Java

import android.content.Context;
import android.util.Log;
...
import com.google.android.managementapi.commands.LocalCommandClientFactory;
import com.google.android.managementapi.commands.model.Command;
import com.google.android.managementapi.commands.model.IssueCommandRequest;
import com.google.android.managementapi.commands.model.IssueCommandRequest.Wipe;
import com.google.android.managementapi.commands.model.IssueCommandRequest.Wipe.WipeDataFlag;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.MoreExecutors;

...
  void issueWipeCommand(
      Context context,
      ImmutableList<WipeDataFlag> wipeDataFlags,
      String wipeReason) {
    Futures.addCallback(
        LocalCommandClientFactory.create(context)
            .issueCommand(createWipeRequest(wipeDataFlags, wipeReason)),
        new FutureCallback<Command>() {
          @Override
          public void onSuccess(Command result) {
            Log.i(TAG, "Successfully issued wipe command: " + result.getCommandId());
          }

          @Override
          public void onFailure(Throwable t) {
            Log.e(TAG, "Failed to issue wipe command", t);
          }
        },
        MoreExecutors.directExecutor());
  }

  IssueCommandRequest createWipeRequest(
      ImmutableList<WipeDataFlag> wipeDataFlags, String wipeReason) {
    return IssueCommandRequest.builder()
        .setWipe(
            Wipe.builder()
                .setWipeDataFlags(wipeDataFlags)
                .setWipeReason(wipeReason)
                .build())
        .build();
  }
...

获取命令

扩展程序应用可以查询之前发出的命令请求的状态。如需检索命令的状态,您需要提供命令 ID(可从发出命令请求中获取)。以下代码段展示了如何向 ADP 发送 GetCommandRequest。

import android.util.Log;
...
import com.google.android.managementapi.commands.LocalCommandClientFactory;
...
import com.google.android.managementapi.commands.model.GetCommandRequest;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.MoreExecutors;

...
  void getCommand(String commandId) {
    Futures.addCallback(
        LocalCommandClientFactory.create(getApplication())
            .getCommand(GetCommandRequest.builder().setCommandId(commandId).build()),
        new FutureCallback<Command>() {
          @Override
          public void onSuccess(Command result) {
            // Process the returned command result here
            Log.i(Constants.TAG, "Successfully issued command");
          }

          @Override
          public void onFailure(Throwable t) {
            Log.e(Constants.TAG, "Failed to issue command", t);
          }
        },
        MoreExecutors.directExecutor());
  }
  ...

监听命令状态更改回调

扩展程序应用可以选择注册回调,以便按照以下步骤接收长时间运行的命令的状态更改更新:

  1. 命令状态更改会通知给 CommandListener,请在您的应用中实现此接口,并提供有关如何处理收到的状态更新的实现。
  2. 扩展 NotificationReceiverService,并通过 getCommandListener 方法提供 CommandListener 实例。
  3. 配置应用政策,以便为您的应用分配 COMPANION_APP 角色(请参阅政策配置)。

    import com.google.android.managementapi.commands.CommandListener;
    import com.google.android.managementapi.notification.NotificationReceiverService;
    
    ...
    
    public class SampleCommandService extends NotificationReceiverService {
    
     @Override
     public CommandListener getCommandListener() {
       // return the concrete implementation from previous step
       return ...;
     }
    }
    

策略配置

为了使配套应用能够直接与 ADP 通信,EMM 必须使用应用政策中的 roles 字段为应用分配 COMPANION_APP 角色:

 "applications": [{
   "packageName": "com.amapi.extensibility.demo",
   "installType": "FORCE_INSTALLED",
   "roles": [
     { "roleType": "COMPANION_APP" }
   ]
 }]

如需允许配套应用发出本地 Wipe 命令,请将 extraCapabilities.localDeviceWipeCommand 设置为 LOCAL_DEVICE_WIPE_COMMAND_ALLOWED:

 "applications": [{
   "packageName": "com.amapi.extensibility.demo",
   "installType": "FORCE_INSTALLED",
   "roles": [
     {
       "roleType": "COMPANION_APP",
       "extraCapabilities": {
         "localDeviceWipeCommand": "LOCAL_DEVICE_WIPE_COMMAND_ALLOWED"
       }
     }
   ]
 }]

如需了解其他可用选项,请参阅使用应用角色政策配置设备。

ADP 会使用应用清单中的元数据自动发现监听命令状态更改的服务,而无需再在政策中指定为 notificationReceiver。如需详细了解如何准备应用和清单,请参阅管理应用角色。

测试

单元测试

LocalCommandClient 是一个接口,因此可以提供可测试的实现。

集成测试

如需使用 ADP 进行测试,您需要提供以下信息:

  1. 扩展应用的软件包名称。
  2. 与应用包关联的签名的 base64 编码 SHA-256 哈希。
  3. (可选)如果测试回调,则为新引入的服务中支持回调的服务的完全限定名称。(示例中 CommandService 的完全限定名称)。