แอปส่วนขยายและคำสั่งในเครื่อง

SDK ของ Android Management API (AMAPI) ช่วยให้แอปส่วนขยายที่ EMM ระบุสามารถสื่อสารกับ Android Device Policy (ADP) ได้โดยตรงและเรียกใช้ Commands ในอุปกรณ์

ผสานรวมกับ AMAPI SDK ให้ข้อมูลเพิ่มเติมเกี่ยวกับไลบรารีนี้และวิธีเพิ่มลงในแอปพลิเคชัน

เมื่อผสานรวม 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 ทั้ง 2 รายการเป็นพารามิเตอร์ที่ไม่บังคับ หากไม่ต้องการระบุค่า ให้ละเว้นเมธอด Setter ของ Builder หรือส่งคอลเล็กชันว่างหรือสตริงว่าง (อย่าส่ง 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()
...

Java

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();
  }
...

รับคำสั่ง

แอปส่วนขยายสามารถค้นหาสถานะของคำขอคำสั่งที่ออกก่อนหน้านี้ได้ หากต้องการเรียกดูสถานะของคำสั่ง คุณจะต้องใช้รหัสคำสั่ง (ดูได้จากคำขอคำสั่งปัญหา) ข้อมูลโค้ดต่อไปนี้แสดงวิธีส่ง 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());
  }
  ...

ฟัง Callback การเปลี่ยนแปลงสถานะคำสั่ง

แอปส่วนขยายสามารถเลือกที่จะลงทะเบียน Callback เพื่อรับข้อมูลอัปเดตเกี่ยวกับการเปลี่ยนแปลงสถานะของคำสั่งที่ใช้เวลานานได้โดยทำตามขั้นตอนต่อไปนี้

  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 จะค้นพบบริการที่คอยฟังการเปลี่ยนแปลงสถานะคำสั่งโดยอัตโนมัติโดยใช้ข้อมูลเมตาในไฟล์ Manifest ของแอป และไม่จำเป็นต้องระบุในนโยบายเป็น notificationReceiver อีกต่อไป โปรดดูรายละเอียดเกี่ยวกับวิธีเตรียมแอปและไฟล์ Manifest ที่หัวข้อจัดการบทบาทของแอปพลิเคชัน

การทดสอบ

การทำ Unit Test

LocalCommandClient เป็นอินเทอร์เฟซ จึงช่วยให้คุณระบุการใช้งานที่ทดสอบได้

การทดสอบการผสานรวม

คุณจะต้องใช้ข้อมูลต่อไปนี้เพื่อทดสอบกับ ADP

  1. ชื่อแพ็กเกจของแอปส่วนขยาย
  2. แฮช SHA-256 ที่เข้ารหัส Base64 ของลายเซ็นที่เชื่อมโยงกับแพ็กเกจแอป
  3. ไม่บังคับ หากทดสอบ Callback - ชื่อที่สมบูรณ์ในตัวเองของบริการจากบริการที่เปิดตัวใหม่เพื่อรองรับ Callback (ชื่อที่สมบูรณ์ในตัวเองของ CommandService ในตัวอย่าง)