Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0e00cf71 authored by Sumedh Sen's avatar Sumedh Sen
Browse files

Migrate Unarchival to Pia V2

Bug: 398032488
Test: atest CtsPackageUninstallTestCases:ArchiveTest
Flag: android.content.pm.use_pia_v2
Change-Id: Id8eb952ceb3baf70849579bb58ef27f6f5634ae2
parent e6690b25
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -57,6 +57,7 @@ object PackageUtil {
    const val ARGS_APP_LABEL: String = "app_label"
    const val ARGS_APP_SNIPPET: String = "app_snippet"
    const val ARGS_ERROR_DIALOG_TYPE: String = "error_dialog_type"
    const val ARGS_INSTALLER_LABEL: String = "installer_label"
    const val ARGS_IS_ARCHIVE: String = "is_archive"
    const val ARGS_IS_CLONE_USER: String = "clone_user"
    const val ARGS_IS_UPDATING: String = "is_updating"
@@ -204,7 +205,7 @@ object PackageUtil {
     * @param permission the permission name to check
     * @return `true` if the caller is requesting the said permission in its Manifest
     */
    private fun isUidRequestingPermission(
    fun isUidRequestingPermission(
        pm: PackageManager,
        uid: Int,
        permission: String,
+121 −1
Original line number Diff line number Diff line
@@ -16,6 +16,126 @@

package com.android.packageinstaller.v2.model

import android.Manifest
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.IntentSender
import android.content.pm.PackageInstaller
import android.content.pm.PackageManager
import android.content.pm.PackageManager.NameNotFoundException
import android.os.Process
import android.util.Log
import com.android.packageinstaller.v2.model.PackageUtil.getPackageNameForUid
import com.android.packageinstaller.v2.model.PackageUtil.isPermissionGranted
import com.android.packageinstaller.v2.model.PackageUtil.isUidRequestingPermission
import java.io.IOException

class UnarchiveRepository(private val context: Context)
class UnarchiveRepository(private val context: Context) {

    private val packageManager: PackageManager = context.packageManager
    private val packageInstaller: PackageInstaller = packageManager.packageInstaller

    private lateinit var intent: Intent
    private lateinit var targetPackageName: String
    private lateinit var intentSender: IntentSender

    fun performPreUnarchivalChecks(intent: Intent, callerInfo: CallerInfo): UnarchiveStage {
        this.intent = intent

        val callingUid = callerInfo.uid
        if (callingUid == Process.INVALID_UID) {
            Log.e(LOG_TAG, "Could not determine the launching uid.")
            return UnarchiveAborted(UnarchiveAborted.ABORT_REASON_GENERIC_ERROR)
        }

        val callingPackage = getPackageNameForUid(context, callingUid, null)
        if (callingPackage == null) {
            Log.e(LOG_TAG, "Package not found for originating uid $callingUid")
            return UnarchiveAborted(UnarchiveAborted.ABORT_REASON_GENERIC_ERROR)
        }


        // We don't check the AppOpsManager here for REQUEST_INSTALL_PACKAGES because the requester
        // is not the source of the installation.
        val hasRequestInstallPermission = isUidRequestingPermission(
            packageManager, callingUid, Manifest.permission.REQUEST_INSTALL_PACKAGES
        )
        val hasInstallPermission =
            isPermissionGranted(context, Manifest.permission.INSTALL_PACKAGES, callingUid)

        if (!hasRequestInstallPermission && !hasInstallPermission) {
            Log.e(
                LOG_TAG, ("Uid " + callingUid + " does not have "
                    + Manifest.permission.REQUEST_INSTALL_PACKAGES + " or "
                    + Manifest.permission.INSTALL_PACKAGES)
            )
            return UnarchiveAborted(UnarchiveAborted.ABORT_REASON_GENERIC_ERROR)
        }

        targetPackageName = intent.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME)!!
        intentSender =
            intent.getParcelableExtra(EXTRA_UNARCHIVE_INTENT_SENDER, IntentSender::class.java)!!

        return UnarchiveReady()
    }

    fun showUnarchivalConfirmation(): UnarchiveStage {
        var appTitle: String
        try {
            appTitle = getAppTitle(targetPackageName, PackageManager.MATCH_ARCHIVED_PACKAGES)
        } catch (e: NameNotFoundException) {
            Log.e(LOG_TAG, "Invalid packageName $targetPackageName: ", e)
            return UnarchiveAborted(UnarchiveAborted.ABORT_REASON_GENERIC_ERROR)
        }

        val installSource = packageManager.getInstallSourceInfo(targetPackageName)
        val installingPackageName =
            installSource.updateOwnerPackageName ?: installSource.installingPackageName
        if (installingPackageName == null) {
            return UnarchiveAborted(UnarchiveAborted.ABORT_REASON_GENERIC_ERROR)
        }

        var installerTitle: String
        try {
            installerTitle = getAppTitle(installingPackageName, 0)
        } catch (e: NameNotFoundException) {
            Log.e(LOG_TAG, "Could not find installer", e)
            return UnarchiveAborted(UnarchiveAborted.ABORT_REASON_GENERIC_ERROR)
        }

        return UnarchiveUserActionRequired(appTitle, installerTitle)
    }

    @Throws(NameNotFoundException::class)
    private fun getAppTitle(packageName: String, flags: Long): String {
        val appInfo = packageManager.getApplicationInfo(
            packageName,
            PackageManager.ApplicationInfoFlags.of(flags)
        )
        return appInfo.loadLabel(packageManager).toString()
    }

    @SuppressLint("MissingPermission")
    fun beginUnarchive(): UnarchiveStage {
        try {
            packageInstaller.requestUnarchive(targetPackageName, intentSender)
        } catch (e: Exception) {
            when (e) {
                is IOException, is NameNotFoundException ->
                    Log.e(LOG_TAG, "RequestUnarchive failed with %s." + e.message)
            }
            return UnarchiveAborted(UnarchiveAborted.ABORT_REASON_GENERIC_ERROR)
        }
        return UnarchiveAborted(UnarchiveAborted.ABORT_REASON_UNARCHIVE_DONE, Activity.RESULT_OK)
    }

    companion object {
        private val LOG_TAG = UnarchiveRepository::class.java.simpleName
        private const val EXTRA_UNARCHIVE_INTENT_SENDER =
            "android.content.pm.extra.UNARCHIVE_INTENT_SENDER"
    }

    data class CallerInfo(val packageName: String?, val uid: Int)
}
+44 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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

import android.app.Activity

sealed class UnarchiveStage(val stageCode: Int) {

    companion object {
        const val STAGE_DEFAULT = -1
        const val STAGE_ABORTED = 0
        const val STAGE_READY = 1
        const val STAGE_USER_ACTION_REQUIRED = 2
    }
}

data class UnarchiveAborted(
    val abortReason: Int,
    val activityResultCode: Int = Activity.RESULT_FIRST_USER,
) : UnarchiveStage(STAGE_ABORTED) {
    companion object {
        const val ABORT_REASON_GENERIC_ERROR = 0
        const val ABORT_REASON_UNARCHIVE_DONE = 1
    }
}

class UnarchiveReady() : UnarchiveStage(STAGE_READY)

data class UnarchiveUserActionRequired(val appTitle: String, val installerTitle: String) :
    UnarchiveStage(STAGE_USER_ACTION_REQUIRED)
+24 −0
Original line number Diff line number Diff line
/*
 * 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
 *
 *      http://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

interface UnarchiveActionListener {
    /**
     * Indicates that the user has confirmed proceeding with app unarchival
     */
    fun beginUnarchive()
}
+55 −1
Original line number Diff line number Diff line
@@ -18,13 +18,20 @@ package com.android.packageinstaller.v2.ui

import android.os.Bundle
import android.os.Process
import android.view.WindowManager
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.UnarchiveAborted
import com.android.packageinstaller.v2.model.UnarchiveRepository
import com.android.packageinstaller.v2.model.UnarchiveStage
import com.android.packageinstaller.v2.model.UnarchiveUserActionRequired
import com.android.packageinstaller.v2.ui.fragments.UnarchiveConfirmationFragment
import com.android.packageinstaller.v2.viewmodel.UnarchiveViewModel
import com.android.packageinstaller.v2.viewmodel.UnarchiveViewModelFactory

class UnarchiveLaunch: FragmentActivity() {
class UnarchiveLaunch : FragmentActivity(), UnarchiveActionListener {

    companion object {
        @JvmField val EXTRA_CALLING_PKG_UID: String =
@@ -37,13 +44,60 @@ class UnarchiveLaunch: FragmentActivity() {

    private var unarchiveViewModel: UnarchiveViewModel? = null
    private var unarchiveRepository: UnarchiveRepository? = null
    private var fragmentManager: FragmentManager? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        window.addSystemFlags(
            WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS
        )
        super.onCreate(savedInstanceState)

        fragmentManager = supportFragmentManager

        unarchiveRepository = UnarchiveRepository(applicationContext)
        unarchiveViewModel = ViewModelProvider(
            this, UnarchiveViewModelFactory(application, unarchiveRepository!!)
        )[UnarchiveViewModel::class.java]

        val info = UnarchiveRepository.CallerInfo(
            intent.getStringExtra(EXTRA_CALLING_PKG_NAME),
            intent.getIntExtra(EXTRA_CALLING_PKG_UID, Process.INVALID_UID)
        )

        unarchiveViewModel!!.preprocessIntent(intent, info)
        unarchiveViewModel!!.currentUnarchiveStage.observe(this) { stage: UnarchiveStage ->
            onUnarchiveStageChange(stage)
        }
    }

    private fun onUnarchiveStageChange(stage: UnarchiveStage) {
        when (stage.stageCode) {
            UnarchiveStage.STAGE_ABORTED -> {
                val aborted = stage as UnarchiveAborted
                setResult(aborted.activityResultCode)
                finish()
            }

            UnarchiveStage.STAGE_USER_ACTION_REQUIRED -> {
                val uar = stage as UnarchiveUserActionRequired
                val confirmationDialog = UnarchiveConfirmationFragment.newInstance(uar)
                showDialogInner(confirmationDialog)
            }
        }
    }

    override fun beginUnarchive() {
        unarchiveViewModel!!.beginUnarchive()
    }

    /**
     * Replace any visible dialog by the dialog returned by UnarchiveRepository
     *
     * @param newDialog The new dialog to display
     */
    private fun showDialogInner(newDialog: DialogFragment?) {
        val currentDialog = fragmentManager!!.findFragmentByTag(TAG_DIALOG) as DialogFragment?
        currentDialog?.dismissAllowingStateLoss()
        newDialog?.show(fragmentManager!!, TAG_DIALOG)
    }
}
Loading