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 }