Process the incoming uninstall intent to this app

This change will simply accept the incoming intent, extract some data from it like the action and whether uninstall package is valid, callback exists or not etc.

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

Change-Id: I6280bef06689735214eb05c2f93753c6f01afe5b
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.java
index 6533c50..3a0e1e3 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.java
@@ -16,14 +16,127 @@
 
 package com.android.packageinstaller.v2.model;
 
+import static com.android.packageinstaller.v2.model.uninstallstagedata.UninstallAborted.ABORT_REASON_APP_UNAVAILABLE;
+import static com.android.packageinstaller.v2.model.uninstallstagedata.UninstallAborted.ABORT_REASON_GENERIC_ERROR;
+import static com.android.packageinstaller.v2.model.uninstallstagedata.UninstallAborted.ABORT_REASON_USER_NOT_ALLOWED;
+
+import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.UninstallCompleteCallback;
+import android.net.Uri;
+import android.os.Process;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.Log;
+import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallAborted;
+import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallReady;
+import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallStage;
+import java.util.List;
 
 public class UninstallRepository {
 
     private static final String TAG = UninstallRepository.class.getSimpleName();
     private final Context mContext;
+    private final PackageManager mPackageManager;
+    private final UserManager mUserManager;
+    public UserHandle mUninstalledUser;
+    public UninstallCompleteCallback mCallback;
+    private ActivityInfo mTargetActivityInfo;
+    private Intent mIntent;
+    private String mTargetPackageName;
+    private String mCallingActivity;
+    private boolean mUninstallFromAllUsers;
 
     public UninstallRepository(Context context) {
         mContext = context;
+        mPackageManager = context.getPackageManager();
+        mUserManager = context.getSystemService(UserManager.class);
+    }
+
+    public UninstallStage performPreUninstallChecks(Intent intent, CallerInfo callerInfo) {
+        mIntent = intent;
+
+        int callingUid = callerInfo.getUid();
+        mCallingActivity = callerInfo.getActivityName();
+
+        if (callingUid == Process.INVALID_UID) {
+            Log.e(TAG, "Could not determine the launching uid.");
+            return new UninstallAborted(ABORT_REASON_GENERIC_ERROR);
+            // TODO: should we give any indication to the user?
+        }
+
+        // Get intent information.
+        // We expect an intent with URI of the form package:<packageName>#<className>
+        // className is optional; if specified, it is the activity the user chose to uninstall
+        final Uri packageUri = intent.getData();
+        if (packageUri == null) {
+            Log.e(TAG, "No package URI in intent");
+            return new UninstallAborted(ABORT_REASON_APP_UNAVAILABLE);
+        }
+        mTargetPackageName = packageUri.getEncodedSchemeSpecificPart();
+        if (mTargetPackageName == null) {
+            Log.e(TAG, "Invalid package name in URI: " + packageUri);
+            return new UninstallAborted(ABORT_REASON_APP_UNAVAILABLE);
+        }
+
+        mUninstallFromAllUsers = intent.getBooleanExtra(Intent.EXTRA_UNINSTALL_ALL_USERS,
+            false);
+        if (mUninstallFromAllUsers && !mUserManager.isAdminUser()) {
+            Log.e(TAG, "Only admin user can request uninstall for all users");
+            return new UninstallAborted(ABORT_REASON_USER_NOT_ALLOWED);
+        }
+
+        mUninstalledUser = intent.getParcelableExtra(Intent.EXTRA_USER, UserHandle.class);
+        if (mUninstalledUser == null) {
+            mUninstalledUser = Process.myUserHandle();
+        } else {
+            List<UserHandle> profiles = mUserManager.getUserProfiles();
+            if (!profiles.contains(mUninstalledUser)) {
+                Log.e(TAG, "User " + Process.myUserHandle() + " can't request uninstall "
+                    + "for user " + mUninstalledUser);
+                return new UninstallAborted(ABORT_REASON_USER_NOT_ALLOWED);
+            }
+        }
+
+        mCallback = intent.getParcelableExtra(PackageInstaller.EXTRA_CALLBACK,
+            PackageManager.UninstallCompleteCallback.class);
+
+        // The class name may have been specified (e.g. when deleting an app from all apps)
+        final String className = packageUri.getFragment();
+        if (className != null) {
+            try {
+                mTargetActivityInfo = mPackageManager.getActivityInfo(
+                    new ComponentName(mTargetPackageName, className),
+                    PackageManager.ComponentInfoFlags.of(0));
+            } catch (PackageManager.NameNotFoundException e) {
+                Log.e(TAG, "Unable to get className");
+                // Continue as the ActivityInfo isn't critical.
+            }
+        }
+
+        return new UninstallReady();
+    }
+
+    public static class CallerInfo {
+
+        private final String mActivityName;
+        private final int mUid;
+
+        public CallerInfo(String activityName, int uid) {
+            mActivityName = activityName;
+            mUid = uid;
+        }
+
+        public String getActivityName() {
+            return mActivityName;
+        }
+
+        public int getUid() {
+            return mUid;
+        }
     }
 }
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallAborted.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallAborted.java
new file mode 100644
index 0000000..9aea6b1
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallAborted.java
@@ -0,0 +1,71 @@
+/*
+ * 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.model.uninstallstagedata;
+
+import android.app.Activity;
+import com.android.packageinstaller.R;
+
+public class UninstallAborted extends UninstallStage {
+
+    public static final int ABORT_REASON_GENERIC_ERROR = 0;
+    public static final int ABORT_REASON_APP_UNAVAILABLE = 1;
+    public static final int ABORT_REASON_USER_NOT_ALLOWED = 2;
+    private final int mStage = UninstallStage.STAGE_ABORTED;
+    private final int mAbortReason;
+    private final int mDialogTitleResource;
+    private final int mDialogTextResource;
+    private final int mActivityResultCode = Activity.RESULT_FIRST_USER;
+
+    public UninstallAborted(int abortReason) {
+        mAbortReason = abortReason;
+        switch (abortReason) {
+            case ABORT_REASON_APP_UNAVAILABLE -> {
+                mDialogTitleResource = R.string.app_not_found_dlg_title;
+                mDialogTextResource = R.string.app_not_found_dlg_text;
+            }
+            case ABORT_REASON_USER_NOT_ALLOWED -> {
+                mDialogTitleResource = 0;
+                mDialogTextResource = R.string.user_is_not_allowed_dlg_text;
+            }
+            default -> {
+                mDialogTitleResource = 0;
+                mDialogTextResource = R.string.generic_error_dlg_text;
+            }
+        }
+    }
+
+    public int getAbortReason() {
+        return mAbortReason;
+    }
+
+    public int getActivityResultCode() {
+        return mActivityResultCode;
+    }
+
+    public int getDialogTitleResource() {
+        return mDialogTitleResource;
+    }
+
+    public int getDialogTextResource() {
+        return mDialogTextResource;
+    }
+
+    @Override
+    public int getStageCode() {
+        return mStage;
+    }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallReady.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallReady.java
new file mode 100644
index 0000000..0108cb4
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallReady.java
@@ -0,0 +1,27 @@
+/*
+ * 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.model.uninstallstagedata;
+
+public class UninstallReady extends UninstallStage {
+
+    private final int mStage = UninstallStage.STAGE_READY;
+
+    @Override
+    public int getStageCode() {
+        return mStage;
+    }
+}
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 5d47da1..7b010d4 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallLaunch.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallLaunch.java
@@ -16,13 +16,17 @@
 
 package com.android.packageinstaller.v2.ui;
 
+import static android.os.Process.INVALID_UID;
 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
 
+import android.content.Intent;
 import android.os.Bundle;
 import androidx.annotation.Nullable;
 import androidx.fragment.app.FragmentActivity;
 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.UninstallStage;
 import com.android.packageinstaller.v2.viewmodel.UninstallViewModel;
 import com.android.packageinstaller.v2.viewmodel.UninstallViewModelFactory;
 
@@ -49,5 +53,21 @@
         mUninstallViewModel = new ViewModelProvider(this,
             new UninstallViewModelFactory(this.getApplication(), mUninstallRepository)).get(
             UninstallViewModel.class);
+
+        Intent intent = getIntent();
+        CallerInfo callerInfo = new CallerInfo(
+            intent.getStringExtra(EXTRA_CALLING_ACTIVITY_NAME),
+            intent.getIntExtra(EXTRA_CALLING_PKG_UID, INVALID_UID));
+        mUninstallViewModel.preprocessIntent(intent, callerInfo);
+
+        mUninstallViewModel.getCurrentUninstallStage().observe(this,
+            this::onUninstallStageChange);
+    }
+
+    /**
+     * Main controller of the UI. This method shows relevant dialogs / fragments based on the
+     * uninstall stage
+     */
+    private void onUninstallStageChange(UninstallStage uninstallStage) {
     }
 }
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModel.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModel.java
index 8187ea5..e524bca 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModel.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModel.java
@@ -17,17 +17,31 @@
 package com.android.packageinstaller.v2.viewmodel;
 
 import android.app.Application;
+import android.content.Intent;
 import androidx.annotation.NonNull;
 import androidx.lifecycle.AndroidViewModel;
+import androidx.lifecycle.MutableLiveData;
 import com.android.packageinstaller.v2.model.UninstallRepository;
+import com.android.packageinstaller.v2.model.UninstallRepository.CallerInfo;
+import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallStage;
 
 public class UninstallViewModel extends AndroidViewModel {
 
     private static final String TAG = UninstallViewModel.class.getSimpleName();
     private final UninstallRepository mRepository;
+    private final MutableLiveData<UninstallStage> mCurrentUninstallStage = new MutableLiveData<>();
 
     public UninstallViewModel(@NonNull Application application, UninstallRepository repository) {
         super(application);
         mRepository = repository;
     }
+
+    public MutableLiveData<UninstallStage> getCurrentUninstallStage() {
+        return mCurrentUninstallStage;
+    }
+
+    public void preprocessIntent(Intent intent, CallerInfo callerInfo) {
+        UninstallStage stage = mRepository.performPreUninstallChecks(intent, callerInfo);
+        mCurrentUninstallStage.setValue(stage);
+    }
 }