כספק EMM שמבוסס על Android Management API, אתם יכולים לנהל מרחוק אפליקציות מותאמות אישית במכשירים. הנתון כולל גם התקנות וגם הסרות של האפליקציות האלה. כדי להשיג את הפונקציונליות הזו, צריך לפתח אפליקציית תוסף באופן מקומי באמצעות AMAPI SDK.
דרישות מוקדמות
- אפליקציית התוסף שלך משולבת עם AMAPI SDK.
- המכשיר מנוהל באופן מלא.
- נדרשת גרסת AMAPI SDK v1.6.0-rc01 ואילך.
1. הכנת האפליקציה לשימוש בתכונה
1.1. שילוב עם AMAPI SDK באפליקציית התוסף
כדי לנהל אפליקציות בהתאמה אישית, צריך לשלב את AMAPI SDK באפליקציית התוסף. מידע נוסף על הספרייה הזו ועל אופן ההוספה שלה לאפליקציה זמין במדריך לשילוב AMAPI SDK.
1.2. עדכון המניפסט של האפליקציה כדי לתמוך ב-FileProvider
- מוסיפים ל-
AndroidManifest.xml
את רכיב<queries>
של אפליקציית Android Device Policy (ADP) כמו שמוצג במדריך השילוב של AMAPI SDK. - מטמיעים את קטע הקוד
<provider>
הבא ב-AndroidManifest.xml
של האפליקציה בתוך התג<application>
. קטע הקוד הזה משמש לאחסון קבצים כשמשתפים את קובץ ה-APK של האפליקציה בהתאמה אישית, וכך מאפשר להתקין אפליקציות בהתאמה אישית באמצעות 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>
- יוצרים קובץ XML חדש בספרייה
res/xml/
של האפליקציה, שמכיל את נתיב האחסון של קובצי 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. שילוב עם התכונה של אפליקציה בהתאמה אישית ב-AMAPI SDK
2.1. הכנת קובץ ה-APK בהתאמה אישית להתקנה
לפני הפריסה, צריך להכין את קובץ ה-APK של האפליקציה להתקנה. קטע הקוד הבא מדגים את התהליך:
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. שליחת בקשה להתקנת אפליקציה בהתאמה אישית
קטע הקוד הבא מראה איך שולחים בקשה להתקנת אפליקציה בהתאמה אישית:
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. שליחת בקשה לקבלת רשימה של אפליקציות מותקנות
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. הקצאת הרשאות למכשיר באמצעות מדיניות מותאמת אישית לניהול אפליקציות
מגדירים
policy
עם האפליקציות המותאמות אישית שרוצים לנהל.{ "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" } } ] } ```
יוצרים טוקן רישום למכשיר על ידי קריאה ל-enterprises.enrollmentTokens.create, עם
allowPersonalUsage
שמוגדר ל-PERSONAL_USAGE_DISALLOWED
.מקצים את המכשיר במצב מנוהל באופן מלא באמצעות טוקן ההרשמה.
מתקינים את אפליקציית ההרחבות מ-Managed Play.
האפליקציה שלך עם יכולת ההרחבה:
- יכולים להוריד את קובץ ה-APK של האפליקציה המותאמת אישית
- יכול לשלוח בקשה להתקנת האפליקציה המותאמת אישית (ראו קטע קוד שמוצג למעלה)
- אמורה להתקבל תשובה
API
Server-client API
כדאי לעיין בשדות ובסוגי הנתונים החדשים שמופיעים ברשימה: