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.os.UserHandle 30 import android.service.controls.ControlsProviderService 31 import androidx.annotation.VisibleForTesting 32 import androidx.annotation.WorkerThread 33 import com.android.settingslib.applications.DefaultAppInfo 34 import com.android.systemui.R 35 import java.util.Objects 36 37 open class ControlsServiceInfo( 38 private val context: Context, 39 val serviceInfo: ServiceInfo 40 ) : DefaultAppInfo( 41 context, 42 context.packageManager, 43 context.userId, 44 serviceInfo.componentName 45 ) { 46 private val _panelActivity: ComponentName? 47 48 init { 49 val metadata = serviceInfo.metaData 50 ?.getString(ControlsProviderService.META_DATA_PANEL_ACTIVITY) ?: "" 51 val unflatenned = ComponentName.unflattenFromString(metadata) 52 if (unflatenned != null && unflatenned.packageName == componentName.packageName) { 53 _panelActivity = unflatenned 54 } else { 55 _panelActivity = null 56 } 57 } 58 59 /** 60 * Component name of an activity that will be shown embedded in the device controls space 61 * instead of using the controls rendered by SystemUI. 62 * 63 * The activity must be in the same package, exported, enabled and protected by the 64 * [Manifest.permission.BIND_CONTROLS] permission. Additionally, only packages declared in 65 * [R.array.config_controlsPreferredPackages] can declare activities for use as a panel. 66 */ 67 var panelActivity: ComponentName? = null 68 protected set 69 70 private var resolved: Boolean = false 71 72 @WorkerThread resolvePanelActivitynull73 fun resolvePanelActivity( 74 allowAllApps: Boolean = false 75 ) { 76 if (resolved) return 77 resolved = true 78 val validPackages = context.resources 79 .getStringArray(R.array.config_controlsPreferredPackages) 80 if (componentName.packageName !in validPackages && !allowAllApps) return 81 panelActivity = _panelActivity?.let { 82 val resolveInfos = mPm.queryIntentActivitiesAsUser( 83 Intent().setComponent(it), 84 PackageManager.ResolveInfoFlags.of( 85 MATCH_DIRECT_BOOT_AWARE.toLong() or 86 MATCH_DIRECT_BOOT_UNAWARE.toLong() 87 ), 88 UserHandle.of(userId) 89 ) 90 if (resolveInfos.isNotEmpty() && verifyResolveInfo(resolveInfos[0])) { 91 it 92 } else { 93 null 94 } 95 } 96 } 97 98 /** 99 * Verifies that the panel activity is enabled, exported and protected by the correct 100 * permission. This last check is to prevent apps from forgetting to protect the activity, as 101 * they won't be able to see the panel until they do. 102 */ 103 @WorkerThread verifyResolveInfonull104 private fun verifyResolveInfo(resolveInfo: ResolveInfo): Boolean { 105 return resolveInfo.activityInfo?.let { 106 it.permission == Manifest.permission.BIND_CONTROLS && 107 it.exported && isComponentActuallyEnabled(it) 108 } ?: false 109 } 110 111 @WorkerThread isComponentActuallyEnablednull112 private fun isComponentActuallyEnabled(activityInfo: ActivityInfo): Boolean { 113 return when (mPm.getComponentEnabledSetting(activityInfo.componentName)) { 114 PackageManager.COMPONENT_ENABLED_STATE_ENABLED -> true 115 PackageManager.COMPONENT_ENABLED_STATE_DISABLED -> false 116 PackageManager.COMPONENT_ENABLED_STATE_DEFAULT -> activityInfo.enabled 117 else -> false 118 } 119 } 120 equalsnull121 override fun equals(other: Any?): Boolean { 122 return other is ControlsServiceInfo && 123 userId == other.userId && 124 componentName == other.componentName && 125 panelActivity == other.panelActivity 126 } 127 hashCodenull128 override fun hashCode(): Int { 129 return Objects.hash(userId, componentName, panelActivity) 130 } 131 copynull132 fun copy(): ControlsServiceInfo { 133 return ControlsServiceInfo(context, serviceInfo).also { 134 it.panelActivity = this.panelActivity 135 } 136 } 137 toStringnull138 override fun toString(): String { 139 return """ 140 ControlsServiceInfo(serviceInfo=$serviceInfo, panelActivity=$panelActivity, resolved=$resolved) 141 """.trimIndent() 142 } 143 }