1 /* <lambda>null2 * Copyright (C) 2024 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.Manifest 20 import android.app.Activity 21 import android.content.BroadcastReceiver 22 import android.content.Context 23 import android.content.Intent 24 import android.content.pm.ApplicationInfo 25 import android.net.Uri 26 import android.os.UserHandle 27 import android.util.Log 28 import com.android.settingslib.spaprivileged.model.app.hasFlag 29 import com.android.settingslib.spaprivileged.model.app.isActiveAdmin 30 import com.android.settingslib.spaprivileged.model.app.userId 31 import kotlin.coroutines.resume 32 import kotlinx.coroutines.Dispatchers 33 import kotlinx.coroutines.flow.Flow 34 import kotlinx.coroutines.flow.conflate 35 import kotlinx.coroutines.flow.flowOn 36 import kotlinx.coroutines.flow.map 37 import kotlinx.coroutines.flow.onEach 38 import kotlinx.coroutines.suspendCancellableCoroutine 39 40 class AppForceStopRepository( 41 private val packageInfoPresenter: PackageInfoPresenter, 42 private val appButtonRepository: AppButtonRepository = 43 AppButtonRepository(packageInfoPresenter.context), 44 ) { 45 private val context = packageInfoPresenter.context 46 47 /** 48 * Flow of whether a package can be force stopped. 49 */ 50 fun canForceStopFlow(): Flow<Boolean> = packageInfoPresenter.flow 51 .map { packageInfo -> 52 val app = packageInfo?.applicationInfo ?: return@map false 53 canForceStop(app) 54 } 55 .conflate() 56 .onEach { Log.d(TAG, "canForceStopFlow: $it") } 57 .flowOn(Dispatchers.Default) 58 59 /** 60 * Gets whether a package can be force stopped. 61 */ 62 private suspend fun canForceStop(app: ApplicationInfo): Boolean = when { 63 // User can't force stop device admin. 64 app.isActiveAdmin(context) -> false 65 66 appButtonRepository.isDisallowControl(app) -> false 67 68 // If the app isn't explicitly stopped, then always show the force stop button. 69 !app.hasFlag(ApplicationInfo.FLAG_STOPPED) -> true 70 71 else -> queryAppRestart(app) 72 } 73 74 /** 75 * Queries if app has restarted. 76 * 77 * @return true means app can be force stop again. 78 */ 79 private suspend fun queryAppRestart(app: ApplicationInfo): Boolean { 80 val packageName = app.packageName 81 val intent = Intent( 82 Intent.ACTION_QUERY_PACKAGE_RESTART, 83 Uri.fromParts("package", packageName, null) 84 ).apply { 85 putExtra(Intent.EXTRA_PACKAGES, arrayOf(packageName)) 86 putExtra(Intent.EXTRA_UID, app.uid) 87 putExtra(Intent.EXTRA_USER_HANDLE, app.userId) 88 } 89 Log.d(TAG, "Sending broadcast to query restart status for $packageName") 90 91 return suspendCancellableCoroutine { continuation -> 92 val receiver: BroadcastReceiver = object : BroadcastReceiver() { 93 override fun onReceive(context: Context, intent: Intent) { 94 val enabled = resultCode != Activity.RESULT_CANCELED 95 Log.d(TAG, "Got broadcast response: Restart status for $packageName $enabled") 96 continuation.resume(enabled) 97 } 98 } 99 context.sendOrderedBroadcastAsUser( 100 intent, 101 UserHandle.CURRENT, 102 Manifest.permission.HANDLE_QUERY_PACKAGE_RESTART, 103 receiver, 104 null, 105 Activity.RESULT_CANCELED, 106 null, 107 null, 108 ) 109 } 110 } 111 112 private companion object { 113 private const val TAG = "AppForceStopRepository" 114 } 115 } 116