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àoAndroidManifest.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
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" } } ] } ```
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ànhPERSONAL_USAGE_DISALLOWED
.Cung cấp thiết bị ở chế độ được quản lý hoàn toàn bằng mã thông báo đăng ký.
Cài đặt ứng dụng có khả năng mở rộng qua Managed Play.
Ứ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ê: