Manage custom apps with AMAPI

  • The Android Management API allows EMMs to remotely manage custom apps on fully managed devices by integrating with the AMAPI SDK v1.6.0-rc01 or higher.

  • Managing custom apps requires integrating with the AMAPI SDK and updating the app's manifest to supportFileProvider.

  • The process involves preparing the custom APK file by storing it in a designated directory obtained from the AMAPI SDK and then issuing a command to install the custom app using the SDK'sLocalCommandClient.

  • Device provisioning for custom app management includes setting up a policy with custom app configurations, creating an enrollment token with personal usage disallowed, and provisioning the device in fully managed mode.

  • The extensibility app, installed from Managed Play, facilitates downloading the custom app APK, issuing installation requests, and receiving responses.

As an Android Management API based EMM, you can remotely manage customapplications on devices. This includes both installing and uninstalling theseapps. This functionality is achieved by developing an extension app locallyusing the AMAPI SDK.

Important: The functionality is only supported in fully managed devices.

Prerequisites

1. Prepare your app for using the feature

1.1. Integrate with the AMAPI SDK in your extension app

The custom app management process requires you to integrate the AMAPI SDK inyour extension app. You can find more information about this library and how toadd it to your app in theAMAPI SDK integration guide.

1.2. Update your app's manifest to supportFileProvider

  • Add to yourAndroidManifest.xml the<queries> element for the AndroidDevice Policy (ADP) application as shown in theAMAPI SDK integration guide.
  • Implement the following<provider> snippet into your app'sAndroidManifest.xml inside the<application> tag. This snippet is used tostore files when sharing the custom app APK, enabling the installation of customapps using AMAPI.

AndroidManifest.xml:

<?xmlversion="1.0"encoding="utf-8"?><manifestxmlns:android="http://schemas.android.com/apk/res/android"package="com.example.customapp"><queries><packageandroid:name="com.google.android.apps.work.clouddpc"/></queries><application><!--Thisisusedtostorefileswhensharingthecustomappapk.--><providerandroid:name="com.google.android.managementapi.customapp.provider.CustomAppProvider"android:authorities="${applicationId}.AmapiCustomAppProvider"android:exported="false"android:grantUriPermissions="true"><meta-dataandroid:name="android.support.FILE_PROVIDER_PATHS"android:resource="@xml/file_provider_paths"/></provider></application></manifest>
  • Create a new XML file in your app'sres/xml/ directory containing thestorage path for custom apks.

file_provider_paths.xml:

<?xmlversion="1.0"encoding="utf-8"?><pathsxmlns:android="http://schemas.android.com/apk/res/android"><cache-pathname="android_managementapi_custom_apks"path="com.google.android.managementapi/customapp/apks/"/></paths>

2. Integrate with the custom app feature of the AMAPI SDK

2.1. Prepare the custom APK file for installation

Before deploying, the application's APK file must be prepared for installation.The following code snippet demonstrates the process:

Kotlin

importandroid.net.Uriimportandroidx.core.net.Uriimportjava.io.File...importcom.google.android.managementapi.commands.LocalCommandClientimportcom.google.android.managementapi.commands.LocalCommandClient.InstallCustomAppCommandHelperimportcom.google.android.managementapi.commands.LocalCommandClientFactory...funprepareApkFile():Uri?{// Get the storage location of custom APK files from AM APIvalclient:LocalCommandClient=LocalCommandClientFactory.create(context)valinstallCustomAppCommandHelper=client.installCustomAppCommandHelpervalcustomApksStorageDir:File=installCustomAppCommandHelper.customApksStorageDirectory?:returnnull// Once you get hold of the custom APKs storage directory, you must store your custom APK// in that location before issuing the install command.valcustomApkFile:File=fetchMyAppToDir(customApksStorageDir)?:returnnullvalcustomApkFileUri:Uri=customApkFile.toUri()returncustomApkFileUri}

Java

importandroid.net.Uri;importandroidx.core.net.Uri;importjava.io.File;...importcom.google.android.managementapi.commands.LocalCommandClient;importcom.google.android.managementapi.commands.LocalCommandClient.InstallCustomAppCommandHelper;importcom.google.android.managementapi.commands.LocalCommandClientFactory;...UriprepareApkFile(){// Get the storage location of custom APK files from AM APILocalCommandClientclient=LocalCommandClientFactory.create();InstallCustomAppCommandHelperinstallCustomAppCommandHelper=client.getInstallCustomAppCommandHelper();FilecustomApksStorageDir=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.FilecustomApkFile=fetchMyAppToDir(customApksStorageDir);UricustomApkFileUri=Uri.fromFile(customApkFile);...}

2.2. Issue a request to install a custom app

The following snippet shows how to issue a request to install a custom app:

Kotlin

importandroid.content.Contextimportandroid.net.Uriimportandroid.util.Logimportcom.google.android.managementapi.commands.LocalCommandClientFactoryimportcom.google.android.managementapi.commands.model.Commandimportcom.google.android.managementapi.commands.model.IssueCommandRequestimportcom.google.android.managementapi.commands.model.IssueCommandRequest.InstallCustomAppimportkotlinx.coroutines.CoroutineScopeimportkotlinx.coroutines.launchimportkotlinx.coroutines.guava.awaitimportkotlinx.coroutines.withContextimportjava.lang.ExceptionprivateconstvalTAG="MyClass"...// Requires a file URI of the APK file.funissueInstallCustomAppCommand(packageName:String,fileUri:Uri){coroutineScope.launch{try{withContext(coroutineScope.coroutineContext){valresult: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)}}}privatefuncreateInstallCustomAppRequest(packageName:String,fileUri:Uri):IssueCommandRequest{returnIssueCommandRequest.builder().setInstallCustomApp(InstallCustomApp.builder().setPackageName(packageName).setPackageUri(fileUri.toString()).build()).build()}}

Java

importandroid.util.Log;...importcom.google.android.managementapi.commands.LocalCommandClientFactory;importcom.google.android.managementapi.commands.model.Command;importcom.google.android.managementapi.commands.model.GetCommandRequest;importcom.google.android.managementapi.commands.model.IssueCommandRequest;importcom.google.android.managementapi.commands.model.IssueCommandRequest.ClearAppsData;importcom.google.common.collect.ImmutableList;importcom.google.common.util.concurrent.FutureCallback;importcom.google.common.util.concurrent.Futures;importcom.google.common.util.concurrent.MoreExecutors;...// Requires a file URI of the APK file.voidissueInstallCustomAppCommand(StringpackageName,UrifileUri){Futures.addCallback(LocalCommandClientFactory.create(getContext()).issueCommand(createInstallCustomAppRequest(packageName,fileUri)),newFutureCallback(){@OverridepublicvoidonSuccess(Commandresult){// Process the returned command result here.Log.i(TAG,"Successfully issued command");}@OverridepublicvoidonFailure(Throwablet){Log.e(TAG,"Failed to issue command",t);}},MoreExecutors.directExecutor());}IssueCommandRequestcreateInstallCustomAppRequest(StringpackageName,UrifileUri){returnIssueCommandRequest.builder().setInstallCustomApp(InstallCustomApp.builder().setPackageName(packageName).setPackageUri(fileUri.toString()).build()).build();}

2.3. Issue a request to get installed apps

Kotlin

importandroid.content.Contextimportcom.google.android.managementapi.device.DeviceClientFactoryimportcom.google.android.managementapi.device.model.GetDeviceRequestimportkotlinx.coroutines.guava.awaitsuspendfungetInstalledApps(context:Context)=DeviceClientFactory.create(context).getDevice(GetDeviceRequest.getDefaultInstance()).await().getApplicationReports()

Java

importandroid.content.Context;importcom.google.android.managementapi.device.DeviceClientFactory;importcom.google.android.managementapi.device.model.GetDeviceRequest;importcom.google.android.managementapi.device.model.Device;importcom.google.common.util.concurrent.Futures;importcom.google.common.util.concurrent.ListenableFuture;importcom.google.common.util.concurrent.MoreExecutors;importjava.util.List;importjava.util.concurrent.Executor;publicListenableFuture<List>getInstalledApps(){ListenableFuturedeviceFuture=DeviceClientFactory.create(context).getDevice(GetDeviceRequest.getDefaultInstance());returnFutures.transform(deviceFuture,Device::getApplicationReports,executor// Use the provided executor);}

3. Provision the device with custom apps management policies

  1. Set up apolicy with the custom apps you intend to manage.

    {"statusReportingSettings":{"applicationReportsEnabled":true},"applications":[{"signingKeyCerts":[{"signingKeyCertFingerprintSha256":<sha256signingkeycertificatehashvalue>}],"packageName":"<emm_extensibility_app>","installType":"AVAILABLE","defaultPermissionPolicy":"GRANT","extensionConfig":{"notificationReceiver":"com.example.customapp.NotificationReceiverService"}},{"signingKeyCerts":[{"signingKeyCertFingerprintSha256":<sha256signingkeycertificatehashvalue>},],"packageName":"<custom_app>","installType":"CUSTOM","defaultPermissionPolicy":"GRANT","customAppConfig":{"userUninstallSettings":"DISALLOW_UNINSTALL_BY_USER"}}]}```
  2. Create an enrollment token for the device by callingenterprises.enrollmentTokens.create, withallowPersonalUsageset toPERSONAL_USAGE_DISALLOWED.

  3. Provision the device infully managed mode with the enrollment token.

  4. Install your extensibility app from the Managed Play.

  5. Your extensibility app:

    • can download the APK file of the custom app
    • can issue a request to install the custom app (refer tocode snippetshown earlier)
    • should receive a response
  6. If the custom app is removed from the policy, the installed sideloadedapplication won't get uninstalled if it conforms toplayStoreMode.

  7. For more details, checkCUSTOM install type.

API

Server-client API

Refer to the new fields and enums listed:

Except as otherwise noted, the content of this page is licensed under theCreative Commons Attribution 4.0 License, and code samples are licensed under theApache 2.0 License. For details, see theGoogle Developers Site Policies. Java is a registered trademark of Oracle and/or its affiliates.

Last updated 2025-11-25 UTC.