拡張機能アプリとローカル コマンド

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 が含まれています。コマンド ID は、後で長時間実行コマンドのステータスをクエリするために使用できます。

デバイスまたは仕事用プロファイルをワイプする

IT 管理者が LOCAL_DEVICE_WIPE_COMMAND_ALLOWED 追加機能を持つ COMPANION_APP ロールを割り当てると、コンパニオン アプリはローカルの Wipe コマンドを発行できます。会社所有デバイスの場合は、デバイスが出荷時の設定にリセットされます。仕事用プロファイルが設定された個人所有デバイスの場合は、仕事用プロファイルが削除されます。アプリにこのロールと追加機能がない場合、リクエストは失敗します。詳細については、ポリシーの構成をご覧ください。

どちらの Wipe パラメータも省略可能です。値を指定しない場合は、ビルダー セッターを省略するか、空のコレクションまたは空の文字列を渡します(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();
  }
...

get コマンド

拡張機能アプリは、以前に発行されたコマンド リクエストのステータスをクエリできます。コマンドのステータスを取得するには、コマンド ID(コマンド発行リクエストから取得可能)が必要です。次のスニペットは、GetCommandRequest を ADP に送信する方法を示しています。

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. アプリ パッケージに関連付けられた Signature の Base64 でエンコードされた SHA-256 ハッシュ。
  3. 必要に応じて、コールバックをテストする場合は、コールバックをサポートするために新しく導入されたサービスからのサービスの完全修飾名。(例では CommandService の完全修飾名)。