1 /*
2  * Copyright (C) 2019 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.systemui.controls
18 
19 import android.Manifest
20 import android.content.ComponentName
21 import android.content.Context
22 import android.content.Intent
23 import android.content.pm.ActivityInfo
24 import android.content.pm.PackageManager
25 import android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE
26 import android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE
27 import android.content.pm.ResolveInfo
28 import android.content.pm.ServiceInfo
29 import android.graphics.drawable.Drawable
30 import android.os.UserHandle
31 import android.service.controls.ControlsProviderService
32 import android.util.IconDrawableFactory
33 import androidx.annotation.WorkerThread
34 import com.android.settingslib.applications.DefaultAppInfo
35 import com.android.systemui.res.R
36 import java.util.Objects
37 
38 open class ControlsServiceInfo(
39     private val context: Context,
40     val serviceInfo: ServiceInfo
41 ) : DefaultAppInfo(
42     context,
43     context.packageManager,
44     context.userId,
45     serviceInfo.componentName
46 ) {
47     private val _panelActivity: ComponentName?
48 
49     init {
50         val metadata = serviceInfo.metaData
51             ?.getString(ControlsProviderService.META_DATA_PANEL_ACTIVITY) ?: ""
52         val unflatenned = ComponentName.unflattenFromString(metadata)
53         if (unflatenned != null && unflatenned.packageName == componentName.packageName) {
54             _panelActivity = unflatenned
55         } else {
56             _panelActivity = null
57         }
58     }
59 
60     /**
61      * Component name of an activity that will be shown embedded in the device controls space
62      * instead of using the controls rendered by SystemUI.
63      *
64      * The activity must be in the same package, exported, enabled and protected by the
65      * [Manifest.permission.BIND_CONTROLS] permission. Additionally, only packages declared in
66      * [R.array.config_controlsPreferredPackages] can declare activities for use as a panel.
67      */
68     var panelActivity: ComponentName? = null
69         protected set
70 
71     private var resolved: Boolean = false
72 
73     @WorkerThread
resolvePanelActivitynull74     fun resolvePanelActivity() {
75         if (resolved) return
76         resolved = true
77         panelActivity = _panelActivity?.let {
78             val resolveInfos = mPm.queryIntentActivitiesAsUser(
79                 Intent().setComponent(it),
80                 PackageManager.ResolveInfoFlags.of(
81                     MATCH_DIRECT_BOOT_AWARE.toLong() or
82                             MATCH_DIRECT_BOOT_UNAWARE.toLong()
83                 ),
84                 UserHandle.of(userId)
85             )
86             if (resolveInfos.isNotEmpty() && verifyResolveInfo(resolveInfos[0])) {
87                 it
88             } else {
89                 null
90             }
91         }
92     }
93 
94     /**
95      * Verifies that the panel activity is enabled, exported and protected by the correct
96      * permission. This last check is to prevent apps from forgetting to protect the activity, as
97      * they won't be able to see the panel until they do.
98      */
99     @WorkerThread
verifyResolveInfonull100     private fun verifyResolveInfo(resolveInfo: ResolveInfo): Boolean {
101         return resolveInfo.activityInfo?.let {
102             it.permission == Manifest.permission.BIND_CONTROLS &&
103                     it.exported && isComponentActuallyEnabled(it)
104         } ?: false
105     }
106 
107     @WorkerThread
isComponentActuallyEnablednull108     private fun isComponentActuallyEnabled(activityInfo: ActivityInfo): Boolean {
109         return when (mPm.getComponentEnabledSetting(activityInfo.componentName)) {
110             PackageManager.COMPONENT_ENABLED_STATE_ENABLED -> true
111             PackageManager.COMPONENT_ENABLED_STATE_DISABLED -> false
112             PackageManager.COMPONENT_ENABLED_STATE_DEFAULT -> activityInfo.enabled
113             else -> false
114         }
115     }
116 
117     @WorkerThread
loadLabelnull118     override fun loadLabel(): CharSequence {
119         return componentName?.let {
120             val appInfo = mPm.getApplicationInfoAsUser(componentName.packageName, 0, userId)
121             appInfo.loadLabel(mPm)
122         }
123             ?: packageItemInfo?.loadLabel(mPm)
124             ?: throw IllegalArgumentException("Package info is missing")
125     }
126 
127     @WorkerThread
loadIconnull128     override fun loadIcon(): Drawable {
129         val packageName =
130             componentName?.packageName
131                 ?: packageItemInfo?.packageName
132                 ?: throw IllegalArgumentException("Package info is missing")
133         val factory = IconDrawableFactory.newInstance(context)
134         val appInfo = mPm.getApplicationInfoAsUser(packageName, 0, userId)
135         return factory.getBadgedIcon(appInfo)
136     }
137 
equalsnull138     override fun equals(other: Any?): Boolean {
139         return other is ControlsServiceInfo &&
140                 userId == other.userId &&
141                 componentName == other.componentName &&
142                 panelActivity == other.panelActivity
143     }
144 
hashCodenull145     override fun hashCode(): Int {
146         return Objects.hash(userId, componentName, panelActivity)
147     }
148 
copynull149     fun copy(): ControlsServiceInfo {
150         return ControlsServiceInfo(context, serviceInfo).also {
151             it.panelActivity = this.panelActivity
152         }
153     }
154 
toStringnull155     override fun toString(): String {
156         return """
157             ControlsServiceInfo(serviceInfo=$serviceInfo, panelActivity=$panelActivity, resolved=$resolved)
158         """.trimIndent()
159     }
160 }