1 /* 2 * 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.accessibility.hearingaid; 18 19 import android.content.ComponentName; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.pm.ActivityInfo; 23 import android.content.pm.PackageManager; 24 import android.content.res.Resources; 25 import android.graphics.drawable.Drawable; 26 import android.util.Log; 27 28 import androidx.annotation.VisibleForTesting; 29 30 import com.google.common.collect.ImmutableList; 31 32 import java.util.ArrayList; 33 import java.util.Arrays; 34 import java.util.List; 35 36 /** 37 * Utility class for managing and parsing tool items related to hearing devices. 38 */ 39 public class HearingDevicesToolItemParser { 40 private static final String TAG = "HearingDevicesToolItemParser"; 41 private static final String SPLIT_DELIMITER = "/"; 42 private static final String RES_TYPE = "drawable"; 43 @VisibleForTesting 44 static final int MAX_NUM = 2; 45 46 /** 47 * Parses the string arrays to create a list of {@link ToolItem}. 48 * 49 * This method validates the structure of {@code toolNameArray} and {@code toolIconArray}. 50 * If {@code toolIconArray} is empty or mismatched in length with {@code toolNameArray}, the 51 * icon from {@link ActivityInfo#loadIcon(PackageManager)} will be used instead. 52 * 53 * @param context A valid context. 54 * @param toolNameArray An array of tool names in the format of {@link ComponentName}. 55 * @param toolIconArray An optional array of resource names for tool icons (can be empty). 56 * @return A list of {@link ToolItem} or an empty list if there are errors during parsing. 57 */ parseStringArray(Context context, String[] toolNameArray, String[] toolIconArray)58 public static ImmutableList<ToolItem> parseStringArray(Context context, String[] toolNameArray, 59 String[] toolIconArray) { 60 if (toolNameArray.length == 0) { 61 Log.i(TAG, "Empty hearing device related tool name in array."); 62 return ImmutableList.of(); 63 } 64 // For the performance concern, especially `getIdentifier` in `parseValidIcon`, we will 65 // limit the maximum number. 66 String[] nameArrayCpy = Arrays.copyOfRange(toolNameArray, 0, 67 Math.min(toolNameArray.length, MAX_NUM)); 68 String[] iconArrayCpy = Arrays.copyOfRange(toolIconArray, 0, 69 Math.min(toolIconArray.length, MAX_NUM)); 70 71 final PackageManager packageManager = context.getPackageManager(); 72 final ImmutableList.Builder<ToolItem> toolItemList = ImmutableList.builder(); 73 final List<ActivityInfo> activityInfoList = parseValidActivityInfo(context, nameArrayCpy); 74 final List<Drawable> iconList = parseValidIcon(context, iconArrayCpy); 75 final int size = activityInfoList.size(); 76 // Only use custom icon if provided icon's list size is equal to provided name's list size. 77 final boolean useCustomIcons = (size == iconList.size()); 78 79 for (int i = 0; i < size; i++) { 80 toolItemList.add(new ToolItem( 81 activityInfoList.get(i).loadLabel(packageManager).toString(), 82 useCustomIcons ? iconList.get(i) 83 : activityInfoList.get(i).loadIcon(packageManager), 84 new Intent(Intent.ACTION_MAIN).setComponent( 85 activityInfoList.get(i).getComponentName()), 86 useCustomIcons 87 )); 88 } 89 90 return toolItemList.build(); 91 } 92 parseValidActivityInfo(Context context, String[] toolNameArray)93 private static List<ActivityInfo> parseValidActivityInfo(Context context, 94 String[] toolNameArray) { 95 final PackageManager packageManager = context.getPackageManager(); 96 final List<ActivityInfo> activityInfoList = new ArrayList<>(); 97 for (String toolName : toolNameArray) { 98 String[] nameParts = toolName.split(SPLIT_DELIMITER); 99 if (nameParts.length == 2) { 100 ComponentName componentName = ComponentName.unflattenFromString(toolName); 101 try { 102 ActivityInfo activityInfo = packageManager.getActivityInfo( 103 componentName, /* flags= */ 0); 104 activityInfoList.add(activityInfo); 105 } catch (PackageManager.NameNotFoundException e) { 106 Log.e(TAG, "Unable to find hearing device related tool: " 107 + componentName.flattenToString()); 108 } 109 } else { 110 Log.e(TAG, "Malformed hearing device related tool name item in array: " 111 + toolName); 112 } 113 } 114 return activityInfoList; 115 } 116 parseValidIcon(Context context, String[] toolIconArray)117 private static List<Drawable> parseValidIcon(Context context, String[] toolIconArray) { 118 final List<Drawable> drawableList = new ArrayList<>(); 119 for (String icon : toolIconArray) { 120 int resId = context.getResources().getIdentifier(icon, RES_TYPE, 121 context.getPackageName()); 122 try { 123 drawableList.add(context.getDrawable(resId)); 124 } catch (Resources.NotFoundException e) { 125 Log.e(TAG, "Resource does not exist: " + icon); 126 } 127 } 128 return drawableList; 129 } 130 } 131