diff options
| author | 2024-05-29 18:01:08 +0000 | |
|---|---|---|
| committer | 2024-05-29 18:01:08 +0000 | |
| commit | b509a311a98c0f6b51edf3538e70ccbdb29f872b (patch) | |
| tree | b235ffe2eefb18f05ca39228b0a0952264608c27 | |
| parent | dbfe1f259524b0a4a81981fbf8443006a91819f5 (diff) | |
| parent | 739988e9bd68e207c8e355a07a34947b58fa5180 (diff) | |
Merge "Add Hearing devices related tools" into main
12 files changed, 589 insertions, 5 deletions
diff --git a/packages/SystemUI/aconfig/accessibility.aconfig b/packages/SystemUI/aconfig/accessibility.aconfig index 55edff6d9518..d201071620e4 100644 --- a/packages/SystemUI/aconfig/accessibility.aconfig +++ b/packages/SystemUI/aconfig/accessibility.aconfig @@ -81,3 +81,13 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "hearing_devices_dialog_related_tools" + namespace: "accessibility" + description: "Shows the related tools for hearing devices dialog." + bug: "341648471" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/packages/SystemUI/res/drawable/qs_hearing_devices_related_tools_background.xml b/packages/SystemUI/res/drawable/qs_hearing_devices_related_tools_background.xml new file mode 100644 index 000000000000..627b92b8a779 --- /dev/null +++ b/packages/SystemUI/res/drawable/qs_hearing_devices_related_tools_background.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2021 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<ripple + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + android:color="?android:attr/colorControlHighlight"> + <item> + <shape android:shape="rectangle"> + <solid android:color="@android:color/transparent"/> + <corners android:radius="@dimen/hearing_devices_preset_spinner_background_radius"/> + <stroke + android:width="1dp" + android:color="?androidprv:attr/textColorTertiary" /> + </shape> + </item> +</ripple> diff --git a/packages/SystemUI/res/layout/hearing_devices_tile_dialog.xml b/packages/SystemUI/res/layout/hearing_devices_tile_dialog.xml index 2bf6f80c32d0..4a7bef9f48b9 100644 --- a/packages/SystemUI/res/layout/hearing_devices_tile_dialog.xml +++ b/packages/SystemUI/res/layout/hearing_devices_tile_dialog.xml @@ -36,9 +36,8 @@ style="@style/BluetoothTileDialog.Device" android:layout_width="match_parent" android:layout_height="wrap_content" - android:minHeight="@dimen/hearing_devices_preset_spinner_height" android:layout_marginTop="@dimen/hearing_devices_layout_margin" - android:layout_marginBottom="@dimen/hearing_devices_layout_margin" + android:minHeight="@dimen/hearing_devices_preset_spinner_height" android:gravity="center_vertical" android:background="@drawable/hearing_devices_preset_spinner_background" android:popupBackground="@drawable/hearing_devices_preset_spinner_popup_background" @@ -54,9 +53,10 @@ android:visibility="gone"/> <androidx.constraintlayout.widget.Barrier - android:id="@+id/device_barrier" + android:id="@+id/device_function_barrier" android:layout_width="wrap_content" android:layout_height="wrap_content" + app:barrierAllowsGoneWidgets="false" app:barrierDirection="bottom" app:constraint_referenced_ids="device_list,preset_spinner" /> @@ -71,7 +71,8 @@ android:contentDescription="@string/accessibility_hearing_device_pair_new_device" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintTop_toBottomOf="@id/device_barrier" + app:layout_constraintTop_toBottomOf="@id/device_function_barrier" + app:layout_constraintBottom_toTopOf="@id/related_tools_scroll" android:drawableStart="@drawable/ic_add" android:drawablePadding="20dp" android:drawableTint="?android:attr/textColorPrimary" @@ -83,4 +84,32 @@ android:maxLines="1" android:ellipsize="end" /> + <androidx.constraintlayout.widget.Barrier + android:id="@+id/device_barrier" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + app:barrierAllowsGoneWidgets="false" + app:barrierDirection="bottom" + app:constraint_referenced_ids="device_function_barrier, pair_new_device_button" /> + + <HorizontalScrollView + android:id="@+id/related_tools_scroll" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/bluetooth_dialog_layout_margin" + android:layout_marginEnd="@dimen/bluetooth_dialog_layout_margin" + android:layout_marginTop="@dimen/hearing_devices_layout_margin" + android:scrollbars="none" + android:fillViewport="true" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toBottomOf="@id/preset_spinner"> + <LinearLayout + android:id="@+id/related_tools_container" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="horizontal"> + </LinearLayout> + </HorizontalScrollView> + </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/hearing_tool_item.xml b/packages/SystemUI/res/layout/hearing_tool_item.xml new file mode 100644 index 000000000000..84462d08d4a0 --- /dev/null +++ b/packages/SystemUI/res/layout/hearing_tool_item.xml @@ -0,0 +1,53 @@ +<!-- + Copyright (C) 2024 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/tool_view" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:gravity="center" + android:focusable="true" + android:clickable="true" + android:layout_weight="1"> + <FrameLayout + android:id="@+id/icon_frame" + android:layout_width="@dimen/hearing_devices_tool_icon_frame_width" + android:layout_height="@dimen/hearing_devices_tool_icon_frame_height" + android:background="@drawable/qs_hearing_devices_related_tools_background" + android:focusable="false" > + <ImageView + android:id="@+id/tool_icon" + android:layout_width="@dimen/hearing_devices_tool_icon_size" + android:layout_height="@dimen/hearing_devices_tool_icon_size" + android:layout_gravity="center" + android:scaleType="fitCenter" + android:focusable="false" /> + </FrameLayout> + <TextView + android:id="@+id/tool_name" + android:textDirection="locale" + android:textAlignment="center" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/hearing_devices_layout_margin" + android:ellipsize="end" + android:maxLines="1" + android:textSize="12sp" + android:textAppearance="@style/TextAppearance.Dialog.Body.Message" + android:focusable="false" /> +</LinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 638785402055..fb883640c9a9 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -125,6 +125,20 @@ <!-- Use collapsed layout for media player in landscape QQS --> <bool name="config_quickSettingsMediaLandscapeCollapsed">true</bool> + <!-- For hearing devices related tool list. Need to be in ComponentName format (package/class). + Should be activity to be launched. + Already contains tool that holds intent: "com.android.settings.action.live_caption". + Maximum number is 3. --> + <string-array name="config_quickSettingsHearingDevicesRelatedToolName" translatable="false"> + </string-array> + + <!-- The drawable resource names. If provided, it will replace the corresponding icons in + config_quickSettingsHearingDevicesRelatedToolName. Can be empty to use original icons. + Already contains tool that holds intent: "com.android.settings.action.live_caption". + Maximum number is 3. --> + <string-array name="config_quickSettingsHearingDevicesRelatedToolIcon" translatable="false"> + </string-array> + <!-- Show indicator for Wifi on but not connected. --> <bool name="config_showWifiIndicatorWhenEnabled">false</bool> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 7d7a5d4dbf14..edd3d77555f7 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1778,6 +1778,9 @@ <dimen name="hearing_devices_preset_spinner_text_padding_vertical">15dp</dimen> <dimen name="hearing_devices_preset_spinner_arrow_size">24dp</dimen> <dimen name="hearing_devices_preset_spinner_background_radius">28dp</dimen> + <dimen name="hearing_devices_tool_icon_frame_width">94dp</dimen> + <dimen name="hearing_devices_tool_icon_frame_height">64dp</dimen> + <dimen name="hearing_devices_tool_icon_size">28dp</dimen> <!-- Height percentage of the parent container occupied by the communal view --> <item name="communal_source_height_percentage" format="float" type="dimen">0.80</item> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 6f2806d80ef3..c038a8207d43 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -917,6 +917,8 @@ <string name="hearing_devices_presets_error">Couldn\'t update preset</string> <!-- QuickSettings: Title for hearing aids presets. Preset is a set of hearing aid settings. User can apply different settings in different environments (e.g. Outdoor, Restaurant, Home) [CHAR LIMIT=40]--> <string name="hearing_devices_preset_label">Preset</string> + <!-- QuickSettings: Tool name for hearing devices dialog related tools [CHAR LIMIT=40]--> + <string name="live_caption_title">Live Caption</string> <!--- Title of dialog triggered if the microphone is disabled but an app tried to access it. [CHAR LIMIT=150] --> <string name="sensor_privacy_start_use_mic_dialog_title">Unblock device microphone?</string> diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java index 28dd2338ff2b..961d6aa1b821 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java @@ -25,10 +25,14 @@ import android.bluetooth.BluetoothHapClient; import android.bluetooth.BluetoothHapPresetInfo; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.res.Resources; import android.media.AudioManager; import android.os.Bundle; import android.os.Handler; import android.provider.Settings; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.View.Visibility; @@ -36,7 +40,10 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.Button; +import android.widget.ImageView; +import android.widget.LinearLayout; import android.widget.Spinner; +import android.widget.TextView; import android.widget.Toast; import androidx.annotation.NonNull; @@ -69,6 +76,7 @@ import dagger.assisted.Assisted; import dagger.assisted.AssistedFactory; import dagger.assisted.AssistedInject; +import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; @@ -78,12 +86,15 @@ import java.util.stream.Collectors; */ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, HearingDeviceItemCallback, BluetoothCallback { - + private static final String TAG = "HearingDevicesDialogDelegate"; @VisibleForTesting static final String ACTION_BLUETOOTH_DEVICE_DETAILS = "com.android.settings.BLUETOOTH_DEVICE_DETAIL_SETTINGS"; private static final String EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":settings:show_fragment_args"; private static final String KEY_BLUETOOTH_ADDRESS = "device_address"; + @VisibleForTesting + static final Intent LIVE_CAPTION_INTENT = new Intent( + "com.android.settings.action.live_caption"); private final SystemUIDialog.Factory mSystemUIDialogFactory; private final DialogTransitionAnimator mDialogTransitionAnimator; private final ActivityStarter mActivityStarter; @@ -102,6 +113,7 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, private Spinner mPresetSpinner; private ArrayAdapter<String> mPresetInfoAdapter; private Button mPairButton; + private LinearLayout mRelatedToolsContainer; private final HearingDevicesPresetsController.PresetCallback mPresetCallback = new HearingDevicesPresetsController.PresetCallback() { @Override @@ -253,10 +265,14 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, mPairButton = dialog.requireViewById(R.id.pair_new_device_button); mDeviceList = dialog.requireViewById(R.id.device_list); mPresetSpinner = dialog.requireViewById(R.id.preset_spinner); + mRelatedToolsContainer = dialog.requireViewById(R.id.related_tools_container); setupDeviceListView(dialog); setupPresetSpinner(dialog); setupPairNewDeviceButton(dialog, mShowPairNewDevice ? VISIBLE : GONE); + if (com.android.systemui.Flags.hearingDevicesDialogRelatedTools()) { + setupRelatedToolsView(dialog); + } } @Override @@ -351,6 +367,34 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, } } + private void setupRelatedToolsView(SystemUIDialog dialog) { + final Context context = dialog.getContext(); + final List<ToolItem> toolItemList = new ArrayList<>(); + final String[] toolNameArray; + final String[] toolIconArray; + + ToolItem preInstalledItem = getLiveCaption(context); + if (preInstalledItem != null) { + toolItemList.add(preInstalledItem); + } + try { + toolNameArray = context.getResources().getStringArray( + R.array.config_quickSettingsHearingDevicesRelatedToolName); + toolIconArray = context.getResources().getStringArray( + R.array.config_quickSettingsHearingDevicesRelatedToolIcon); + toolItemList.addAll( + HearingDevicesToolItemParser.parseStringArray(context, toolNameArray, + toolIconArray)); + } catch (Resources.NotFoundException e) { + Log.i(TAG, "No hearing devices related tool config resource"); + } + final int listSize = toolItemList.size(); + for (int i = 0; i < listSize; i++) { + View view = createHearingToolView(context, toolItemList.get(i)); + mRelatedToolsContainer.addView(view); + } + } + private void refreshPresetInfoAdapter(List<BluetoothHapPresetInfo> presetInfos, int activePresetIndex) { mPresetInfoAdapter.clear(); @@ -400,6 +444,40 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, return null; } + @NonNull + private View createHearingToolView(Context context, ToolItem item) { + View view = LayoutInflater.from(context).inflate(R.layout.hearing_tool_item, + mRelatedToolsContainer, false); + ImageView icon = view.requireViewById(R.id.tool_icon); + TextView text = view.requireViewById(R.id.tool_name); + view.setContentDescription(item.getToolName()); + icon.setImageDrawable(item.getToolIcon()); + text.setText(item.getToolName()); + Intent intent = item.getToolIntent(); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + view.setOnClickListener( + v -> { + dismissDialogIfExists(); + mActivityStarter.postStartActivityDismissingKeyguard(intent, /* delay= */ 0, + mDialogTransitionAnimator.createActivityTransitionController(view)); + }); + return view; + } + + private ToolItem getLiveCaption(Context context) { + final PackageManager packageManager = context.getPackageManager(); + LIVE_CAPTION_INTENT.setPackage(packageManager.getSystemCaptionsServicePackageName()); + final List<ResolveInfo> resolved = packageManager.queryIntentActivities(LIVE_CAPTION_INTENT, + /* flags= */ 0); + if (!resolved.isEmpty()) { + return new ToolItem(context.getString(R.string.live_caption_title), + context.getDrawable(R.drawable.ic_volume_odi_captions), + LIVE_CAPTION_INTENT); + } + + return null; + } + private void dismissDialogIfExists() { if (mDialog != null) { mDialog.dismiss(); diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesToolItemParser.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesToolItemParser.java new file mode 100644 index 000000000000..2006726e6847 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesToolItemParser.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.accessibility.hearingaid; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.util.Log; + +import androidx.annotation.VisibleForTesting; + +import com.google.common.collect.ImmutableList; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Utility class for managing and parsing tool items related to hearing devices. + */ +public class HearingDevicesToolItemParser { + private static final String TAG = "HearingDevicesToolItemParser"; + private static final String SPLIT_DELIMITER = "/"; + private static final String RES_TYPE = "drawable"; + @VisibleForTesting + static final int MAX_NUM = 3; + + /** + * Parses the string arrays to create a list of {@link ToolItem}. + * + * This method validates the structure of {@code toolNameArray} and {@code toolIconArray}. + * If {@code toolIconArray} is empty or mismatched in length with {@code toolNameArray}, the + * icon from {@link ActivityInfo#loadIcon(PackageManager)} will be used instead. + * + * @param context A valid context. + * @param toolNameArray An array of tool names in the format of {@link ComponentName}. + * @param toolIconArray An optional array of resource names for tool icons (can be empty). + * @return A list of {@link ToolItem} or an empty list if there are errors during parsing. + */ + public static ImmutableList<ToolItem> parseStringArray(Context context, String[] toolNameArray, + String[] toolIconArray) { + if (toolNameArray.length == 0) { + Log.i(TAG, "Empty hearing device related tool name in array."); + return ImmutableList.of(); + } + // For the performance concern, especially `getIdentifier` in `parseValidIcon`, we will + // limit the maximum number. + String[] nameArrayCpy = Arrays.copyOfRange(toolNameArray, 0, + Math.min(toolNameArray.length, MAX_NUM)); + String[] iconArrayCpy = Arrays.copyOfRange(toolIconArray, 0, + Math.min(toolIconArray.length, MAX_NUM)); + + final PackageManager packageManager = context.getPackageManager(); + final ImmutableList.Builder<ToolItem> toolItemList = ImmutableList.builder(); + final List<ActivityInfo> activityInfoList = parseValidActivityInfo(context, nameArrayCpy); + final List<Drawable> iconList = parseValidIcon(context, iconArrayCpy); + final int size = activityInfoList.size(); + // Only use custom icon if provided icon's list size is equal to provided name's list size. + final boolean useCustomIcons = (size == iconList.size()); + + for (int i = 0; i < size; i++) { + toolItemList.add(new ToolItem( + activityInfoList.get(i).loadLabel(packageManager).toString(), + useCustomIcons ? iconList.get(i) + : activityInfoList.get(i).loadIcon(packageManager), + new Intent(Intent.ACTION_MAIN).setComponent( + activityInfoList.get(i).getComponentName()) + )); + } + + return toolItemList.build(); + } + + private static List<ActivityInfo> parseValidActivityInfo(Context context, + String[] toolNameArray) { + final PackageManager packageManager = context.getPackageManager(); + final List<ActivityInfo> activityInfoList = new ArrayList<>(); + for (String toolName : toolNameArray) { + String[] nameParts = toolName.split(SPLIT_DELIMITER); + if (nameParts.length == 2) { + ComponentName componentName = ComponentName.unflattenFromString(toolName); + try { + ActivityInfo activityInfo = packageManager.getActivityInfo( + componentName, /* flags= */ 0); + activityInfoList.add(activityInfo); + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Unable to find hearing device related tool: " + + componentName.flattenToString()); + } + } else { + Log.e(TAG, "Malformed hearing device related tool name item in array: " + + toolName); + } + } + return activityInfoList; + } + + private static List<Drawable> parseValidIcon(Context context, String[] toolIconArray) { + final List<Drawable> drawableList = new ArrayList<>(); + for (String icon : toolIconArray) { + int resId = context.getResources().getIdentifier(icon, RES_TYPE, + context.getPackageName()); + try { + drawableList.add(context.getDrawable(resId)); + } catch (Resources.NotFoundException e) { + Log.e(TAG, "Resource does not exist: " + icon); + } + } + return drawableList; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/ToolItem.kt b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/ToolItem.kt new file mode 100644 index 000000000000..66bb2b5e2328 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/ToolItem.kt @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.accessibility.hearingaid + +import android.content.Intent +import android.graphics.drawable.Drawable + +data class ToolItem( + var toolName: String = "", + var toolIcon: Drawable, + var toolIntent: Intent, +) diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java index 8895a5e1a97c..0db0de2bcd7e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java @@ -16,6 +16,7 @@ package com.android.systemui.accessibility.hearingaid; +import static com.android.systemui.accessibility.hearingaid.HearingDevicesDialogDelegate.LIVE_CAPTION_INTENT; import static com.android.systemui.statusbar.phone.SystemUIDialog.DEFAULT_DISMISS_ON_DEVICE_LOCK; import static com.google.common.truth.Truth.assertThat; @@ -24,16 +25,24 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.content.ComponentName; import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.graphics.drawable.Drawable; import android.media.AudioManager; import android.os.Handler; +import android.platform.test.annotations.EnableFlags; import android.provider.Settings; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.View; +import android.widget.LinearLayout; import androidx.test.filters.SmallTest; @@ -44,6 +53,7 @@ import com.android.settingslib.bluetooth.HapClientProfile; import com.android.settingslib.bluetooth.LocalBluetoothAdapter; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; +import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.DialogTransitionAnimator; import com.android.systemui.bluetooth.qsdialog.DeviceItem; @@ -54,6 +64,7 @@ import com.android.systemui.res.R; import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.statusbar.phone.SystemUIDialogManager; +import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -75,6 +86,10 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase { public MockitoRule mockito = MockitoJUnit.rule(); private static final String DEVICE_ADDRESS = "AA:BB:CC:DD:EE:FF"; + private static final String TEST_PKG = "pkg"; + private static final String TEST_CLS = "cls"; + private static final ComponentName TEST_COMPONENT = new ComponentName(TEST_PKG, TEST_CLS); + private static final String TEST_LABEL = "label"; @Mock private SystemUIDialog.Factory mSystemUIDialogFactory; @@ -104,6 +119,12 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase { private CachedBluetoothDevice mCachedDevice; @Mock private DeviceItem mHearingDeviceItem; + @Mock + private PackageManager mPackageManager; + @Mock + private ActivityInfo mActivityInfo; + @Mock + private Drawable mDrawable; private SystemUIDialog mDialog; private HearingDevicesDialogDelegate mDialogDelegate; private TestableLooper mTestableLooper; @@ -122,6 +143,7 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase { when(mSysUiState.setFlag(anyLong(), anyBoolean())).thenReturn(mSysUiState); when(mCachedDevice.getAddress()).thenReturn(DEVICE_ADDRESS); when(mHearingDeviceItem.getCachedBluetoothDevice()).thenReturn(mCachedDevice); + mContext.setMockPackageManager(mPackageManager); setUpPairNewDeviceDialog(); @@ -170,6 +192,45 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase { verify(mCachedDevice).disconnect(); } + @Test + @EnableFlags(Flags.FLAG_HEARING_DEVICES_DIALOG_RELATED_TOOLS) + public void showDialog_hasLiveCaption_noRelatedToolsInConfig_showOneRelatedTool() { + when(mPackageManager.queryIntentActivities( + eq(LIVE_CAPTION_INTENT), anyInt())).thenReturn( + List.of(new ResolveInfo())); + mContext.getOrCreateTestableResources().addOverride( + R.array.config_quickSettingsHearingDevicesRelatedToolName, new String[]{}); + + setUpPairNewDeviceDialog(); + mDialog.show(); + + LinearLayout relatedToolsView = (LinearLayout) getRelatedToolsView(mDialog); + assertThat(relatedToolsView.getChildCount()).isEqualTo(1); + } + + @Test + @EnableFlags(Flags.FLAG_HEARING_DEVICES_DIALOG_RELATED_TOOLS) + public void showDialog_hasLiveCaption_oneRelatedToolInConfig_showTwoRelatedTools() + throws PackageManager.NameNotFoundException { + when(mPackageManager.queryIntentActivities( + eq(LIVE_CAPTION_INTENT), anyInt())).thenReturn( + List.of(new ResolveInfo())); + mContext.getOrCreateTestableResources().addOverride( + R.array.config_quickSettingsHearingDevicesRelatedToolName, + new String[]{TEST_PKG + "/" + TEST_CLS}); + when(mPackageManager.getActivityInfo(eq(TEST_COMPONENT), anyInt())).thenReturn( + mActivityInfo); + when(mActivityInfo.loadLabel(mPackageManager)).thenReturn(TEST_LABEL); + when(mActivityInfo.loadIcon(mPackageManager)).thenReturn(mDrawable); + when(mActivityInfo.getComponentName()).thenReturn(TEST_COMPONENT); + + setUpPairNewDeviceDialog(); + mDialog.show(); + + LinearLayout relatedToolsView = (LinearLayout) getRelatedToolsView(mDialog); + assertThat(relatedToolsView.getChildCount()).isEqualTo(2); + } + private void setUpPairNewDeviceDialog() { mDialogDelegate = new HearingDevicesDialogDelegate( mContext, @@ -219,4 +280,18 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase { private View getPairNewDeviceButton(SystemUIDialog dialog) { return dialog.requireViewById(R.id.pair_new_device_button); } + + private View getRelatedToolsView(SystemUIDialog dialog) { + return dialog.requireViewById(R.id.related_tools_container); + } + + @After + public void reset() { + if (mDialogDelegate != null) { + mDialogDelegate = null; + } + if (mDialog != null) { + mDialog.dismiss(); + } + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesToolItemParserTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesToolItemParserTest.java new file mode 100644 index 000000000000..717292378913 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesToolItemParserTest.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.accessibility.hearingaid; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +import static java.util.Collections.emptyList; + +import android.content.ComponentName; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.graphics.drawable.Drawable; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.List; + +/** + * Tests for {@link HearingDevicesToolItemParser}. + */ +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +@SmallTest +public class HearingDevicesToolItemParserTest extends SysuiTestCase { + @Rule + public MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private PackageManager mPackageManager; + @Mock + private ActivityInfo mActivityInfo; + @Mock + private Drawable mDrawable; + private static final String TEST_PKG = "pkg"; + private static final String TEST_CLS = "cls"; + private static final ComponentName TEST_COMPONENT = new ComponentName(TEST_PKG, TEST_CLS); + private static final String TEST_NO_EXIST_PKG = "NoPkg"; + private static final String TEST_NO_EXIST_CLS = "NoCls"; + private static final ComponentName TEST_NO_EXIST_COMPONENT = new ComponentName( + TEST_NO_EXIST_PKG, TEST_NO_EXIST_CLS); + + private static final String TEST_LABEL = "label"; + + @Before + public void setUp() throws PackageManager.NameNotFoundException { + mContext.setMockPackageManager(mPackageManager); + + when(mPackageManager.getActivityInfo(eq(TEST_COMPONENT), anyInt())).thenReturn( + mActivityInfo); + when(mPackageManager.getActivityInfo(eq(TEST_NO_EXIST_COMPONENT), anyInt())).thenThrow( + new PackageManager.NameNotFoundException()); + when(mActivityInfo.loadLabel(mPackageManager)).thenReturn(TEST_LABEL); + when(mActivityInfo.loadIcon(mPackageManager)).thenReturn(mDrawable); + when(mActivityInfo.getComponentName()).thenReturn(TEST_COMPONENT); + } + + @Test + public void parseStringArray_noString_emptyResult() { + assertThat(HearingDevicesToolItemParser.parseStringArray(mContext, new String[]{}, + new String[]{})).isEqualTo(emptyList()); + } + + @Test + public void parseStringArray_oneToolName_oneExpectedToolItem() { + String[] toolName = new String[]{TEST_PKG + "/" + TEST_CLS}; + + List<ToolItem> toolItemList = HearingDevicesToolItemParser.parseStringArray(mContext, + toolName, new String[]{}); + + assertThat(toolItemList.size()).isEqualTo(1); + assertThat(toolItemList.get(0).getToolName()).isEqualTo(TEST_LABEL); + assertThat(toolItemList.get(0).getToolIntent().getComponent()).isEqualTo(TEST_COMPONENT); + } + + @Test + public void parseStringArray_fourToolName_maxThreeToolItem() { + String componentNameString = TEST_PKG + "/" + TEST_CLS; + String[] fourToolName = + new String[]{componentNameString, componentNameString, componentNameString, + componentNameString}; + + List<ToolItem> toolItemList = HearingDevicesToolItemParser.parseStringArray(mContext, + fourToolName, new String[]{}); + assertThat(toolItemList.size()).isEqualTo(HearingDevicesToolItemParser.MAX_NUM); + } + + @Test + public void parseStringArray_oneWrongFormatToolName_noToolItem() { + String[] wrongFormatToolName = new String[]{TEST_PKG}; + + List<ToolItem> toolItemList = HearingDevicesToolItemParser.parseStringArray(mContext, + wrongFormatToolName, new String[]{}); + assertThat(toolItemList.size()).isEqualTo(0); + } + + @Test + public void parseStringArray_oneNotExistToolName_noToolItem() { + String[] notExistToolName = new String[]{TEST_NO_EXIST_PKG + "/" + TEST_NO_EXIST_CLS}; + + List<ToolItem> toolItemList = HearingDevicesToolItemParser.parseStringArray(mContext, + notExistToolName, new String[]{}); + assertThat(toolItemList.size()).isEqualTo(0); + } +} |