Handle uninstall abort case and show appropriate dialog

When an uninstall is aborted due to app not being present or permission, appOp denial, show appropriate dialog and handle user action on dismissing the dialog.

Bug: 182205982
Test: builds successfully
Test: No CTS Tests. Flag to use new app is turned off by default

Change-Id: I0a4fbf04e6e33e2e05f38fd7d82f7a5218b59033
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallLaunch.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallLaunch.java
index 8e9038e..1f5bbf1 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallLaunch.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallLaunch.java
@@ -19,14 +19,20 @@
 import static android.os.Process.INVALID_UID;
 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
 
+import android.app.Activity;
 import android.content.Intent;
 import android.os.Bundle;
+import android.util.Log;
 import androidx.annotation.Nullable;
+import androidx.fragment.app.DialogFragment;
 import androidx.fragment.app.FragmentActivity;
+import androidx.fragment.app.FragmentManager;
 import androidx.lifecycle.ViewModelProvider;
 import com.android.packageinstaller.v2.model.UninstallRepository;
 import com.android.packageinstaller.v2.model.UninstallRepository.CallerInfo;
+import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallAborted;
 import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallStage;
+import com.android.packageinstaller.v2.ui.fragments.UninstallErrorFragment;
 import com.android.packageinstaller.v2.viewmodel.UninstallViewModel;
 import com.android.packageinstaller.v2.viewmodel.UninstallViewModelFactory;
 
@@ -37,9 +43,11 @@
     public static final String EXTRA_CALLING_ACTIVITY_NAME =
         UninstallLaunch.class.getPackageName() + ".callingActivityName";
     public static final String TAG = UninstallLaunch.class.getSimpleName();
+    private static final String TAG_DIALOG = "dialog";
 
     private UninstallViewModel mUninstallViewModel;
     private UninstallRepository mUninstallRepository;
+    private FragmentManager mFragmentManager;
 
     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {
@@ -49,6 +57,8 @@
         // be stale, if e.g. the app was uninstalled while the activity was destroyed.
         super.onCreate(null);
 
+        mFragmentManager = getSupportFragmentManager();
+
         mUninstallRepository = new UninstallRepository(getApplicationContext());
         mUninstallViewModel = new ViewModelProvider(this,
             new UninstallViewModelFactory(this.getApplication(), mUninstallRepository)).get(
@@ -69,6 +79,42 @@
      * uninstall stage
      */
     private void onUninstallStageChange(UninstallStage uninstallStage) {
+        if (uninstallStage.getStageCode() == UninstallStage.STAGE_ABORTED) {
+            UninstallAborted aborted = (UninstallAborted) uninstallStage;
+            if (aborted.getAbortReason() == UninstallAborted.ABORT_REASON_APP_UNAVAILABLE ||
+                aborted.getAbortReason() == UninstallAborted.ABORT_REASON_USER_NOT_ALLOWED) {
+                UninstallErrorFragment errorDialog = new UninstallErrorFragment(aborted);
+                showDialogInner(errorDialog);
+            } else {
+                setResult(aborted.getActivityResultCode(), null, true);
+            }
+        } else {
+            Log.e(TAG, "Invalid stage: " + uninstallStage.getStageCode());
+            showDialogInner(null);
+        }
+    }
+
+    /**
+     * Replace any visible dialog by the dialog returned by InstallRepository
+     *
+     * @param newDialog The new dialog to display
+     */
+    private void showDialogInner(DialogFragment newDialog) {
+        DialogFragment currentDialog = (DialogFragment) mFragmentManager.findFragmentByTag(
+            TAG_DIALOG);
+        if (currentDialog != null) {
+            currentDialog.dismissAllowingStateLoss();
+        }
+        if (newDialog != null) {
+            newDialog.show(mFragmentManager, TAG_DIALOG);
+        }
+    }
+
+    public void setResult(int resultCode, Intent data, boolean shouldFinish) {
+        super.setResult(resultCode, data);
+        if (shouldFinish) {
+            finish();
+        }
     }
 
     @Override
@@ -77,5 +123,6 @@
 
     @Override
     public void onNegativeResponse() {
+        setResult(Activity.RESULT_FIRST_USER, null, true);
     }
 }
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallErrorFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallErrorFragment.java
new file mode 100644
index 0000000..305daba
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallErrorFragment.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.packageinstaller.v2.ui.fragments;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.DialogFragment;
+import com.android.packageinstaller.R;
+import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallAborted;
+import com.android.packageinstaller.v2.ui.UninstallActionListener;
+
+/**
+ * Dialog to show when an app cannot be uninstalled
+ */
+public class UninstallErrorFragment extends DialogFragment {
+
+    private final UninstallAborted mDialogData;
+    private UninstallActionListener mUninstallActionListener;
+
+    public UninstallErrorFragment(UninstallAborted dialogData) {
+        mDialogData = dialogData;
+    }
+
+    @Override
+    public void onAttach(@NonNull Context context) {
+        super.onAttach(context);
+        mUninstallActionListener = (UninstallActionListener) context;
+    }
+
+    @NonNull
+    @Override
+    public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
+        AlertDialog.Builder builder = new AlertDialog.Builder(requireContext())
+            .setMessage(mDialogData.getDialogTextResource())
+            .setNegativeButton(R.string.ok,
+                (dialogInt, which) -> mUninstallActionListener.onNegativeResponse());
+
+        if (mDialogData.getDialogTitleResource() != 0) {
+            builder.setTitle(mDialogData.getDialogTitleResource());
+        }
+        return builder.create();
+    }
+
+    @Override
+    public void onCancel(@NonNull DialogInterface dialog) {
+        super.onCancel(dialog);
+        mUninstallActionListener.onNegativeResponse();
+    }
+}