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