Self-contained source: https://github.com/kwende/PackageInstallerExample
Tested on: Pixel 2 Pie 9.0, API 28 emulator
We have single-purpose Android devices (whose sole reason is to run a single app) in the field. To better the user experience, we are looking into silently updating the App. It's my understanding that in order to do so, we need to be running as a "Device Owner" app, and then use the PackageInstaller class. However, despite following the lead of several posts I've found online (both on SO and elsewhere), I've been unable to get the PackageInstaller class to install either my custom APK, or any random APK I've tried. I'm looking for advice.
To walk through the source code, firstly I've got main MainActivity here. The pertinent source is as follows:
protected override void OnCreate(Bundle savedInstanceState)
{
// Looks like permissions to read and write external storage must be explicit.
//https://stackoverflow.com/questions/31746787/xamarin-android-system-unauthorizedaccessexception-access-to-the-path-is-de
if (ContextCompat.CheckSelfPermission(this, Manifest.Permission.WriteExternalStorage) != (int)Permission.Granted)
{
ActivityCompat.RequestPermissions(this, new string[] { Manifest.Permission.WriteExternalStorage }, 0);
}
if (ContextCompat.CheckSelfPermission(this, Manifest.Permission.ReadExternalStorage) != (int)Permission.Granted)
{
ActivityCompat.RequestPermissions(this, new string[] { Manifest.Permission.ReadExternalStorage }, 0);
}
...
// This will prompt the user to allow/disallow the elevation of this app as "device owner".
DevicePolicyManager devicePolicyManager = (DevicePolicyManager)GetSystemService(Context.DevicePolicyService);
ComponentName demoDeviceAdmin = new ComponentName(this, Java.Lang.Class.FromType(typeof(DeviceAdminBroadcastReceiver)));
Intent intent = new Intent(DevicePolicyManager.ActionAddDeviceAdmin);
intent.PutExtra(DevicePolicyManager.ExtraDeviceAdmin, demoDeviceAdmin);
intent.PutExtra(DevicePolicyManager.ExtraAddExplanation, "Device administrator");
StartActivity(intent);
IntentFilter filter = new IntentFilter();
filter.AddAction("ANY_UNIQUE_NAME_WILL_DO");
RegisterReceiver(new InstallerStatusBroadcastReceiver(), filter);
}
In this source, I'm explicitly asking for permission to read/write to storage, as well as prompting the user to give my app "Device Owner" permissions, and finally registering a broadcast receiver to get the result of my installation attempt. All of this appears to work successfully.
The next, and probably most important, part of code is in MainPage.xaml.cs here. Again, the code most interesting to this discussion follows:
// path to the APK to be installed. This could be something downloaded, or it could a file on the SD card.
const string localPath = "/storage/emulated/0/Download/.......";
// the following code was largely inspired from
// here: https://github.com/nagamanojv/android-kiosk-example/blob/master/KioskExample/app/src/main/java/com/sureshjoshi/android/kioskexample/MainActivity.java.
// here: https://forums.xamarin.com/discussion/73589/does-anyone-try-to-install-package-with-packageinstaller-i-get-files-still-open-exception
// here: https://forums.xamarin.com/discussion/170925/xamarin-android-10-how-to-install-3rd-party-apk
//instantiate a package installer and its parameters.
PackageInstaller installer = Android.App.Application.Context.PackageManager.PackageInstaller;
PackageInstaller.SessionParams sessionParams = new PackageInstaller.SessionParams(PackageInstallMode.FullInstall);
int sessionId = installer.CreateSession(sessionParams);
PackageInstaller.Session session = installer.OpenSession(sessionId);
using (var input = new FileStream(localPath, FileMode.Open, FileAccess.Read))
{
using (var packageInSession = session.OpenWrite("package", 0, -1))
{
input.CopyTo(packageInSession);
}
}
//That this is necessary could be a Xamarin bug.
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
PendingIntent pendingIntent = PendingIntent.GetBroadcast(Android.App.Application.Context, sessionId, new Intent("ANY_UNIQUE_NAME_WILL_DO"), 0);
session.Commit(pendingIntent.IntentSender);
return;
The numerous posts and code samples that I've seen (such as this, this and this) seem to indicate to me that this is mostly what's required, then, to silently install an APK. However, it fails. I know it fails because 1) nothing gets installed and, 2) the broadcast receiver I've set up to get notified of the installation status always seems to indicate a failure (code here). Again, for the sake of posterity, the code I'm looking at specifically is this (and I believe it fails because status is always -1):
int status = intent.GetIntExtra(PackageInstaller.ExtraStatus, -1);
string moreMessage = intent.GetStringExtra(PackageInstaller.ExtraStatusMessage);
// error codes and whatnot pulled from //https://developer.android.com/reference/android/content/pm/PackageInstaller#STATUS_FAILURE
switch (status)
{
// STATUS_FAILURE_INVALID
case 4:
Toast.MakeText(context, "STATUS_FAILURE_INVALID!", ToastLength.Short).Show();
//The operation failed because one or more of the APKs was invalid. For example, they might be malformed, corrupt, incorrectly signed, mismatched, etc.
break;
}
return;
What am I missing? I've given the app the following permissions:
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.DELETE_PACKAGES" />
<uses-permission android:name="android.permission.GET_PACKAGE_SIZE" />
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.BROADCAST_PACKAGE_REMOVED" />
...but despite all of this, no APK is ever installed and no meaningful error code is returned. I also wondered if the APKs themselves I was trying to install were bad (despite trying several); all of them installed when I tapped-to-install them (or "clicked" since I've been running in emulators). Therefore, I have to conclude the APKs are not corrupt.
Any thoughts? Any advice? Thanks so much.