AMAPI ile özel uygulamaları yönetme

Android Management API tabanlı bir EMM olarak, cihazlardaki özel uygulamaları uzaktan yönetebilirsiniz. Bu işlemlere söz konusu uygulamaların yüklenmesi ve kaldırılması dahildir. Bu işlev, AMAPI SDK kullanılarak yerel olarak bir uzantı uygulaması geliştirilerek sağlanır.

Ön koşullar

  • Uzantı uygulamanız AMAPI SDK ile entegre edilmiş olmalıdır.
  • Cihaz tümüyle yönetiliyor olmalıdır.
  • AMAPI SDK v1.6.0-rc01 veya sonraki bir sürümünün kullanılması gereklidir.

1. Uygulamanızı özelliği kullanmaya hazırlama

1.1. Uzantı uygulamanızda AMAPI SDK'sı ile entegrasyon yapma

Özel uygulama yönetimi süreci, AMAPI SDK'sını uzantı uygulamanıza entegre etmenizi gerektirir. Bu kitaplık ve uygulamanıza nasıl ekleneceği hakkında daha fazla bilgiyi AMAPI SDK entegrasyon kılavuzunda bulabilirsiniz.

1.2. FileProvider özelliğini desteklemek için uygulamanızın manifest dosyasını güncelleyin

  • AndroidManifest.xml öğesine <queries> öğesini Android Cihaz Politikası (ADP) uygulaması için AMAPI SDK entegrasyon kılavuzunda gösterildiği şekilde ekleyin .
  • Aşağıdaki <provider> snippet'ini uygulamanızın <application> etiketi içindeki AndroidManifest.xml bölümüne uygulayın. Bu snippet, özel uygulama APK'sı paylaşılırken dosyaları depolamak ve AMAPI kullanılarak özel uygulamaların yüklenmesini sağlamak için kullanılır.

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>
  • Uygulamanızın res/xml/ dizininde, özel APK'lerin depolama yolunu içeren yeni bir XML dosyası oluşturun.

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'sının özel uygulama özelliğiyle entegrasyon

2.1. Özel APK dosyasını yüklemeye hazırlama

Uygulamanın APK dosyası, dağıtılmadan önce kuruluma hazırlanmalıdır. Aşağıdaki kod snippet'inde bu süreç gösterilmektedir:

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. Özel bir uygulamanın yüklenmesi için istek gönderme

Aşağıdaki snippet'te, özel bir uygulamanın yüklenmesi için nasıl istek gönderileceği gösterilmektedir:

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. Yüklü uygulamaları almak için istekte bulunma

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. Cihazı özel uygulama yönetimi politikalarıyla sağlama

  1. Yönetmeyi planladığınız özel uygulamalarla bir policy oluşturun.

      {
        "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"
          }
        }
        ]
      }
      ```
    
  2. allowPersonalUsage, PERSONAL_USAGE_DISALLOWED olarak ayarlanmışken enterprises.enrollmentTokens.create işlevini çağırarak cihaz için bir kayıt jetonu oluşturun.

  3. Cihazı kayıt jetonuyla tümüyle yönetilen modda hazırlayın.

  4. Genişletilebilirlik uygulamanızı Managed Play'den yükleyin.

  5. Genişletilebilirlik uygulamanız:

    • özel uygulamanın APK dosyasını indirebilir.
    • Özel uygulamanın yüklenmesi için istekte bulunabilir (daha önce gösterilen kod snippet'ine bakın).
    • yanıt almalıdır.

API

Sunucu-istemci API'si

Listelenen yeni alanlara ve numaralandırılmış değerlere bakın: