From f1af8c529295c60e516b2cc1d1ac812b8d6047ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Hern=C3=A1ndez?= Date: Thu, 15 Jun 2023 18:37:52 +0200 Subject: [PATCH 01/10] Settings: don't try to allow NLSes with too-long component names * NotificationAccessConfirmationActivity (triggered through CompanionDeviceManager) -> Don't show the dialog, bail out early similarly to other invalid inputs. * NotificationAccessSettings (from Special App Access) -> No changes, but use the canonical constant now. * NotificationAccessDetails -> Disable the toggle, unless the NLS was previously approved (in which case it can still be removed). Fixes: 260570119 Fixes: 286043036 Test: atest + manually Change-Id: Ifc048311746c027e3683cdcf65f1079d04cf7c56 Merged-In: Ifc048311746c027e3683cdcf65f1079d04cf7c56 --- .../notificationaccess/NotificationAccessDetails.java | 3 +++ .../notification/NotificationAccessConfirmationActivity.java | 4 +++- .../settings/notification/NotificationAccessSettings.java | 5 ++--- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessDetails.java b/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessDetails.java index 6a4c22efc7a..c77c2b9100b 100644 --- a/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessDetails.java +++ b/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessDetails.java @@ -181,7 +181,10 @@ public class NotificationAccessDetails extends AppInfoBase { public void updatePreference(SwitchPreference preference) { final CharSequence label = mPackageInfo.applicationInfo.loadLabel(mPm); + final boolean isAllowedCn = mComponentName.flattenToShortString().length() + <= NotificationManager.MAX_SERVICE_COMPONENT_NAME_LENGTH; preference.setChecked(isServiceEnabled(mComponentName)); + preference.setEnabled(preference.isChecked() || isAllowedCn); preference.setOnPreferenceChangeListener((p, newValue) -> { final boolean access = (Boolean) newValue; if (!access) { diff --git a/src/com/android/settings/notification/NotificationAccessConfirmationActivity.java b/src/com/android/settings/notification/NotificationAccessConfirmationActivity.java index dfe6df2a5ca..a6b565ae6ba 100644 --- a/src/com/android/settings/notification/NotificationAccessConfirmationActivity.java +++ b/src/com/android/settings/notification/NotificationAccessConfirmationActivity.java @@ -67,7 +67,9 @@ public class NotificationAccessConfirmationActivity extends Activity mUserId = getIntent().getIntExtra(EXTRA_USER_ID, UserHandle.USER_NULL); CharSequence mAppLabel; - if (mComponentName == null || mComponentName.getPackageName() == null) { + if (mComponentName == null || mComponentName.getPackageName() == null + || mComponentName.flattenToString().length() + > NotificationManager.MAX_SERVICE_COMPONENT_NAME_LENGTH) { finish(); return; } diff --git a/src/com/android/settings/notification/NotificationAccessSettings.java b/src/com/android/settings/notification/NotificationAccessSettings.java index 79e75bd822c..20e019eb88e 100644 --- a/src/com/android/settings/notification/NotificationAccessSettings.java +++ b/src/com/android/settings/notification/NotificationAccessSettings.java @@ -58,8 +58,6 @@ import java.util.List; public class NotificationAccessSettings extends EmptyTextSettings { private static final String TAG = "NotifAccessSettings"; - private static final int MAX_CN_LENGTH = 500; - private static final ManagedServiceSettings.Config CONFIG = new ManagedServiceSettings.Config.Builder() .setTag(TAG) @@ -135,7 +133,8 @@ public class NotificationAccessSettings extends EmptyTextSettings { for (ServiceInfo service : services) { final ComponentName cn = new ComponentName(service.packageName, service.name); boolean isAllowed = mNm.isNotificationListenerAccessGranted(cn); - if (!isAllowed && cn.flattenToString().length() > MAX_CN_LENGTH) { + if (!isAllowed && cn.flattenToString().length() + > NotificationManager.MAX_SERVICE_COMPONENT_NAME_LENGTH) { continue; } From 2f393303c91db86c0aaea6df468ad87f5eff9227 Mon Sep 17 00:00:00 2001 From: Michael Mikhail Date: Wed, 21 Jun 2023 16:58:20 +0000 Subject: [PATCH 02/10] Add content description to volume title in settings. Adds content description that is announced by talkback when a11y focus is on volume preference. This improves talkback announcement when view changes. Fixes: 285529113 Bug: 285455826 Fixes: 285487766 Test: atest VolumeSeekBarPreferenceControllerTest Test: atest VolumeSeekBarPreferenceTest Change-Id: Ibe80b4b1d489dc058df1cc79c96b034d5ddc6e56 --- res/values/strings.xml | 12 ++++++ .../MediaVolumePreferenceController.java | 13 ++++++ ...otificationVolumePreferenceController.java | 43 ++++++++++++------- ...odeAffectedVolumePreferenceController.java | 30 ++++++++++++- ...eparateRingVolumePreferenceController.java | 1 + .../notification/VolumeSeekBarPreference.java | 41 +++++++++++++++++- .../VolumeSeekBarPreferenceController.java | 2 + ...VolumeSeekBarPreferenceControllerTest.java | 12 ++++-- .../VolumeSeekBarPreferenceTest.java | 33 ++++++++++++++ 9 files changed, 164 insertions(+), 23 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 04d9f17de4b..3a463430aca 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -7182,6 +7182,18 @@ Notification volume + + Ringer silent + + + Ringer vibrate + + + Notification volume muted, notifications will vibrate + + + %1$s muted + Unavailable because ring is muted diff --git a/src/com/android/settings/notification/MediaVolumePreferenceController.java b/src/com/android/settings/notification/MediaVolumePreferenceController.java index e40a2b4af98..79df55a0048 100644 --- a/src/com/android/settings/notification/MediaVolumePreferenceController.java +++ b/src/com/android/settings/notification/MediaVolumePreferenceController.java @@ -52,6 +52,7 @@ public class MediaVolumePreferenceController extends VolumeSeekBarPreferenceCont public MediaVolumePreferenceController(Context context) { super(context, KEY_MEDIA_VOLUME); + mVolumePreferenceListener = this::updateContentDescription; } @Override @@ -109,6 +110,18 @@ public class MediaVolumePreferenceController extends VolumeSeekBarPreferenceCont return false; } + private void updateContentDescription() { + if (mPreference != null) { + if (mPreference.isMuted()) { + mPreference.updateContentDescription( + mContext.getString(R.string.volume_content_description_silent_mode, + mPreference.getTitle())); + } else { + mPreference.updateContentDescription(mPreference.getTitle()); + } + } + } + @Override public SliceAction getSliceEndItem(Context context) { if (!isSupportEndItem()) { diff --git a/src/com/android/settings/notification/NotificationVolumePreferenceController.java b/src/com/android/settings/notification/NotificationVolumePreferenceController.java index cf8a33f765c..02dc96a2804 100644 --- a/src/com/android/settings/notification/NotificationVolumePreferenceController.java +++ b/src/com/android/settings/notification/NotificationVolumePreferenceController.java @@ -75,6 +75,7 @@ public class NotificationVolumePreferenceController extends updateEffectsSuppressor(); selectPreferenceIconState(); + updateContentDescription(); updateEnabledState(); } @@ -120,23 +121,32 @@ public class NotificationVolumePreferenceController extends } @Override - protected void selectPreferenceIconState() { + protected int getEffectiveRingerMode() { + if (mVibrator == null && mRingerMode == AudioManager.RINGER_MODE_VIBRATE) { + return AudioManager.RINGER_MODE_SILENT; + } else if (mRingerMode == AudioManager.RINGER_MODE_NORMAL) { + if (mHelper.getStreamVolume(AudioManager.STREAM_NOTIFICATION) == 0) { + // Ring is in normal, but notification is in silent. + return AudioManager.RINGER_MODE_SILENT; + } + } + return mRingerMode; + } + + @Override + protected void updateContentDescription() { if (mPreference != null) { - if (mVibrator != null && mRingerMode == AudioManager.RINGER_MODE_VIBRATE) { - mMuteIcon = mVibrateIconId; - mPreference.showIcon(mVibrateIconId); - } else if (mRingerMode == AudioManager.RINGER_MODE_SILENT - || mVibrator == null && mRingerMode == AudioManager.RINGER_MODE_VIBRATE) { - mMuteIcon = mSilentIconId; - mPreference.showIcon(mSilentIconId); - } else { // ringmode normal: could be that we are still silent - if (mHelper.getStreamVolume(AudioManager.STREAM_NOTIFICATION) == 0) { - // ring is in normal, but notification is in silent - mMuteIcon = mSilentIconId; - mPreference.showIcon(mSilentIconId); - } else { - mPreference.showIcon(mNormalIconId); - } + int ringerMode = getEffectiveRingerMode(); + if (ringerMode == AudioManager.RINGER_MODE_VIBRATE) { + mPreference.updateContentDescription( + mContext.getString( + R.string.notification_volume_content_description_vibrate_mode)); + } else if (ringerMode == AudioManager.RINGER_MODE_SILENT) { + mPreference.updateContentDescription( + mContext.getString(R.string.volume_content_description_silent_mode, + mPreference.getTitle())); + } else { + mPreference.updateContentDescription(mPreference.getTitle()); } } } @@ -169,6 +179,7 @@ public class NotificationVolumePreferenceController extends break; case NOTIFICATION_VOLUME_CHANGED: selectPreferenceIconState(); + updateContentDescription(); updateEnabledState(); break; } diff --git a/src/com/android/settings/notification/RingerModeAffectedVolumePreferenceController.java b/src/com/android/settings/notification/RingerModeAffectedVolumePreferenceController.java index 36877707257..ab65f8f5a9d 100644 --- a/src/com/android/settings/notification/RingerModeAffectedVolumePreferenceController.java +++ b/src/com/android/settings/notification/RingerModeAffectedVolumePreferenceController.java @@ -26,6 +26,7 @@ import android.os.Vibrator; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; +import com.android.settings.R; import java.util.Objects; @@ -54,6 +55,7 @@ public abstract class RingerModeAffectedVolumePreferenceController extends if (mVibrator != null && !mVibrator.hasVibrator()) { mVibrator = null; } + mVolumePreferenceListener = this::updateContentDescription; } protected void updateEffectsSuppressor() { @@ -123,6 +125,7 @@ public abstract class RingerModeAffectedVolumePreferenceController extends } mRingerMode = ringerMode; selectPreferenceIconState(); + updateContentDescription(); return true; } @@ -131,10 +134,11 @@ public abstract class RingerModeAffectedVolumePreferenceController extends */ protected void selectPreferenceIconState() { if (mPreference != null) { - if (mRingerMode == AudioManager.RINGER_MODE_NORMAL) { + int ringerMode = getEffectiveRingerMode(); + if (ringerMode == AudioManager.RINGER_MODE_NORMAL) { mPreference.showIcon(mNormalIconId); } else { - if (mRingerMode == AudioManager.RINGER_MODE_VIBRATE && mVibrator != null) { + if (ringerMode == AudioManager.RINGER_MODE_VIBRATE) { mMuteIcon = mVibrateIconId; } else { mMuteIcon = mSilentIconId; @@ -144,6 +148,28 @@ public abstract class RingerModeAffectedVolumePreferenceController extends } } + protected int getEffectiveRingerMode() { + if (mVibrator == null && mRingerMode == AudioManager.RINGER_MODE_VIBRATE) { + return AudioManager.RINGER_MODE_SILENT; + } + return mRingerMode; + } + + protected void updateContentDescription() { + if (mPreference != null) { + int ringerMode = getEffectiveRingerMode(); + if (ringerMode == AudioManager.RINGER_MODE_VIBRATE) { + mPreference.updateContentDescription( + mContext.getString(R.string.ringer_content_description_vibrate_mode)); + } else if (ringerMode == AudioManager.RINGER_MODE_SILENT) { + mPreference.updateContentDescription( + mContext.getString(R.string.ringer_content_description_silent_mode)); + } else { + mPreference.updateContentDescription(mPreference.getTitle()); + } + } + } + protected abstract boolean hintsMatch(int hints); } diff --git a/src/com/android/settings/notification/SeparateRingVolumePreferenceController.java b/src/com/android/settings/notification/SeparateRingVolumePreferenceController.java index b8a99085f6d..91926e3c977 100644 --- a/src/com/android/settings/notification/SeparateRingVolumePreferenceController.java +++ b/src/com/android/settings/notification/SeparateRingVolumePreferenceController.java @@ -65,6 +65,7 @@ public class SeparateRingVolumePreferenceController extends mReceiver.register(true); updateEffectsSuppressor(); selectPreferenceIconState(); + updateContentDescription(); if (mPreference != null) { mPreference.setVisible(getAvailabilityStatus() == AVAILABLE); diff --git a/src/com/android/settings/notification/VolumeSeekBarPreference.java b/src/com/android/settings/notification/VolumeSeekBarPreference.java index 14955c426e1..f3c1471be4f 100644 --- a/src/com/android/settings/notification/VolumeSeekBarPreference.java +++ b/src/com/android/settings/notification/VolumeSeekBarPreference.java @@ -47,10 +47,13 @@ public class VolumeSeekBarPreference extends SeekBarPreference { protected SeekBar mSeekBar; private int mStream; - private SeekBarVolumizer mVolumizer; + @VisibleForTesting + SeekBarVolumizer mVolumizer; private Callback mCallback; + private Listener mListener; private ImageView mIconView; private TextView mSuppressionTextView; + private TextView mTitle; private String mSuppressionText; private boolean mMuted; private boolean mZenMuted; @@ -98,6 +101,10 @@ public class VolumeSeekBarPreference extends SeekBarPreference { mCallback = callback; } + public void setListener(Listener listener) { + mListener = listener; + } + public void onActivityResume() { if (mStopped) { init(); @@ -118,6 +125,7 @@ public class VolumeSeekBarPreference extends SeekBarPreference { mSeekBar = (SeekBar) view.findViewById(com.android.internal.R.id.seekbar); mIconView = (ImageView) view.findViewById(com.android.internal.R.id.icon); mSuppressionTextView = (TextView) view.findViewById(R.id.suppression_text); + mTitle = (TextView) view.findViewById(com.android.internal.R.id.title); init(); } @@ -142,6 +150,9 @@ public class VolumeSeekBarPreference extends SeekBarPreference { mMuted = muted; mZenMuted = zenMuted; updateIconView(); + if (mListener != null) { + mListener.onUpdateMuteState(); + } } @Override public void onStartTrackingTouch(SeekBarVolumizer sbv) { @@ -165,6 +176,9 @@ public class VolumeSeekBarPreference extends SeekBarPreference { mVolumizer.setSeekBar(mSeekBar); updateIconView(); updateSuppressionText(); + if (mListener != null) { + mListener.onUpdateMuteState(); + } if (!isEnabled()) { mSeekBar.setEnabled(false); mVolumizer.stop(); @@ -175,7 +189,7 @@ public class VolumeSeekBarPreference extends SeekBarPreference { if (mIconView == null) return; if (mIconResId != 0) { mIconView.setImageResource(mIconResId); - } else if (mMuteIconResId != 0 && mMuted && !mZenMuted) { + } else if (mMuteIconResId != 0 && isMuted()) { mIconView.setImageResource(mMuteIconResId); } else { mIconView.setImageDrawable(getIcon()); @@ -208,6 +222,10 @@ public class VolumeSeekBarPreference extends SeekBarPreference { updateSuppressionText(); } + protected boolean isMuted() { + return mMuted && !mZenMuted; + } + protected void updateSuppressionText() { if (mSuppressionTextView != null && mSeekBar != null) { mSuppressionTextView.setText(mSuppressionText); @@ -216,6 +234,14 @@ public class VolumeSeekBarPreference extends SeekBarPreference { } } + /** + * Update content description of title to improve talkback announcements. + */ + protected void updateContentDescription(CharSequence contentDescription) { + if (mTitle == null) return; + mTitle.setContentDescription(contentDescription); + } + public interface Callback { void onSampleStarting(SeekBarVolumizer sbv); void onStreamValueChanged(int stream, int progress); @@ -225,4 +251,15 @@ public class VolumeSeekBarPreference extends SeekBarPreference { */ void onStartTrackingTouch(SeekBarVolumizer sbv); } + + /** + * Listener to view updates in volumeSeekbarPreference. + */ + public interface Listener { + + /** + * Listener to mute state updates. + */ + void onUpdateMuteState(); + } } diff --git a/src/com/android/settings/notification/VolumeSeekBarPreferenceController.java b/src/com/android/settings/notification/VolumeSeekBarPreferenceController.java index 0414565721e..285e8ddbeb9 100644 --- a/src/com/android/settings/notification/VolumeSeekBarPreferenceController.java +++ b/src/com/android/settings/notification/VolumeSeekBarPreferenceController.java @@ -36,6 +36,7 @@ public abstract class VolumeSeekBarPreferenceController extends protected VolumeSeekBarPreference mPreference; protected VolumeSeekBarPreference.Callback mVolumePreferenceCallback; protected AudioHelper mHelper; + protected VolumeSeekBarPreference.Listener mVolumePreferenceListener; public VolumeSeekBarPreferenceController(Context context, String key) { super(context, key); @@ -62,6 +63,7 @@ public abstract class VolumeSeekBarPreferenceController extends protected void setupVolPreference(PreferenceScreen screen) { mPreference = screen.findPreference(getPreferenceKey()); mPreference.setCallback(mVolumePreferenceCallback); + mPreference.setListener(mVolumePreferenceListener); mPreference.setStream(getAudioStream()); mPreference.setMuteIcon(getMuteIcon()); } diff --git a/tests/robotests/src/com/android/settings/notification/VolumeSeekBarPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/VolumeSeekBarPreferenceControllerTest.java index 2d54c38e249..f7e32a2c9d9 100644 --- a/tests/robotests/src/com/android/settings/notification/VolumeSeekBarPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/VolumeSeekBarPreferenceControllerTest.java @@ -49,6 +49,8 @@ public class VolumeSeekBarPreferenceControllerTest { @Mock private VolumeSeekBarPreference.Callback mCallback; @Mock + private VolumeSeekBarPreference.Listener mListener; + @Mock private AudioHelper mHelper; private VolumeSeekBarPreferenceControllerTestable mController; @@ -59,7 +61,7 @@ public class VolumeSeekBarPreferenceControllerTest { when(mScreen.findPreference(nullable(String.class))).thenReturn(mPreference); when(mPreference.getKey()).thenReturn("key"); mController = new VolumeSeekBarPreferenceControllerTestable(mContext, mCallback, true, - mPreference.getKey()); + mPreference.getKey(), mListener); mController.setAudioHelper(mHelper); } @@ -70,18 +72,20 @@ public class VolumeSeekBarPreferenceControllerTest { verify(mPreference).setCallback(mCallback); verify(mPreference).setStream(VolumeSeekBarPreferenceControllerTestable.AUDIO_STREAM); verify(mPreference).setMuteIcon(VolumeSeekBarPreferenceControllerTestable.MUTE_ICON); + verify(mPreference).setListener(mListener); } @Test public void displayPreference_notAvailable_shouldNotUpdatePreference() { mController = new VolumeSeekBarPreferenceControllerTestable(mContext, mCallback, false, - mPreference.getKey()); + mPreference.getKey(), mListener); mController.displayPreference(mScreen); verify(mPreference, never()).setCallback(any(VolumeSeekBarPreference.Callback.class)); verify(mPreference, never()).setStream(anyInt()); verify(mPreference, never()).setMuteIcon(anyInt()); + verify(mPreference, never()).setListener(mListener); } @Test @@ -157,10 +161,12 @@ public class VolumeSeekBarPreferenceControllerTest { private boolean mAvailable; VolumeSeekBarPreferenceControllerTestable(Context context, - VolumeSeekBarPreference.Callback callback, boolean available, String key) { + VolumeSeekBarPreference.Callback callback, boolean available, String key, + VolumeSeekBarPreference.Listener listener) { super(context, key); setCallback(callback); mAvailable = available; + mVolumePreferenceListener = listener; } @Override diff --git a/tests/robotests/src/com/android/settings/notification/VolumeSeekBarPreferenceTest.java b/tests/robotests/src/com/android/settings/notification/VolumeSeekBarPreferenceTest.java index d74f76a7cea..59f0bcb91b9 100644 --- a/tests/robotests/src/com/android/settings/notification/VolumeSeekBarPreferenceTest.java +++ b/tests/robotests/src/com/android/settings/notification/VolumeSeekBarPreferenceTest.java @@ -18,11 +18,14 @@ package com.android.settings.notification; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doCallRealMethod; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; import android.media.AudioManager; +import android.preference.SeekBarVolumizer; +import android.widget.SeekBar; import org.junit.Before; import org.junit.Test; @@ -34,18 +37,28 @@ import org.robolectric.RobolectricTestRunner; @RunWith(RobolectricTestRunner.class) public class VolumeSeekBarPreferenceTest { + private static final CharSequence CONTENT_DESCRIPTION = "TEST"; @Mock private AudioManager mAudioManager; @Mock private VolumeSeekBarPreference mPreference; @Mock private Context mContext; + @Mock + private SeekBar mSeekBar; + @Mock + private SeekBarVolumizer mVolumizer; + private VolumeSeekBarPreference.Listener mListener; @Before public void setUp() { MockitoAnnotations.initMocks(this); when(mContext.getSystemService(Context.AUDIO_SERVICE)).thenReturn(mAudioManager); + doCallRealMethod().when(mPreference).updateContentDescription(CONTENT_DESCRIPTION); + mPreference.mSeekBar = mSeekBar; mPreference.mAudioManager = mAudioManager; + mPreference.mVolumizer = mVolumizer; + mListener = () -> mPreference.updateContentDescription(CONTENT_DESCRIPTION); } @Test @@ -65,4 +78,24 @@ public class VolumeSeekBarPreferenceTest { verify(mPreference).setMin(min); verify(mPreference).setProgress(progress); } + + @Test + public void init_listenerIsCalled() { + doCallRealMethod().when(mPreference).setListener(mListener); + doCallRealMethod().when(mPreference).init(); + + mPreference.setListener(mListener); + mPreference.init(); + + verify(mPreference).updateContentDescription(CONTENT_DESCRIPTION); + } + + @Test + public void init_listenerNotSet_noException() { + doCallRealMethod().when(mPreference).init(); + + mPreference.init(); + + verify(mPreference, never()).updateContentDescription(CONTENT_DESCRIPTION); + } } From 7c9958aafd7041d0d306e729f6a59f62d594841e Mon Sep 17 00:00:00 2001 From: Michael Mikhail Date: Mon, 26 Jun 2023 20:16:30 +0000 Subject: [PATCH 03/10] Trigger talkback for notification volume change Triggers talkback to say the content description of the title of notification volume when notification volume is set to vibrate or silent mode. Fixes: 285453719 Fixes: 285455826 Test: manually checked when the ring volume is set to 0% and when the notification volume is set to 0%. Check the video in the bug link. Change-Id: I4bd65bdbfa41793fc8e32c295185363ca36cc0d7 --- .../NotificationVolumePreferenceController.java | 6 ++++++ .../settings/notification/VolumeSeekBarPreference.java | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/src/com/android/settings/notification/NotificationVolumePreferenceController.java b/src/com/android/settings/notification/NotificationVolumePreferenceController.java index 02dc96a2804..fe7b70bf129 100644 --- a/src/com/android/settings/notification/NotificationVolumePreferenceController.java +++ b/src/com/android/settings/notification/NotificationVolumePreferenceController.java @@ -26,6 +26,7 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; import android.service.notification.NotificationListenerService; +import android.view.View; import androidx.lifecycle.OnLifecycleEvent; import androidx.preference.PreferenceScreen; @@ -138,14 +139,19 @@ public class NotificationVolumePreferenceController extends if (mPreference != null) { int ringerMode = getEffectiveRingerMode(); if (ringerMode == AudioManager.RINGER_MODE_VIBRATE) { + mPreference.setAccessibilityLiveRegion(View.ACCESSIBILITY_LIVE_REGION_POLITE); mPreference.updateContentDescription( mContext.getString( R.string.notification_volume_content_description_vibrate_mode)); } else if (ringerMode == AudioManager.RINGER_MODE_SILENT) { + mPreference.setAccessibilityLiveRegion(View.ACCESSIBILITY_LIVE_REGION_POLITE); mPreference.updateContentDescription( mContext.getString(R.string.volume_content_description_silent_mode, mPreference.getTitle())); } else { + // Set a11y mode to none in order not to trigger talkback while changing + // notification volume in normal mode. + mPreference.setAccessibilityLiveRegion(View.ACCESSIBILITY_LIVE_REGION_NONE); mPreference.updateContentDescription(mPreference.getTitle()); } } diff --git a/src/com/android/settings/notification/VolumeSeekBarPreference.java b/src/com/android/settings/notification/VolumeSeekBarPreference.java index f3c1471be4f..0000eba2ba7 100644 --- a/src/com/android/settings/notification/VolumeSeekBarPreference.java +++ b/src/com/android/settings/notification/VolumeSeekBarPreference.java @@ -242,6 +242,11 @@ public class VolumeSeekBarPreference extends SeekBarPreference { mTitle.setContentDescription(contentDescription); } + protected void setAccessibilityLiveRegion(int mode) { + if (mTitle == null) return; + mTitle.setAccessibilityLiveRegion(mode); + } + public interface Callback { void onSampleStarting(SeekBarVolumizer sbv); void onStreamValueChanged(int stream, int progress); From 2ca3f3232cd5a05055e39f94d83cf96a05305f52 Mon Sep 17 00:00:00 2001 From: Hao Dong Date: Mon, 15 May 2023 19:34:07 +0000 Subject: [PATCH 04/10] Fix Fingerprint enrollment UI when display size is largest. 1. Remove land/udfps_enroll_enrolling and use the default land layout instead. Swap header and content when necessary to avoid overlap. 2. Add UdfpsEnrollEnrollingView.java Test: manual test - 1. Set system display and font size largest 2. Launch fingerprint enrollment and check UI. Test: atest FingerprintEnrollEnrollingTest Bug: 269060514 Bug: 283169056 Change-Id: Ifbe6c92c4213979952f2f89a1cd595c9c4bff6ec Merged-In: Ifbe6c92c4213979952f2f89a1cd595c9c4bff6ec --- res/layout-land/udfps_enroll_enrolling.xml | 101 --------- res/layout/udfps_enroll_enrolling.xml | 4 +- .../biometrics/BiometricEnrollBase.java | 17 +- .../FingerprintEnrollEnrolling.java | 197 ++---------------- .../FingerprintEnrollEnrollingTest.java | 22 +- 5 files changed, 42 insertions(+), 299 deletions(-) delete mode 100644 res/layout-land/udfps_enroll_enrolling.xml diff --git a/res/layout-land/udfps_enroll_enrolling.xml b/res/layout-land/udfps_enroll_enrolling.xml deleted file mode 100644 index 743684fb02e..00000000000 --- a/res/layout-land/udfps_enroll_enrolling.xml +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/res/layout/udfps_enroll_enrolling.xml b/res/layout/udfps_enroll_enrolling.xml index 05556ffe46c..366a87c4740 100644 --- a/res/layout/udfps_enroll_enrolling.xml +++ b/res/layout/udfps_enroll_enrolling.xml @@ -15,7 +15,7 @@ ~ limitations under the License. --> - - + diff --git a/src/com/android/settings/biometrics/BiometricEnrollBase.java b/src/com/android/settings/biometrics/BiometricEnrollBase.java index 2f852f08b9f..6e110799c46 100644 --- a/src/com/android/settings/biometrics/BiometricEnrollBase.java +++ b/src/com/android/settings/biometrics/BiometricEnrollBase.java @@ -133,6 +133,7 @@ public abstract class BiometricEnrollBase extends InstrumentedActivity { protected long mChallenge; protected boolean mFromSettingsSummary; protected FooterBarMixin mFooterBarMixin; + protected boolean mShouldSetFooterBarBackground = true; @Nullable protected ScreenSizeFoldProvider mScreenSizeFoldProvider; @Nullable @@ -191,12 +192,14 @@ public abstract class BiometricEnrollBase extends InstrumentedActivity { super.onPostCreate(savedInstanceState); initViews(); - @SuppressLint("VisibleForTests") - final LinearLayout buttonContainer = mFooterBarMixin != null - ? mFooterBarMixin.getButtonContainer() - : null; - if (buttonContainer != null) { - buttonContainer.setBackgroundColor(getBackgroundColor()); + if (mShouldSetFooterBarBackground) { + @SuppressLint("VisibleForTests") + final LinearLayout buttonContainer = mFooterBarMixin != null + ? mFooterBarMixin.getButtonContainer() + : null; + if (buttonContainer != null) { + buttonContainer.setBackgroundColor(getBackgroundColor()); + } } } @@ -331,7 +334,7 @@ public abstract class BiometricEnrollBase extends InstrumentedActivity { } @ColorInt - private int getBackgroundColor() { + public int getBackgroundColor() { final ColorStateList stateList = Utils.getColorAttr(this, android.R.attr.windowBackground); return stateList != null ? stateList.getDefaultColor() : Color.TRANSPARENT; } diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java index 9bb563b7a6a..dbdb024973a 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java @@ -32,10 +32,8 @@ import android.content.Intent; import android.content.res.ColorStateList; import android.content.res.Configuration; import android.content.res.Resources; -import android.graphics.Point; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; -import android.graphics.Rect; import android.graphics.drawable.Animatable2; import android.graphics.drawable.AnimatedVectorDrawable; import android.graphics.drawable.Drawable; @@ -48,22 +46,16 @@ import android.os.VibrationAttributes; import android.os.VibrationEffect; import android.os.Vibrator; import android.text.TextUtils; -import android.util.FeatureFlagUtils; import android.util.Log; -import android.view.DisplayInfo; import android.view.MotionEvent; import android.view.OrientationEventListener; import android.view.Surface; import android.view.View; -import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; -import android.widget.FrameLayout; -import android.widget.ImageView; -import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.RelativeLayout; import android.widget.TextView; @@ -79,25 +71,20 @@ import com.android.settings.biometrics.BiometricUtils; import com.android.settings.biometrics.BiometricsEnrollEnrolling; import com.android.settings.core.instrumentation.InstrumentedDialogFragment; import com.android.settingslib.display.DisplayDensityUtils; -import com.android.settingslib.udfps.UdfpsOverlayParams; -import com.android.settingslib.udfps.UdfpsUtils; import com.airbnb.lottie.LottieAnimationView; import com.airbnb.lottie.LottieCompositionFactory; import com.airbnb.lottie.LottieProperty; import com.airbnb.lottie.model.KeyPath; -import com.google.android.setupcompat.template.FooterActionButton; import com.google.android.setupcompat.template.FooterBarMixin; import com.google.android.setupcompat.template.FooterButton; import com.google.android.setupcompat.util.WizardManagerHelper; -import com.google.android.setupdesign.GlifLayout; import com.google.android.setupdesign.template.DescriptionMixin; import com.google.android.setupdesign.template.HeaderMixin; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.List; -import java.util.Locale; /** * Activity which handles the actual enrolling for fingerprint. @@ -176,8 +163,6 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling { @VisibleForTesting @Nullable UdfpsEnrollHelper mUdfpsEnrollHelper; - // TODO(b/260617060): Do not hard-code mScaleFactor, referring to AuthController. - private float mScaleFactor = 1.0f; private ObjectAnimator mProgressAnim; private TextView mErrorText; private Interpolator mFastOutSlowInInterpolator; @@ -206,7 +191,7 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling { private boolean mHaveShownSfpsLeftEdgeLottie; private boolean mHaveShownSfpsRightEdgeLottie; private boolean mShouldShowLottie; - private UdfpsUtils mUdfpsUtils; + private ObjectAnimator mHelpAnimation; private OrientationEventListener mOrientationEventListener; @@ -251,88 +236,17 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling { mAccessibilityManager = getSystemService(AccessibilityManager.class); mIsAccessibilityEnabled = mAccessibilityManager.isEnabled(); - mUdfpsUtils = new UdfpsUtils(); - final boolean isLayoutRtl = (TextUtils.getLayoutDirectionFromLocale( - Locale.getDefault()) == View.LAYOUT_DIRECTION_RTL); listenOrientationEvent(); if (mCanAssumeUdfps) { - int rotation = getApplicationContext().getDisplay().getRotation(); - final GlifLayout layout = (GlifLayout) getLayoutInflater().inflate( - R.layout.udfps_enroll_enrolling, null, false); - final UdfpsEnrollView udfpsEnrollView = layout.findViewById(R.id.udfps_animation_view); - updateUdfpsEnrollView(udfpsEnrollView, props.get(0)); - switch (rotation) { - case Surface.ROTATION_90: - final View sudContent = layout.findViewById(R.id.sud_layout_content); - if (sudContent != null) { - sudContent.setPadding(sudContent.getPaddingLeft(), 0, - sudContent.getPaddingRight(), sudContent.getPaddingBottom()); - } + final UdfpsEnrollEnrollingView layout = + (UdfpsEnrollEnrollingView) getLayoutInflater().inflate( + R.layout.udfps_enroll_enrolling, null, false); + setUdfpsEnrollHelper(); + layout.initView(props.get(0), mUdfpsEnrollHelper, mAccessibilityManager); - final LinearLayout layoutContainer = layout.findViewById( - R.id.layout_container); - final LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.MATCH_PARENT, - LinearLayout.LayoutParams.MATCH_PARENT); - - lp.setMarginEnd((int) getResources().getDimension( - R.dimen.rotation_90_enroll_margin_end)); - layoutContainer.setPaddingRelative((int) getResources().getDimension( - R.dimen.rotation_90_enroll_padding_start), 0, isLayoutRtl - ? 0 : (int) getResources().getDimension( - R.dimen.rotation_90_enroll_padding_end), 0); - layoutContainer.setLayoutParams(lp); - - setOnHoverListener(true, layout, udfpsEnrollView); - setContentView(layout, lp); - break; - - case Surface.ROTATION_0: - case Surface.ROTATION_180: - // In the portrait mode, layout_container's height is 0, so it's - // always shown at the bottom of the screen. - final FrameLayout portraitLayoutContainer = layout.findViewById( - R.id.layout_container); - - // In the portrait mode, the title and lottie animation view may - // overlap when title needs three lines, so adding some paddings - // between them, and adjusting the fp progress view here accordingly. - final int layoutLottieAnimationPadding = (int) getResources() - .getDimension(R.dimen.udfps_lottie_padding_top); - portraitLayoutContainer.setPadding(0, - layoutLottieAnimationPadding, 0, 0); - final ImageView progressView = udfpsEnrollView.findViewById( - R.id.udfps_enroll_animation_fp_progress_view); - progressView.setPadding(0, -(layoutLottieAnimationPadding), - 0, layoutLottieAnimationPadding); - final ImageView fingerprintView = udfpsEnrollView.findViewById( - R.id.udfps_enroll_animation_fp_view); - fingerprintView.setPadding(0, -layoutLottieAnimationPadding, - 0, layoutLottieAnimationPadding); - - // TODO(b/260970216) Instead of hiding the description text view, we should - // make the header view scrollable if the text is too long. - // If description text view has overlap with udfps progress view, hide it. - View view = layout.getDescriptionTextView(); - layout.getViewTreeObserver().addOnDrawListener(() -> { - if (view.getVisibility() == View.VISIBLE - && hasOverlap(view, udfpsEnrollView)) { - view.setVisibility(View.GONE); - } - }); - - setOnHoverListener(false, layout, udfpsEnrollView); - setContentView(layout); - break; - - case Surface.ROTATION_270: - default: - setOnHoverListener(true, layout, udfpsEnrollView); - setContentView(layout); - break; - } + setContentView(layout); setDescriptionText(R.string.security_settings_udfps_enroll_start_message); } else if (mCanAssumeSfps) { setContentView(R.layout.sfps_enroll_enrolling); @@ -372,22 +286,11 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling { .build() ); - if (FeatureFlagUtils.isEnabled(getApplicationContext(), - FeatureFlagUtils.SETTINGS_SHOW_UDFPS_ENROLL_IN_SETTINGS)) { - // Remove the space view and make the width of footer button container WRAP_CONTENT - // to avoid hiding the udfps view progress bar bottom. - final LinearLayout buttonContainer = mFooterBarMixin.getButtonContainer(); - View spaceView = null; - for (int i = 0; i < buttonContainer.getChildCount(); i++) { - if (!(buttonContainer.getChildAt(i) instanceof FooterActionButton)) { - spaceView = buttonContainer.getChildAt(i); - break; - } - } - if (spaceView != null) { - spaceView.setVisibility(View.GONE); - buttonContainer.getLayoutParams().width = ViewGroup.LayoutParams.WRAP_CONTENT; - } + // If it's udfps, set the background color only for secondary button if necessary. + if (mCanAssumeUdfps) { + mShouldSetFooterBarBackground = false; + ((UdfpsEnrollEnrollingView) getLayout()).setSecondaryButtonBackground( + getBackgroundColor()); } final LayerDrawable fingerprintDrawable = mProgressBar != null @@ -1237,30 +1140,7 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling { } } - private UdfpsEnrollView updateUdfpsEnrollView(UdfpsEnrollView udfpsEnrollView, - FingerprintSensorPropertiesInternal udfpsProps) { - DisplayInfo displayInfo = new DisplayInfo(); - getDisplay().getDisplayInfo(displayInfo); - mScaleFactor = mUdfpsUtils.getScaleFactor(displayInfo); - Rect udfpsBounds = udfpsProps.getLocation().getRect(); - udfpsBounds.scale(mScaleFactor); - - final Rect overlayBounds = new Rect( - 0, /* left */ - displayInfo.getNaturalHeight() / 2, /* top */ - displayInfo.getNaturalWidth(), /* right */ - displayInfo.getNaturalHeight() /* botom */); - - UdfpsOverlayParams params = new UdfpsOverlayParams( - udfpsBounds, - overlayBounds, - displayInfo.getNaturalWidth(), - displayInfo.getNaturalHeight(), - mScaleFactor, - displayInfo.rotation); - - udfpsEnrollView.setOverlayParams(params); - + private void setUdfpsEnrollHelper() { mUdfpsEnrollHelper = (UdfpsEnrollHelper) getSupportFragmentManager().findFragmentByTag( FingerprintEnrollEnrolling.TAG_UDFPS_HELPER); if (mUdfpsEnrollHelper == null) { @@ -1270,57 +1150,6 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling { .add(mUdfpsEnrollHelper, FingerprintEnrollEnrolling.TAG_UDFPS_HELPER) .commitAllowingStateLoss(); } - udfpsEnrollView.setEnrollHelper(mUdfpsEnrollHelper); - - return udfpsEnrollView; - } - - private void setOnHoverListener(boolean isLandscape, GlifLayout enrollLayout, - UdfpsEnrollView udfpsEnrollView) { - if (!mIsAccessibilityEnabled) return; - - final Context context = getApplicationContext(); - final View.OnHoverListener onHoverListener = (v, event) -> { - // Map the touch to portrait mode if the device is in - // landscape mode. - final Point scaledTouch = - mUdfpsUtils.getTouchInNativeCoordinates(event.getPointerId(0), - event, udfpsEnrollView.getOverlayParams()); - - if (mUdfpsUtils.isWithinSensorArea(event.getPointerId(0), event, - udfpsEnrollView.getOverlayParams())) { - return false; - } - - final String theStr = mUdfpsUtils.onTouchOutsideOfSensorArea( - mAccessibilityManager.isTouchExplorationEnabled(), context, - scaledTouch.x, scaledTouch.y, udfpsEnrollView.getOverlayParams()); - if (theStr != null) { - v.announceForAccessibility(theStr); - } - return false; - }; - - enrollLayout.findManagedViewById(isLandscape ? R.id.sud_landscape_content_area - : R.id.sud_layout_content).setOnHoverListener(onHoverListener); - } - - - @VisibleForTesting boolean hasOverlap(View view1, View view2) { - int[] firstPosition = new int[2]; - int[] secondPosition = new int[2]; - - view1.getLocationOnScreen(firstPosition); - view2.getLocationOnScreen(secondPosition); - - // Rect constructor parameters: left, top, right, bottom - Rect rectView1 = new Rect(firstPosition[0], firstPosition[1], - firstPosition[0] + view1.getMeasuredWidth(), - firstPosition[1] + view1.getMeasuredHeight()); - Rect rectView2 = new Rect(secondPosition[0], secondPosition[1], - secondPosition[0] + view2.getMeasuredWidth(), - secondPosition[1] + view2.getMeasuredHeight()); - return rectView1.intersect(rectView2); } public static class IconTouchDialog extends InstrumentedDialogFragment { diff --git a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrollingTest.java b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrollingTest.java index e5b283ae185..0f12d1e5023 100644 --- a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrollingTest.java +++ b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrollingTest.java @@ -56,6 +56,7 @@ import android.os.Vibrator; import android.view.Display; import android.view.Surface; import android.view.View; +import android.view.accessibility.AccessibilityManager; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.TextView; @@ -314,11 +315,17 @@ public class FingerprintEnrollEnrollingTest { @Test public void fingerprintUdfpsOverlayEnrollment_descriptionViewGoneWithOverlap() { initializeActivityWithoutCreate(TYPE_UDFPS_OPTICAL); - doReturn(true).when(mActivity).hasOverlap(any(), any()); when(mMockDisplay.getRotation()).thenReturn(Surface.ROTATION_0); createActivity(); - final GlifLayout defaultLayout = spy(mActivity.findViewById(R.id.setup_wizard_layout)); + final UdfpsEnrollEnrollingView defaultLayout = spy( + mActivity.findViewById(R.id.setup_wizard_layout)); + doReturn(true).when(defaultLayout).hasOverlap(any(), any()); + + // Somehow spy doesn't work, and we need to call initView manually. + defaultLayout.initView(mFingerprintManager.getSensorPropertiesInternal().get(0), + mActivity.mUdfpsEnrollHelper, + mActivity.getSystemService(AccessibilityManager.class)); final TextView descriptionTextView = defaultLayout.getDescriptionTextView(); defaultLayout.getViewTreeObserver().dispatchOnDraw(); @@ -328,11 +335,17 @@ public class FingerprintEnrollEnrollingTest { @Test public void fingerprintUdfpsOverlayEnrollment_descriptionViewVisibleWithoutOverlap() { initializeActivityWithoutCreate(TYPE_UDFPS_OPTICAL); - doReturn(false).when(mActivity).hasOverlap(any(), any()); when(mMockDisplay.getRotation()).thenReturn(Surface.ROTATION_0); createActivity(); - final GlifLayout defaultLayout = spy(mActivity.findViewById(R.id.setup_wizard_layout)); + final UdfpsEnrollEnrollingView defaultLayout = spy( + mActivity.findViewById(R.id.setup_wizard_layout)); + doReturn(false).when(defaultLayout).hasOverlap(any(), any()); + + // Somehow spy doesn't work, and we need to call initView manually. + defaultLayout.initView(mFingerprintManager.getSensorPropertiesInternal().get(0), + mActivity.mUdfpsEnrollHelper, + mActivity.getSystemService(AccessibilityManager.class)); final TextView descriptionTextView = defaultLayout.getDescriptionTextView(); defaultLayout.getViewTreeObserver().dispatchOnDraw(); @@ -591,7 +604,6 @@ public class FingerprintEnrollEnrollingTest { mContext = spy(RuntimeEnvironment.application); mActivity = spy(FingerprintEnrollEnrolling.class); - when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(props); when(mContext.getDisplay()).thenReturn(mMockDisplay); when(mMockDisplay.getRotation()).thenReturn(Surface.ROTATION_0); From 7b99d9246456510da989ac30e9a3134b080b9631 Mon Sep 17 00:00:00 2001 From: Hao Dong Date: Wed, 14 Jun 2023 22:27:58 +0000 Subject: [PATCH 05/10] Fix pattern rotation header text. Bug: 278055194 Test: atest ChooseLockPatternTest Change-Id: I36ec325b46d7a7f583d94dc26f9962cadc3874cd Merged-In: I36ec325b46d7a7f583d94dc26f9962cadc3874cd --- .../settings/password/ChooseLockPattern.java | 20 +++++++------------ .../password/ChooseLockPatternTest.java | 20 +++++++++++++++++++ 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/src/com/android/settings/password/ChooseLockPattern.java b/src/com/android/settings/password/ChooseLockPattern.java index dc36220afef..a2fd986dd0b 100644 --- a/src/com/android/settings/password/ChooseLockPattern.java +++ b/src/com/android/settings/password/ChooseLockPattern.java @@ -442,7 +442,8 @@ public class ChooseLockPattern extends SettingsActivity { protected boolean mForFace; protected boolean mForBiometrics; - private static final String KEY_UI_STAGE = "uiStage"; + @VisibleForTesting + static final String KEY_UI_STAGE = "uiStage"; private static final String KEY_PATTERN_CHOICE = "chosenPattern"; private static final String KEY_CURRENT_PATTERN = "currentPattern"; @@ -718,10 +719,6 @@ public class ChooseLockPattern extends SettingsActivity { final GlifLayout layout = getActivity().findViewById(R.id.setup_wizard_layout); mUiStage = stage; - if (stage == Stage.Introduction) { - layout.setDescriptionText(stage.headerMessage); - } - // header text, footer text, visibility and // enabled state all known from the stage if (stage == Stage.ChoiceTooShort) { @@ -744,16 +741,13 @@ public class ChooseLockPattern extends SettingsActivity { Theme theme = getActivity().getTheme(); theme.resolveAttribute(R.attr.colorError, typedValue, true); mHeaderText.setTextColor(typedValue.data); + } else if (mDefaultHeaderColorList != null) { + mHeaderText.setTextColor(mDefaultHeaderColorList); + } - } else { - if (mDefaultHeaderColorList != null) { - mHeaderText.setTextColor(mDefaultHeaderColorList); - } - if (stage == Stage.NeedToConfirm) { - mHeaderText.setText(stage.headerMessage); - layout.setHeaderText(R.string.lockpassword_draw_your_pattern_again_header); - } + if (stage == Stage.ConfirmWrong || stage == Stage.NeedToConfirm) { + layout.setHeaderText(R.string.lockpassword_draw_your_pattern_again_header); } updateFooterLeftButton(stage); diff --git a/tests/robotests/src/com/android/settings/password/ChooseLockPatternTest.java b/tests/robotests/src/com/android/settings/password/ChooseLockPatternTest.java index 442d021fd31..301a6db8ecd 100644 --- a/tests/robotests/src/com/android/settings/password/ChooseLockPatternTest.java +++ b/tests/robotests/src/com/android/settings/password/ChooseLockPatternTest.java @@ -18,12 +18,15 @@ package com.android.settings.password; import static android.view.WindowManager.LayoutParams.FLAG_SECURE; +import static com.android.settings.password.ChooseLockPattern.ChooseLockPatternFragment.KEY_UI_STAGE; + import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import static org.robolectric.RuntimeEnvironment.application; import android.content.Intent; +import android.os.Bundle; import android.os.UserHandle; import android.view.View; @@ -34,6 +37,8 @@ import com.android.settings.password.ChooseLockPattern.ChooseLockPatternFragment import com.android.settings.password.ChooseLockPattern.IntentBuilder; import com.android.settings.testutils.shadow.ShadowUtils; +import com.google.android.setupdesign.GlifLayout; + import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; @@ -119,6 +124,21 @@ public class ChooseLockPatternTest { assertThat(flags & FLAG_SECURE).isEqualTo(FLAG_SECURE); } + @Test + public void headerText_stageConfirmWrong() { + ChooseLockPattern activity = createActivity(true); + ChooseLockPatternFragment fragment = (ChooseLockPatternFragment) + activity.getSupportFragmentManager().findFragmentById(R.id.main_content); + final GlifLayout layout = fragment.getView().findViewById(R.id.setup_wizard_layout); + Bundle savedInstanceState = new Bundle(); + savedInstanceState.putInt(KEY_UI_STAGE, + ChooseLockPatternFragment.Stage.ConfirmWrong.ordinal()); + + fragment.onViewCreated(layout, savedInstanceState); + assertThat(layout.getHeaderText().toString()).isEqualTo(activity.getResources().getString( + R.string.lockpassword_draw_your_pattern_again_header)); + } + private ChooseLockPattern createActivity(boolean addFingerprintExtra) { return Robolectric.buildActivity( ChooseLockPattern.class, From ab029e44f57f175eb8644627823af4458beb1cf3 Mon Sep 17 00:00:00 2001 From: Zoey Chen Date: Tue, 27 Jun 2023 17:56:29 +0000 Subject: [PATCH 06/10] [Regional Preference] Add content descripiton to the img for Talkback Test: manual Bug: 289176529 Change-Id: Iaa7e6d8fca1036be58ea5bb17e722935f5f9f424 --- res/layout/preference_check_icon.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/res/layout/preference_check_icon.xml b/res/layout/preference_check_icon.xml index 1b759fcc045..bd0dd79b421 100644 --- a/res/layout/preference_check_icon.xml +++ b/res/layout/preference_check_icon.xml @@ -20,4 +20,5 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" - android:layout_marginHorizontal="16dp"/> \ No newline at end of file + android:layout_marginHorizontal="16dp" + android:contentDescription="@*android:string/checked"/> \ No newline at end of file From c871c5654dfa1d7641018328573eb5b02949850d Mon Sep 17 00:00:00 2001 From: Allen Su Date: Wed, 26 Apr 2023 07:27:43 +0000 Subject: [PATCH 07/10] [Panlingual]Log metrics for App's locale from the suggested Bug: 258128535 Test: atest AppLocalePickerActivityTest Change-Id: I007c0e76d8b88f08518ba2696d42bd1db194f5b7 Merged-In: I007c0e76d8b88f08518ba2696d42bd1db194f5b7 --- .../localepicker/AppLocalePickerActivity.java | 38 +++++++++++++++++++ .../AppLocalePickerActivityTest.java | 37 ++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/src/com/android/settings/localepicker/AppLocalePickerActivity.java b/src/com/android/settings/localepicker/AppLocalePickerActivity.java index 092207bdcf5..d1e113767ff 100644 --- a/src/com/android/settings/localepicker/AppLocalePickerActivity.java +++ b/src/com/android/settings/localepicker/AppLocalePickerActivity.java @@ -18,6 +18,7 @@ package com.android.settings.localepicker; import android.app.FragmentTransaction; import android.app.LocaleManager; +import android.app.settings.SettingsEnums; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Bundle; @@ -37,15 +38,22 @@ import com.android.settings.R; import com.android.settings.applications.AppLocaleUtil; import com.android.settings.applications.appinfo.AppLocaleDetails; import com.android.settings.core.SettingsBaseActivity; +import com.android.settings.overlay.FeatureFactory; +import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; public class AppLocalePickerActivity extends SettingsBaseActivity implements LocalePickerWithRegion.LocaleSelectedListener, MenuItem.OnActionExpandListener { private static final String TAG = AppLocalePickerActivity.class.getSimpleName(); + private static final int SIM_LOCALE = 1 << 0; + private static final int SYSTEM_LOCALE = 1 << 1; + private static final int APP_LOCALE = 1 << 2; + private static final int IME_LOCALE = 1 << 3; private String mPackageName; private LocalePickerWithRegion mLocalePickerWithRegion; private AppLocaleDetails mAppLocaleDetails; private View mAppLocaleDetailContainer; + private MetricsFeatureProvider mMetricsFeatureProvider; @Override public void onCreate(Bundle savedInstanceState) { @@ -71,6 +79,7 @@ public class AppLocalePickerActivity extends SettingsBaseActivity setTitle(R.string.app_locale_picker_title); getActionBar().setDisplayHomeAsUpEnabled(true); + mMetricsFeatureProvider = FeatureFactory.getFactory(this).getMetricsFeatureProvider(); mLocalePickerWithRegion = LocalePickerWithRegion.createLanguagePicker( this, @@ -99,6 +108,7 @@ public class AppLocalePickerActivity extends SettingsBaseActivity if (localeInfo == null || localeInfo.getLocale() == null || localeInfo.isSystemLocale()) { setAppDefaultLocale(""); } else { + logLocaleSource(localeInfo); setAppDefaultLocale(localeInfo.getLocale().toLanguageTag()); } finish(); @@ -177,4 +187,32 @@ public class AppLocalePickerActivity extends SettingsBaseActivity return false; } + + private void logLocaleSource(LocaleStore.LocaleInfo localeInfo) { + if (!localeInfo.isSuggested() || localeInfo.isAppCurrentLocale()) { + return; + } + int localeSource = 0; + if (hasSuggestionType(localeInfo, + LocaleStore.LocaleInfo.SUGGESTION_TYPE_SYSTEM_AVAILABLE_LANGUAGE)) { + localeSource |= SYSTEM_LOCALE; + } + if (hasSuggestionType(localeInfo, + LocaleStore.LocaleInfo.SUGGESTION_TYPE_OTHER_APP_LANGUAGE)) { + localeSource |= APP_LOCALE; + } + if (hasSuggestionType(localeInfo, LocaleStore.LocaleInfo.SUGGESTION_TYPE_IME_LANGUAGE)) { + localeSource |= IME_LOCALE; + } + if (hasSuggestionType(localeInfo, LocaleStore.LocaleInfo.SUGGESTION_TYPE_SIM)) { + localeSource |= SIM_LOCALE; + } + mMetricsFeatureProvider.action(this, + SettingsEnums.ACTION_CHANGE_APP_LANGUAGE_FROM_SUGGESTED, localeSource); + } + + private static boolean hasSuggestionType(LocaleStore.LocaleInfo localeInfo, + int suggestionType) { + return localeInfo.isSuggestionOfType(suggestionType); + } } diff --git a/tests/robotests/src/com/android/settings/localepicker/AppLocalePickerActivityTest.java b/tests/robotests/src/com/android/settings/localepicker/AppLocalePickerActivityTest.java index 48caecdf6f7..8fb3a5d06ab 100644 --- a/tests/robotests/src/com/android/settings/localepicker/AppLocalePickerActivityTest.java +++ b/tests/robotests/src/com/android/settings/localepicker/AppLocalePickerActivityTest.java @@ -18,6 +18,8 @@ package com.android.settings.localepicker; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; @@ -27,6 +29,7 @@ import static org.mockito.Mockito.when; import android.app.Activity; import android.app.ApplicationPackageManager; import android.app.LocaleConfig; +import android.app.settings.SettingsEnums; import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; @@ -45,6 +48,7 @@ import androidx.annotation.ArrayRes; import com.android.internal.app.LocaleStore; import com.android.settings.applications.AppInfoBase; import com.android.settings.applications.AppLocaleUtil; +import com.android.settings.testutils.FakeFeatureFactory; import org.junit.After; import org.junit.Before; @@ -79,6 +83,7 @@ import java.util.Locale; public class AppLocalePickerActivityTest { private static final String TEST_PACKAGE_NAME = "com.android.settings"; private static final Uri TEST_PACKAGE_URI = Uri.parse("package:" + TEST_PACKAGE_NAME); + private FakeFeatureFactory mFeatureFactory; @Mock LocaleStore.LocaleInfo mLocaleInfo; @@ -99,6 +104,7 @@ public class AppLocalePickerActivityTest { when(mLocaleConfig.getStatus()).thenReturn(LocaleConfig.STATUS_SUCCESS); when(mLocaleConfig.getSupportedLocales()).thenReturn(LocaleList.forLanguageTags("en-US")); ReflectionHelpers.setStaticField(AppLocaleUtil.class, "sLocaleConfig", mLocaleConfig); + mFeatureFactory = FakeFeatureFactory.setupForTest(); } @After @@ -210,6 +216,37 @@ public class AppLocalePickerActivityTest { assertThat(controller.get().isFinishing()).isTrue(); } + @Test + public void onLocaleSelected_logLocaleSource() { + ActivityController controller = + initActivityController(true); + LocaleList.setDefault(LocaleList.forLanguageTags("ja-JP,en-CA,en-US")); + Locale locale = new Locale("en", "US"); + when(mLocaleInfo.getLocale()).thenReturn(locale); + when(mLocaleInfo.isSystemLocale()).thenReturn(false); + when(mLocaleInfo.isSuggested()).thenReturn(true); + when(mLocaleInfo.isSuggestionOfType(LocaleStore.LocaleInfo.SUGGESTION_TYPE_SIM)).thenReturn( + true); + when(mLocaleInfo.isSuggestionOfType( + LocaleStore.LocaleInfo.SUGGESTION_TYPE_SYSTEM_AVAILABLE_LANGUAGE)).thenReturn( + true); + when(mLocaleInfo.isSuggestionOfType( + LocaleStore.LocaleInfo.SUGGESTION_TYPE_OTHER_APP_LANGUAGE)).thenReturn( + true); + when(mLocaleInfo.isSuggestionOfType( + LocaleStore.LocaleInfo.SUGGESTION_TYPE_IME_LANGUAGE)).thenReturn( + true); + + controller.create(); + AppLocalePickerActivity mActivity = controller.get(); + mActivity.onLocaleSelected(mLocaleInfo); + + int localeSource = 15; // SIM_LOCALE | SYSTEM_LOCALE |IME_LOCALE|APP_LOCALE + verify(mFeatureFactory.metricsFeatureProvider).action( + any(), eq(SettingsEnums.ACTION_CHANGE_APP_LANGUAGE_FROM_SUGGESTED), + eq(localeSource)); + } + private ActivityController initActivityController( boolean hasPackageName) { Intent data = new Intent(); From 6358f613cfdd632e3f794fa513cfffde13b89795 Mon Sep 17 00:00:00 2001 From: Zoey Chen Date: Fri, 30 Jun 2023 02:59:10 +0000 Subject: [PATCH 08/10] [Panlingual] Change the metric's string in Languages Bug: 279915462 Test: manual Change-Id: I8e59f9acb068640da64d2a29cc6074b388994554 --- .../settings/localepicker/LocaleDragAndDropAdapter.java | 4 ++-- src/com/android/settings/localepicker/LocaleListEditor.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java b/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java index 6c254d954c1..3d7976ab624 100644 --- a/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java +++ b/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java @@ -216,7 +216,7 @@ class LocaleDragAndDropAdapter if (fromPosition != toPosition) { FeatureFactory.getFactory(mContext).getMetricsFeatureProvider() .action(mContext, SettingsEnums.ACTION_REORDER_LANGUAGE, - mDragLocale.getLocale().getDisplayName() + " move to " + toPosition); + mDragLocale.getLocale().toLanguageTag() + " move to " + toPosition); } notifyItemChanged(fromPosition); // to update the numbers @@ -259,7 +259,7 @@ class LocaleDragAndDropAdapter if (localeInfo.getChecked()) { FeatureFactory.getFactory(mContext).getMetricsFeatureProvider() .action(mContext, SettingsEnums.ACTION_REMOVE_LANGUAGE, - localeInfo.getLocale().getDisplayName()); + localeInfo.getLocale().toLanguageTag()); mFeedItemList.remove(i); } } diff --git a/src/com/android/settings/localepicker/LocaleListEditor.java b/src/com/android/settings/localepicker/LocaleListEditor.java index 17d3a6aa996..dfdb9428f9c 100644 --- a/src/com/android/settings/localepicker/LocaleListEditor.java +++ b/src/com/android/settings/localepicker/LocaleListEditor.java @@ -203,7 +203,7 @@ public class LocaleListEditor extends RestrictedSettingsFragment implements View mAdapter.addLocale(localeInfo); updateVisibilityOfRemoveMenu(); mMetricsFeatureProvider.action(getContext(), SettingsEnums.ACTION_ADD_LANGUAGE, - localeInfo.getLocale().getDisplayName()); + localeInfo.getLocale().toLanguageTag()); } else if (requestCode == DIALOG_CONFIRM_SYSTEM_DEFAULT) { localeInfo = mAdapter.getFeedItemList().get(0); if (resultCode == Activity.RESULT_OK) { @@ -218,7 +218,7 @@ public class LocaleListEditor extends RestrictedSettingsFragment implements View localeDialogFragment.show(mFragmentManager, TAG_DIALOG_NOT_AVAILABLE); mMetricsFeatureProvider.action(getContext(), SettingsEnums.ACTION_NOT_SUPPORTED_SYSTEM_LANGUAGE, - localeInfo.getLocale().getDisplayName()); + localeInfo.getLocale().toLanguageTag()); } } else { mAdapter.notifyListChanged(localeInfo); From 36d71f8785392ca664ae1af18fde40d348e1716f Mon Sep 17 00:00:00 2001 From: Quang Anh Luong Date: Fri, 30 Jun 2023 12:21:30 +0900 Subject: [PATCH 09/10] Check P2P channel before requesting network info WifiP2pSettings requests network info from WifiP2pManager whenever it gets onDeviceInfoAvailable, but sChannel may be null causing an IllegalArgumentException. Check that sChannel is not null before requesting network info. Bug: 289004627 Test: atest WifiP2pSettingsTest Change-Id: Ied8c3f8a894683d7b8e368e5c52343adb7d05e4b --- src/com/android/settings/wifi/p2p/WifiP2pSettings.java | 3 +++ .../android/settings/wifi/p2p/WifiP2pSettingsTest.java | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/src/com/android/settings/wifi/p2p/WifiP2pSettings.java b/src/com/android/settings/wifi/p2p/WifiP2pSettings.java index c2111d64e02..1a268f5152b 100644 --- a/src/com/android/settings/wifi/p2p/WifiP2pSettings.java +++ b/src/com/android/settings/wifi/p2p/WifiP2pSettings.java @@ -617,6 +617,9 @@ public class WifiP2pSettings extends DashboardFragment } private void onDeviceAvailable() { + if (mWifiP2pManager == null || sChannel == null) { + return; + } mWifiP2pManager.requestNetworkInfo(sChannel, networkInfo -> { if (sChannel == null) return; mWifiP2pManager.requestConnectionInfo(sChannel, wifip2pinfo -> { diff --git a/tests/robotests/src/com/android/settings/wifi/p2p/WifiP2pSettingsTest.java b/tests/robotests/src/com/android/settings/wifi/p2p/WifiP2pSettingsTest.java index fbe184d7270..25a59a9e158 100644 --- a/tests/robotests/src/com/android/settings/wifi/p2p/WifiP2pSettingsTest.java +++ b/tests/robotests/src/com/android/settings/wifi/p2p/WifiP2pSettingsTest.java @@ -21,6 +21,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -150,6 +151,13 @@ public class WifiP2pSettingsTest { verify(mWifiP2pManager, times(1)).requestNetworkInfo(any(), any()); } + @Test + public void onDeviceInfoAvailable_nullChannel_shouldBeIgnored() { + mFragment.sChannel = null; + mFragment.onDeviceInfoAvailable(mock(WifiP2pDevice.class)); + verify(mWifiP2pManager, never()).requestNetworkInfo(any(), any()); + } + @Test public void beSearching_getP2pStateDisabledIntent_shouldBeFalse() { final Bundle bundle = new Bundle(); From 0b3da89a91e2749976ea5c7cff3b83a54a2d62c9 Mon Sep 17 00:00:00 2001 From: tom hsu Date: Wed, 3 May 2023 20:54:42 +0800 Subject: [PATCH 10/10] [Settings] Fix inactive SIM show display name with subectiption id. - Cached active SIM' display name with last 4 digits phone number to avoid that this active SIM change to inactive SIM then show the display name with subscription id. Bug: 271567615 Test: Manual test passed Test: atest passed Change-Id: I119d60ab4e92b1f04fb42f96df10bc81aa378a7c --- .../settings/network/SubscriptionUtil.java | 71 ++++++++++++++----- .../network/SubscriptionUtilTest.java | 39 +++++++++- 2 files changed, 91 insertions(+), 19 deletions(-) diff --git a/src/com/android/settings/network/SubscriptionUtil.java b/src/com/android/settings/network/SubscriptionUtil.java index 9d953bf3139..0cd12fe6354 100644 --- a/src/com/android/settings/network/SubscriptionUtil.java +++ b/src/com/android/settings/network/SubscriptionUtil.java @@ -23,6 +23,7 @@ import static com.android.internal.util.CollectionUtils.emptyIfNull; import android.annotation.Nullable; import android.content.Context; +import android.content.SharedPreferences; import android.os.ParcelUuid; import android.provider.Settings; import android.telephony.PhoneNumberUtils; @@ -61,6 +62,10 @@ import java.util.stream.Stream; public class SubscriptionUtil { private static final String TAG = "SubscriptionUtil"; private static final String PROFILE_GENERIC_DISPLAY_NAME = "CARD"; + @VisibleForTesting + static final String SUB_ID = "sub_id"; + @VisibleForTesting + static final String KEY_UNIQUE_SUBSCRIPTION_DISPLAYNAME = "unique_subscription_displayName"; private static List sAvailableResultsForTesting; private static List sActiveResultsForTesting; @@ -265,20 +270,21 @@ public class SubscriptionUtil { // Map of SubscriptionId to DisplayName final Supplier> originalInfos = () -> getAvailableSubscriptions(context) - .stream() - .filter(i -> { - // Filter out null values. - return (i != null && i.getDisplayName() != null); - }) - .map(i -> { - DisplayInfo info = new DisplayInfo(); - info.subscriptionInfo = i; - String displayName = i.getDisplayName().toString(); - info.originalName = TextUtils.equals(displayName, PROFILE_GENERIC_DISPLAY_NAME) - ? context.getResources().getString(R.string.sim_card) - : displayName.trim(); - return info; - }); + .stream() + .filter(i -> { + // Filter out null values. + return (i != null && i.getDisplayName() != null); + }) + .map(i -> { + DisplayInfo info = new DisplayInfo(); + info.subscriptionInfo = i; + String displayName = i.getDisplayName().toString(); + info.originalName = + TextUtils.equals(displayName, PROFILE_GENERIC_DISPLAY_NAME) + ? context.getResources().getString(R.string.sim_card) + : displayName.trim(); + return info; + }); // TODO(goldmanj) consider using a map of DisplayName to SubscriptionInfos. // A Unique set of display names @@ -292,6 +298,14 @@ public class SubscriptionUtil { // If a display name is duplicate, append the final 4 digits of the phone number. // Creates a mapping of Subscription id to original display name + phone number display name final Supplier> uniqueInfos = () -> originalInfos.get().map(info -> { + String cachedDisplayName = getDisplayNameFromSharedPreference( + context, info.subscriptionInfo.getSubscriptionId()); + if (!TextUtils.isEmpty(cachedDisplayName)) { + Log.d(TAG, "use cached display name : " + cachedDisplayName); + info.uniqueName = cachedDisplayName; + return info; + } + if (duplicateOriginalNames.contains(info.originalName)) { // This may return null, if the user cannot view the phone number itself. final String phoneNumber = getBidiFormattedPhoneNumber(context, @@ -299,15 +313,17 @@ public class SubscriptionUtil { String lastFourDigits = ""; if (phoneNumber != null) { lastFourDigits = (phoneNumber.length() > 4) - ? phoneNumber.substring(phoneNumber.length() - 4) : phoneNumber; + ? phoneNumber.substring(phoneNumber.length() - 4) : phoneNumber; } - if (TextUtils.isEmpty(lastFourDigits)) { info.uniqueName = info.originalName; } else { info.uniqueName = info.originalName + " " + lastFourDigits; + Log.d(TAG, "Cache display name [" + info.uniqueName + "] for sub id " + + info.subscriptionInfo.getSubscriptionId()); + saveDisplayNameToSharedPreference( + context, info.subscriptionInfo.getSubscriptionId(), info.uniqueName); } - } else { info.uniqueName = info.originalName; } @@ -371,6 +387,27 @@ public class SubscriptionUtil { return getUniqueSubscriptionDisplayName(info.getSubscriptionId(), context); } + + private static SharedPreferences getDisplayNameSharedPreferences(Context context) { + return context.getSharedPreferences( + KEY_UNIQUE_SUBSCRIPTION_DISPLAYNAME, Context.MODE_PRIVATE); + } + + private static SharedPreferences.Editor getDisplayNameSharedPreferenceEditor(Context context) { + return getDisplayNameSharedPreferences(context).edit(); + } + + private static void saveDisplayNameToSharedPreference( + Context context, int subId, CharSequence displayName) { + getDisplayNameSharedPreferenceEditor(context) + .putString(SUB_ID + subId, String.valueOf(displayName)) + .apply(); + } + + private static String getDisplayNameFromSharedPreference(Context context, int subid) { + return getDisplayNameSharedPreferences(context).getString(SUB_ID + subid, ""); + } + public static String getDisplayName(SubscriptionInfo info) { final CharSequence name = info.getDisplayName(); if (name != null) { diff --git a/tests/unit/src/com/android/settings/network/SubscriptionUtilTest.java b/tests/unit/src/com/android/settings/network/SubscriptionUtilTest.java index 63dca7e88eb..f0630423953 100644 --- a/tests/unit/src/com/android/settings/network/SubscriptionUtilTest.java +++ b/tests/unit/src/com/android/settings/network/SubscriptionUtilTest.java @@ -16,26 +16,32 @@ package com.android.settings.network; +import static com.android.settings.network.SubscriptionUtil.KEY_UNIQUE_SUBSCRIPTION_DISPLAYNAME; +import static com.android.settings.network.SubscriptionUtil.SUB_ID; + import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import android.content.Context; +import android.content.SharedPreferences; import android.content.res.Resources; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.text.TextUtils; -import com.android.settings.R; - import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.android.settings.R; + import org.junit.Before; import org.junit.Ignore; import org.junit.Test; @@ -444,6 +450,35 @@ public class SubscriptionUtilTest { assertTrue(TextUtils.isEmpty(name)); } + @Test + public void getUniqueDisplayName_hasRecord_useRecordBeTheResult() { + final SubscriptionInfo info1 = mock(SubscriptionInfo.class); + final SubscriptionInfo info2 = mock(SubscriptionInfo.class); + when(info1.getSubscriptionId()).thenReturn(SUBID_1); + when(info2.getSubscriptionId()).thenReturn(SUBID_2); + when(info1.getDisplayName()).thenReturn(CARRIER_1); + when(info2.getDisplayName()).thenReturn(CARRIER_1); + when(mSubMgr.getAvailableSubscriptionInfoList()).thenReturn( + Arrays.asList(info1, info2)); + + SharedPreferences sp = mock(SharedPreferences.class); + when(mContext.getSharedPreferences( + KEY_UNIQUE_SUBSCRIPTION_DISPLAYNAME, Context.MODE_PRIVATE)).thenReturn(sp); + when(sp.getString(eq(SUB_ID + SUBID_1), anyString())).thenReturn(CARRIER_1 + "6789"); + when(sp.getString(eq(SUB_ID + SUBID_2), anyString())).thenReturn(CARRIER_1 + "4321"); + + + final CharSequence nameOfSub1 = + SubscriptionUtil.getUniqueSubscriptionDisplayName(info1, mContext); + final CharSequence nameOfSub2 = + SubscriptionUtil.getUniqueSubscriptionDisplayName(info2, mContext); + + assertThat(nameOfSub1).isNotNull(); + assertThat(nameOfSub2).isNotNull(); + assertEquals(CARRIER_1 + "6789", nameOfSub1.toString()); + assertEquals(CARRIER_1 + "4321", nameOfSub2.toString()); + } + @Test public void isInactiveInsertedPSim_nullSubInfo_doesNotCrash() { assertThat(SubscriptionUtil.isInactiveInsertedPSim(null)).isFalse();