diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 2295ee3dd1a..551a7dedb69 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -5439,6 +5439,16 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml index 883147a1783..8faf62a123f 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -4580,17 +4580,17 @@ Bounce keys - The keyboard ignores quickly repeated presses of the same key + Ignore quickly repeated presses of the same key - Bounce key threshold + Bounce key delay - Choose the duration of time your keyboard ignores repeated key presses + Choose how long the keyboard ignores repeated keystrokes - 0.2s + 0.2 seconds - 0.4s + 0.4 seconds - 0.6s + 0.6 seconds Custom @@ -4599,7 +4599,7 @@ Slow keys - Adjusts the time it takes for a key press to activate + Change how long you need to hold down a key before it\'s registered Sticky keys @@ -4650,6 +4650,8 @@ Touchpad & mouse Mouse + + Pointer speed, swap buttons, button customisation Pointer speed, gestures @@ -5395,6 +5397,10 @@ High contrast text Change text color to black or white. Maximizes contrast with the background. + + High contrast text has a new look and feel. + + Open Settings Maximize text contrast @@ -8806,13 +8812,13 @@ Full list - + The current default placement is a full shelf and notification stack. Compact - + New notifications are collapsed into a shelf on your lockscreen. @@ -8877,16 +8883,16 @@ When your device is locked, how do you want profile notifications to show? - + Hide seen notifications - + Seen notifications are removed from the lock screen. - + Hide silent notifications - + Silent notifications and conversations are removed from the lock screen. diff --git a/res/xml/accessibility_text_reading_options.xml b/res/xml/accessibility_text_reading_options.xml index 795c4ffb9fc..8eed107d60a 100644 --- a/res/xml/accessibility_text_reading_options.xml +++ b/res/xml/accessibility_text_reading_options.xml @@ -25,7 +25,7 @@ android:key="preview" android:selectable="false"/> - - + + + + mSizeData; - private static final String KEY_SAVED_QS_TOOLTIP_RESHOW = "qs_tooltip_reshow"; private boolean mSeekByTouch; private Optional mInteractionListener = Optional.empty(); - private LabeledSeekBarPreference mSeekBarPreference; + private AccessibilitySeekBarPreference mSeekBarPreference; private int mLastProgress; - private boolean mNeedsQSTooltipReshow = false; - private AccessibilityQuickSettingsTooltipWindow mTooltipWindow; private final Handler mHandler; private String[] mStateLabels = null; @@ -101,30 +96,21 @@ abstract class PreviewSizeSeekBarController extends BasePreferenceController imp } @Override - public void onCreate(Bundle savedInstanceState) { - // Restore the tooltip. - if (savedInstanceState != null - && savedInstanceState.containsKey(KEY_SAVED_QS_TOOLTIP_RESHOW)) { - mNeedsQSTooltipReshow = savedInstanceState.getBoolean(KEY_SAVED_QS_TOOLTIP_RESHOW); + public void onStart() { + if (mSeekBarPreference.getNeedsQSTooltipReshow()) { + mHandler.post(this::showQuickSettingsTooltipIfNeeded); } } + @Override + public void onStop() { + // all the messages/callbacks will be removed. + mHandler.removeCallbacksAndMessages(null); + } + @Override public void onDestroy() { - // remove runnables in the queue. - mHandler.removeCallbacksAndMessages(null); - final boolean isTooltipWindowShowing = mTooltipWindow != null && mTooltipWindow.isShowing(); - if (isTooltipWindowShowing) { - mTooltipWindow.dismiss(); - } - } - - @Override - public void onSaveInstanceState(Bundle outState) { - final boolean isTooltipWindowShowing = mTooltipWindow != null && mTooltipWindow.isShowing(); - if (mNeedsQSTooltipReshow || isTooltipWindowShowing) { - outState.putBoolean(KEY_SAVED_QS_TOOLTIP_RESHOW, /* value= */ true); - } + mSeekBarPreference.dismissTooltip(); } void setInteractionListener(ProgressInteractionListener interactionListener) { @@ -148,9 +134,6 @@ abstract class PreviewSizeSeekBarController extends BasePreferenceController imp mSeekBarPreference.setProgress(initialIndex); mSeekBarPreference.setContinuousUpdates(true); mSeekBarPreference.setOnSeekBarChangeListener(mSeekBarChangeListener); - if (mNeedsQSTooltipReshow) { - mHandler.post(this::showQuickSettingsTooltipIfNeeded); - } setSeekbarStateDescription(mSeekBarPreference.getProgress()); } @@ -216,7 +199,8 @@ abstract class PreviewSizeSeekBarController extends BasePreferenceController imp return; } - if (!mNeedsQSTooltipReshow && AccessibilityQuickSettingUtils.hasValueInSharedPreferences( + if (!mSeekBarPreference.getNeedsQSTooltipReshow() + && AccessibilityQuickSettingUtils.hasValueInSharedPreferences( mContext, tileComponentName)) { // Returns if quick settings tooltip only show once. return; @@ -228,14 +212,15 @@ abstract class PreviewSizeSeekBarController extends BasePreferenceController imp // is not ready when we would like to show the tooltip. If the seekbar is not ready, // we give up showing the tooltip and also do not reshow it in the future. if (mSeekBarPreference.getSeekbar() != null) { - mTooltipWindow = new AccessibilityQuickSettingsTooltipWindow(mContext); - mTooltipWindow.setup(getTileTooltipContent(), + final AccessibilityQuickSettingsTooltipWindow tooltipWindow = + mSeekBarPreference.createTooltipWindow(); + tooltipWindow.setup(getTileTooltipContent(), R.drawable.accessibility_auto_added_qs_tooltip_illustration); - mTooltipWindow.showAtTopCenter(mSeekBarPreference.getSeekbar()); + tooltipWindow.showAtTopCenter(mSeekBarPreference.getSeekbar()); } AccessibilityQuickSettingUtils.optInValueToSharedPreferences(mContext, tileComponentName); - mNeedsQSTooltipReshow = false; + mSeekBarPreference.setNeedsQSTooltipReshow(false); } /** Returns the accessibility Quick Settings tile component name. */ diff --git a/src/com/android/settings/accessibility/TextReadingPreviewController.java b/src/com/android/settings/accessibility/TextReadingPreviewController.java index a983105cfad..99f1f3fa0c3 100644 --- a/src/com/android/settings/accessibility/TextReadingPreviewController.java +++ b/src/com/android/settings/accessibility/TextReadingPreviewController.java @@ -33,7 +33,6 @@ import com.android.settings.accessibility.TextReadingPreferenceFragment.EntryPoi import com.android.settings.core.BasePreferenceController; import com.android.settings.core.instrumentation.SettingsStatsLog; import com.android.settings.display.PreviewPagerAdapter; -import com.android.settings.widget.LabeledSeekBarPreference; import java.util.Objects; @@ -58,8 +57,8 @@ class TextReadingPreviewController extends BasePreferenceController implements private int mLastDisplayProgress; private long mLastCommitTime; private TextReadingPreviewPreference mPreviewPreference; - private LabeledSeekBarPreference mFontSizePreference; - private LabeledSeekBarPreference mDisplaySizePreference; + private AccessibilitySeekBarPreference mFontSizePreference; + private AccessibilitySeekBarPreference mDisplaySizePreference; @EntryPoint private int mEntryPoint; diff --git a/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdater.java b/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdater.java index 14f55b81264..bd160e17527 100644 --- a/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdater.java +++ b/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdater.java @@ -39,6 +39,7 @@ public class AvailableMediaBluetoothDeviceUpdater extends BluetoothDeviceUpdater private final AudioManager mAudioManager; private final LocalBluetoothManager mLocalBtManager; + private int mAudioMode; public AvailableMediaBluetoothDeviceUpdater( Context context, @@ -47,21 +48,23 @@ public class AvailableMediaBluetoothDeviceUpdater extends BluetoothDeviceUpdater super(context, devicePreferenceCallback, metricsCategory); mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); mLocalBtManager = Utils.getLocalBtManager(context); + mAudioMode = mAudioManager.getMode(); } @Override public void onAudioModeChanged() { + // TODO: move to background thread + mAudioMode = mAudioManager.getMode(); forceUpdate(); } @Override public boolean isFilterMatched(CachedBluetoothDevice cachedDevice) { - final int audioMode = mAudioManager.getMode(); final int currentAudioProfile; - if (audioMode == AudioManager.MODE_RINGTONE - || audioMode == AudioManager.MODE_IN_CALL - || audioMode == AudioManager.MODE_IN_COMMUNICATION) { + if (mAudioMode == AudioManager.MODE_RINGTONE + || mAudioMode == AudioManager.MODE_IN_CALL + || mAudioMode == AudioManager.MODE_IN_COMMUNICATION) { // in phone call currentAudioProfile = BluetoothProfile.HEADSET; } else { diff --git a/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdater.java b/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdater.java index 2107569d86b..7cc874caba9 100644 --- a/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdater.java +++ b/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdater.java @@ -39,26 +39,29 @@ public class ConnectedBluetoothDeviceUpdater extends BluetoothDeviceUpdater { private static final String PREF_KEY_PREFIX = "connected_bt_"; private final AudioManager mAudioManager; + private int mAudioMode; public ConnectedBluetoothDeviceUpdater(Context context, DevicePreferenceCallback devicePreferenceCallback, int metricsCategory) { super(context, devicePreferenceCallback, metricsCategory); mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); + mAudioMode = mAudioManager.getMode(); } @Override public void onAudioModeChanged() { + // TODO: move to background thread + mAudioMode = mAudioManager.getMode(); forceUpdate(); } @Override public boolean isFilterMatched(CachedBluetoothDevice cachedDevice) { - final int audioMode = mAudioManager.getMode(); final int currentAudioProfile; - if (audioMode == AudioManager.MODE_RINGTONE - || audioMode == AudioManager.MODE_IN_CALL - || audioMode == AudioManager.MODE_IN_COMMUNICATION) { + if (mAudioMode == AudioManager.MODE_RINGTONE + || mAudioMode == AudioManager.MODE_IN_CALL + || mAudioMode == AudioManager.MODE_IN_COMMUNICATION) { // in phone call currentAudioProfile = BluetoothProfile.HEADSET; } else { diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelper.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelper.java index 25a9135701b..c86222ca362 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelper.java +++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelper.java @@ -21,6 +21,8 @@ import static com.android.settings.connecteddevice.audiosharing.audiostreams.Aud import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamMediaService.DEVICES; import static java.util.Collections.emptyList; +import static java.util.Collections.emptyMap; +import static java.util.stream.Collectors.toMap; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothLeAudioContentMetadata; @@ -48,7 +50,9 @@ import com.google.common.base.Strings; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Optional; +import java.util.function.Function; import java.util.stream.Stream; import javax.annotation.Nullable; @@ -149,6 +153,16 @@ public class AudioStreamsHelper { .toList(); } + /** Retrieves a list of all LE broadcast receive states keyed by each active device. */ + public Map> getAllSourcesByDevice() { + if (mLeBroadcastAssistant == null) { + Log.w(TAG, "getAllSourcesByDevice(): LeBroadcastAssistant is null!"); + return emptyMap(); + } + return getConnectedBluetoothDevices(mBluetoothManager, /* inSharingOnly= */ true).stream() + .collect(toMap(Function.identity(), mLeBroadcastAssistant::getAllSources)); + } + /** Retrieves a list of all LE broadcast receive states from sinks with source present. */ @VisibleForTesting public List getAllPresentSources() { diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallback.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallback.java index f0034316372..87cea2c1e94 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallback.java +++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallback.java @@ -16,7 +16,6 @@ package com.android.settings.connecteddevice.audiosharing.audiostreams; - import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothLeBroadcastMetadata; import android.bluetooth.BluetoothLeBroadcastReceiveState; @@ -43,13 +42,13 @@ public class AudioStreamsProgressCategoryCallback extends AudioStreamsBroadcastA super.onReceiveStateChanged(sink, sourceId, state); if (AudioStreamsHelper.isConnected(state)) { - mCategoryController.handleSourceConnected(state); + mCategoryController.handleSourceConnected(sink, state); } else if (AudioStreamsHelper.isBadCode(state)) { mCategoryController.handleSourceConnectBadCode(state); } else if (BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(mContext) && AudioStreamsHelper.hasSourcePresent(state)) { // Keep this check as the last, source might also present in above states - mCategoryController.handleSourcePresent(state); + mCategoryController.handleSourcePresent(sink, state); } } diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java index f0a0c5b8f7f..6831c5a12e3 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java +++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java @@ -17,10 +17,12 @@ package com.android.settings.connecteddevice.audiosharing.audiostreams; import static java.util.Collections.emptyList; +import static java.util.stream.Collectors.toMap; import android.app.AlertDialog; import android.app.settings.SettingsEnums; import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothLeBroadcastMetadata; import android.bluetooth.BluetoothLeBroadcastReceiveState; import android.bluetooth.BluetoothProfile; @@ -49,6 +51,8 @@ import com.android.settingslib.utils.ThreadUtils; import java.util.Comparator; import java.util.List; +import java.util.Map; +import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; import java.util.concurrent.Executors; @@ -391,34 +395,19 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro // Expect one of the following: // 1) No preference existed, create new preference with state SOURCE_ADDED // 2) Any other state, move to SOURCE_ADDED - void handleSourceConnected(BluetoothLeBroadcastReceiveState receiveState) { + void handleSourceConnected( + BluetoothDevice device, BluetoothLeBroadcastReceiveState receiveState) { if (DEBUG) { Log.d(TAG, "handleSourceConnected()"); } if (!AudioStreamsHelper.isConnected(receiveState)) { return; } - var broadcastIdConnected = receiveState.getBroadcastId(); - if (mSourceFromQrCode != null && mSourceFromQrCode.getBroadcastId() == UNSET_BROADCAST_ID) { - // mSourceFromQrCode could have no broadcast Id, we fill in the broadcast Id from the - // connected source receiveState. - if (DEBUG) { - Log.d( - TAG, - "handleSourceConnected() : processing mSourceFromQrCode with broadcastId" - + " unset"); - } - boolean updated = - maybeUpdateId( - AudioStreamsHelper.getBroadcastName(receiveState), - receiveState.getBroadcastId()); - if (updated && mBroadcastIdToPreferenceMap.containsKey(UNSET_BROADCAST_ID)) { - var preference = mBroadcastIdToPreferenceMap.remove(UNSET_BROADCAST_ID); - mBroadcastIdToPreferenceMap.put(receiveState.getBroadcastId(), preference); - } - } - + Optional metadata = + getMetadataMatchingByBroadcastId( + device, receiveState.getSourceId(), broadcastIdConnected); + handleQrCodeWithUnsetBroadcastIdIfNeeded(metadata, receiveState); mBroadcastIdToPreferenceMap.compute( broadcastIdConnected, (k, existingPreference) -> { @@ -428,7 +417,12 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro // we retrieves the connected source during onStart() from // AudioStreamsHelper#getAllConnectedSources() even before the source is // founded by scanning. - return addNewPreference(receiveState, AudioStreamState.SOURCE_ADDED); + return metadata.isPresent() + ? addNewPreference( + metadata.get(), + AudioStreamState.SOURCE_ADDED, + SourceOriginForLogging.UNKNOWN) + : addNewPreference(receiveState, AudioStreamState.SOURCE_ADDED); } if (existingPreference.getAudioStreamState() == AudioStreamState.WAIT_FOR_SYNC && existingPreference.getAudioStreamBroadcastId() == UNSET_BROADCAST_ID @@ -473,7 +467,8 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro // Find preference by receiveState and decide next state. // Expect one preference existed, move to SOURCE_PRESENT - void handleSourcePresent(BluetoothLeBroadcastReceiveState receiveState) { + void handleSourcePresent( + BluetoothDevice device, BluetoothLeBroadcastReceiveState receiveState) { if (DEBUG) { Log.d(TAG, "handleSourcePresent()"); } @@ -482,25 +477,10 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro } var broadcastIdConnected = receiveState.getBroadcastId(); - if (mSourceFromQrCode != null && mSourceFromQrCode.getBroadcastId() == UNSET_BROADCAST_ID) { - // mSourceFromQrCode could have no broadcast Id, we fill in the broadcast Id from the - // connected source receiveState. - if (DEBUG) { - Log.d( - TAG, - "handleSourcePresent() : processing mSourceFromQrCode with broadcastId" - + " unset"); - } - boolean updated = - maybeUpdateId( - AudioStreamsHelper.getBroadcastName(receiveState), - receiveState.getBroadcastId()); - if (updated && mBroadcastIdToPreferenceMap.containsKey(UNSET_BROADCAST_ID)) { - var preference = mBroadcastIdToPreferenceMap.remove(UNSET_BROADCAST_ID); - mBroadcastIdToPreferenceMap.put(receiveState.getBroadcastId(), preference); - } - } - + Optional metadata = + getMetadataMatchingByBroadcastId( + device, receiveState.getSourceId(), broadcastIdConnected); + handleQrCodeWithUnsetBroadcastIdIfNeeded(metadata, receiveState); mBroadcastIdToPreferenceMap.compute( broadcastIdConnected, (k, existingPreference) -> { @@ -511,7 +491,12 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro // we retrieves the connected source during onStart() from // AudioStreamsHelper#getAllPresentSources() even before the source is // founded by scanning. - return addNewPreference(receiveState, AudioStreamState.SOURCE_PRESENT); + return metadata.isPresent() + ? addNewPreference( + metadata.get(), + AudioStreamState.SOURCE_PRESENT, + SourceOriginForLogging.UNKNOWN) + : addNewPreference(receiveState, AudioStreamState.SOURCE_PRESENT); } if (existingPreference.getAudioStreamState() == AudioStreamState.WAIT_FOR_SYNC && existingPreference.getAudioStreamBroadcastId() == UNSET_BROADCAST_ID @@ -598,28 +583,85 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro // Handle QR code scan, display currently connected streams then start scanning // sequentially handleSourceFromQrCodeIfExists(); + Map> sources = + mAudioStreamsHelper.getAllSourcesByDevice(); + Map> connectedSources = + getConnectedSources(sources); if (isAudioSharingHysteresisModeFixAvailable(mContext)) { // With hysteresis mode, we prioritize showing connected sources first. // If no connected sources are found, we then show present sources. - List sources = - mAudioStreamsHelper.getAllConnectedSources(); - if (!sources.isEmpty()) { - sources.forEach(this::handleSourceConnected); + if (!connectedSources.isEmpty()) { + connectedSources.forEach( + (device, stateList) -> + stateList.forEach( + state -> handleSourceConnected(device, state))); } else { - mAudioStreamsHelper - .getAllPresentSources() - .forEach(this::handleSourcePresent); + Map> + presentSources = getPresentSources(sources); + presentSources.forEach( + (device, stateList) -> + stateList.forEach( + state -> handleSourcePresent(device, state))); } } else { - mAudioStreamsHelper - .getAllConnectedSources() - .forEach(this::handleSourceConnected); + connectedSources.forEach( + (device, stateList) -> + stateList.forEach( + state -> handleSourceConnected(device, state))); } mLeBroadcastAssistant.startSearchingForSources(emptyList()); mMediaControlHelper.start(); }); } + private Map> getConnectedSources( + Map> sources) { + return sources.entrySet().stream() + .filter( + entry -> + entry.getValue().stream().anyMatch(AudioStreamsHelper::isConnected)) + .collect(toMap(Map.Entry::getKey, Map.Entry::getValue)); + } + + private Map> getPresentSources( + Map> sources) { + return sources.entrySet().stream() + .filter( + entry -> + entry.getValue().stream() + .anyMatch(AudioStreamsHelper::hasSourcePresent)) + .collect(toMap(Map.Entry::getKey, Map.Entry::getValue)); + } + + private Optional getMetadataMatchingByBroadcastId( + BluetoothDevice device, int sourceId, int broadcastId) { + return Optional.ofNullable( + mLeBroadcastAssistant != null + ? mLeBroadcastAssistant.getSourceMetadata(device, sourceId) + : null) + .filter(m -> m.getBroadcastId() == broadcastId); + } + + private void handleQrCodeWithUnsetBroadcastIdIfNeeded( + Optional metadata, + BluetoothLeBroadcastReceiveState receiveState) { + if (mSourceFromQrCode != null && mSourceFromQrCode.getBroadcastId() == UNSET_BROADCAST_ID) { + if (DEBUG) { + Log.d(TAG, "Processing mSourceFromQrCode with unset broadcastId"); + } + boolean updated = + maybeUpdateId( + metadata.isPresent() + ? AudioStreamsHelper.getBroadcastName(metadata.get()) + : AudioStreamsHelper.getBroadcastName(receiveState), + receiveState.getBroadcastId()); + if (updated && mBroadcastIdToPreferenceMap.containsKey(UNSET_BROADCAST_ID)) { + var preference = mBroadcastIdToPreferenceMap.remove(UNSET_BROADCAST_ID); + mBroadcastIdToPreferenceMap.put(receiveState.getBroadcastId(), preference); + } + } + } + private void stopScanning() { if (mLeBroadcastAssistant == null) { Log.w(TAG, "stopScanning(): LeBroadcastAssistant is null!"); diff --git a/src/com/android/settings/inputmethod/TouchpadAndMouseSettingsController.java b/src/com/android/settings/inputmethod/TouchpadAndMouseSettingsController.java index 86e044c4291..163027bf54e 100644 --- a/src/com/android/settings/inputmethod/TouchpadAndMouseSettingsController.java +++ b/src/com/android/settings/inputmethod/TouchpadAndMouseSettingsController.java @@ -26,6 +26,7 @@ import androidx.preference.Preference; import com.android.settings.core.BasePreferenceController; import com.android.settings.core.PreferenceControllerMixin; +import com.android.settings.keyboard.Flags; import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.core.lifecycle.events.OnStart; import com.android.settingslib.core.lifecycle.events.OnStop; @@ -85,13 +86,15 @@ public class TouchpadAndMouseSettingsController extends BasePreferenceController @Override public int getAvailabilityStatus() { + boolean isNewPageFlagDisabled = !Flags.keyboardAndTouchpadA11yNewPageEnabled(); boolean isFeatureOn = FeatureFlagUtils .isEnabled(mContext, FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_TRACKPAD); boolean isTouchpad = InputPeripheralsSettingsUtils.isTouchpad(); boolean isPointerCustomizationEnabled = android.view.flags.Flags.enableVectorCursorA11ySettings(); boolean isMouse = InputPeripheralsSettingsUtils.isMouse(); - return (isFeatureOn && isTouchpad) || (isPointerCustomizationEnabled && isMouse) ? AVAILABLE + return ((isFeatureOn && isTouchpad) || (isPointerCustomizationEnabled && isMouse)) + && isNewPageFlagDisabled ? AVAILABLE : CONDITIONALLY_UNAVAILABLE; } } diff --git a/src/com/android/settings/network/NetworkProviderSettings.java b/src/com/android/settings/network/NetworkProviderSettings.java index c776987856e..1fc91014f10 100644 --- a/src/com/android/settings/network/NetworkProviderSettings.java +++ b/src/com/android/settings/network/NetworkProviderSettings.java @@ -706,7 +706,7 @@ public class NetworkProviderSettings extends RestrictedDashboardFragment forget(mSelectedWifiEntry); return true; case MENU_ID_SHARE: - WifiDppUtils.showLockScreen(getContext(), + WifiDppUtils.showLockScreenForWifiSharing(getContext(), () -> launchWifiDppConfiguratorActivity(mSelectedWifiEntry)); return true; case MENU_ID_MODIFY: diff --git a/src/com/android/settings/network/OWNERS b/src/com/android/settings/network/OWNERS index a63a825b0ea..ad3c9dada67 100644 --- a/src/com/android/settings/network/OWNERS +++ b/src/com/android/settings/network/OWNERS @@ -1,12 +1,8 @@ # Default reviewers for this and subdirectories. -allenwtsu@google.com -andychou@google.com -bonianchen@google.com -changbetty@google.com -leechou@google.com +chaohuiw@google.com +evanwu@google.com songferngwang@google.com tomhsu@google.com wengsu@google.com -zoeychen@google.com # Emergency approvers in case the above are not available diff --git a/src/com/android/settings/service/PreferenceService.kt b/src/com/android/settings/service/PreferenceService.kt index 3a677629afd..d07e78d333e 100644 --- a/src/com/android/settings/service/PreferenceService.kt +++ b/src/com/android/settings/service/PreferenceService.kt @@ -16,6 +16,7 @@ package com.android.settings.service +import android.app.Application import android.os.Binder import android.os.OutcomeReceiver import android.os.Process @@ -26,38 +27,49 @@ import android.service.settings.preferences.MetadataResult import android.service.settings.preferences.SetValueRequest import android.service.settings.preferences.SetValueResult import android.service.settings.preferences.SettingsPreferenceService +import com.android.settingslib.graph.GetPreferenceGraphApiHandler +import com.android.settingslib.graph.GetPreferenceGraphRequest import com.android.settingslib.graph.PreferenceGetterApiHandler +import com.android.settingslib.graph.PreferenceGetterFlags import com.android.settingslib.graph.PreferenceSetterApiHandler import com.android.settingslib.ipc.ApiPermissionChecker import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job +import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.launch import java.lang.Exception class PreferenceService : SettingsPreferenceService() { - private val scope = CoroutineScope(Job() + Dispatchers.Main) + private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO) private val getApiHandler = PreferenceGetterApiHandler(1, ApiPermissionChecker.alwaysAllow()) private val setApiHandler = PreferenceSetterApiHandler(2, ApiPermissionChecker.alwaysAllow()) + private val graphApi = GraphProvider(3) override fun onGetAllPreferenceMetadata( request: MetadataRequest, callback: OutcomeReceiver ) { - // TODO(379750656): Update graph API to be usable outside SettingsLib - callback.onError(UnsupportedOperationException("Not yet supported")) + scope.launch { + val graphProto = graphApi.invoke(application, Process.myUid(), Binder.getCallingUid(), + GetPreferenceGraphRequest( + includeValue = false, + flags = PreferenceGetterFlags.METADATA + )) + val result = transformCatalystGetMetadataResponse(this@PreferenceService, graphProto) + callback.onResult(result) + } } override fun onGetPreferenceValue( request: GetValueRequest, callback: OutcomeReceiver ) { - scope.launch(Dispatchers.IO) { + scope.launch { val apiRequest = transformFrameworkGetValueRequest(request) val response = getApiHandler.invoke(application, Process.myUid(), - Binder.getCallingPid(), apiRequest) + Binder.getCallingUid(), apiRequest) val result = transformCatalystGetValueResponse( this@PreferenceService, request, @@ -75,7 +87,7 @@ class PreferenceService : SettingsPreferenceService() { request: SetValueRequest, callback: OutcomeReceiver ) { - scope.launch(Dispatchers.IO) { + scope.launch { val apiRequest = transformFrameworkSetValueRequest(request) if (apiRequest == null) { callback.onResult( @@ -83,10 +95,20 @@ class PreferenceService : SettingsPreferenceService() { ) } else { val response = setApiHandler.invoke(application, Process.myUid(), - Binder.getCallingPid(), apiRequest) + Binder.getCallingUid(), apiRequest) callback.onResult(transformCatalystSetValueResponse(response)) } } } + + // Basic implementation - we already have permission to access Graph for Metadata via superclass + private class GraphProvider(override val id: Int) : GetPreferenceGraphApiHandler(emptySet()) { + override fun hasPermission( + application: Application, + myUid: Int, + callingUid: Int, + request: GetPreferenceGraphRequest + ) = true + } } diff --git a/src/com/android/settings/service/PreferenceServiceRequestTransformer.kt b/src/com/android/settings/service/PreferenceServiceRequestTransformer.kt index 7a4c7fc9dff..18307e0baf3 100644 --- a/src/com/android/settings/service/PreferenceServiceRequestTransformer.kt +++ b/src/com/android/settings/service/PreferenceServiceRequestTransformer.kt @@ -19,6 +19,7 @@ package com.android.settings.service import android.content.Context import android.service.settings.preferences.GetValueRequest import android.service.settings.preferences.GetValueResult +import android.service.settings.preferences.MetadataResult import android.service.settings.preferences.SetValueRequest import android.service.settings.preferences.SetValueResult import android.service.settings.preferences.SettingsPreferenceMetadata @@ -34,9 +35,55 @@ import com.android.settingslib.graph.preferenceValueProto import com.android.settingslib.graph.proto.PreferenceProto import com.android.settingslib.graph.proto.PreferenceValueProto import com.android.settingslib.graph.getText +import com.android.settingslib.graph.proto.PreferenceGraphProto +import com.android.settingslib.graph.proto.PreferenceOrGroupProto import com.android.settingslib.graph.toIntent import com.android.settingslib.metadata.SensitivityLevel +/** Transform Catalyst Graph result to Framework GET METADATA result */ +fun transformCatalystGetMetadataResponse( + context: Context, + graph: PreferenceGraphProto +): MetadataResult { + val preferences = mutableSetOf() + // recursive function to visit all nodes in preference group + fun traverseGroupOrPref( + screenKey: String, + groupOrPref: PreferenceOrGroupProto, + ) { + when (groupOrPref.kindCase) { + PreferenceOrGroupProto.KindCase.PREFERENCE -> + preferences.add( + PreferenceWithScreen(screenKey, groupOrPref.preference) + ) + PreferenceOrGroupProto.KindCase.GROUP -> { + for (child in groupOrPref.group.preferencesList) { + traverseGroupOrPref(screenKey, child) + } + } + else -> {} + } + } + // traverse all screens and all preferences on screen + for ((screenKey, screen) in graph.screensMap) { + for (groupOrPref in screen.root.preferencesList) { + traverseGroupOrPref(screenKey, groupOrPref) + } + } + + return if (preferences.isNotEmpty()) { + MetadataResult.Builder(MetadataResult.RESULT_OK) + .setMetadataList( + preferences.map { + it.preference.toMetadata(context, it.screenKey) + } + ) + .build() + } else { + MetadataResult.Builder(MetadataResult.RESULT_UNSUPPORTED).build() + } +} + /** Translate Framework GET VALUE request to Catalyst GET VALUE request */ fun transformFrameworkGetValueRequest( request: GetValueRequest, @@ -133,6 +180,11 @@ fun transformCatalystSetValueResponse(@PreferenceSetterResult response: Int): Se return SetValueResult.Builder(resultCode).build() } +private data class PreferenceWithScreen( + val screenKey: String, + val preference: PreferenceProto, +) + private fun PreferenceProto.toMetadata( context: Context, screenKey: String diff --git a/src/com/android/settings/wifi/details2/AddDevicePreferenceController2.java b/src/com/android/settings/wifi/details2/AddDevicePreferenceController2.java index 8f9741a6b34..4ffe279d6d4 100644 --- a/src/com/android/settings/wifi/details2/AddDevicePreferenceController2.java +++ b/src/com/android/settings/wifi/details2/AddDevicePreferenceController2.java @@ -57,7 +57,8 @@ public class AddDevicePreferenceController2 extends BasePreferenceController { @Override public boolean handlePreferenceTreeClick(Preference preference) { if (KEY_ADD_DEVICE.equals(preference.getKey())) { - WifiDppUtils.showLockScreen(mContext, () -> launchWifiDppConfiguratorQrCodeScanner()); + WifiDppUtils.showLockScreenForWifiSharing(mContext, + () -> launchWifiDppConfiguratorQrCodeScanner()); return true; /* click is handled */ } diff --git a/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2.java b/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2.java index a8d7f417a4a..ecddecfce74 100644 --- a/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2.java +++ b/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2.java @@ -980,7 +980,8 @@ public class WifiDetailPreferenceController2 extends AbstractPreferenceControlle * Share the wifi network with QR code. */ private void shareNetwork() { - WifiDppUtils.showLockScreen(mContext, () -> launchWifiDppConfiguratorActivity()); + WifiDppUtils.showLockScreenForWifiSharing(mContext, + () -> launchWifiDppConfiguratorActivity()); } /** diff --git a/src/com/android/settings/wifi/dpp/WifiDppUtils.java b/src/com/android/settings/wifi/dpp/WifiDppUtils.java index 23a6a5423e4..24ab496cc3f 100644 --- a/src/com/android/settings/wifi/dpp/WifiDppUtils.java +++ b/src/com/android/settings/wifi/dpp/WifiDppUtils.java @@ -16,6 +16,8 @@ package com.android.settings.wifi.dpp; +import android.annotation.NonNull; +import android.annotation.SuppressLint; import android.app.KeyguardManager; import android.content.Context; import android.content.Intent; @@ -33,6 +35,9 @@ import android.os.Vibrator; import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.KeyProperties; import android.text.TextUtils; +import android.util.Log; + +import androidx.annotation.VisibleForTesting; import com.android.settings.R; import com.android.settings.Utils; @@ -58,6 +63,8 @@ import javax.crypto.SecretKey; * @see WifiQrCode */ public class WifiDppUtils { + private static final String TAG = "WifiDppUtils"; + /** * The fragment tag specified to FragmentManager for container activities to manage fragments. */ @@ -109,7 +116,15 @@ public class WifiDppUtils { private static final Duration VIBRATE_DURATION_QR_CODE_RECOGNITION = Duration.ofMillis(3); - private static final String AES_CBC_PKCS7_PADDING = "AES/CBC/PKCS7Padding"; + /** + * Parameters to check whether the device has been locked recently + */ + @VisibleForTesting + public static final String AES_CBC_PKCS7_PADDING = "AES/CBC/PKCS7Padding"; + @VisibleForTesting + public static final String WIFI_SHARING_KEY_ALIAS = "wifi_sharing_auth_key"; + @VisibleForTesting + public static final int WIFI_SHARING_MAX_UNLOCK_SECONDS = 60; /** * Returns whether the device support WiFi DPP. @@ -426,51 +441,75 @@ public class WifiDppUtils { * Shows authentication screen to confirm credentials (pin, pattern or password) for the current * user of the device. * - * @param context The {@code Context} used to get {@code KeyguardManager} service + * @param context The {@code Context} used to get {@code KeyguardManager} service * @param successRunnable The {@code Runnable} which will be executed if the user does not setup * device security or if lock screen is unlocked */ - public static void showLockScreen(Context context, Runnable successRunnable) { - final KeyguardManager keyguardManager = (KeyguardManager) context.getSystemService( - Context.KEYGUARD_SERVICE); - - if (keyguardManager.isKeyguardSecure()) { - final BiometricPrompt.AuthenticationCallback authenticationCallback = - new BiometricPrompt.AuthenticationCallback() { - @Override - public void onAuthenticationSucceeded( - BiometricPrompt.AuthenticationResult result) { - successRunnable.run(); - } - - @Override - public void onAuthenticationError(int errorCode, CharSequence errString) { - //Do nothing - } - }; - - final int userId = UserHandle.myUserId(); - - final BiometricPrompt.Builder builder = new BiometricPrompt.Builder(context) - .setTitle(context.getText(R.string.wifi_dpp_lockscreen_title)); - - if (keyguardManager.isDeviceSecure()) { - builder.setDeviceCredentialAllowed(true); - builder.setTextForDeviceCredential( - null /* title */, - Utils.getConfirmCredentialStringForUser( - context, userId, Utils.getCredentialType(context, userId)), - null /* description */); - } - - final BiometricPrompt bp = builder.build(); - final Handler handler = new Handler(Looper.getMainLooper()); - bp.authenticate(new CancellationSignal(), - runnable -> handler.post(runnable), - authenticationCallback); - } else { + public static void showLockScreen(@NonNull Context context, @NonNull Runnable successRunnable) { + KeyguardManager keyguardManager = context.getSystemService(KeyguardManager.class); + if (keyguardManager == null || !keyguardManager.isKeyguardSecure()) { successRunnable.run(); + return; } + showLockScreen(context, successRunnable, keyguardManager); + } + + /** + * Shows authentication screen to confirm credentials (pin, pattern or password) for the + * current user of the device. But if the device has been unlocked recently, the + * authentication screen will be skipped. + * + * @param context The {@code Context} used to get {@code KeyguardManager} service + * @param successRunnable The {@code Runnable} which will be executed if the user does not setup + * device security or if lock screen is unlocked + */ + public static void showLockScreenForWifiSharing(@NonNull Context context, + @NonNull Runnable successRunnable) { + KeyguardManager keyguardManager = context.getSystemService(KeyguardManager.class); + if (keyguardManager == null || !keyguardManager.isKeyguardSecure()) { + successRunnable.run(); + return; + } + if (isUnlockedWithinSeconds(WIFI_SHARING_KEY_ALIAS, WIFI_SHARING_MAX_UNLOCK_SECONDS)) { + Log.d(TAG, "Bypassing the lock screen because the device was unlocked recently."); + successRunnable.run(); + return; + } + showLockScreen(context, successRunnable, keyguardManager); + } + + @SuppressLint("MissingPermission") + private static void showLockScreen(@NonNull Context context, @NonNull Runnable successRunnable, + @NonNull KeyguardManager keyguardManager) { + BiometricPrompt.AuthenticationCallback authenticationCallback = + new BiometricPrompt.AuthenticationCallback() { + @Override + public void onAuthenticationSucceeded( + BiometricPrompt.AuthenticationResult result) { + successRunnable.run(); + } + + @Override + public void onAuthenticationError(int errorCode, CharSequence errString) { + //Do nothing + } + }; + int userId = UserHandle.myUserId(); + BiometricPrompt.Builder builder = new BiometricPrompt.Builder(context) + .setTitle(context.getText(R.string.wifi_dpp_lockscreen_title)); + if (keyguardManager.isDeviceSecure()) { + builder.setDeviceCredentialAllowed(true); + builder.setTextForDeviceCredential( + null /* title */, + Utils.getConfirmCredentialStringForUser( + context, userId, Utils.getCredentialType(context, userId)), + null /* description */); + } + BiometricPrompt bp = builder.build(); + Handler handler = new Handler(Looper.getMainLooper()); + bp.authenticate(new CancellationSignal(), + runnable -> handler.post(runnable), + authenticationCallback); } /** diff --git a/src/com/android/settings/wifi/tether/WifiTetherSSIDPreferenceController.java b/src/com/android/settings/wifi/tether/WifiTetherSSIDPreferenceController.java index 1bcff1ea45e..d2d26ab84fa 100644 --- a/src/com/android/settings/wifi/tether/WifiTetherSSIDPreferenceController.java +++ b/src/com/android/settings/wifi/tether/WifiTetherSSIDPreferenceController.java @@ -123,7 +123,7 @@ public class WifiTetherSSIDPreferenceController extends WifiTetherBasePreference } private void shareHotspotNetwork(Intent intent) { - WifiDppUtils.showLockScreen(mContext, () -> { + WifiDppUtils.showLockScreenForWifiSharing(mContext, () -> { mMetricsFeatureProvider.action(SettingsEnums.PAGE_UNKNOWN, SettingsEnums.ACTION_SETTINGS_SHARE_WIFI_HOTSPOT_QR_CODE, SettingsEnums.SETTINGS_WIFI_DPP_CONFIGURATOR, diff --git a/tests/robotests/src/com/android/settings/accessibility/HighContrastTextMigrationReceiverTest.java b/tests/robotests/src/com/android/settings/accessibility/HighContrastTextMigrationReceiverTest.java new file mode 100644 index 00000000000..0fedddc7416 --- /dev/null +++ b/tests/robotests/src/com/android/settings/accessibility/HighContrastTextMigrationReceiverTest.java @@ -0,0 +1,241 @@ +/* + * 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.settings.accessibility; + +import static com.android.settings.SettingsActivity.EXTRA_FRAGMENT_ARG_KEY; +import static com.android.settings.SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS; +import static com.android.settings.accessibility.AccessibilityUtil.State.OFF; +import static com.android.settings.accessibility.AccessibilityUtil.State.ON; +import static com.android.settings.accessibility.HighContrastTextMigrationReceiver.ACTION_RESTORED; +import static com.android.settings.accessibility.HighContrastTextMigrationReceiver.NOTIFICATION_CHANNEL; +import static com.android.settings.accessibility.HighContrastTextMigrationReceiver.NOTIFICATION_ID; +import static com.android.settings.accessibility.HighContrastTextMigrationReceiver.PromptState.PROMPT_SHOWN; +import static com.android.settings.accessibility.HighContrastTextMigrationReceiver.PromptState.PROMPT_UNNECESSARY; +import static com.android.settings.accessibility.HighContrastTextMigrationReceiver.PromptState.UNKNOWN; + +import static com.google.common.truth.Truth.assertThat; + +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.os.Bundle; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; +import android.provider.Settings; + +import androidx.test.core.app.ApplicationProvider; + +import com.android.graphics.hwui.flags.Flags; +import com.android.settings.R; +import com.android.settings.Utils; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.Shadows; +import org.robolectric.shadows.ShadowNotification; +import org.robolectric.shadows.ShadowNotificationManager; +import org.robolectric.shadows.ShadowPackageManager; + +/** Tests for {@link HighContrastTextMigrationReceiver}. */ +@RunWith(RobolectricTestRunner.class) +public class HighContrastTextMigrationReceiverTest { + + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + private final Context mContext = ApplicationProvider.getApplicationContext(); + private HighContrastTextMigrationReceiver mReceiver; + private ShadowNotificationManager mShadowNotificationManager; + + @Before + public void setUp() { + NotificationManager notificationManager = + mContext.getSystemService(NotificationManager.class); + mShadowNotificationManager = Shadows.shadowOf(notificationManager); + + // Setup Settings app as a system app + ShadowPackageManager shadowPm = Shadows.shadowOf(mContext.getPackageManager()); + ComponentName textReadingComponent = new ComponentName(Utils.SETTINGS_PACKAGE_NAME, + com.android.settings.Settings.TextReadingSettingsActivity.class.getName()); + ActivityInfo activityInfo = shadowPm.addActivityIfNotPresent(textReadingComponent); + activityInfo.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM; + shadowPm.addOrUpdateActivity(activityInfo); + + mReceiver = new HighContrastTextMigrationReceiver(); + } + + @Test + @DisableFlags(Flags.FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT) + public void onReceive_flagOff_settingsNotSet() { + mReceiver.onReceive(mContext, new Intent(ACTION_RESTORED)); + + assertPromptStateAndHctState(/* promptState= */ UNKNOWN, /* hctState= */ OFF); + } + + @Test + @EnableFlags(Flags.FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT) + public void onRestored_hctStateOn_showPromptHctKeepsOn() { + setPromptStateAndHctState(/* promptState= */ UNKNOWN, /* hctState= */ ON); + + mReceiver.onReceive(mContext, new Intent(ACTION_RESTORED)); + + assertPromptStateAndHctState(/* promptState= */ PROMPT_SHOWN, ON); + verifyNotificationSent(); + } + + @Test + @EnableFlags(Flags.FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT) + public void onRestored_hctStateOff_showPromptHctKeepsOff() { + setPromptStateAndHctState(/* promptState= */ UNKNOWN, /* hctState= */ OFF); + + mReceiver.onReceive(mContext, new Intent(ACTION_RESTORED)); + + assertPromptStateAndHctState(/* promptState= */ PROMPT_SHOWN, OFF); + verifyNotificationSent(); + } + + @Test + @EnableFlags(Flags.FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT) + public void onPreBootCompleted_promptStateUnknownHctOn_showPromptAndAutoDisableHct() { + setPromptStateAndHctState(/* promptState= */ UNKNOWN, /* hctState= */ ON); + + Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED); + mReceiver.onReceive(mContext, intent); + + assertPromptStateAndHctState(/* promptState= */ PROMPT_SHOWN, /* hctState= */ OFF); + verifyNotificationSent(); + } + + @Test + @EnableFlags(Flags.FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT) + public void onPreBootCompleted_promptStateUnknownAndHctOff_promptIsUnnecessaryHctKeepsOff() { + setPromptStateAndHctState(/* promptState= */ UNKNOWN, /* hctState= */ OFF); + + Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED); + mReceiver.onReceive(mContext, intent); + + assertPromptStateAndHctState(/* promptState= */ PROMPT_UNNECESSARY, /* hctState= */ OFF); + verifyNotificationNotSent(); + } + + @Test + @EnableFlags(Flags.FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT) + public void onPreBootCompleted_promptStateShownAndHctOn_promptStateUnchangedHctKeepsOn() { + setPromptStateAndHctState(/* promptState= */ PROMPT_SHOWN, /* hctState= */ ON); + + Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED); + mReceiver.onReceive(mContext, intent); + + assertPromptStateAndHctState(/* promptState= */ PROMPT_SHOWN, /* hctState= */ ON); + verifyNotificationNotSent(); + } + + @Test + @EnableFlags(Flags.FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT) + public void onPreBootCompleted_promptStateShownAndHctOff_promptStateUnchangedHctKeepsOff() { + setPromptStateAndHctState(/* promptState= */ PROMPT_SHOWN, /* hctState= */ OFF); + + Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED); + mReceiver.onReceive(mContext, intent); + + assertPromptStateAndHctState(/* promptState= */ PROMPT_SHOWN, /* hctState= */ OFF); + verifyNotificationNotSent(); + } + + @Test + @EnableFlags(Flags.FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT) + public void onPreBootCompleted_promptStateUnnecessaryAndHctOn_promptStateUnchangedHctKeepsOn() { + setPromptStateAndHctState(/* promptState= */ PROMPT_UNNECESSARY, /* hctState= */ ON); + + Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED); + mReceiver.onReceive(mContext, intent); + + assertPromptStateAndHctState(/* promptState= */ PROMPT_UNNECESSARY, /* hctState= */ ON); + verifyNotificationNotSent(); + } + + @Test + @EnableFlags(Flags.FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT) + public void onPreBootCompleted_promptStateUnnecessaryHctOff_promptStateUnchangedHctKeepsOff() { + setPromptStateAndHctState(/* promptState= */ PROMPT_UNNECESSARY, /* hctState= */ OFF); + + Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED); + mReceiver.onReceive(mContext, intent); + + assertPromptStateAndHctState(/* promptState= */ PROMPT_UNNECESSARY, /* hctState= */ OFF); + verifyNotificationNotSent(); + } + + private void verifyNotificationNotSent() { + Notification notification = mShadowNotificationManager.getNotification(NOTIFICATION_ID); + assertThat(notification).isNull(); + } + + private void verifyNotificationSent() { + // Verify hct channel created + assertThat(mShadowNotificationManager.getNotificationChannels().stream().anyMatch( + channel -> channel.getId().equals(NOTIFICATION_CHANNEL))).isTrue(); + + // Verify hct notification is sent with correct content + Notification notification = mShadowNotificationManager.getNotification(NOTIFICATION_ID); + assertThat(notification).isNotNull(); + + ShadowNotification shadowNotification = Shadows.shadowOf(notification); + assertThat(shadowNotification.getContentTitle()).isEqualTo(mContext.getString( + R.string.accessibility_toggle_high_text_contrast_preference_title)); + assertThat(shadowNotification.getContentText()).isEqualTo( + mContext.getString(R.string.accessibility_notification_high_contrast_text_content)); + + assertThat(notification.actions.length).isEqualTo(1); + assertThat(notification.actions[0].title.toString()).isEqualTo( + mContext.getString(R.string.accessibility_notification_high_contrast_text_action)); + + PendingIntent pendingIntent = notification.actions[0].actionIntent; + Intent settingsIntent = Shadows.shadowOf(pendingIntent).getSavedIntent(); + Bundle fragmentArgs = settingsIntent.getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS); + assertThat(fragmentArgs).isNotNull(); + assertThat(fragmentArgs.getString(EXTRA_FRAGMENT_ARG_KEY)) + .isEqualTo(TextReadingPreferenceFragment.HIGH_TEXT_CONTRAST_KEY); + } + + private void assertPromptStateAndHctState( + @HighContrastTextMigrationReceiver.PromptState int promptState, + @AccessibilityUtil.State int hctState) { + assertThat(Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_HCT_RECT_PROMPT_STATUS, UNKNOWN)) + .isEqualTo(promptState); + assertThat(Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED, OFF)) + .isEqualTo(hctState); + } + + private void setPromptStateAndHctState( + @HighContrastTextMigrationReceiver.PromptState int promptState, + @AccessibilityUtil.State int hctState) { + Settings.Secure.putInt(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_HCT_RECT_PROMPT_STATUS, promptState); + Settings.Secure.putInt(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED, hctState); + } +} diff --git a/tests/robotests/src/com/android/settings/accessibility/PreviewSizeSeekBarControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/PreviewSizeSeekBarControllerTest.java index 05273fc050d..ba9eaa5871e 100644 --- a/tests/robotests/src/com/android/settings/accessibility/PreviewSizeSeekBarControllerTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/PreviewSizeSeekBarControllerTest.java @@ -29,7 +29,6 @@ import static org.mockito.Mockito.when; import android.app.Activity; import android.content.ComponentName; import android.content.Intent; -import android.os.Bundle; import android.view.LayoutInflater; import android.widget.PopupWindow; import android.widget.SeekBar; @@ -44,7 +43,6 @@ import androidx.test.ext.junit.rules.ActivityScenarioRule; import com.android.settings.R; import com.android.settings.SettingsPreferenceFragment; import com.android.settings.testutils.shadow.ShadowFragment; -import com.android.settings.widget.LabeledSeekBarPreference; import com.android.settingslib.testutils.shadow.ShadowInteractionJankMonitor; import com.google.android.setupcompat.util.WizardManagerHelper; @@ -77,7 +75,7 @@ public class PreviewSizeSeekBarControllerTest { private Activity mContext; private PreviewSizeSeekBarController mSeekBarController; private FontSizeData mFontSizeData; - private LabeledSeekBarPreference mSeekBarPreference; + private AccessibilitySeekBarPreference mSeekBarPreference; private PreferenceScreen mPreferenceScreen; private TestFragment mFragment; @@ -109,7 +107,7 @@ public class PreviewSizeSeekBarControllerTest { mPreferenceScreen = spy(new PreferenceScreen(mContext, /* attrs= */ null)); when(mPreferenceScreen.getPreferenceManager()).thenReturn(mPreferenceManager); doReturn(mPreferenceScreen).when(mFragment).getPreferenceScreen(); - mSeekBarPreference = spy(new LabeledSeekBarPreference(mContext, /* attrs= */ null)); + mSeekBarPreference = spy(new AccessibilitySeekBarPreference(mContext, /* attrs= */ null)); mSeekBarPreference.setKey(FONT_SIZE_KEY); LayoutInflater inflater = LayoutInflater.from(mContext); @@ -246,12 +244,11 @@ public class PreviewSizeSeekBarControllerTest { @Test @Config(shadows = ShadowFragment.class) - public void restoreValueFromSavedInstanceState_showTooltipView() { - final Bundle savedInstanceState = new Bundle(); - savedInstanceState.putBoolean(KEY_SAVED_QS_TOOLTIP_RESHOW, /* value= */ true); - mSeekBarController.onCreate(savedInstanceState); + public void enabledNeedsQSTooltipReshow_showTooltipView() { + mSeekBarPreference.setNeedsQSTooltipReshow(true); mSeekBarController.displayPreference(mPreferenceScreen); + mSeekBarController.onStart(); ShadowLooper.idleMainLooper(); assertThat(getLatestPopupWindow().isShowing()).isTrue(); diff --git a/tests/robotests/src/com/android/settings/accessibility/TextReadingPreviewControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/TextReadingPreviewControllerTest.java index f768e42f2e6..375952f725c 100644 --- a/tests/robotests/src/com/android/settings/accessibility/TextReadingPreviewControllerTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/TextReadingPreviewControllerTest.java @@ -28,7 +28,6 @@ import androidx.test.core.app.ApplicationProvider; import com.android.settings.display.PreviewPagerAdapter; import com.android.settings.testutils.shadow.ShadowInteractionJankMonitor; -import com.android.settings.widget.LabeledSeekBarPreference; import org.junit.Before; import org.junit.Test; @@ -54,8 +53,8 @@ public class TextReadingPreviewControllerTest { private final Context mContext = ApplicationProvider.getApplicationContext(); private TextReadingPreviewController mPreviewController; private TextReadingPreviewPreference mPreviewPreference; - private LabeledSeekBarPreference mFontSizePreference; - private LabeledSeekBarPreference mDisplaySizePreference; + private AccessibilitySeekBarPreference mFontSizePreference; + private AccessibilitySeekBarPreference mDisplaySizePreference; @Mock private DisplaySizeData mDisplaySizeData; @@ -73,8 +72,8 @@ public class TextReadingPreviewControllerTest { mPreviewPreference = spy(new TextReadingPreviewPreference(mContext, /* attr= */ null)); mPreviewController = new TextReadingPreviewController(mContext, PREVIEW_KEY, fontSizeData, mDisplaySizeData); - mFontSizePreference = new LabeledSeekBarPreference(mContext, /* attr= */ null); - mDisplaySizePreference = new LabeledSeekBarPreference(mContext, /* attr= */ null); + mFontSizePreference = new AccessibilitySeekBarPreference(mContext, /* attr= */ null); + mDisplaySizePreference = new AccessibilitySeekBarPreference(mContext, /* attr= */ null); } @Test diff --git a/tests/robotests/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdaterTest.java b/tests/robotests/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdaterTest.java index 9609af4a5e3..2251c3bff5a 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdaterTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdaterTest.java @@ -124,24 +124,17 @@ public class AvailableMediaBluetoothDeviceUpdaterTest { when(mCachedBluetoothDevice.getDrawableWithDescription()).thenReturn(pairs); when(mCachedBluetoothDevice.getMemberDevice()).thenReturn(ImmutableSet.of()); - mBluetoothDeviceUpdater = - spy( - new AvailableMediaBluetoothDeviceUpdater( - mContext, mDevicePreferenceCallback, /* metricsCategory= */ 0)); - mBluetoothDeviceUpdater.setPrefContext(mContext); mPreference = new BluetoothDevicePreference( mContext, mCachedBluetoothDevice, false, BluetoothDevicePreference.SortType.TYPE_DEFAULT); - doNothing().when(mBluetoothDeviceUpdater).addPreference(any()); - doNothing().when(mBluetoothDeviceUpdater).removePreference(any()); } @Test public void onAudioModeChanged_hfpDeviceConnected_inCall_addPreference() { - mAudioManager.setMode(AudioManager.MODE_IN_CALL); + setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_IN_CALL); when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class))) .thenReturn(true); when(mCachedBluetoothDevice.isConnectedHfpDevice()).thenReturn(true); @@ -153,7 +146,7 @@ public class AvailableMediaBluetoothDeviceUpdaterTest { @Test public void onAudioModeChanged_hfpDeviceConnected_notInCall_removePreference() { - mAudioManager.setMode(AudioManager.MODE_NORMAL); + setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_NORMAL); when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class))) .thenReturn(true); when(mCachedBluetoothDevice.isConnectedHfpDevice()).thenReturn(true); @@ -165,7 +158,7 @@ public class AvailableMediaBluetoothDeviceUpdaterTest { @Test public void onAudioModeChanged_a2dpDeviceConnected_inCall_removePreference() { - mAudioManager.setMode(AudioManager.MODE_IN_CALL); + setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_IN_CALL); when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class))) .thenReturn(true); when(mCachedBluetoothDevice.isConnectedA2dpDevice()).thenReturn(true); @@ -177,7 +170,7 @@ public class AvailableMediaBluetoothDeviceUpdaterTest { @Test public void onAudioModeChanged_a2dpDeviceConnected_notInCall_addPreference() { - mAudioManager.setMode(AudioManager.MODE_NORMAL); + setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_NORMAL); when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class))) .thenReturn(true); when(mCachedBluetoothDevice.isConnectedA2dpDevice()).thenReturn(true); @@ -189,7 +182,7 @@ public class AvailableMediaBluetoothDeviceUpdaterTest { @Test public void onProfileConnectionStateChanged_a2dpDeviceConnected_notInCall_addPreference() { - mAudioManager.setMode(AudioManager.MODE_NORMAL); + setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_NORMAL); when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class))) .thenReturn(true); when(mCachedBluetoothDevice.isConnectedA2dpDevice()).thenReturn(true); @@ -202,7 +195,7 @@ public class AvailableMediaBluetoothDeviceUpdaterTest { @Test public void onProfileConnectionStateChanged_a2dpDeviceConnected_inCall_removePreference() { - mAudioManager.setMode(AudioManager.MODE_IN_CALL); + setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_IN_CALL); when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class))) .thenReturn(true); when(mCachedBluetoothDevice.isConnectedA2dpDevice()).thenReturn(true); @@ -215,7 +208,7 @@ public class AvailableMediaBluetoothDeviceUpdaterTest { @Test public void onProfileConnectionStateChanged_hfpDeviceConnected_notInCall_removePreference() { - mAudioManager.setMode(AudioManager.MODE_NORMAL); + setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_NORMAL); when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class))) .thenReturn(true); when(mCachedBluetoothDevice.isConnectedHfpDevice()).thenReturn(true); @@ -228,7 +221,7 @@ public class AvailableMediaBluetoothDeviceUpdaterTest { @Test public void onProfileConnectionStateChanged_hfpDeviceConnected_inCall_addPreference() { - mAudioManager.setMode(AudioManager.MODE_IN_CALL); + setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_IN_CALL); when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class))) .thenReturn(true); when(mCachedBluetoothDevice.isConnectedHfpDevice()).thenReturn(true); @@ -241,7 +234,7 @@ public class AvailableMediaBluetoothDeviceUpdaterTest { @Test public void onProfileConnectionStateChanged_ashaHearingAidConnected_notInCall_addPreference() { - mAudioManager.setMode(AudioManager.MODE_NORMAL); + setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_NORMAL); when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class))) .thenReturn(true); when(mCachedBluetoothDevice.isConnectedAshaHearingAidDevice()).thenReturn(true); @@ -256,7 +249,7 @@ public class AvailableMediaBluetoothDeviceUpdaterTest { @Test public void onProfileConnectionStateChanged_ashaHearingAidConnected_inCall_addPreference() { - mAudioManager.setMode(AudioManager.MODE_IN_CALL); + setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_IN_CALL); when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class))) .thenReturn(true); when(mCachedBluetoothDevice.isConnectedAshaHearingAidDevice()).thenReturn(true); @@ -272,7 +265,7 @@ public class AvailableMediaBluetoothDeviceUpdaterTest { @Test public void onProfileConnectionStateChanged_leaConnected_notInCallSharingFlagOff_addPref() { mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); - mAudioManager.setMode(AudioManager.MODE_NORMAL); + setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_NORMAL); when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class))) .thenReturn(true); when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true); @@ -292,7 +285,7 @@ public class AvailableMediaBluetoothDeviceUpdaterTest { @Test public void onProfileConnectionStateChanged_leaConnected_notInCallNotInSharing_addPref() { mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); - mAudioManager.setMode(AudioManager.MODE_NORMAL); + setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_NORMAL); when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class))) .thenReturn(true); when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true); @@ -309,7 +302,7 @@ public class AvailableMediaBluetoothDeviceUpdaterTest { @Test public void onProfileConnectionStateChanged_leaConnected_inCallSharingFlagOff_addPref() { mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); - mAudioManager.setMode(AudioManager.MODE_IN_CALL); + setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_IN_CALL); when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class))) .thenReturn(true); when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true); @@ -326,7 +319,7 @@ public class AvailableMediaBluetoothDeviceUpdaterTest { @Test public void onProfileConnectionStateChanged_leaConnected_inCallNotInSharing_addPref() { mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); - mAudioManager.setMode(AudioManager.MODE_IN_CALL); + setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_IN_CALL); when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class))) .thenReturn(true); when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true); @@ -344,7 +337,7 @@ public class AvailableMediaBluetoothDeviceUpdaterTest { public void onProfileConnectionStateChanged_leaConnected_notInCallInSharing_removePref() { mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX); - mAudioManager.setMode(AudioManager.MODE_NORMAL); + setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_NORMAL); when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class))) .thenReturn(true); when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true); @@ -367,7 +360,7 @@ public class AvailableMediaBluetoothDeviceUpdaterTest { onProfileConnectionStateChanged_leaConnected_noInCallInSharing_hysteresis_removePref() { mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX); - mAudioManager.setMode(AudioManager.MODE_NORMAL); + setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_NORMAL); when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class))) .thenReturn(true); when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true); @@ -388,7 +381,7 @@ public class AvailableMediaBluetoothDeviceUpdaterTest { public void onProfileConnectionStateChanged_leaConnected_inCallSharing_removePref() { mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX); - mAudioManager.setMode(AudioManager.MODE_NORMAL); + setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_NORMAL); when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class))) .thenReturn(true); when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true); @@ -410,7 +403,7 @@ public class AvailableMediaBluetoothDeviceUpdaterTest { public void onProfileConnectionStateChanged_leaConnected_inCallSharing_hysteresis_removePref() { mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX); - mAudioManager.setMode(AudioManager.MODE_NORMAL); + setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_NORMAL); when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class))) .thenReturn(true); when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true); @@ -430,7 +423,7 @@ public class AvailableMediaBluetoothDeviceUpdaterTest { @Test public void onProfileConnectionStateChanged_deviceIsNotInList_notInCall_invokesRemovePreference() { - mAudioManager.setMode(AudioManager.MODE_NORMAL); + setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_NORMAL); when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class))) .thenReturn(true); when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true); @@ -446,7 +439,7 @@ public class AvailableMediaBluetoothDeviceUpdaterTest { @Test public void onProfileConnectionStateChanged_deviceIsNotInList_inCall_invokesRemovePreference() { - mAudioManager.setMode(AudioManager.MODE_IN_CALL); + setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_IN_CALL); when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class))) .thenReturn(true); when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true); @@ -462,6 +455,7 @@ public class AvailableMediaBluetoothDeviceUpdaterTest { @Test public void onProfileConnectionStateChanged_deviceDisconnected_removePreference() { + setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_NORMAL); mBluetoothDeviceUpdater.onProfileConnectionStateChanged( mCachedBluetoothDevice, BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.A2DP); @@ -470,8 +464,19 @@ public class AvailableMediaBluetoothDeviceUpdaterTest { @Test public void onClick_Preference_setActive() { + setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_NORMAL); mBluetoothDeviceUpdater.onPreferenceClick(mPreference); verify(mDevicePreferenceCallback).onDeviceClick(mPreference); } + + private void setUpDeviceUpdaterWithAudioMode(int audioMode) { + mAudioManager.setMode(audioMode); + mBluetoothDeviceUpdater = + spy(new AvailableMediaBluetoothDeviceUpdater( + mContext, mDevicePreferenceCallback, /* metricsCategory= */ 0)); + mBluetoothDeviceUpdater.setPrefContext(mContext); + doNothing().when(mBluetoothDeviceUpdater).addPreference(any()); + doNothing().when(mBluetoothDeviceUpdater).removePreference(any()); + } } diff --git a/tests/robotests/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdaterTest.java b/tests/robotests/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdaterTest.java index b2449dab39e..f68a8d4cf6a 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdaterTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdaterTest.java @@ -112,16 +112,11 @@ public class ConnectedBluetoothDeviceUpdaterTest { when(mCachedBluetoothDevice.getAddress()).thenReturn(MAC_ADDRESS); when(mCachedBluetoothDevice.getDrawableWithDescription()).thenReturn(pairs); mShadowCachedBluetoothDeviceManager.setCachedDevicesCopy(mCachedDevices); - mBluetoothDeviceUpdater = spy(new ConnectedBluetoothDeviceUpdater(mContext, - mDevicePreferenceCallback, /* metricsCategory= */ 0)); - mBluetoothDeviceUpdater.setPrefContext(mContext); - doNothing().when(mBluetoothDeviceUpdater).addPreference(any()); - doNothing().when(mBluetoothDeviceUpdater).removePreference(any()); } @Test public void onAudioModeChanged_hfpDeviceConnected_notInCall_addPreference() { - mAudioManager.setMode(AudioManager.MODE_NORMAL); + setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_NORMAL); when(mBluetoothDeviceUpdater. isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true); when(mCachedBluetoothDevice.isConnectedHfpDevice()).thenReturn(true); @@ -133,7 +128,7 @@ public class ConnectedBluetoothDeviceUpdaterTest { @Test public void onAudioModeChanged_hfpDeviceConnected_inCall_removePreference() { - mAudioManager.setMode(AudioManager.MODE_IN_CALL); + setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_IN_CALL); when(mBluetoothDeviceUpdater. isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true); when(mCachedBluetoothDevice.isConnectedHfpDevice()).thenReturn(true); @@ -145,7 +140,7 @@ public class ConnectedBluetoothDeviceUpdaterTest { @Test public void onAudioModeChanged_a2dpDeviceConnected_notInCall_removePreference() { - mAudioManager.setMode(AudioManager.MODE_NORMAL); + setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_NORMAL); when(mBluetoothDeviceUpdater. isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true); when(mCachedBluetoothDevice.isConnectedA2dpDevice()).thenReturn(true); @@ -157,7 +152,7 @@ public class ConnectedBluetoothDeviceUpdaterTest { @Test public void onAudioModeChanged_a2dpDeviceConnected_inCall_addPreference() { - mAudioManager.setMode(AudioManager.MODE_IN_CALL); + setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_IN_CALL); when(mBluetoothDeviceUpdater. isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true); when(mCachedBluetoothDevice.isConnectedA2dpDevice()).thenReturn(true); @@ -169,7 +164,7 @@ public class ConnectedBluetoothDeviceUpdaterTest { @Test public void onProfileConnectionStateChanged_a2dpDeviceConnected_inCall_addPreference() { - mAudioManager.setMode(AudioManager.MODE_IN_CALL); + setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_IN_CALL); when(mBluetoothDeviceUpdater. isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true); when(mCachedBluetoothDevice.isConnectedA2dpDevice()).thenReturn(true); @@ -182,7 +177,7 @@ public class ConnectedBluetoothDeviceUpdaterTest { @Test public void onProfileConnectionStateChanged_deviceIsNotInList_inCall_invokesRemovePreference() { - mAudioManager.setMode(AudioManager.MODE_IN_CALL); + setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_IN_CALL); when(mBluetoothDeviceUpdater. isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true); when(mCachedBluetoothDevice.isConnectedA2dpDevice()).thenReturn(true); @@ -196,7 +191,7 @@ public class ConnectedBluetoothDeviceUpdaterTest { @Test public void onProfileConnectionStateChanged_a2dpDeviceConnected_notInCall_removePreference() { - mAudioManager.setMode(AudioManager.MODE_NORMAL); + setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_NORMAL); when(mBluetoothDeviceUpdater. isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true); when(mCachedBluetoothDevice.isConnectedA2dpDevice()).thenReturn(true); @@ -209,7 +204,7 @@ public class ConnectedBluetoothDeviceUpdaterTest { @Test public void onProfileConnectionStateChanged_hfpDeviceConnected_inCall_removePreference() { - mAudioManager.setMode(AudioManager.MODE_IN_CALL); + setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_IN_CALL); when(mBluetoothDeviceUpdater. isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true); when(mCachedBluetoothDevice.isConnectedHfpDevice()).thenReturn(true); @@ -222,7 +217,7 @@ public class ConnectedBluetoothDeviceUpdaterTest { @Test public void onProfileConnectionStateChanged_hfpDeviceConnected_notInCall_addPreference() { - mAudioManager.setMode(AudioManager.MODE_NORMAL); + setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_NORMAL); when(mBluetoothDeviceUpdater. isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true); when(mCachedBluetoothDevice.isConnectedHfpDevice()).thenReturn(true); @@ -236,7 +231,7 @@ public class ConnectedBluetoothDeviceUpdaterTest { @Test public void onProfileConnectionStateChanged_ashaHearingAidConnected_inCall_removePreference() { - mAudioManager.setMode(AudioManager.MODE_IN_CALL); + setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_IN_CALL); when(mBluetoothDeviceUpdater. isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true); when(mCachedBluetoothDevice.isConnectedAshaHearingAidDevice()).thenReturn(true); @@ -250,7 +245,7 @@ public class ConnectedBluetoothDeviceUpdaterTest { @Test public void onProfileConnectionStateChanged_ashaHearingAidConnected_notInCall_removePreference() { - mAudioManager.setMode(AudioManager.MODE_NORMAL); + setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_NORMAL); when(mBluetoothDeviceUpdater. isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true); when(mCachedBluetoothDevice.isConnectedAshaHearingAidDevice()).thenReturn(true); @@ -263,7 +258,7 @@ public class ConnectedBluetoothDeviceUpdaterTest { @Test public void onProfileConnectionStateChanged_leAudioDeviceConnected_inCall_removesPreference() { - mAudioManager.setMode(AudioManager.MODE_IN_CALL); + setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_IN_CALL); when(mBluetoothDeviceUpdater .isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true); when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true); @@ -277,7 +272,7 @@ public class ConnectedBluetoothDeviceUpdaterTest { @Test public void onProfileConnectionStateChanged_leAudioDeviceConnected_notInCall_removesPreference() { - mAudioManager.setMode(AudioManager.MODE_NORMAL); + setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_NORMAL); when(mBluetoothDeviceUpdater .isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true); when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true); @@ -290,7 +285,7 @@ public class ConnectedBluetoothDeviceUpdaterTest { @Test public void onProfileConnectionStateChanged_deviceIsNotInList_inCall_invokesRemovesPreference() { - mAudioManager.setMode(AudioManager.MODE_IN_CALL); + setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_IN_CALL); when(mBluetoothDeviceUpdater .isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true); when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true); @@ -305,7 +300,7 @@ public class ConnectedBluetoothDeviceUpdaterTest { @Test public void onProfileConnectionStateChanged_deviceIsNotInList_notInCall_invokesRemovesPreference () { - mAudioManager.setMode(AudioManager.MODE_NORMAL); + setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_NORMAL); when(mBluetoothDeviceUpdater .isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true); when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true); @@ -319,6 +314,7 @@ public class ConnectedBluetoothDeviceUpdaterTest { @Test public void onProfileConnectionStateChanged_deviceDisconnected_removePreference() { + setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_NORMAL); mBluetoothDeviceUpdater.onProfileConnectionStateChanged(mCachedBluetoothDevice, BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.A2DP); @@ -327,6 +323,7 @@ public class ConnectedBluetoothDeviceUpdaterTest { @Test public void addPreference_addPreference_shouldHideSecondTarget() { + setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_NORMAL); BluetoothDevicePreference btPreference = new BluetoothDevicePreference(mContext, mCachedBluetoothDevice, true, BluetoothDevicePreference.SortType.TYPE_DEFAULT); @@ -340,7 +337,7 @@ public class ConnectedBluetoothDeviceUpdaterTest { @Test @RequiresFlagsEnabled(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE) public void update_notExclusiveManagedDevice_addDevice() { - mAudioManager.setMode(AudioManager.MODE_NORMAL); + setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_NORMAL); when(mBluetoothDeviceUpdater .isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true); when(mCachedBluetoothDevice.isConnectedHfpDevice()).thenReturn(true); @@ -356,7 +353,7 @@ public class ConnectedBluetoothDeviceUpdaterTest { @RequiresFlagsEnabled(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE) public void update_exclusivelyManagedDevice_packageNotInstalled_addDevice() throws Exception { - mAudioManager.setMode(AudioManager.MODE_NORMAL); + setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_NORMAL); when(mBluetoothDeviceUpdater .isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true); when(mCachedBluetoothDevice.isConnectedHfpDevice()).thenReturn(true); @@ -376,7 +373,7 @@ public class ConnectedBluetoothDeviceUpdaterTest { throws Exception { ApplicationInfo appInfo = new ApplicationInfo(); appInfo.enabled = false; - mAudioManager.setMode(AudioManager.MODE_NORMAL); + setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_NORMAL); when(mBluetoothDeviceUpdater .isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true); when(mCachedBluetoothDevice.isConnectedHfpDevice()).thenReturn(true); @@ -393,7 +390,7 @@ public class ConnectedBluetoothDeviceUpdaterTest { @RequiresFlagsEnabled(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE) public void update_exclusivelyManagedDevice_packageInstalledAndEnabled_removePreference() throws Exception { - mAudioManager.setMode(AudioManager.MODE_NORMAL); + setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_NORMAL); when(mBluetoothDeviceUpdater .isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true); when(mCachedBluetoothDevice.isConnectedHfpDevice()).thenReturn(true); @@ -407,4 +404,13 @@ public class ConnectedBluetoothDeviceUpdaterTest { verify(mBluetoothDeviceUpdater).removePreference(mCachedBluetoothDevice); verify(mBluetoothDeviceUpdater, never()).addPreference(mCachedBluetoothDevice); } + + private void setUpDeviceUpdaterWithAudioMode(int audioMode) { + mAudioManager.setMode(audioMode); + mBluetoothDeviceUpdater = spy(new ConnectedBluetoothDeviceUpdater(mContext, + mDevicePreferenceCallback, /* metricsCategory= */ 0)); + mBluetoothDeviceUpdater.setPrefContext(mContext); + doNothing().when(mBluetoothDeviceUpdater).addPreference(any()); + doNothing().when(mBluetoothDeviceUpdater).removePreference(any()); + } } diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallbackTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallbackTest.java index 4e962c7deb3..6aff8c38d7e 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallbackTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallbackTest.java @@ -54,7 +54,7 @@ import java.util.List; @RunWith(RobolectricTestRunner.class) @Config( shadows = { - ShadowBluetoothAdapter.class, + ShadowBluetoothAdapter.class, }) public class AudioStreamsProgressCategoryCallbackTest { @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); @@ -70,8 +70,8 @@ public class AudioStreamsProgressCategoryCallbackTest { @Before public void setUp() { mSetFlagsRule.disableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX); - ShadowBluetoothAdapter shadowBluetoothAdapter = Shadow.extract( - BluetoothAdapter.getDefaultAdapter()); + ShadowBluetoothAdapter shadowBluetoothAdapter = + Shadow.extract(BluetoothAdapter.getDefaultAdapter()); shadowBluetoothAdapter.setEnabled(true); shadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported( BluetoothStatusCodes.FEATURE_SUPPORTED); @@ -87,7 +87,7 @@ public class AudioStreamsProgressCategoryCallbackTest { when(mState.getBisSyncState()).thenReturn(bisSyncState); mCallback.onReceiveStateChanged(mDevice, /* sourceId= */ 0, mState); - verify(mController).handleSourceConnected(any()); + verify(mController).handleSourceConnected(any(), any()); } @Test @@ -102,7 +102,7 @@ public class AudioStreamsProgressCategoryCallbackTest { when(mSourceDevice.getAddress()).thenReturn(address); mCallback.onReceiveStateChanged(mDevice, /* sourceId= */ 0, mState); - verify(mController).handleSourcePresent(any()); + verify(mController).handleSourcePresent(any(), any()); } @Test diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryControllerTest.java index 78d4d6e1361..f042329200a 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryControllerTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryControllerTest.java @@ -32,6 +32,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -41,7 +42,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.robolectric.Shadows.shadowOf; -import static java.util.Collections.emptyList; +import static java.util.Collections.emptyMap; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; @@ -93,6 +94,7 @@ import org.robolectric.shadows.androidx.fragment.FragmentController; import java.util.ArrayList; import java.util.List; +import java.util.Map; @RunWith(RobolectricTestRunner.class) @Config( @@ -134,8 +136,8 @@ public class AudioStreamsProgressCategoryControllerTest { @Before public void setUp() { - ShadowBluetoothAdapter shadowBluetoothAdapter = Shadow.extract( - BluetoothAdapter.getDefaultAdapter()); + ShadowBluetoothAdapter shadowBluetoothAdapter = + Shadow.extract(BluetoothAdapter.getDefaultAdapter()); shadowBluetoothAdapter.setEnabled(true); shadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported( BluetoothStatusCodes.FEATURE_SUPPORTED); @@ -143,7 +145,7 @@ public class AudioStreamsProgressCategoryControllerTest { BluetoothStatusCodes.FEATURE_SUPPORTED); ShadowAudioStreamsHelper.setUseMock(mAudioStreamsHelper); when(mAudioStreamsHelper.getLeBroadcastAssistant()).thenReturn(mLeBroadcastAssistant); - when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(emptyList()); + when(mAudioStreamsHelper.getAllSourcesByDevice()).thenReturn(emptyMap()); mSetFlagsRule.disableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX); ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBtManager; @@ -310,14 +312,12 @@ public class AudioStreamsProgressCategoryControllerTest { // Setup a device ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(mDevice); - List connectedList = new ArrayList<>(); // Empty connected device list - when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(connectedList); + when(mAudioStreamsHelper.getAllSourcesByDevice()).thenReturn(emptyMap()); mController.onStart(mLifecycleOwner); shadowOf(Looper.getMainLooper()).idle(); - verify(mAudioStreamsHelper).getAllPresentSources(); verify(mLeBroadcastAssistant).startSearchingForSources(any()); var dialog = ShadowAlertDialog.getLatestAlertDialog(); @@ -355,7 +355,7 @@ public class AudioStreamsProgressCategoryControllerTest { } @Test - public void testOnStart_handleSourceAlreadyConnected() { + public void testOnStart_handleSourceAlreadyConnected_useNameFromMetadata() { // Setup a device ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(mDevice); @@ -363,8 +363,14 @@ public class AudioStreamsProgressCategoryControllerTest { BluetoothLeBroadcastReceiveState connected = createConnectedMock(ALREADY_CONNECTED_BROADCAST_ID); List list = new ArrayList<>(); + var data = mock(BluetoothLeAudioContentMetadata.class); + when(connected.getSubgroupMetadata()).thenReturn(ImmutableList.of(data)); + when(data.getProgramInfo()).thenReturn(BROADCAST_NAME_1); list.add(connected); - when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(list); + when(mMetadata.getBroadcastId()).thenReturn(ALREADY_CONNECTED_BROADCAST_ID); + when(mMetadata.getBroadcastName()).thenReturn(BROADCAST_NAME_2); + when(mLeBroadcastAssistant.getSourceMetadata(any(), anyInt())).thenReturn(mMetadata); + when(mAudioStreamsHelper.getAllSourcesByDevice()).thenReturn(Map.of(mSourceDevice, list)); // Handle already connected source in onStart mController.displayPreference(mScreen); @@ -382,6 +388,7 @@ public class AudioStreamsProgressCategoryControllerTest { assertThat(preference.getValue()).isNotNull(); assertThat(preference.getValue().getAudioStreamBroadcastId()) .isEqualTo(ALREADY_CONNECTED_BROADCAST_ID); + assertThat(preference.getValue().getTitle()).isEqualTo(BROADCAST_NAME_2); assertThat(state.getValue()).isEqualTo(SOURCE_ADDED); } @@ -409,7 +416,8 @@ public class AudioStreamsProgressCategoryControllerTest { var data = mock(BluetoothLeAudioContentMetadata.class); when(connected.getSubgroupMetadata()).thenReturn(ImmutableList.of(data)); when(data.getProgramInfo()).thenReturn(BROADCAST_NAME_1); - when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(ImmutableList.of(connected)); + when(mAudioStreamsHelper.getAllSourcesByDevice()) + .thenReturn(Map.of(mSourceDevice, ImmutableList.of(connected))); // Handle both source from qr code and already connected source in onStart mController.displayPreference(mScreen); @@ -578,8 +586,8 @@ public class AudioStreamsProgressCategoryControllerTest { // Setup source already connected BluetoothLeBroadcastReceiveState connected = createConnectedMock(ALREADY_CONNECTED_BROADCAST_ID); - when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(ImmutableList.of(connected)); - + when(mAudioStreamsHelper.getAllSourcesByDevice()) + .thenReturn(Map.of(mSourceDevice, List.of(connected))); // Handle source already connected in onStart mController.displayPreference(mScreen); mController.onStart(mLifecycleOwner); @@ -687,7 +695,8 @@ public class AudioStreamsProgressCategoryControllerTest { // Setup already connected source BluetoothLeBroadcastReceiveState connected = createConnectedMock(ALREADY_CONNECTED_BROADCAST_ID); - when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(ImmutableList.of(connected)); + when(mAudioStreamsHelper.getAllSourcesByDevice()) + .thenReturn(Map.of(mSourceDevice, List.of(connected))); // Handle connected source in onStart mController.displayPreference(mScreen); @@ -695,7 +704,7 @@ public class AudioStreamsProgressCategoryControllerTest { shadowOf(Looper.getMainLooper()).idle(); // The connect source is no longer connected - when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(emptyList()); + when(mAudioStreamsHelper.getAllSourcesByDevice()).thenReturn(emptyMap()); mController.handleSourceRemoved(); shadowOf(Looper.getMainLooper()).idle(); @@ -728,7 +737,8 @@ public class AudioStreamsProgressCategoryControllerTest { // Setup a connected source BluetoothLeBroadcastReceiveState connected = createConnectedMock(ALREADY_CONNECTED_BROADCAST_ID); - when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(ImmutableList.of(connected)); + when(mAudioStreamsHelper.getAllSourcesByDevice()) + .thenReturn(Map.of(mSourceDevice, List.of(connected))); // Handle connected source in onStart mController.displayPreference(mScreen); @@ -834,7 +844,7 @@ public class AudioStreamsProgressCategoryControllerTest { when(receiveState.getBisSyncState()).thenReturn(bisSyncState); // The new found source is identified as failed to connect - mController.handleSourcePresent(receiveState); + mController.handleSourcePresent(mSourceDevice, receiveState); shadowOf(Looper.getMainLooper()).idle(); ArgumentCaptor preference = diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/testshadows/ShadowAudioStreamsHelper.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/testshadows/ShadowAudioStreamsHelper.java index c7d0c60efa8..e5e51fce717 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/testshadows/ShadowAudioStreamsHelper.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/testshadows/ShadowAudioStreamsHelper.java @@ -16,6 +16,7 @@ package com.android.settings.connecteddevice.audiosharing.audiostreams.testshadows; +import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothLeBroadcastMetadata; import android.bluetooth.BluetoothLeBroadcastReceiveState; @@ -31,6 +32,7 @@ import org.robolectric.annotation.Implements; import org.robolectric.annotation.Resetter; import java.util.List; +import java.util.Map; import java.util.Optional; @Implements(value = AudioStreamsHelper.class, callThroughByDefault = true) @@ -59,6 +61,11 @@ public class ShadowAudioStreamsHelper { return sMockHelper.getAllConnectedSources(); } + @Implementation + public Map> getAllSourcesByDevice() { + return sMockHelper.getAllSourcesByDevice(); + } + @Implementation public List getAllPresentSources() { return sMockHelper.getAllPresentSources(); diff --git a/tests/robotests/src/com/android/settings/inputmethod/TouchpadAndMouseSettingsControllerTest.java b/tests/robotests/src/com/android/settings/inputmethod/TouchpadAndMouseSettingsControllerTest.java index 9d3cc5ff77f..f3fe93dec4b 100644 --- a/tests/robotests/src/com/android/settings/inputmethod/TouchpadAndMouseSettingsControllerTest.java +++ b/tests/robotests/src/com/android/settings/inputmethod/TouchpadAndMouseSettingsControllerTest.java @@ -23,12 +23,15 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assume.assumeTrue; import android.content.Context; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.flag.junit.SetFlagsRule; import android.view.InputDevice; import androidx.preference.Preference; import androidx.test.core.app.ApplicationProvider; import com.android.settings.core.BasePreferenceController; +import com.android.settings.keyboard.Flags; import com.android.settings.testutils.shadow.ShadowInputDevice; import org.junit.Before; @@ -47,6 +50,8 @@ import org.robolectric.annotation.Config; ShadowInputDevice.class, }) public class TouchpadAndMouseSettingsControllerTest { + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @Rule public MockitoRule rule = MockitoJUnit.rule(); @@ -80,6 +85,7 @@ public class TouchpadAndMouseSettingsControllerTest { } @Test + @DisableFlags(Flags.FLAG_KEYBOARD_AND_TOUCHPAD_A11Y_NEW_PAGE_ENABLED) public void getAvailabilityStatus_isTouchpadAvailable() { int deviceId = 1; ShadowInputDevice.sDeviceIds = new int[]{deviceId}; @@ -92,6 +98,7 @@ public class TouchpadAndMouseSettingsControllerTest { } @Test + @DisableFlags(Flags.FLAG_KEYBOARD_AND_TOUCHPAD_A11Y_NEW_PAGE_ENABLED) public void getAvailabilityStatus_isMouseAvailable() { assumeTrue(enableVectorCursorA11ySettings()); diff --git a/tests/robotests/src/com/android/settings/service/PreferenceServiceRequestTransformerTest.kt b/tests/robotests/src/com/android/settings/service/PreferenceServiceRequestTransformerTest.kt index f064b221282..7631a00e9d1 100644 --- a/tests/robotests/src/com/android/settings/service/PreferenceServiceRequestTransformerTest.kt +++ b/tests/robotests/src/com/android/settings/service/PreferenceServiceRequestTransformerTest.kt @@ -16,7 +16,6 @@ package com.android.settings.service -import android.content.ComponentName import android.content.Context import android.content.Intent import android.platform.test.annotations.RequiresFlagsEnabled @@ -24,6 +23,7 @@ import android.platform.test.flag.junit.CheckFlagsRule import android.platform.test.flag.junit.DeviceFlagsValueProvider import android.service.settings.preferences.GetValueRequest import android.service.settings.preferences.GetValueResult +import android.service.settings.preferences.MetadataResult import android.service.settings.preferences.SetValueRequest import android.service.settings.preferences.SetValueResult import android.service.settings.preferences.SettingsPreferenceMetadata @@ -37,9 +37,15 @@ import com.android.settingslib.graph.PreferenceGetterErrorCode import com.android.settingslib.graph.PreferenceGetterFlags import com.android.settingslib.graph.PreferenceGetterResponse import com.android.settingslib.graph.PreferenceSetterResult +import com.android.settingslib.graph.preferenceGroupProto +import com.android.settingslib.graph.preferenceOrGroupProto +import com.android.settingslib.graph.preferenceProto +import com.android.settingslib.graph.preferenceScreenProto +import com.android.settingslib.graph.proto.PreferenceGraphProto import com.android.settingslib.graph.proto.PreferenceProto import com.android.settingslib.graph.proto.PreferenceValueProto import com.android.settingslib.graph.proto.TextProto +import com.android.settingslib.graph.textProto import com.android.settingslib.graph.toProto import com.android.settingslib.metadata.SensitivityLevel import com.google.common.truth.Truth.assertThat @@ -54,6 +60,73 @@ class PreferenceServiceRequestTransformerTest { @get:Rule val checkFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule() + @Test + fun transformCatalystGetMetadataResponse_emptyGraph_returnsFrameworkResponseWithError() { + val context: Context = ApplicationProvider.getApplicationContext() + val graphProto = PreferenceGraphProto.newBuilder().build() + val fResult = transformCatalystGetMetadataResponse(context, graphProto) + with(fResult) { + assertThat(resultCode).isEqualTo(MetadataResult.RESULT_UNSUPPORTED) + assertThat(metadataList).isEmpty() + } + } + + @Test + fun transformCatalystGetMetadataResponse_populatedGraph_returnsFrameworkResponseWithSuccess() { + val context: Context = ApplicationProvider.getApplicationContext() + val screen = preferenceScreenProto { + root = preferenceGroupProto { + addAllPreferences( + listOf( + preferenceOrGroupProto { + group = preferenceGroupProto { + addPreferences( + preferenceOrGroupProto { + preference = preferenceProto { + key = "key1" + title = textProto { string = "title1" } + enabled = true + } + } + ) + } + }, + preferenceOrGroupProto { + preference = preferenceProto { + key = "key2" + title = textProto { string = "title2" } + enabled = false + } + } + ) + ) + } + } + val graphProto = PreferenceGraphProto.newBuilder().putScreens("screen", screen).build() + + val fResult = transformCatalystGetMetadataResponse(context, graphProto) + with(fResult) { + assertThat(resultCode).isEqualTo(MetadataResult.RESULT_OK) + assertThat(metadataList.size).isEqualTo(2) + } + assertThat( + fResult.metadataList.any { + it.key == "key1" && + it.screenKey == "screen" && + it.title == "title1" && + it.isEnabled == true + } + ).isTrue() + assertThat( + fResult.metadataList.any { + it.key == "key2" && + it.screenKey == "screen" && + it.title == "title2" && + it.isEnabled == false + } + ).isTrue() + } + @Test fun transformFrameworkGetValueRequest_returnsValidCatalystRequest() { val fRequest = GetValueRequest.Builder("screen", "pref").build() diff --git a/tests/spa_unit/src/com/android/settings/spa/wifi/dpp/WifiDppUtilsTest.kt b/tests/spa_unit/src/com/android/settings/spa/wifi/dpp/WifiDppUtilsTest.kt new file mode 100644 index 00000000000..31ee9e6a2c7 --- /dev/null +++ b/tests/spa_unit/src/com/android/settings/spa/wifi/dpp/WifiDppUtilsTest.kt @@ -0,0 +1,118 @@ +/* + * 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.settings.spa.wifi.dpp + +import android.app.KeyguardManager +import android.content.Context +import android.hardware.biometrics.BiometricPrompt +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.dx.mockito.inline.extended.ExtendedMockito +import com.android.settings.wifi.dpp.WifiDppUtils +import java.security.InvalidKeyException +import java.security.Key +import javax.crypto.Cipher +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.any +import org.mockito.Mockito.anyInt +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.MockitoSession +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.doThrow +import org.mockito.kotlin.mock +import org.mockito.kotlin.spy +import org.mockito.kotlin.stub +import org.mockito.kotlin.whenever +import org.mockito.quality.Strictness + +@RunWith(AndroidJUnit4::class) +class WifiDppUtilsTest { + private lateinit var mockSession: MockitoSession + + private val runnable = mock() + private val cipher = mock() + private var mockKeyguardManager = mock() + private var context: Context = + spy(ApplicationProvider.getApplicationContext()) { + on { getSystemService(KeyguardManager::class.java) } doReturn mockKeyguardManager + } + + @Before + fun setUp() { + mockSession = + ExtendedMockito.mockitoSession() + .initMocks(this) + .mockStatic(Cipher::class.java) + .mockStatic(BiometricPrompt::class.java) + .mockStatic(BiometricPrompt.Builder::class.java) + .strictness(Strictness.LENIENT) + .startMocking() + whenever(context.applicationContext).thenReturn(context) + } + + @After + fun tearDown() { + mockSession.finishMocking() + } + + @Test + fun showLockScreen_notKeyguardSecure_runRunnable() { + mockKeyguardManager.stub { on { isKeyguardSecure } doReturn false } + + WifiDppUtils.showLockScreen(context, runnable) + + verify(runnable).run() + } + + @Test + fun showLockScreen_isKeyguardSecure_doNotRunRunnable() { + mockKeyguardManager.stub { on { isKeyguardSecure } doReturn true } + + try { + WifiDppUtils.showLockScreen(context, runnable) + } catch (_: Exception) {} + + verify(runnable, never()).run() + } + + @Test + fun showLockScreenForWifiSharing_deviceUnlockedRecently_runRunnable() { + mockKeyguardManager.stub { on { isKeyguardSecure } doReturn true } + whenever(Cipher.getInstance(WifiDppUtils.AES_CBC_PKCS7_PADDING)).thenReturn(cipher) + + WifiDppUtils.showLockScreenForWifiSharing(context, runnable) + + verify(runnable).run() + } + + @Test + fun showLockScreenForWifiSharing_deviceNotUnlockedRecently_doNotRunRunnable() { + mockKeyguardManager.stub { on { isKeyguardSecure } doReturn true } + whenever(Cipher.getInstance(WifiDppUtils.AES_CBC_PKCS7_PADDING)).thenReturn(cipher) + doThrow(InvalidKeyException()).whenever(cipher).init(anyInt(), any()) + + try { + WifiDppUtils.showLockScreenForWifiSharing(context, runnable) + } catch (_: Exception) {} + + verify(runnable, never()).run() + } +}