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 с текущим статусом команды и ее идентификатором, который можно использовать для запроса статуса любых длительных команд.
Удаление данных с устройства или из рабочего профиля
Сопутствующее приложение может выполнить локальную команду Wipe, если системный администратор назначит роль COMPANION_APP с дополнительной функцией LOCAL_DEVICE_WIPE_COMMAND_ALLOWED. На корпоративных устройствах это приведет к сбросу настроек, а на личных устройствах с рабочим профилем – к удалению рабочего профиля. Если у приложения нет этой роли и дополнительной возможности, запрос завершится ошибкой. Подробнее о настройке правил…
Оба параметра 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();
}
...
Получить команду
Приложение-расширение может запрашивать статус ранее отправленных команд. Чтобы получить статус команды, вам понадобится ее идентификатор (доступен в запросе на выполнение команды). В следующем фрагменте кода показано, как отправить 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());
}
...
Как отслеживать обратные вызовы об изменении статуса команды
Приложение-расширение может зарегистрировать обратный вызов, чтобы получать обновления об изменении статуса долго выполняющихся команд. Для этого сделайте следующее:
- Об изменении статуса команды сообщается в
CommandListener. Реализуйте этот интерфейс в своем приложении и укажите, как обрабатывать полученные обновления статуса. - Расширьте
NotificationReceiverServiceи предоставьте экземплярCommandListenerс помощью методаgetCommandListener. Настройте правило приложения, чтобы назначить ему роль
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-система должна назначить ему роль COMPANION_APP с помощью поля roles в правилах для приложений:
"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 потребуется следующая информация:
- Название пакета приложения-расширения.
- Хеш SHA-256 в кодировке Base64 для подписи, связанной с пакетом приложения.
- Если вы тестируете обратный вызов, укажите полное название сервиса, который его поддерживает. (в примере – полное имя
CommandService).