تطبيقات الإضافات والأوامر المحلية

تتيح حزمة تطوير البرامج (SDK) لواجهة Android Management API (AMAPI) لتطبيق إضافة محدّد من خلال "إدارة الخدمات الجوّالة للمؤسسات" (EMM) التواصل مباشرةً مع تطبيق Android Device Policy (ADP) وتنفيذ Commands على الجهاز.

الدمج مع حزمة تطوير البرامج (SDK) لواجهة برمجة التطبيقات AMAPI توفّر مزيدًا من المعلومات حول هذه المكتبة وكيفية إضافتها إلى تطبيقك.

بعد دمج حزمة تطوير البرامج (SDK)، يمكن لتطبيق الإضافة التواصل مع ADP لإجراء ما يلي:

إصدار أمر

يمكن لتطبيق الإضافة طلب إصدار الأوامر باستخدام ADP. يحتوي IssueCommandRequest على عنصر الطلب الذي سيتضمّن تفاصيل عن الأمر الذي سيتم إصداره ومَعلمات محدّدة.

محو بيانات التطبيق

يوضّح المقتطف التالي كيفية إصدار طلب لمحو بيانات الحزمة:

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;

...
  void issueClearAppDataCommand(ImmutableList<String> packageNames) {
    Futures.addCallback(
        LocalCommandClientFactory.create(getContext())
            .issueCommand(createClearAppRequest(packageNames)),
        new FutureCallback<Command>() {
          @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 createClearAppRequest(ImmutableList<String> packageNames) {
    return IssueCommandRequest.builder()
        .setClearAppsData(
            ClearAppsData.builder()
                .setPackageNames(packageNames)
                .build()
        )
        .build();
  }
...

يوضّح المثال السابق إصدار طلب واضح لبيانات التطبيق لحِزم محدّدة والانتظار حتى يتم إصدار الأمر بنجاح. في حال نجاح الإصدار، سيتم عرض عنصر Command مع حالة الأمر الحالية ورقم تعريف الأمر الذي يمكن استخدامه لاحقًا للاستعلام عن حالة أي أوامر طويلة الأمد.

حجب بيانات الجهاز أو ملف العمل

يمكن للتطبيق المصاحب إصدار أمر Wipe محلي عندما يمنح مشرف تكنولوجيا المعلومات دور COMPANION_APP مع إمكانية LOCAL_DEVICE_WIPE_COMMAND_ALLOWED الإضافية. على الأجهزة المملوكة للشركة، يؤدي ذلك إلى إعادة ضبط الجهاز على الإعدادات الأصلية، أما على الأجهزة الشخصية التي تتضمّن ملف عمل، فيؤدي ذلك إلى إزالة ملف العمل. إذا لم يكن التطبيق يملك هذا الدور والإمكانية الإضافية، سيتعذّر تنفيذ الطلب. لمعرفة التفاصيل، يُرجى الاطّلاع على ضبط السياسات.

كلتا مَعلمتَي Wipe اختياريتان. إذا كنت لا تريد تحديد قيمة، يمكنك حذف دالة الضبط في أداة الإنشاء أو تمرير مجموعة فارغة أو سلسلة فارغة (لا تمرِّر null).

  • WipeDataFlag تتحكّم القيم في البيانات التي يتم حجبها:
    • ‫PRESERVE_RESET_PROTECTION_DATA: يحافظ على بيانات "حماية الجهاز بعد إعادة ضبطه على الإعدادات الأصلية"، والتي يتم محوها تلقائيًا. لا ينطبق هذا الإعداد إلا على الأجهزة المملوكة للشركة.
    • WIPE_EXTERNAL_STORAGE: يتم أيضًا حجب بيانات وحدة التخزين الخارجية، مثل بطاقات SD. لا ينطبق هذا الإعداد إلا على الأجهزة المملوكة للشركة.
    • ‫WIPE_ESIMS: لإزالة جميع شرائح eSIM من الأجهزة المملوكة للشركة على الأجهزة المملوكة شخصيًا، تتم إزالة شرائح eSIM المُدارة فقط (على Android 16 أو الإصدارات الأحدث، تتم دائمًا إزالة شرائح eSIM المُدارة مع ملف العمل، سواء تم ضبط هذه العلامة أم لا).
  • ‫wipeReason: رسالة قصيرة تصل إلى 200 حرف، تظهر للمستخدم عند إزالة ملف العمل من جهاز مملوك شخصيًا. ولا يؤثّر ذلك في الأجهزة المملوكة للشركة.

يوضّح المقتطف التالي كيفية إصدار طلب لمحو بيانات الجهاز أو ملف العمل:

Kotlin

import android.content.Context
import android.util.Log
...
import com.google.android.managementapi.commands.CommandException
import com.google.android.managementapi.commands.LocalCommandClientFactory
import com.google.android.managementapi.commands.model.IssueCommandRequest
import com.google.android.managementapi.commands.model.IssueCommandRequest.Wipe
import com.google.android.managementapi.commands.model.IssueCommandRequest.Wipe.WipeDataFlag

...
  suspend fun issueWipeCommand(
      context: Context,
      wipeDataFlags: List<WipeDataFlag>,
      wipeReason: String,
  ) {
    try {
      val command =
          LocalCommandClientFactory.create(context)
              .issueCommandAwait(createWipeRequest(wipeDataFlags, wipeReason))
      Log.i(TAG, "Successfully issued wipe command: ${command.commandId}")
    } catch (e: CommandException) {
      Log.e(TAG, "Failed to issue wipe command", e)
    }
  }

  fun createWipeRequest(
      wipeDataFlags: List<WipeDataFlag>,
      wipeReason: String,
  ): IssueCommandRequest =
      IssueCommandRequest.builder()
          .setWipe(
              Wipe.builder()
                  .setWipeDataFlags(wipeDataFlags)
                  .setWipeReason(wipeReason)
                  .build()
          )
          .build()
...

جافا

import android.content.Context;
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.Wipe;
import com.google.android.managementapi.commands.model.IssueCommandRequest.Wipe.WipeDataFlag;
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;

...
  void issueWipeCommand(
      Context context,
      ImmutableList<WipeDataFlag> wipeDataFlags,
      String wipeReason) {
    Futures.addCallback(
        LocalCommandClientFactory.create(context)
            .issueCommand(createWipeRequest(wipeDataFlags, wipeReason)),
        new FutureCallback<Command>() {
          @Override
          public void onSuccess(Command result) {
            Log.i(TAG, "Successfully issued wipe command: " + result.getCommandId());
          }

          @Override
          public void onFailure(Throwable t) {
            Log.e(TAG, "Failed to issue wipe command", t);
          }
        },
        MoreExecutors.directExecutor());
  }

  IssueCommandRequest createWipeRequest(
      ImmutableList<WipeDataFlag> wipeDataFlags, String wipeReason) {
    return IssueCommandRequest.builder()
        .setWipe(
            Wipe.builder()
                .setWipeDataFlags(wipeDataFlags)
                .setWipeReason(wipeReason)
                .build())
        .build();
  }
...

Get Command

يمكن لتطبيق الإضافة الاستعلام عن حالة طلبات الأوامر التي تم إصدارها سابقًا. لاسترداد حالة أمر، ستحتاج إلى معرّف الأمر (المتاح من طلب إصدار الأمر). يوضّح المقتطف التالي كيفية إرسال GetCommandRequest إلى ADP.

import android.util.Log;
...
import com.google.android.managementapi.commands.LocalCommandClientFactory;
...
import com.google.android.managementapi.commands.model.GetCommandRequest;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.MoreExecutors;

...
  void getCommand(String commandId) {
    Futures.addCallback(
        LocalCommandClientFactory.create(getApplication())
            .getCommand(GetCommandRequest.builder().setCommandId(commandId).build()),
        new FutureCallback<Command>() {
          @Override
          public void onSuccess(Command result) {
            // Process the returned command result here
            Log.i(Constants.TAG, "Successfully issued command");
          }

          @Override
          public void onFailure(Throwable t) {
            Log.e(Constants.TAG, "Failed to issue command", t);
          }
        },
        MoreExecutors.directExecutor());
  }
  ...

الاستماع إلى عمليات معاودة الاتصال لتغيير حالة الطلب

يمكن لتطبيق الإضافة تسجيل استدعاء اختياري لتلقّي تحديثات بشأن تغييرات حالة الأوامر التي تستغرق وقتًا طويلاً باتّباع الخطوات التالية:

  1. يتم إرسال إشعارات بتغييرات حالة الأمر إلى CommandListener، لذا عليك تنفيذ هذه الواجهة في تطبيقك وتوفير طريقة للتعامل مع تحديثات الحالة التي يتم تلقّيها.
  2. يمكنك توسيع NotificationReceiverService وتقديم مثيل CommandListener من خلال طريقة getCommandListener.
  3. اضبط سياسة التطبيق لتعيين دور COMPANION_APP لتطبيقك (راجِع ضبط السياسة).

    import com.google.android.managementapi.commands.CommandListener;
    import com.google.android.managementapi.notification.NotificationReceiverService;
    
    ...
    
    public class SampleCommandService extends NotificationReceiverService {
    
     @Override
     public CommandListener getCommandListener() {
       // return the concrete implementation from previous step
       return ...;
     }
    }
    

إعدادات السياسة

للسماح للتطبيق المصاحب بالتواصل مباشرةً مع ADP، على "إدارة الخدمات الجوّالة للمؤسسات" (EMM) إسناد دور COMPANION_APP إلى التطبيق باستخدام حقل roles في سياسة التطبيق:

 "applications": [{
   "packageName": "com.amapi.extensibility.demo",
   "installType": "FORCE_INSTALLED",
   "roles": [
     { "roleType": "COMPANION_APP" }
   ]
 }]

للسماح للتطبيق المصاحب بإصدار أمر Wipe محلي، اضبط extraCapabilities.localDeviceWipeCommand على LOCAL_DEVICE_WIPE_COMMAND_ALLOWED:

 "applications": [{
   "packageName": "com.amapi.extensibility.demo",
   "installType": "FORCE_INSTALLED",
   "roles": [
     {
       "roleType": "COMPANION_APP",
       "extraCapabilities": {
         "localDeviceWipeCommand": "LOCAL_DEVICE_WIPE_COMMAND_ALLOWED"
       }
     }
   ]
 }]

لمعرفة الخيارات الإضافية المتاحة، يُرجى الاطّلاع على توفير الجهاز باستخدام سياسات أدوار التطبيقات.

يتم تلقائيًا اكتشاف الخدمة التي تستمع إلى تغييرات حالة الأمر من خلال ADP باستخدام البيانات الوصفية في بيان التطبيق، ولم يعد من الضروري تحديدها في السياسة على أنّها notificationReceiver. لمعرفة التفاصيل حول كيفية إعداد تطبيقك وملف البيان، يُرجى الاطّلاع على إدارة أدوار التطبيق.

الاختبار

اختبار الوحدة

LocalCommandClient هي واجهة، وبالتالي تتيح توفير عملية استخدام قابلة للاختبار.

اختبار التكامل

ستحتاج إلى المعلومات التالية لإجراء الاختبار باستخدام ADP:

  1. اسم حزمة تطبيق الإضافة
  2. تجزئة SHA-256 بترميز Base64 للتوقيع المرتبط بحزمة التطبيق
  3. يمكنك اختياريًا، في حال اختبار معاودة الاتصال، إدخال الاسم المؤهَّل بالكامل للخدمة من الخدمة التي تم تقديمها حديثًا لإتاحة معاودة الاتصال. (الاسم المؤهَّل بالكامل لـ CommandService في المثال)