Zarządzanie aplikacjami niestandardowymi za pomocą interfejsu AMAPI

Jako usługa EMM oparta na interfejsie Android Management API możesz zdalnie zarządzać aplikacjami niestandardowymi na urządzeniach. Obejmuje to zarówno instalowanie, jak i odinstalowywanie tych aplikacji. Możesz to zrobić, tworząc lokalnie aplikację rozszerzającą za pomocą pakietu AMAPI SDK.

Wymagania wstępne

1. Przygotowanie aplikacji do korzystania z tej funkcji

1.1. Integracja z pakietem AMAPI SDK w aplikacji rozszerzenia

Proces zarządzania aplikacjami niestandardowymi wymaga zintegrowania pakietu AMAPI SDK z aplikacją rozszerzającą. Więcej informacji o tej bibliotece i sposobie dodawania jej do aplikacji znajdziesz w przewodniku po integracji pakietu AMAPI SDK.

1.2. Zaktualizuj manifest aplikacji, aby obsługiwał FileProvider

  • Dodaj do elementu AndroidManifest.xml element <queries> dla aplikacji Android Device Policy (ADP), jak pokazano w przewodniku po integracji pakietu AMAPI SDK.
  • Wdróż w aplikacji ten fragment kodu <provider>AndroidManifest.xml w tagu <application>. Ten fragment kodu służy do przechowywania plików podczas udostępniania pliku APK aplikacji niestandardowej, co umożliwia instalowanie aplikacji niestandardowych za pomocą interfejsu AMAPI.

AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.customapp">
  <queries>
    <package android:name="com.google.android.apps.work.clouddpc" />
  </queries>

  <application>

    <!--This is used to store files when sharing the custom app apk.-->
    <provider
        android:name="com.google.android.managementapi.customapp.provider.CustomAppProvider"
        android:authorities="${applicationId}.AmapiCustomAppProvider"
        android:exported="false"
        android:grantUriPermissions="true">
      <meta-data
          android:name="android.support.FILE_PROVIDER_PATHS"
          android:resource="@xml/file_provider_paths" />
    </provider>
  </application>
</manifest>
  • Utwórz nowy plik XML w katalogu res/xml/ aplikacji, który będzie zawierać ścieżkę do niestandardowych plików APK.

file_provider_paths.xml:

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
  <cache-path
      name="android_managementapi_custom_apks"
      path="com.google.android.managementapi/customapp/apks/" />
</paths>

2. Integracja z funkcją aplikacji niestandardowych pakietu AMAPI SDK

2.1. Przygotowywanie niestandardowego pliku APK do instalacji

Przed wdrożeniem plik APK aplikacji musi zostać przygotowany do instalacji. Proces ten ilustruje poniższy fragment kodu:

Kotlin

import android.net.Uri
import androidx.core.net.Uri
import java.io.File
...
import com.google.android.managementapi.commands.LocalCommandClient
import com.google.android.managementapi.commands.LocalCommandClient.InstallCustomAppCommandHelper
import com.google.android.managementapi.commands.LocalCommandClientFactory

...

fun prepareApkFile(): Uri? {
    // Get the storage location of custom APK files from AM API
    val client: LocalCommandClient = LocalCommandClientFactory.create(context)
    val installCustomAppCommandHelper = client.installCustomAppCommandHelper

    val customApksStorageDir: File = installCustomAppCommandHelper.customApksStorageDirectory ?: return null

    // Once you get hold of the custom APKs storage directory, you must store your custom APK
    // in that location before issuing the install command.
    val customApkFile: File = fetchMyAppToDir(customApksStorageDir) ?: return null
    val customApkFileUri: Uri = customApkFile.toUri()

    return customApkFileUri
}

Java

import android.net.Uri;
import androidx.core.net.Uri;
import java.io.File;
...
import com.google.android.managementapi.commands.LocalCommandClient;
import com.google.android.managementapi.commands.LocalCommandClient.InstallCustomAppCommandHelper;
import com.google.android.managementapi.commands.LocalCommandClientFactory;

...

Uri prepareApkFile() {
  // Get the storage location of custom APK files from AM API
  LocalCommandClient client = LocalCommandClientFactory.create();
  InstallCustomAppCommandHelper installCustomAppCommandHelper = client.getInstallCustomAppCommandHelper();
  File customApksStorageDir = installCustomAppCommandHelper.getCustomApksStorageDirectory();

  // Once you get hold of the custom APKs storage directory, you must store your custom APK
  // in that location before issuing the install command.
  File customApkFile = fetchMyAppToDir(customApksStorageDir);
  Uri customApkFileUri = Uri.fromFile(customApkFile);
  ...
}

2.2. Wysyłanie prośby o zainstalowanie aplikacji niestandardowej

Poniższy fragment kodu pokazuje, jak wysłać żądanie instalacji aplikacji niestandardowej:

Kotlin

import android.content.Context
import android.net.Uri
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.InstallCustomApp
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.guava.await
import kotlinx.coroutines.withContext
import java.lang.Exception

private const val TAG = "MyClass"

...

    // Requires a file URI of the APK file.
    fun issueInstallCustomAppCommand(packageName: String, fileUri: Uri) {
        coroutineScope.launch {
            try {
                withContext(coroutineScope.coroutineContext) {
                    val result: Command = LocalCommandClientFactory.create(context)
                        .issueCommand(createInstallCustomAppRequest(packageName, fileUri)).await()
                    // Process the returned command result here.
                    Log.i(TAG, "Successfully issued command: $result")
                }
            } catch (t: Exception) {
                Log.e(TAG, "Failed to issue command", t)
                // Handle the exception (e.g., show an error message)
            } finally {
                // Make sure to clean up the apk file after the command is executed.
                cleanUpApkFile(fileUri)
            }
        }
    }

    private fun createInstallCustomAppRequest(packageName: String, fileUri: Uri): IssueCommandRequest {
        return IssueCommandRequest.builder()
            .setInstallCustomApp(
                InstallCustomApp.builder()
                    .setPackageName(packageName)
                    .setPackageUri(fileUri.toString())
                    .build()
            )
            .build()
    }
}

Java

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;

...

  // Requires a file URI of the APK file.
  void issueInstallCustomAppCommand(String packageName, Uri fileUri) {
    Futures.addCallback(
        LocalCommandClientFactory.create(getContext())
            .issueCommand(createInstallCustomAppRequest(packageName, fileUri)),
        new FutureCallback() {
          @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 createInstallCustomAppRequest(String packageName, Uri fileUri) {
    return IssueCommandRequest.builder()
        .setInstallCustomApp(
            InstallCustomApp.builder()
                .setPackageName(packageName)
                .setPackageUri(fileUri.toString())
                .build()
        )
        .build();
  }

2.3. Wysyłanie prośby o pobranie zainstalowanych aplikacji

Kotlin

import android.content.Context
import com.google.android.managementapi.device.DeviceClientFactory
import com.google.android.managementapi.device.model.GetDeviceRequest
import kotlinx.coroutines.guava.await

  suspend fun getInstalledApps(context: Context) =
    DeviceClientFactory.create(context)
      .getDevice(GetDeviceRequest.getDefaultInstance())
      .await()
      .getApplicationReports()

Java

import android.content.Context;
import com.google.android.managementapi.device.DeviceClientFactory;
import com.google.android.managementapi.device.model.GetDeviceRequest;
import com.google.android.managementapi.device.model.Device;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import java.util.List;
import java.util.concurrent.Executor;

public ListenableFuture<List> getInstalledApps() {
        ListenableFuture deviceFuture =
            DeviceClientFactory.create(context)
                .getDevice(GetDeviceRequest.getDefaultInstance());

        return Futures.transform(
            deviceFuture,
            Device::getApplicationReports,
            executor // Use the provided executor
        );
    }

3. wdrażać na urządzeniu niestandardowe zasady zarządzania aplikacjami;

  1. Skonfiguruj policy z aplikacjami niestandardowymi, którymi chcesz zarządzać.

      {
        "statusReportingSettings": {
          "applicationReportsEnabled": true
        },
        "applications": [
        {
          "signingKeyCerts": [
            {
            "signingKeyCertFingerprintSha256": <sha256 signing key certificate hash value>
            }
          ],
          "packageName": "<emm_extensibility_app>",
          "installType": "AVAILABLE",
          "lockTaskAllowed": true,
          "defaultPermissionPolicy": "GRANT",
          "extensionConfig": {
              "notificationReceiver": "com.example.customapp.NotificationReceiverService"
          }
        },
        {
          "signingKeyCerts": [
            {
            "signingKeyCertFingerprintSha256": <sha256 signing key certificate hash value>
            },
          ],
          "packageName": "<custom_app>",
          "installType": "CUSTOM",
          "lockTaskAllowed": true,
          "defaultPermissionPolicy": "GRANT",
          "customAppConfig": {
        "userUninstallSettings": "DISALLOW_UNINSTALL_BY_USER"
          }
        }
        ]
      }
      ```
    
  2. Utwórz token rejestracji urządzenia, wywołując funkcję enterprises.enrollmentTokens.create, z parametrem allowPersonalUsage ustawionym na PERSONAL_USAGE_DISALLOWED.

  3. Udostępnij urządzenie w trybie pełnego zarządzania za pomocą tokena rejestracji.

  4. Zainstaluj aplikację rozszerzającą z zarządzanego Sklepu Play.

  5. Aplikacja rozszerzająca:

    • może pobrać plik APK aplikacji niestandardowej,
    • może wysłać żądanie zainstalowania aplikacji niestandardowej (patrz fragment kodu pokazany wcześniej);
    • powinna otrzymać odpowiedź.

Interfejs API

Interfejs API serwer-klient

Sprawdź nowe pola i wyliczenia: