Manage custom apps with AMAPI Stay organized with collections Save and categorize content based on your preferences.
Page Summary
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 support
FileProvider.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's
LocalCommandClient.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
- Your extension app is integrated with theAMAPI SDK.
- The device isfully managed.
- AMAPI SDK v1.6.0-rc01 or higher is required.
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 your
AndroidManifest.xmlthe<queries>element for the AndroidDevice Policy (ADP) application as shown in theAMAPI SDK integration guide. - Implement the following
<provider>snippet into your app'sAndroidManifest.xmlinside 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's
res/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
Set up a
policywith 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"}}]}```Create an enrollment token for the device by callingenterprises.enrollmentTokens.create, with
allowPersonalUsageset toPERSONAL_USAGE_DISALLOWED.Provision the device infully managed mode with the enrollment token.
Install your extensibility app from the Managed Play.
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
If the custom app is removed from the policy, the installed sideloadedapplication won't get uninstalled if it conforms to
playStoreMode.For more details, check
CUSTOMinstall 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.
