1 /* <lambda>null2 * Copyright (C) 2024 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.keyboard.shortcut.data.repository 18 19 import android.annotation.SuppressLint 20 import android.app.role.RoleManager 21 import android.content.ComponentName 22 import android.content.Context 23 import android.content.Intent 24 import android.content.Intent.ACTION_MAIN 25 import android.content.Intent.CATEGORY_LAUNCHER 26 import android.content.pm.ActivityInfo 27 import android.content.pm.PackageManager.MATCH_DEFAULT_ONLY 28 import android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE 29 import android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE 30 import android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES 31 import android.content.pm.PackageManager.NameNotFoundException 32 import android.graphics.drawable.Icon 33 import android.hardware.input.AppLaunchData 34 import android.hardware.input.AppLaunchData.CategoryData 35 import android.hardware.input.AppLaunchData.ComponentData 36 import android.hardware.input.AppLaunchData.RoleData 37 import android.hardware.input.InputGestureData 38 import android.hardware.input.InputGestureData.KeyTrigger 39 import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION 40 import android.hardware.input.KeyGestureEvent.KeyGestureType 41 import android.util.Log 42 import com.android.internal.app.ResolverActivity 43 import com.android.systemui.keyboard.shortcut.data.model.InternalGroupsSource 44 import com.android.systemui.keyboard.shortcut.data.model.InternalKeyboardShortcutGroup 45 import com.android.systemui.keyboard.shortcut.data.model.InternalKeyboardShortcutInfo 46 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType 47 import com.android.systemui.res.R 48 import com.android.systemui.settings.UserTracker 49 import javax.inject.Inject 50 51 /** 52 * Serves as a bridge for converting InputGestureData API Models to Shortcut Helper Data Layer 53 * Models and vice versa. 54 */ 55 class InputGestureDataAdapter 56 @Inject 57 constructor( 58 private val userTracker: UserTracker, 59 private val inputGestureMaps: InputGestureMaps, 60 private val context: Context, 61 ) { 62 private val userContext: Context 63 get() = userTracker.createCurrentUserContext(userTracker.userContext) 64 65 fun toInternalGroupSources(inputGestures: List<InputGestureData>): List<InternalGroupsSource> { 66 val ungroupedInternalGroupSources = 67 inputGestures.mapNotNull { gestureData -> 68 val keyTrigger = gestureData.trigger as KeyTrigger 69 val keyGestureType = gestureData.action.keyGestureType() 70 val appLaunchData: AppLaunchData? = gestureData.action.appLaunchData() 71 fetchGroupLabelByGestureType(keyGestureType)?.let { groupLabel -> 72 toInternalKeyboardShortcutInfo(keyGestureType, keyTrigger, appLaunchData) 73 ?.let { internalKeyboardShortcutInfo -> 74 val group = 75 InternalKeyboardShortcutGroup( 76 label = groupLabel, 77 items = listOf(internalKeyboardShortcutInfo), 78 ) 79 80 fetchShortcutCategoryTypeByGestureType(keyGestureType)?.let { 81 InternalGroupsSource(groups = listOf(group), type = it) 82 } 83 } 84 } 85 } 86 87 return ungroupedInternalGroupSources 88 } 89 90 fun getKeyGestureTypeForShortcut( 91 shortcutLabel: String, 92 shortcutCategoryType: ShortcutCategoryType, 93 ): Int? { 94 if (shortcutCategoryType == ShortcutCategoryType.AppCategories) { 95 return KEY_GESTURE_TYPE_LAUNCH_APPLICATION 96 } 97 val result = inputGestureMaps.shortcutLabelToKeyGestureTypeMap[shortcutLabel] 98 return result 99 } 100 101 private fun toInternalKeyboardShortcutInfo( 102 keyGestureType: Int, 103 keyTrigger: KeyTrigger, 104 appLaunchData: AppLaunchData?, 105 ): InternalKeyboardShortcutInfo? { 106 fetchShortcutLabelByGestureType(keyGestureType, appLaunchData)?.let { 107 return InternalKeyboardShortcutInfo( 108 label = it, 109 keycode = keyTrigger.keycode, 110 modifiers = keyTrigger.modifierState, 111 isCustomShortcut = true, 112 icon = appLaunchData?.let { fetchShortcutIconByAppLaunchData(appLaunchData) }, 113 ) 114 } 115 return null 116 } 117 118 @SuppressLint("QueryPermissionsNeeded") 119 private fun fetchShortcutIconByAppLaunchData(appLaunchData: AppLaunchData): Icon? { 120 val intent = fetchIntentFromAppLaunchData(appLaunchData) ?: return null 121 val resolvedActivity = resolveSingleMatchingActivityFrom(intent) 122 123 return if (resolvedActivity == null) { 124 null 125 } else { 126 Icon.createWithResource(context, resolvedActivity.iconResource) 127 } 128 } 129 130 private fun fetchGroupLabelByGestureType(@KeyGestureType keyGestureType: Int): String? { 131 inputGestureMaps.gestureToInternalKeyboardShortcutGroupLabelResIdMap[keyGestureType]?.let { 132 return context.getString(it) 133 } ?: return null 134 } 135 136 private fun fetchShortcutLabelByGestureType( 137 @KeyGestureType keyGestureType: Int, 138 appLaunchData: AppLaunchData?, 139 ): String? { 140 inputGestureMaps.gestureToInternalKeyboardShortcutInfoLabelResIdMap[keyGestureType]?.let { 141 return context.getString(it) 142 } 143 144 if (keyGestureType == KEY_GESTURE_TYPE_LAUNCH_APPLICATION) { 145 return fetchShortcutLabelByAppLaunchData(appLaunchData!!) 146 } 147 148 return null 149 } 150 151 private fun fetchShortcutLabelByAppLaunchData(appLaunchData: AppLaunchData): String? { 152 val intent = fetchIntentFromAppLaunchData(appLaunchData) ?: return null 153 val resolvedActivity = resolveSingleMatchingActivityFrom(intent) 154 155 return if (resolvedActivity == null) { 156 getIntentCategoryLabel(intent.selector?.categories?.iterator()?.next()) 157 } else resolvedActivity.loadLabel(userContext.packageManager).toString() 158 } 159 160 @SuppressLint("QueryPermissionsNeeded") 161 private fun resolveSingleMatchingActivityFrom(intent: Intent): ActivityInfo? { 162 val packageManager = userContext.packageManager 163 val resolvedActivity = 164 intent.resolveActivityInfo(packageManager, /* flags= */ MATCH_DEFAULT_ONLY) 165 ?: return null 166 167 val matchesMultipleActivities = 168 ResolverActivity::class.qualifiedName.equals(resolvedActivity.name) 169 170 return if (matchesMultipleActivities) { 171 return null 172 } else resolvedActivity 173 } 174 175 private fun getIntentCategoryLabel(category: String?): String? { 176 val categoryLabelRes = 177 when (category.toString()) { 178 Intent.CATEGORY_APP_BROWSER -> R.string.keyboard_shortcut_group_applications_browser 179 Intent.CATEGORY_APP_CONTACTS -> 180 R.string.keyboard_shortcut_group_applications_contacts 181 Intent.CATEGORY_APP_EMAIL -> R.string.keyboard_shortcut_group_applications_email 182 Intent.CATEGORY_APP_CALENDAR -> 183 R.string.keyboard_shortcut_group_applications_calendar 184 Intent.CATEGORY_APP_MAPS -> R.string.keyboard_shortcut_group_applications_maps 185 Intent.CATEGORY_APP_MUSIC -> R.string.keyboard_shortcut_group_applications_music 186 Intent.CATEGORY_APP_MESSAGING -> R.string.keyboard_shortcut_group_applications_sms 187 Intent.CATEGORY_APP_CALCULATOR -> 188 R.string.keyboard_shortcut_group_applications_calculator 189 else -> { 190 Log.w(TAG, ("No label for app category $category")) 191 null 192 } 193 } 194 195 return if (categoryLabelRes == null) { 196 return null 197 } else { 198 context.getString(categoryLabelRes) 199 } 200 } 201 202 private fun fetchIntentFromAppLaunchData(appLaunchData: AppLaunchData): Intent? { 203 return when (appLaunchData) { 204 is CategoryData -> 205 Intent.makeMainSelectorActivity( 206 /* selectorAction= */ ACTION_MAIN, 207 /* selectorCategory= */ appLaunchData.category, 208 ) 209 210 is RoleData -> getRoleLaunchIntent(appLaunchData.role) 211 is ComponentData -> 212 resolveComponentNameIntent( 213 packageName = appLaunchData.packageName, 214 className = appLaunchData.className, 215 ) 216 217 else -> null 218 } 219 } 220 221 private fun resolveComponentNameIntent(packageName: String, className: String): Intent? { 222 buildIntentFromComponentName(ComponentName(packageName, className))?.let { 223 return it 224 } 225 buildIntentFromComponentName( 226 ComponentName( 227 userContext.packageManager 228 .canonicalToCurrentPackageNames(arrayOf(packageName))[0], 229 className, 230 ) 231 ) 232 ?.let { 233 return it 234 } 235 return null 236 } 237 238 private fun buildIntentFromComponentName(componentName: ComponentName): Intent? { 239 try { 240 val flags = 241 MATCH_DIRECT_BOOT_UNAWARE or MATCH_DIRECT_BOOT_AWARE or MATCH_UNINSTALLED_PACKAGES 242 // attempt to retrieve activity info to see if a NameNotFoundException is thrown. 243 userContext.packageManager.getActivityInfo(componentName, flags) 244 } catch (e: NameNotFoundException) { 245 Log.w(TAG, "Unable to find activity info for componentName: $componentName") 246 return null 247 } 248 249 return Intent(ACTION_MAIN).apply { 250 addCategory(CATEGORY_LAUNCHER) 251 component = componentName 252 } 253 } 254 255 @SuppressLint("NonInjectedService") 256 private fun getRoleLaunchIntent(role: String): Intent? { 257 val packageManager = userContext.packageManager 258 val roleManager = userContext.getSystemService(RoleManager::class.java)!! 259 if (roleManager.isRoleAvailable(role)) { 260 roleManager.getDefaultApplication(role)?.let { rolePackage -> 261 packageManager.getLaunchIntentForPackage(rolePackage)?.let { 262 return it 263 } ?: Log.w(TAG, "No launch intent for role $role") 264 } ?: Log.w(TAG, "No default application for role $role, user= ${userContext.user}") 265 } else { 266 Log.w(TAG, "Role $role is not available.") 267 } 268 return null 269 } 270 271 private fun fetchShortcutCategoryTypeByGestureType( 272 @KeyGestureType keyGestureType: Int 273 ): ShortcutCategoryType? { 274 return inputGestureMaps.gestureToShortcutCategoryTypeMap[keyGestureType] 275 } 276 277 private companion object { 278 private const val TAG = "InputGestureDataUtils" 279 } 280 } 281