1 /*
<lambda>null2  * Copyright (C) 2022 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  *      http://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.settings.spa.app.appinfo
18 
19 import android.app.ActivityManager
20 import android.app.settings.SettingsEnums
21 import android.content.Context
22 import android.content.Intent
23 import android.content.IntentFilter
24 import android.content.pm.FeatureFlags as PmFeatureFlags
25 import android.content.pm.FeatureFlagsImpl as PmFeatureFlagsImpl
26 import android.content.pm.PackageInfo
27 import android.content.pm.PackageManager
28 import android.os.UserHandle
29 import android.util.Log
30 import androidx.annotation.VisibleForTesting
31 import androidx.compose.runtime.Composable
32 import com.android.settings.flags.FeatureFlags
33 import com.android.settings.flags.FeatureFlagsImpl
34 import com.android.settings.overlay.FeatureFactory.Companion.featureFactory
35 import com.android.settings.spa.app.startUninstallActivity
36 import com.android.settingslib.spa.framework.compose.LocalNavController
37 import com.android.settingslib.spaprivileged.framework.common.activityManager
38 import com.android.settingslib.spaprivileged.framework.common.asUser
39 import com.android.settingslib.spaprivileged.framework.common.broadcastReceiverAsUserFlow
40 import com.android.settingslib.spaprivileged.framework.compose.DisposableBroadcastReceiverAsUser
41 import com.android.settingslib.spaprivileged.model.app.IPackageManagers
42 import com.android.settingslib.spaprivileged.model.app.PackageManagers
43 import kotlinx.coroutines.CoroutineScope
44 import kotlinx.coroutines.Dispatchers
45 import kotlinx.coroutines.flow.SharingStarted
46 import kotlinx.coroutines.flow.StateFlow
47 import kotlinx.coroutines.flow.filter
48 import kotlinx.coroutines.flow.flowOf
49 import kotlinx.coroutines.flow.map
50 import kotlinx.coroutines.flow.merge
51 import kotlinx.coroutines.flow.stateIn
52 import kotlinx.coroutines.launch
53 import kotlinx.coroutines.plus
54 
55 private const val TAG = "PackageInfoPresenter"
56 
57 /**
58  * Presenter which helps to present the status change of [PackageInfo].
59  */
60 class PackageInfoPresenter(
61     val context: Context,
62     val packageName: String,
63     val userId: Int,
64     private val coroutineScope: CoroutineScope,
65     private val packageManagers: IPackageManagers = PackageManagers,
66     private val featureFlags: PmFeatureFlags = PmFeatureFlagsImpl(),
67 ) {
68     private val metricsFeatureProvider = featureFactory.metricsFeatureProvider
69     private val userHandle = UserHandle.of(userId)
70     val userContext by lazy { context.asUser(userHandle) }
71     val userPackageManager: PackageManager by lazy { userContext.packageManager }
72 
73     private val appChangeFlow = context.broadcastReceiverAsUserFlow(
74         intentFilter = IntentFilter().apply {
75             // App enabled / disabled
76             addAction(Intent.ACTION_PACKAGE_CHANGED)
77 
78             // App archived
79             addAction(Intent.ACTION_PACKAGE_REMOVED)
80 
81             // App updated / the updates are uninstalled (system app)
82             addAction(Intent.ACTION_PACKAGE_REPLACED)
83 
84             // App force-stopped
85             addAction(Intent.ACTION_PACKAGE_RESTARTED)
86 
87             addDataScheme("package")
88         },
89         userHandle = userHandle,
90     ).filter(::isInterestedAppChange).filter(::isForThisApp)
91 
92     @VisibleForTesting
93     fun isInterestedAppChange(intent: Intent) =
94         intent.action != Intent.ACTION_PACKAGE_REMOVED ||
95             intent.getBooleanExtra(Intent.EXTRA_ARCHIVAL, false)
96 
97     val flow: StateFlow<PackageInfo?> = merge(flowOf(null), appChangeFlow)
98         .map { getPackageInfo() }
99         .stateIn(coroutineScope + Dispatchers.Default, SharingStarted.Eagerly, null)
100 
101     /**
102      * Detects the package fully removed event, and close the current page.
103      */
104     @Composable
105     fun PackageFullyRemovedEffect() {
106         val intentFilter = IntentFilter(Intent.ACTION_PACKAGE_FULLY_REMOVED).apply {
107             addDataScheme("package")
108         }
109         val navController = LocalNavController.current
110         DisposableBroadcastReceiverAsUser(intentFilter, userHandle) { intent ->
111             if (isForThisApp(intent)) {
112                 navController.navigateBack()
113             }
114         }
115     }
116 
117     private fun isForThisApp(intent: Intent) = packageName == intent.data?.schemeSpecificPart
118 
119     /** Enables this package. */
120     fun enable() {
121         logAction(SettingsEnums.ACTION_SETTINGS_ENABLE_APP)
122         coroutineScope.launch(Dispatchers.IO) {
123             userPackageManager.setApplicationEnabledSetting(
124                 packageName, PackageManager.COMPONENT_ENABLED_STATE_DEFAULT, 0
125             )
126         }
127     }
128 
129     /** Disables this package. */
130     fun disable() {
131         logAction(SettingsEnums.ACTION_SETTINGS_DISABLE_APP)
132         coroutineScope.launch(Dispatchers.IO) {
133             userPackageManager.setApplicationEnabledSetting(
134                 packageName, PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER, 0
135             )
136         }
137     }
138 
139     /** Starts the uninstallation activity. */
140     fun startUninstallActivity(forAllUsers: Boolean = false) {
141         logAction(SettingsEnums.ACTION_SETTINGS_UNINSTALL_APP)
142         context.startUninstallActivity(packageName, userHandle, forAllUsers)
143     }
144 
145     /** Clears this instant app. */
146     fun clearInstantApp() {
147         logAction(SettingsEnums.ACTION_SETTINGS_CLEAR_INSTANT_APP)
148         coroutineScope.launch(Dispatchers.IO) {
149             userPackageManager.deletePackageAsUser(packageName, null, 0, userId)
150         }
151     }
152 
153     /** Force stops this package. */
154     fun forceStop() {
155         logAction(SettingsEnums.ACTION_APP_FORCE_STOP)
156         coroutineScope.launch(Dispatchers.Default) {
157             Log.d(TAG, "Stopping package $packageName")
158             if (android.app.Flags.appRestrictionsApi()) {
159                 val uid = userPackageManager.getPackageUid(packageName, 0)
160                 context.activityManager.noteAppRestrictionEnabled(
161                     packageName, uid,
162                     ActivityManager.RESTRICTION_LEVEL_FORCE_STOPPED, true,
163                     ActivityManager.RESTRICTION_REASON_USER, "settings",
164                     ActivityManager.RESTRICTION_SOURCE_USER, 0)
165             }
166             context.activityManager.forceStopPackageAsUser(packageName, userId)
167         }
168     }
169 
170     fun logAction(category: Int) {
171         metricsFeatureProvider.action(context, category, packageName)
172     }
173 
174     private fun getPackageInfo(): PackageInfo? =
175         packageManagers.getPackageInfoAsUser(
176             packageName = packageName,
177             flags = PackageManager.MATCH_ANY_USER.toLong() or
178                 PackageManager.MATCH_DISABLED_COMPONENTS.toLong() or
179                 PackageManager.GET_PERMISSIONS.toLong() or
180                 if (isArchivingEnabled(featureFlags)) PackageManager.MATCH_ARCHIVED_PACKAGES else 0,
181             userId = userId,
182         )
183 }
184