확장 프로그램 앱 및 로컬 명령어

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()
...

자바

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가 필요합니다 (명령어 실행 요청에서 사용 가능). 다음 스니펫은 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의 정규화된 이름)