Quản lý ứng dụng tuỳ chỉnh bằng AMAPI

Là một EMM dựa trên Android Management API, bạn có thể quản lý từ xa các ứng dụng tuỳ chỉnh trên thiết bị. Điều này bao gồm cả việc cài đặt và gỡ cài đặt các ứng dụng này. Bạn có thể thực hiện chức năng này bằng cách phát triển một ứng dụng tiện ích cục bộ bằng SDK AMAPI.

Điều kiện tiên quyết

  • Ứng dụng tiện ích của bạn được tích hợp với AMAPI SDK.
  • Thiết bị này được quản lý hoàn toàn.
  • Yêu cầu AMAPI SDK phiên bản 1.6.0-rc01 trở lên.

1. Chuẩn bị ứng dụng để sử dụng tính năng này

1.1. Tích hợp với AMAPI SDK trong ứng dụng tiện ích

Quy trình quản lý ứng dụng tuỳ chỉnh yêu cầu bạn tích hợp AMAPI SDK vào ứng dụng tiện ích. Bạn có thể tìm thêm thông tin về thư viện này và cách thêm thư viện vào ứng dụng trong hướng dẫn tích hợp AMAPI SDK.

1.2. Cập nhật tệp kê khai của ứng dụng để hỗ trợ FileProvider

  • Thêm vào AndroidManifest.xml phần tử <queries> cho ứng dụng Chính sách thiết bị Android (ADP) như minh hoạ trong hướng dẫn tích hợp AMAPI SDK.
  • Triển khai đoạn mã <provider> sau đây vào AndroidManifest.xml của ứng dụng bên trong thẻ <application>. Đoạn mã này được dùng để lưu trữ các tệp khi chia sẻ APK của ứng dụng tuỳ chỉnh, cho phép cài đặt các ứng dụng tuỳ chỉnh bằng 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>
  • Tạo một tệp XML mới trong thư mục res/xml/ của ứng dụng, chứa đường dẫn lưu trữ cho các APK tuỳ chỉnh.

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. Tích hợp với tính năng ứng dụng tuỳ chỉnh của AMAPI SDK

2.1. Chuẩn bị tệp APK tuỳ chỉnh để cài đặt

Trước khi triển khai, bạn phải chuẩn bị tệp APK của ứng dụng để cài đặt. Đoạn mã sau đây minh hoạ quy trình này:

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. Đưa ra yêu cầu cài đặt một ứng dụng tuỳ chỉnh

Đoạn mã sau đây cho biết cách đưa ra yêu cầu cài đặt một ứng dụng tuỳ chỉnh:

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. Đưa ra yêu cầu nhận các ứng dụng đã cài đặt

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. Cung cấp thiết bị bằng các chính sách quản lý ứng dụng tuỳ chỉnh

  1. Thiết lập một policy bằng các ứng dụng tuỳ chỉnh mà bạn dự định quản lý.

      {
        "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. Tạo mã thông báo đăng ký cho thiết bị bằng cách gọi enterprises.enrollmentTokens.create, với allowPersonalUsage được đặt thành PERSONAL_USAGE_DISALLOWED.

  3. Cung cấp thiết bị ở chế độ được quản lý hoàn toàn bằng mã thông báo đăng ký.

  4. Cài đặt ứng dụng có khả năng mở rộng qua Managed Play.

  5. Ứng dụng có khả năng mở rộng của bạn:

    • có thể tải tệp APK của ứng dụng tuỳ chỉnh xuống
    • có thể đưa ra yêu cầu cài đặt ứng dụng tuỳ chỉnh (tham khảo đoạn mã đã trình bày trước đó)
    • sẽ nhận được phản hồi

API

API máy chủ-máy khách

Tham khảo các trường và enum mới được liệt kê: