1 /* <lambda>null2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * https://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.packageinstaller.v2.ui 18 19 import android.app.Activity 20 import android.app.NotificationManager 21 import android.content.Intent 22 import android.os.Bundle 23 import android.os.Process 24 import android.util.Log 25 import android.view.WindowManager 26 import android.widget.Toast 27 import androidx.fragment.app.DialogFragment 28 import androidx.fragment.app.FragmentActivity 29 import androidx.fragment.app.FragmentManager 30 import androidx.lifecycle.ViewModelProvider 31 import com.android.packageinstaller.v2.model.PackageUtil.localLogv 32 import com.android.packageinstaller.v2.model.UninstallAborted 33 import com.android.packageinstaller.v2.model.UninstallFailed 34 import com.android.packageinstaller.v2.model.UninstallRepository 35 import com.android.packageinstaller.v2.model.UninstallStage 36 import com.android.packageinstaller.v2.model.UninstallSuccess 37 import com.android.packageinstaller.v2.model.UninstallUninstalling 38 import com.android.packageinstaller.v2.model.UninstallUserActionRequired 39 import com.android.packageinstaller.v2.ui.fragments.UninstallConfirmationFragment 40 import com.android.packageinstaller.v2.ui.fragments.UninstallErrorFragment 41 import com.android.packageinstaller.v2.ui.fragments.UninstallUninstallingFragment 42 import com.android.packageinstaller.v2.viewmodel.UninstallViewModel 43 import com.android.packageinstaller.v2.viewmodel.UninstallViewModelFactory 44 45 class UninstallLaunch : FragmentActivity(), UninstallActionListener { 46 47 companion object { 48 @JvmField val EXTRA_CALLING_PKG_UID = 49 UninstallLaunch::class.java.packageName + ".callingPkgUid" 50 @JvmField val EXTRA_CALLING_ACTIVITY_NAME = 51 UninstallLaunch::class.java.packageName + ".callingActivityName" 52 val LOG_TAG = UninstallLaunch::class.java.simpleName 53 private const val TAG_DIALOG = "dialog" 54 } 55 56 private var uninstallViewModel: UninstallViewModel? = null 57 private var uninstallRepository: UninstallRepository? = null 58 private var fragmentManager: FragmentManager? = null 59 private var notificationManager: NotificationManager? = null 60 override fun onCreate(savedInstanceState: Bundle?) { 61 window.addSystemFlags(WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS) 62 63 // Never restore any state, esp. never create any fragments. The data in the fragment might 64 // be stale, if e.g. the app was uninstalled while the activity was destroyed. 65 super.onCreate(null) 66 fragmentManager = supportFragmentManager 67 notificationManager = getSystemService(NotificationManager::class.java) 68 69 uninstallRepository = UninstallRepository(applicationContext) 70 uninstallViewModel = ViewModelProvider( 71 this, UninstallViewModelFactory(this.application, uninstallRepository!!) 72 ).get(UninstallViewModel::class.java) 73 74 val intent = intent 75 val callerInfo = UninstallRepository.CallerInfo( 76 intent.getStringExtra(EXTRA_CALLING_ACTIVITY_NAME), 77 intent.getIntExtra(EXTRA_CALLING_PKG_UID, Process.INVALID_UID) 78 ) 79 uninstallViewModel!!.preprocessIntent(intent, callerInfo) 80 uninstallViewModel!!.currentUninstallStage.observe(this) { uninstallStage: UninstallStage -> 81 onUninstallStageChange(uninstallStage) 82 } 83 } 84 85 /** 86 * Main controller of the UI. This method shows relevant dialogs / fragments based on the 87 * uninstall stage 88 */ 89 private fun onUninstallStageChange(uninstallStage: UninstallStage) { 90 when (uninstallStage.stageCode) { 91 UninstallStage.STAGE_ABORTED -> { 92 val aborted = uninstallStage as UninstallAborted 93 if (aborted.abortReason == UninstallAborted.ABORT_REASON_APP_UNAVAILABLE || 94 aborted.abortReason == UninstallAborted.ABORT_REASON_USER_NOT_ALLOWED 95 ) { 96 val errorDialog = UninstallErrorFragment(aborted) 97 showDialogInner(errorDialog) 98 } else { 99 setResult(aborted.activityResultCode, null, true) 100 } 101 } 102 103 UninstallStage.STAGE_USER_ACTION_REQUIRED -> { 104 val uar = uninstallStage as UninstallUserActionRequired 105 val confirmationDialog = UninstallConfirmationFragment(uar) 106 showDialogInner(confirmationDialog) 107 } 108 109 UninstallStage.STAGE_UNINSTALLING -> { 110 // TODO: This shows a fragment whether or not user requests a result or not. 111 // Originally, if the user does not request a result, we used to show a notification. 112 // And a fragment if the user requests a result back. Should we consolidate and 113 // show a fragment always? 114 val uninstalling = uninstallStage as UninstallUninstalling 115 val uninstallingDialog = UninstallUninstallingFragment(uninstalling) 116 showDialogInner(uninstallingDialog) 117 } 118 119 UninstallStage.STAGE_FAILED -> { 120 val failed = uninstallStage as UninstallFailed 121 if (!failed.returnResult) { 122 notificationManager!!.notify( 123 failed.uninstallNotificationId!!, failed.uninstallNotification 124 ) 125 } 126 setResult(failed.activityResultCode, failed.resultIntent, true) 127 } 128 129 UninstallStage.STAGE_SUCCESS -> { 130 val success = uninstallStage as UninstallSuccess 131 if (success.message != null) { 132 Toast.makeText(this, success.message, Toast.LENGTH_LONG).show() 133 } 134 setResult(success.activityResultCode, success.resultIntent, true) 135 } 136 137 else -> { 138 Log.e(LOG_TAG, "Invalid stage: " + uninstallStage.stageCode) 139 showDialogInner(null) 140 } 141 } 142 } 143 144 /** 145 * Replace any visible dialog by the dialog returned by InstallRepository 146 * 147 * @param newDialog The new dialog to display 148 */ 149 private fun showDialogInner(newDialog: DialogFragment?) { 150 val currentDialog = fragmentManager!!.findFragmentByTag(TAG_DIALOG) as DialogFragment? 151 currentDialog?.dismissAllowingStateLoss() 152 newDialog?.show(fragmentManager!!, TAG_DIALOG) 153 } 154 155 fun setResult(resultCode: Int, data: Intent?, shouldFinish: Boolean) { 156 super.setResult(resultCode, data) 157 if (shouldFinish) { 158 finish() 159 } 160 } 161 162 override fun onPositiveResponse(keepData: Boolean) { 163 if (localLogv) { 164 Log.d(LOG_TAG, "Staring uninstall") 165 } 166 uninstallViewModel!!.initiateUninstall(keepData) 167 } 168 169 override fun onNegativeResponse() { 170 if (localLogv) { 171 Log.d(LOG_TAG, "Cancelling uninstall") 172 } 173 uninstallViewModel!!.cancelUninstall() 174 setResult(Activity.RESULT_FIRST_USER, null, true) 175 } 176 } 177