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  *      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.PendingIntent
20 import android.content.Intent
21 import android.content.IntentFilter
22 import android.content.pm.ApplicationInfo
23 import android.content.pm.PackageInstaller
24 import android.content.pm.PackageManager
25 import android.os.UserHandle
26 import android.util.Log
27 import android.widget.Toast
28 import androidx.compose.material.icons.Icons
29 import androidx.compose.material.icons.outlined.CloudUpload
30 import androidx.compose.runtime.Composable
31 import androidx.compose.runtime.remember
32 import androidx.lifecycle.compose.collectAsStateWithLifecycle
33 import com.android.settings.R
34 import com.android.settingslib.spa.widget.button.ActionButton
35 import com.android.settingslib.spaprivileged.framework.compose.DisposableBroadcastReceiverAsUser
36 import kotlinx.coroutines.Dispatchers
37 import kotlinx.coroutines.flow.MutableStateFlow
38 import kotlinx.coroutines.flow.asStateFlow
39 import kotlinx.coroutines.flow.flowOn
40 import kotlinx.coroutines.flow.map
41 
42 class AppArchiveButton(
43     packageInfoPresenter: PackageInfoPresenter,
44     private val isHibernationSwitchEnabledStateFlow: MutableStateFlow<Boolean>,
45 ) {
46     private companion object {
47         private const val LOG_TAG = "AppArchiveButton"
48         private const val INTENT_ACTION = "com.android.settings.archive.action"
49     }
50 
51     private val context = packageInfoPresenter.context
52     private val appButtonRepository = AppButtonRepository(context)
53     private val userPackageManager = packageInfoPresenter.userPackageManager
54     private val packageInstaller = userPackageManager.packageInstaller
55     private val packageName = packageInfoPresenter.packageName
56     private val userHandle = UserHandle.of(packageInfoPresenter.userId)
57     private var broadcastReceiverIsCreated = false
58     private lateinit var appLabel: CharSequence
59 
60     @Composable
61     fun getActionButton(app: ApplicationInfo): ActionButton {
62         if (!broadcastReceiverIsCreated) {
63             val intentFilter = IntentFilter(INTENT_ACTION)
64             DisposableBroadcastReceiverAsUser(intentFilter, userHandle) { intent ->
65                 if (app.packageName == intent.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME)) {
66                     onReceive(intent)
67                 }
68             }
69             broadcastReceiverIsCreated = true
70         }
71         appLabel = userPackageManager.getApplicationLabel(app)
72         return ActionButton(
73             text = context.getString(R.string.archive),
74             imageVector = Icons.Outlined.CloudUpload,
75             enabled = remember(app) {
76                 isHibernationSwitchEnabledStateFlow.asStateFlow().map {
77                     it && isActionButtonEnabledForApp(app)
78                 }.flowOn(Dispatchers.Default)
79             }.collectAsStateWithLifecycle(false).value
80         ) { onArchiveClicked(app) }
81     }
82 
83     private fun isActionButtonEnabledForApp(app: ApplicationInfo): Boolean {
84         return app.isActionButtonEnabled() && appButtonRepository.isAllowUninstallOrArchive(
85             context,
86             app
87         )
88     }
89 
90     private fun ApplicationInfo.isActionButtonEnabled(): Boolean {
91         return try {
92             (!isArchived
93                     && userPackageManager.isAppArchivable(packageName)
94                     // We apply the same device policy for both the uninstallation and archive
95                     // button.
96                     && !appButtonRepository.isUninstallBlockedByAdmin(this))
97         } catch (e: PackageManager.NameNotFoundException) {
98             false
99         }
100     }
101 
102     private fun onArchiveClicked(app: ApplicationInfo) {
103         val intent = Intent(INTENT_ACTION)
104         intent.setPackage(context.packageName)
105         val pendingIntent = PendingIntent.getBroadcastAsUser(
106             context, 0, intent,
107             // FLAG_MUTABLE is required by PackageInstaller#requestArchive
108             PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_MUTABLE,
109             userHandle
110         )
111         try {
112             packageInstaller.requestArchive(app.packageName, pendingIntent.intentSender)
113         } catch (e: Exception) {
114             Log.e(LOG_TAG, "Request archive failed", e)
115             Toast.makeText(
116                 context,
117                 context.getString(R.string.archiving_failed),
118                 Toast.LENGTH_SHORT
119             ).show()
120         }
121     }
122 
123     private fun onReceive(intent: Intent) {
124         when (val status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, Int.MIN_VALUE)) {
125             PackageInstaller.STATUS_SUCCESS -> {
126                 Toast.makeText(
127                     context,
128                     context.getString(R.string.archiving_succeeded, appLabel),
129                     Toast.LENGTH_SHORT
130                 ).show()
131             }
132 
133             else -> {
134                 Log.e(LOG_TAG, "Request archiving failed for $packageName with code $status")
135                 Toast.makeText(
136                     context,
137                     context.getString(R.string.archiving_failed),
138                     Toast.LENGTH_SHORT
139                 ).show()
140             }
141         }
142     }
143 }
144