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