بصفتك إدارة خدمات جوّالة للمؤسسات (EMM) تستند إلى Android Management API، يمكنك إدارة التطبيقات المخصّصة عن بُعد على الأجهزة. ويشمل ذلك تثبيت هذه التطبيقات وإلغاء تثبيتها. يتم تحقيق هذه الوظيفة من خلال تطوير تطبيق إضافة محليًا باستخدام حزمة تطوير البرامج (SDK) لواجهة برمجة التطبيقات AMAPI.
المتطلبات الأساسية
- تم دمج تطبيق الإضافة مع حزمة تطوير البرامج (SDK) لواجهة برمجة التطبيقات AMAPI.
- الجهاز مُدار بالكامل.
- يجب توفُّر الإصدار 1.6.0-rc01 أو إصدار أحدث من حزمة تطوير البرامج (SDK) لواجهة برمجة التطبيقات AMAPI.
1. إعداد تطبيقك لاستخدام الميزة
1-1- الدمج مع حزمة تطوير البرامج (SDK) لواجهة برمجة التطبيقات AMAPI في تطبيق الإضافة
تتطلّب عملية إدارة التطبيقات المخصّصة دمج حزمة تطوير البرامج (SDK) لواجهة برمجة التطبيقات AMAPI في تطبيق الإضافة. ويمكنك العثور على مزيد من المعلومات حول هذه المكتبة وكيفية إضافتها إلى تطبيقك في دليل دمج حزمة تطوير البرامج (SDK) لواجهة برمجة التطبيقات AMAPI.
1.2. تعديل بيان التطبيق ليتوافق مع FileProvider
- أضِف إلى
AndroidManifest.xml
العنصر<queries>
لتطبيق "سياسة أجهزة Android" (ADP) كما هو موضّح في دليل دمج حزمة تطوير البرامج (SDK) لواجهة برمجة تطبيقات إدارة Android . - نفِّذ مقتطف الرمز
<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. الدمج مع ميزة التطبيقات المخصّصة في حزمة تطوير البرامج (SDK) لواجهة برمجة تطبيقات إدارة Android
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
.توفير الجهاز في وضع الإدارة الكاملة باستخدام رمز التسجيل
ثبِّت تطبيق الإضافات من "Google Play للأعمال".
تطبيق قابلية التوسيع:
- يمكن تنزيل ملف APK للتطبيق المخصّص
- يمكنه إصدار طلب لتثبيت التطبيق المخصّص (راجِع مقتطف الرمز الموضّح سابقًا)
- يجب أن تتلقّى ردًا
واجهة برمجة التطبيقات
واجهة برمجة التطبيقات بين الخادم والعميل
راجِع الحقول الجديدة وقيم التعداد المُدرَجة: