• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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