Forum Xamarin.Android
We are excited to announce that the Xamarin Forums are moving to the new Microsoft Q&A experience. Q&A is the home for technical questions and answers at across all products at Microsoft now including Xamarin!

We encourage you to head over to Microsoft Q&A for .NET for posting new questions and get involved today.

OnNewIntent is not called after PackageInstaller failure

I use this code for installing APK: https:// stackoverflow. com/a/61889386/14152249 (sorry for whitespaces, can't post links here for a while)

My activity launch mode is set to SingleTop. I call InstallPackageAndroidQAndAbove from OnActivityResult (I need to uninstall app before that and I check if it's uninstalled there). Also I modified that code to pass the PendingIntentFlags.UpdateCurrent flag during the creation of PendingIntent object. The problem is when APK is successfully installed or when installer is waiting for user action, OnNewIntent is always properly called and then I do some stuff. But when user aborts install, OnNewIntent is not called (occasionally though it DOES get called). I couldn't find anything about such a behavior. How can I fix this? Or maybe it has something to do with the device which I'm debugging on (Xiaomi Mi 9T Pro with MIUI 12 based on Android 10)?

I need to show an alert dialog when app failed to install and stop the process. As a workaround I use StartActivityForResult and then in OnActivityResult I wait for 5 seconds and check if app is installed. But this workaround is bad of course because if installation takes more than 5 seconds then alert dialog will be shown anyway though the app itself will be installed.
Also I wrote a simple app with the same functionality in Java and as far as I can see there it works as intended. Could it be a Xamarin bug?

Answers

  • jezhjezh Member, Xamarin Team Xamurai
    edited September 17

    Or maybe it has something to do with the device which I'm debugging on (Xiaomi Mi 9T Pro with MIUI 12 based on Android 10)?

    Are you able to replicate or test this on other devices?

  • solrusolru Member ✭✭
    edited September 23

    @jezh said:

    Or maybe it has something to do with the device which I'm debugging on (Xiaomi Mi 9T Pro with MIUI 12 based on Android 10)?

    Are you able to replicate or test this on other devices?

    Unfortunately, I can't test it on another device. I can only say that on the same device the same code rewritten in Java works well.

    UPD. I tested this app in Nox App Player with Android 7. I tried to install incompatible APK (arm64-v8a only with minAPI = 26). When I manually abort installation the app doesn't show alert dialog and opens installation confirmation dialog again (I placed InstallPackage method call in OnCreate). When I confirm installation (which obviously fails) app shows an alert dialog with exit button as intended.

    Here is the whole code of MainActivity.

           public const string PACKAGE_INSTALLED_ACTION = "com.example.android.apis.content.SESSION_API_PACKAGE_INSTALLED";
            public static readonly int UnknownAppSourceCode = 0;
            public static readonly int InstallCode = 1;
    
            Stream apk = null;
    
            /// <summary>
            /// id: 0 - unknown app sources notice dialog;
            ///     1 - standard OK dialog;
            ///     2 - fail dialog with exit button
            /// </summary>
            public void MessageBox(string Title, string Message, int id)
            {
                Android.App.AlertDialog.Builder builder;
                builder = new Android.App.AlertDialog.Builder(this);
                builder.SetTitle(Title);
                builder.SetMessage(Message);
                builder.SetCancelable(false);
                if (id == 0)
                    builder.SetPositiveButton("OK", MessageBoxOkUnknownAppSourcesAction);
                if (id == 1)
                    builder.SetPositiveButton("OK", delegate { });
                if (id == 2)
                    builder.SetPositiveButton("EXIT", MessageBoxOkFailAction);
                Dialog dialog = builder.Create();
                dialog.Show();
                return;
            }
    
            private void MessageBoxOkUnknownAppSourcesAction(object sender, DialogClickEventArgs e)
            {
                PackageManager pm = this.PackageManager;
    
                Intent intent = new Intent(Android.Provider.Settings.ActionManageUnknownAppSources, Android.Net.Uri.Parse("package:" + Android.App.Application.Context.PackageName));
    
                StartActivityForResult(intent, UnknownAppSourceCode);
            }
    
            private void MessageBoxOkFailAction(object sender, DialogClickEventArgs e)
            {
                System.Environment.Exit(0);
            }
    
            private static void AddApkToInstallSession(Stream APK, PackageInstaller.Session session)
            {
                var packageInSession = session.OpenWrite("package", 0, -1);
    
                try
                {
                    if (APK != null) APK.CopyTo(packageInSession);
                    else throw new Exception("InputStream is null");
                }
                finally
                {
                    packageInSession.Close();
                    APK.Close();
                }
    
                // That this is necessary could be a Xamarin bug
                GC.Collect();
                GC.WaitForPendingFinalizers();
                GC.Collect();
            }
    
            public void InstallPackage(Stream APK)
            {
                var packageInstaller = PackageManager.PackageInstaller;
                var sessionParams = new PackageInstaller.SessionParams(PackageInstallMode.FullInstall);
                int sessionId = packageInstaller.CreateSession(sessionParams);
                var session = packageInstaller.OpenSession(sessionId);
    
                AddApkToInstallSession(APK, 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, PendingIntentFlags.UpdateCurrent);
                var statusReceiver = pendingIntent.IntentSender;
    
                // Commit the session (this will start the installation workflow)
                session.Commit(statusReceiver);
            }
    
            protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
            {
                if (requestCode == UnknownAppSourceCode && Android.OS.Build.VERSION.SdkInt >= BuildVersionCodes.O)
                {
                    PackageManager pm = this.PackageManager;
    
                    if (!pm.CanRequestPackageInstalls())
                        MessageBox("Error", "This app can't get permission to install packages.", 2);
                    else
                        InstallPackage(apk);
                }
            }
    
            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);
                            StartActivityForResult(confirmIntent, InstallCode);
                            break;
                        case (int)PackageInstallStatus.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:
                            MessageBox("Error", "An error occured while installing APK. Please try restarting this app.", 2);
                            break;
                    }
                }
            }
    
            protected override void OnCreate(Bundle savedInstanceState)
            {
                base.OnCreate(savedInstanceState);
                Xamarin.Essentials.Platform.Init(this, savedInstanceState);
                // Set our view from the "main" layout resource
                SetContentView(Resource.Layout.activity_main);
    
                AssetManager assets = this.Assets;
                apk = assets.Open("app.apk");
    
                // Request permission to install packages on first start
                if (Android.OS.Build.VERSION.SdkInt >= BuildVersionCodes.O)
                {
                    PackageManager pm = this.PackageManager;
                    if (!pm.CanRequestPackageInstalls())
                        MessageBox("Attention", "This app requires permission to install applications from unknown sources to continue. After pressing the OK button you will be redirected to your device settings where you should enable this option. Then return to this app using the back button or gesture.", 0);
                    else InstallPackage(apk);
                }
                else InstallPackage(apk);
            }
    
            public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
            {
                Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);
    
                base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
            }
    
  • solrusolru Member ✭✭

    @jezh said:

    Or maybe it has something to do with the device which I'm debugging on (Xiaomi Mi 9T Pro with MIUI 12 based on Android 10)?

    Are you able to replicate or test this on other devices?

    I tested this app in Nox App Player with Android 7. I tried to install incompatible APK (arm64-v8a only with minAPI = 26). When I manually abort installation the app doesn't show an alert dialog and restarts the installation confirmation dialog (I placed InstallPackage method call in OnCreate). When I confirm installation (which obviously fails) the app shows an alert dialog with an exit button as intended.

    Here is the whole code of MainActivity.

    public const string PACKAGE_INSTALLED_ACTION = "com.example.android.apis.content.SESSION_API_PACKAGE_INSTALLED";
            public static readonly int UnknownAppSourceCode = 0;
            public static readonly int InstallCode = 1;
    
            Stream apk = null;
    
            /// <summary>
            /// id: 0 - unknown app sources notice dialog;
            ///     1 - standard OK dialog;
            ///     2 - fail dialog with exit button
            /// </summary>
            public void MessageBox(string Title, string Message, int id)
            {
                Android.App.AlertDialog.Builder builder;
                builder = new Android.App.AlertDialog.Builder(this);
                builder.SetTitle(Title);
                builder.SetMessage(Message);
                builder.SetCancelable(false);
                if (id == 0)
                    builder.SetPositiveButton("OK", MessageBoxOkUnknownAppSourcesAction);
                if (id == 1)
                    builder.SetPositiveButton("OK", delegate { });
                if (id == 2)
                    builder.SetPositiveButton("EXIT", MessageBoxOkFailAction);
                Dialog dialog = builder.Create();
                dialog.Show();
                return;
            }
    
            private void MessageBoxOkUnknownAppSourcesAction(object sender, DialogClickEventArgs e)
            {
                PackageManager pm = this.PackageManager;
                Intent intent = new Intent(Android.Provider.Settings.ActionManageUnknownAppSources, Android.Net.Uri.Parse("package:" + Android.App.Application.Context.PackageName));
                StartActivityForResult(intent, UnknownAppSourceCode);
            }
    
            private void MessageBoxOkFailAction(object sender, DialogClickEventArgs e)
            {
                System.Environment.Exit(0);
            }
    
            private static void AddApkToInstallSession(Stream APK, PackageInstaller.Session session)
            {
                var packageInSession = session.OpenWrite("package", 0, -1);
    
                try
                {
                    if (APK != null) APK.CopyTo(packageInSession);
                    else throw new Exception("InputStream is null");
                }
                finally
                {
                    packageInSession.Close();
                    APK.Close();
                }
    
                // That this is necessary could be a Xamarin bug
                GC.Collect();
                GC.WaitForPendingFinalizers();
                GC.Collect();
            }
    
            public void InstallPackage(Stream APK)
            {
                var packageInstaller = PackageManager.PackageInstaller;
                var sessionParams = new PackageInstaller.SessionParams(PackageInstallMode.FullInstall);
                int sessionId = packageInstaller.CreateSession(sessionParams);
                var session = packageInstaller.OpenSession(sessionId);
    
                AddApkToInstallSession(APK, 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, PendingIntentFlags.UpdateCurrent);
                var statusReceiver = pendingIntent.IntentSender;
    
                // Commit the session (this will start the installation workflow)
                session.Commit(statusReceiver);
            }
    
            protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
            {
                if (requestCode == UnknownAppSourceCode && Android.OS.Build.VERSION.SdkInt >= BuildVersionCodes.O)
                {
                    PackageManager pm = this.PackageManager;
                    if (!pm.CanRequestPackageInstalls())
                        MessageBox("Error", "This app can't get permission to install packages.", 2);
                    else
                        InstallPackage(apk);
                }
            }
    
            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);
                            StartActivityForResult(confirmIntent, InstallCode);
                            break;
                        case (int)PackageInstallStatus.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:
                            MessageBox("Error", "An error occured while installing APK. Please try restarting this app.", 2);
                            break;
                    }
                }
            }
    
            protected override void OnCreate(Bundle savedInstanceState)
            {
                base.OnCreate(savedInstanceState);
                Xamarin.Essentials.Platform.Init(this, savedInstanceState);
                // Set our view from the "main" layout resource
                SetContentView(Resource.Layout.activity_main);
    
                AssetManager assets = this.Assets;
                apk = assets.Open("app.apk");
    
                // Request permission to install packages on first start
                if (Android.OS.Build.VERSION.SdkInt >= BuildVersionCodes.O)
                {
                    PackageManager pm = this.PackageManager;
                    if (!pm.CanRequestPackageInstalls())
                        MessageBox("Attention", "This app requires permission to install applications from unknown sources to continue. After pressing the OK button you will be redirected to your device settings where you should enable this option. Then return to this app using the back button or gesture.", 0);
                    else InstallPackage(apk);
                }
                else InstallPackage(apk);
            }
    
            public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
            {
                Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);
    
                base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
            }
    
  • jezhjezh Member, Xamarin Team Xamurai
    edited September 24

    I tested your code on my device, when I run it on my device, it calls function public void InstallPackage(Stream APK) over and over again.

    Also I wrote a simple app with the same functionality in Java and as far as I can see there it works as intended.

    Could you please post the demo written in java so that we can review the code and test it on our side? Thanks in advance. :)

  • solrusolru Member ✭✭

    I tested this code again and sometimes it also fails to show AlertDialog when I manually abort the installation. But this happens not as frequently as with C# Xamarin code. Maybe something essential in this code is wrong? I never wrote for Android before.

    MainActivity

    package com.test.testinstaller;
    
    import androidx.appcompat.app.AppCompatActivity;
    
    import android.os.Build;
    import android.os.Bundle;
    import android.content.*;
    import android.content.pm.*;
    import android.content.res.*;
    import java.io.*;
    import static com.test.testinstaller.MessageBox.*;
    import static com.test.testinstaller.GlobalData.*;
    
    public class MainActivity extends AppCompatActivity {
    
        InputStream apk = null;
    
        @Override
        protected void onActivityResult(int requestCode, int resultCode, Intent data)
        {
            super.onActivityResult(requestCode, resultCode, data);
            if (requestCode == unknownAppSourceCode && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
            {
                PackageManager pm = getPackageManager();
    
                if (!pm.canRequestPackageInstalls())
                    MessageBox.show(this, "Error", "This app can't get permission to install packages.", msgBoxCode.FailExit);
                else
                {
                    try
                    {
                        Utils.installPackage(this, apk);
                    }
                    catch (final Exception e)
                    {
                        MessageBox.show(this, "Exception", e.getMessage(), msgBoxCode.FailExit);
                    }
                }
            }
        }
    
        @Override
        protected void onNewIntent(Intent intent)
        {
            super.onNewIntent(intent);
            Bundle extras = intent.getExtras();
            if (PACKAGE_INSTALLED_ACTION.equals(intent.getAction()))
            {
                int status = extras.getInt(PackageInstaller.EXTRA_STATUS);
                String message = extras.getString(PackageInstaller.EXTRA_STATUS_MESSAGE);
                switch (status)
                {
                    case PackageInstaller.STATUS_PENDING_USER_ACTION:
                        // Ask user to confirm the installation
                        Intent confirmIntent = (Intent) extras.get(Intent.EXTRA_INTENT);
                        startActivityForResult(confirmIntent, installCode);
                        break;
                    case PackageInstaller.STATUS_SUCCESS:
                        break;
                    case PackageInstaller.STATUS_FAILURE:
                    case PackageInstaller.STATUS_FAILURE_ABORTED:
                    case PackageInstaller.STATUS_FAILURE_BLOCKED:
                    case PackageInstaller.STATUS_FAILURE_CONFLICT:
                    case PackageInstaller.STATUS_FAILURE_INCOMPATIBLE:
                    case PackageInstaller.STATUS_FAILURE_INVALID:
                    case PackageInstaller.STATUS_FAILURE_STORAGE:
                        MessageBox.show(this, "Error", "An error occurred while installing APK. Please try restarting this app." + System.getProperty("line.separator") + message, msgBoxCode.FailExit);
                        break;
                }
            }
        }
    
        @Override
        protected void onCreate(Bundle savedInstanceState)
        {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            AssetManager assets = getAssets();
            try
            {
                apk = assets.open("app.apk");
    
                // Request permission to install packages on first start
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
                {
                    PackageManager pm = getPackageManager();
                    if (!pm.canRequestPackageInstalls())
                        MessageBox.show(this, "Attention", "This app requires permission to install applications from unknown sources to continue. After pressing the OK button you will be redirected to your device settings where you should enable this option. Then return to this app using the back button or gesture.", msgBoxCode.UnknownAppSourceNotice);
                    else Utils.installPackage(this, apk);
                }
                else Utils.installPackage(this, apk);
            }
            catch (final Exception e)
            {
                MessageBox.show(this, "Exception", e.getMessage(), msgBoxCode.FailExit);
            }
        }
    }
    

    GlobalData

    package com.test.testinstaller;
    
    public class GlobalData {
    
        public static final String PACKAGE_INSTALLED_ACTION = "com.example.android.apis.content.SESSION_API_PACKAGE_INSTALLED";
        public static final int unknownAppSourceCode = 1;
        public static final int installCode = 2;
    
    }
    

    MessageBox

    package com.test.testinstaller;
    
    import android.app.Activity;
    import android.app.AlertDialog;
    import android.app.Dialog;
    import android.content.DialogInterface;
    import android.content.Intent;
    import static com.test.testinstaller.GlobalData.*;
    
    public class MessageBox {
    
        public enum msgBoxCode
        {
            OK,
            FailExit,
            UnknownAppSourceNotice
        }
    
        public static void show(final Activity callerActivity, String title, String message, msgBoxCode id)
        {
            AlertDialog.Builder builder;
            builder = new AlertDialog.Builder(callerActivity);
            builder.setTitle(title);
            builder.setMessage(message);
            builder.setCancelable(false);
            switch (id)
            {
                case OK:
                    builder.setPositiveButton("OK", null);
                    break;
                case FailExit:
                    builder.setPositiveButton("EXIT", new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int id) {
                            System.exit(0);
                        }
                    });
                    break;
                case UnknownAppSourceNotice:
                    builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int id) {
                            Intent intent = new Intent(android.provider.Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, android.net.Uri.parse("package:" + callerActivity.getPackageName()));
                            callerActivity.startActivityForResult(intent, unknownAppSourceCode);
                        }
                    });
                    break;
            }
            Dialog dialog = builder.create();
            dialog.show();
            return;
        }
    
    }
    

    Utils

    package com.test.testinstaller;
    
    import android.app.Activity;
    import android.app.PendingIntent;
    import android.content.Intent;
    import android.content.IntentSender;
    import android.content.pm.PackageInstaller;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    
    import static com.test.testinstaller.GlobalData.PACKAGE_INSTALLED_ACTION;
    
    public class Utils {
    
        public static void copy(InputStream input, OutputStream output)
                throws IOException
        {
            byte[] buf = new byte[0x14000];
            int len;
    
            while ((len = input.read(buf)) > 0)
                output.write(buf, 0, len);
        }
    
        private static void addApkToInstallSession(InputStream apk, PackageInstaller.Session session)
                throws Exception
        {
            OutputStream packageInSession = session.openWrite("package", 0, -1);
    
            try
            {
                if (apk != null) copy(apk, packageInSession);
                else throw new Exception("InputStream is null");
            }
            finally
            {
                packageInSession.close();
                if (apk != null) apk.close();
            }
        }
    
        public static void installPackage(Activity callerActivity, InputStream apk) throws Exception
        {
            PackageInstaller packageInstaller = callerActivity.getPackageManager().getPackageInstaller();
            PackageInstaller.SessionParams sessionParams = new PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL);
            int sessionId = packageInstaller.createSession(sessionParams);
            PackageInstaller.Session session = packageInstaller.openSession(sessionId);
    
            addApkToInstallSession(apk, session);
    
            // Create an install status receiver
            Intent intent = new Intent(callerActivity.getApplicationContext(), callerActivity.getClass());
            intent.setAction(PACKAGE_INSTALLED_ACTION);
            PendingIntent pendingIntent = PendingIntent.getActivity(callerActivity.getApplicationContext(), 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
            IntentSender statusReceiver = pendingIntent.getIntentSender();
    
            // Commit the session (this will start the installation workflow)
            session.commit(statusReceiver);
        }
    
    }
    
  • jezhjezh Member, Xamarin Team Xamurai

    I will test your code and come back asap.

  • solrusolru Member ✭✭

    Is there any news on this topic?

Sign In or Register to comment.