Mit dem Android Management API (AMAPI) SDK kann eine vom EMM angegebene Erweiterungs-App direkt mit Android Device Policy (ADP) kommunizieren und Commands auf dem Gerät ausführen.
Unter In das AMAPI SDK einbinden finden Sie weitere Informationen zu dieser Bibliothek und dazu, wie Sie sie Ihrer Anwendung hinzufügen.
Nach der Integration des SDK kann Ihre Erweiterungs-App mit ADP kommunizieren, um:
Befehl ausführen
Eine Erweiterungs-App kann anfordern, dass Befehle über ADP ausgegeben werden.
IssueCommandRequest enthält das Anfrageobjekt mit Details zum auszuführenden Befehl und zu bestimmten Parametern.
App-Daten löschen
Das folgende Snippet zeigt, wie Sie eine Anfrage zum Löschen der Paketdaten senden:
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();
}
...
Im vorherigen Beispiel wurde eine klare Anfrage für App-Daten für bestimmte Pakete gesendet und gewartet, bis der Befehl erfolgreich ausgeführt wurde. Wenn der Befehl erfolgreich ausgegeben wurde, wird ein Command-Objekt mit dem aktuellen Befehlsstatus und der Befehls-ID zurückgegeben. Mit der Befehls-ID kann später der Status von Befehlen mit langer Ausführungszeit abgefragt werden.
Gerät oder Arbeitsprofil löschen
Eine Companion-App kann einen lokalen Wipe-Befehl ausführen, wenn der IT-Administrator die Rolle COMPANION_APP mit der zusätzlichen Funktion LOCAL_DEVICE_WIPE_COMMAND_ALLOWED zuweist. Auf unternehmenseigenen Geräten wird das Gerät auf die Werkseinstellungen zurückgesetzt. Auf privaten Geräten mit einem Arbeitsprofil wird das Arbeitsprofil entfernt. Wenn die App nicht über diese Rolle und zusätzliche Funktion verfügt, schlägt die Anfrage fehl. Weitere Informationen finden Sie unter Richtlinienkonfiguration.
Beide Wipe-Parameter sind optional. Wenn Sie keinen Wert angeben möchten, lassen Sie den Builder-Setter weg oder übergeben Sie eine leere Sammlung oder einen leeren String (übergeben Sie nicht null).
WipeDataFlag-Werte steuern, welche Daten gelöscht werden:PRESERVE_RESET_PROTECTION_DATA: Behält die Daten des Schutzes vor Zurücksetzen bei, die standardmäßig gelöscht werden. Gilt nur für unternehmenseigene Geräte.WIPE_EXTERNAL_STORAGE: Löscht auch externe Speicher wie SD-Karten. Gilt nur für unternehmenseigene Geräte.WIPE_ESIMS: Alle eSIMs werden von unternehmenseigenen Geräten entfernt. Auf privaten Geräten werden nur verwaltete eSIMs entfernt. Unter Android 16 oder höher werden verwaltete eSIMs immer mit dem Arbeitsprofil entfernt, unabhängig davon, ob dieses Flag gesetzt ist oder nicht.
wipeReason: Eine kurze Nachricht mit bis zu 200 Zeichen, die dem Nutzer angezeigt wird, wenn das Arbeitsprofil von einem privaten Gerät entfernt wird. Sie hat keine Auswirkungen auf unternehmenseigene Geräte.
Das folgende Snippet zeigt, wie Sie eine Anfrage zum Löschen des Geräts oder Arbeitsprofils senden:
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();
}
...
Befehl abrufen
Eine Erweiterungs-App kann den Status zuvor ausgegebener Befehlsanfragen abfragen. Zum Abrufen des Status eines Befehls benötigen Sie die Befehls-ID (verfügbar über die Anfrage zum Ausführen des Befehls). Das folgende Snippet zeigt, wie ein GetCommandRequest an ADP gesendet wird.
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());
}
...
Callbacks für Befehlsstatusänderungen überwachen
Eine Erweiterungs-App kann optional einen Callback registrieren, um Updates zu Statusänderungen von lang laufenden Befehlen zu erhalten. Gehen Sie dazu so vor:
- Änderungen des Befehlsstatus werden an
CommandListenergemeldet. Implementiere diese Schnittstelle in deiner App und gib an, wie die empfangenen Statusaktualisierungen verarbeitet werden sollen. - Erweitern Sie
NotificationReceiverServiceund stellen Sie über die MethodegetCommandListenereineCommandListener-Instanz bereit. Konfigurieren Sie die Anwendungsrichtlinie, um Ihrer Anwendung die Rolle
COMPANION_APPzuzuweisen (siehe Richtlinienkonfiguration).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 ...; } }
Richtlinienkonfiguration
Damit die Companion-App direkt mit dem ADP kommunizieren kann, muss der EMM-Anbieter der App die Rolle COMPANION_APP über das Feld roles in der Anwendungsrichtlinie zuweisen:
"applications": [{
"packageName": "com.amapi.extensibility.demo",
"installType": "FORCE_INSTALLED",
"roles": [
{ "roleType": "COMPANION_APP" }
]
}]
Damit die Companion-App einen lokalen Wipe-Befehl ausgeben kann, setze extraCapabilities.localDeviceWipeCommand auf 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"
}
}
]
}]
Weitere Optionen finden Sie unter Gerät mit Richtlinien für App-Rollen bereitstellen.
Der Dienst, der auf Änderungen des Befehlsstatus wartet, wird von ADP automatisch anhand von Metadaten im Manifest der App erkannt und muss nicht mehr in der Richtlinie als notificationReceiver angegeben werden. Weitere Informationen zum Vorbereiten Ihrer Anwendung und des Manifests finden Sie unter Anwendungsrollen verwalten.
Test
Unittests
LocalCommandClient ist eine Schnittstelle und ermöglicht daher eine testbare Implementierung.
Integrationstests
Die folgenden Informationen sind für den Test mit ADP erforderlich:
- Paketname der Erweiterungs-App.
- Der base64-codierte SHA-256-Hash der Signatur, die mit dem App-Paket verknüpft ist.
- Optional, wenn der Callback getestet wird: Voll qualifizierter Name des Dienstes aus dem neu eingeführten Dienst zur Unterstützung des Callbacks. (Voll qualifizierter Name von
CommandServiceim Beispiel).