Search code examples
androidxamarinpackageinstallation

Android PackageInstaller not installing APK


I have an Android app outside of the Play Store. It updates itself by downloading a new APK and invoking the installer dialog using an Intent. The update functionality does not work anymore on Android 10.

I need to use the PackageInstaller API on Android 10 now, but I can't get it to work. My app is not a device or profile owner, but since I don't want a silent install I think it should be fine.

My problem is that as soon as I commit the session absolutely nothing happens.

PackageInstaller installer = activity.PackageManager.PackageInstaller;
PackageInstaller.SessionParams sessionParams = new PackageInstaller.SessionParams(PackageInstallMode.FullInstall);
int sessionId = installer.CreateSession(sessionParams);
PackageInstaller.Session session = installer.OpenSession(sessionId);

var input = new FileStream(pfad, FileMode.Open, FileAccess.Read);
var packageInSession = session.OpenWrite("package", 0, -1);
input.CopyTo(packageInSession);
packageInSession.Close();
input.Close();

//That this is necessary could be a Xamarin bug.
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();

Intent intent = new Intent(activity, activity.Class);
intent.SetAction("com.example.android.apis.content.SESSION_API_PACKAGE_INSTALLED");
PendingIntent pendingIntent = PendingIntent.GetActivity(activity, 0, intent, 0);
IntentSender statusReceiver = pendingIntent.IntentSender;

// Commit the session (this will start the installation workflow).
session.Commit(statusReceiver);

I took a look at the DDMS and got nothing relevant out of it. One thing that might be of interest is that when I Dispose() the streams, I get an IOException: write failed (EBADF) bad file descriptor which would indicate a bad APK. But I doubt it's that because I can install the APK using a file manager without a hitch. Googling the error didn't lead me anywhere.

How can I fix this problem?


Solution

  • There are a couple of things you need to make sure in order for the apk installation to succeed in Android Q:

    • Do not use using statements or try to dispose anything inside the AddApkToInstallSession method. The Dispose causes the installation to fail. Use try/finally and close instead:
    
    private static void AddApkToInstallSession(Context context, Android.Net.Uri apkUri, PackageInstaller.Session session)
    {
      var packageInSession = session.OpenWrite("package", 0, -1);
      var input = context.ContentResolver.OpenInputStream(apkUri);
    
      try
      {
          if (input != null)
          {
              input.CopyTo(packageInSession);
          }
          else
          {
              throw new Exception("Inputstream is null");
          }
      }
      finally
      {
          packageInSession.Close();
          input.Close();
      }
    
      //That this is necessary could be a Xamarin bug.
      GC.Collect();
      GC.WaitForPendingFinalizers();
      GC.Collect();
    }
    
    • You must override the "OnNewIntent" method because you need an intent to confirm the installation of the APK file:
    
    protected override void OnNewIntent(Intent intent)
    {
        Bundle extras = intent.Extras;
        if (PACKAGE_INSTALLED_ACTION.Equals(intent.Action))
        {
            var status = extras.GetInt(PackageInstaller.ExtraStatus);
            var message = extras.GetString(PackageInstaller.ExtraStatusMessage);
            switch (status)
            {
                case (int)PackageInstallStatus.PendingUserAction:
                    // Ask user to confirm the installation
                    var confirmIntent = (Intent)extras.Get(Intent.ExtraIntent);
                    StartActivity(confirmIntent);
                    break;
                case (int)PackageInstallStatus.Success:
                    //TODO: Handle success
                    break;
                case (int)PackageInstallStatus.Failure:
                case (int)PackageInstallStatus.FailureAborted:
                case (int)PackageInstallStatus.FailureBlocked:
                case (int)PackageInstallStatus.FailureConflict:
                case (int)PackageInstallStatus.FailureIncompatible:
                case (int)PackageInstallStatus.FailureInvalid:
                case (int)PackageInstallStatus.FailureStorage:
                    //TODO: Handle failures
                    break;
            }
        }
    }
    
    • The Activity where you override the "OnNewIntent" method must have LaunchMode set to LaunchMode.SingleTop
    • The user must have given the application from which you try to install the APK file the necessary permissions to install APK's. You can check if this is the case by calling PackageManager.CanRequestPackageInstalls(). If this function returns false, you can open the application options window by using this code:
    StartActivity(new Intent(
                Android.Provider.Settings.ActionManageUnknownAppSources,
                Android.Net.Uri.Parse("package:" + Android.App.Application.Context.PackageName)));
    

    so the user can easily set the switch to enable this.

    • This is my main method to initialize the APK installation:
    
    public void InstallPackageAndroidQAndAbove(Android.Net.Uri apkUri)
    {
        var packageInstaller = PackageManager.PackageInstaller;
        var sessionParams = new PackageInstaller.SessionParams(PackageInstallMode.FullInstall);
        int sessionId = packageInstaller.CreateSession(sessionParams);
        var session = packageInstaller.OpenSession(sessionId);
    
        AddApkToInstallSession(apkUri, session);
    
        // Create an install status receiver.
        var intent = new Intent(this, this.Class);
        intent.SetAction(PACKAGE_INSTALLED_ACTION);
        var pendingIntent = PendingIntent.GetActivity(this, 0, intent, 0);
        var statusReceiver = pendingIntent.IntentSender;
    
        // Commit the session (this will start the installation workflow).
        session.Commit(statusReceiver);
    }
    
    • If you are debugging on a Xiaomi device, you must disable MIUI Optimizations under developer options. Otherwise the installation will fail with permission denied error.