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.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 74 fun resolvePanelActivity( 75 allowAllApps: Boolean = false 76 ) { 77 if (resolved) return 78 resolved = true 79 val validPackages = context.resources 80 .getStringArray(R.array.config_controlsPreferredPackages) 81 if (componentName.packageName !in validPackages && !allowAllApps) return 82 panelActivity = _panelActivity?.let { 83 val resolveInfos = mPm.queryIntentActivitiesAsUser( 84 Intent().setComponent(it), 85 PackageManager.ResolveInfoFlags.of( 86 MATCH_DIRECT_BOOT_AWARE.toLong() or 87 MATCH_DIRECT_BOOT_UNAWARE.toLong() 88 ), 89 UserHandle.of(userId) 90 ) 91 if (resolveInfos.isNotEmpty() && verifyResolveInfo(resolveInfos[0])) { 92 it 93 } else { 94 null 95 } 96 } 97 } 98 99 /** 100 * Verifies that the panel activity is enabled, exported and protected by the correct 101 * permission. This last check is to prevent apps from forgetting to protect the activity, as 102 * they won't be able to see the panel until they do. 103 */ 104 @WorkerThread 105 private fun verifyResolveInfo(resolveInfo: ResolveInfo): Boolean { 106 return resolveInfo.activityInfo?.let { 107 it.permission == Manifest.permission.BIND_CONTROLS && 108 it.exported && isComponentActuallyEnabled(it) 109 } ?: false 110 } 111 112 @WorkerThread 113 private fun isComponentActuallyEnabled(activityInfo: ActivityInfo): Boolean { 114 return when (mPm.getComponentEnabledSetting(activityInfo.componentName)) { 115 PackageManager.COMPONENT_ENABLED_STATE_ENABLED -> true 116 PackageManager.COMPONENT_ENABLED_STATE_DISABLED -> false 117 PackageManager.COMPONENT_ENABLED_STATE_DEFAULT -> activityInfo.enabled 118 else -> false 119 } 120 } 121 122 @WorkerThread 123 override fun loadLabel(): CharSequence { 124 return componentName?.let { 125 val appInfo = mPm.getApplicationInfoAsUser(componentName.packageName, 0, userId) 126 appInfo.loadLabel(mPm) 127 } 128 ?: packageItemInfo?.loadLabel(mPm) 129 ?: throw IllegalArgumentException("Package info is missing") 130 } 131 132 @WorkerThread 133 override fun loadIcon(): Drawable { 134 val packageName = 135 componentName?.packageName 136 ?: packageItemInfo?.packageName 137 ?: throw IllegalArgumentException("Package info is missing") 138 val factory = IconDrawableFactory.newInstance(context) 139 val appInfo = mPm.getApplicationInfoAsUser(packageName, 0, userId) 140 return factory.getBadgedIcon(appInfo) 141 } 142 143 override fun equals(other: Any?): Boolean { 144 return other is ControlsServiceInfo && 145 userId == other.userId && 146 componentName == other.componentName && 147 panelActivity == other.panelActivity 148 } 149 150 override fun hashCode(): Int { 151 return Objects.hash(userId, componentName, panelActivity) 152 } 153 154 fun copy(): ControlsServiceInfo { 155 return ControlsServiceInfo(context, serviceInfo).also { 156 it.panelActivity = this.panelActivity 157 } 158 } 159 160 override fun toString(): String { 161 return """ 162 ControlsServiceInfo(serviceInfo=$serviceInfo, panelActivity=$panelActivity, resolved=$resolved) 163 """.trimIndent() 164 } 165 }