diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 66c3beb5bd2..3f5cd84de19 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -783,9 +783,13 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/layout/main_clear_confirm.xml b/res/layout/main_clear_confirm.xml index 6027d18dcc4..d4821329156 100644 --- a/res/layout/main_clear_confirm.xml +++ b/res/layout/main_clear_confirm.xml @@ -19,21 +19,5 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" - android:id="@+id/setup_wizard_layout" android:icon="@drawable/ic_delete_accent" - app:sucHeaderText="@string/main_clear_confirm_title"> - - - - - - + app:sucHeaderText="@string/main_clear_confirm_title" /> diff --git a/res/values/strings.xml b/res/values/strings.xml index 3494afd56ff..22708041c7f 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -4532,8 +4532,8 @@ Click in the bottom right corner of the touchpad for more options Pointer speed - - Pointer fill style + + Pointer color Change pointer fill style to black @@ -4810,6 +4810,12 @@ Display Color and motion + + Pointer & touchpad accessibility + + Pointer color, pointer size & more + + Pointer color customization Color contrast diff --git a/res/xml/accessibility_color_and_motion.xml b/res/xml/accessibility_color_and_motion.xml index 4c4490ca5c6..a500b72d958 100644 --- a/res/xml/accessibility_color_and_motion.xml +++ b/res/xml/accessibility_color_and_motion.xml @@ -72,17 +72,6 @@ android:title="@string/accessibility_toggle_large_pointer_icon_title" settings:controller="com.android.settings.accessibility.LargePointerIconPreferenceController"/> - - + + + + + + + + + + + diff --git a/res/xml/accessibility_pointer_color_customization.xml b/res/xml/accessibility_pointer_color_customization.xml new file mode 100644 index 00000000000..6d767b423f3 --- /dev/null +++ b/res/xml/accessibility_pointer_color_customization.xml @@ -0,0 +1,34 @@ + + + + + + + + + + diff --git a/res/xml/accessibility_settings.xml b/res/xml/accessibility_settings.xml index 65617756524..18e645591ff 100644 --- a/res/xml/accessibility_settings.xml +++ b/res/xml/accessibility_settings.xml @@ -110,6 +110,16 @@ settings:keywords="@string/keywords_vibration" android:summary="@string/accessibility_vibration_settings_summary"/> + + - - \ No newline at end of file diff --git a/res/xml/trackpad_settings.xml b/res/xml/trackpad_settings.xml index 935de82f79d..7e9494476fe 100644 --- a/res/xml/trackpad_settings.xml +++ b/res/xml/trackpad_settings.xml @@ -62,29 +62,14 @@ android:selectable="false" settings:controller="com.android.settings.inputmethod.TrackpadPointerSpeedPreferenceController"/> - - - - - + android:persistent="false" + android:title="@string/accessibility_pointer_and_touchpad_title" + android:summary="@string/accessibility_pointer_and_touchpad_summary" + settings:searchable="true"/> * This is the confirmation screen. */ public class MainClearConfirm extends InstrumentedFragment { @@ -70,9 +65,11 @@ public class MainClearConfirm extends InstrumentedFragment { private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst"; - @VisibleForTesting View mContentView; + @VisibleForTesting + GlifLayout mContentView; private boolean mEraseSdCard; - @VisibleForTesting boolean mEraseEsims; + @VisibleForTesting + boolean mEraseEsims; /** * The user has gone through the multiple confirmation, so now we go ahead @@ -215,9 +212,7 @@ public class MainClearConfirm extends InstrumentedFragment { * Configure the UI for the final confirmation interaction */ private void establishFinalConfirmationState() { - final GlifLayout layout = mContentView.findViewById(R.id.setup_wizard_layout); - - final FooterBarMixin mixin = layout.getMixin(FooterBarMixin.class); + final FooterBarMixin mixin = mContentView.getMixin(FooterBarMixin.class); mixin.setPrimaryButton( new FooterButton.Builder(getActivity()) .setText(R.string.main_clear_button_text) @@ -228,21 +223,6 @@ public class MainClearConfirm extends InstrumentedFragment { ); } - private void setUpActionBarAndTitle() { - final Activity activity = getActivity(); - if (activity == null) { - Log.e(TAG, "No activity attached, skipping setUpActionBarAndTitle"); - return; - } - final ActionBar actionBar = activity.getActionBar(); - if (actionBar == null) { - Log.e(TAG, "No actionbar, skipping setUpActionBarAndTitle"); - return; - } - actionBar.hide(); - activity.getWindow().setStatusBarColor(Color.TRANSPARENT); - } - @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -258,32 +238,26 @@ public class MainClearConfirm extends InstrumentedFragment { .show(); return new View(getActivity()); } - mContentView = inflater.inflate(R.layout.main_clear_confirm, null); - setUpActionBarAndTitle(); + mContentView = (GlifLayout) inflater.inflate(R.layout.main_clear_confirm, null); establishFinalConfirmationState(); - setAccessibilityTitle(); setSubtitle(); + setAccessibilityTitle(); return mContentView; } private void setAccessibilityTitle() { CharSequence currentTitle = getActivity().getTitle(); - TextView confirmationMessage = mContentView.findViewById(R.id.sud_layout_description); + CharSequence confirmationMessage = mContentView.getDescriptionText(); if (confirmationMessage != null) { - String accessibleText = new StringBuilder(currentTitle).append(",").append( - confirmationMessage.getText()).toString(); + String accessibleText = currentTitle + "," + confirmationMessage; getActivity().setTitle(Utils.createAccessibleSequence(currentTitle, accessibleText)); } } @VisibleForTesting void setSubtitle() { - if (mEraseEsims) { - TextView confirmationMessage = mContentView.findViewById(R.id.sud_layout_description); - if (confirmationMessage != null) { - confirmationMessage.setText(R.string.main_clear_final_desc_esim); - } - } + mContentView.setDescriptionText( + mEraseEsims ? R.string.main_clear_final_desc_esim : R.string.main_clear_final_desc); } @Override diff --git a/src/com/android/settings/accessibility/FloatingMenuFadePreferenceController.java b/src/com/android/settings/accessibility/FloatingMenuFadePreferenceController.java index 70882a48d64..2ef0858e839 100644 --- a/src/com/android/settings/accessibility/FloatingMenuFadePreferenceController.java +++ b/src/com/android/settings/accessibility/FloatingMenuFadePreferenceController.java @@ -67,14 +67,11 @@ public class FloatingMenuFadePreferenceController extends BasePreferenceControll @Override public CharSequence getSummary() { - if (mPreference != null) { - return mPreference.isEnabled() - ? "%s" - : mContext.getString( - R.string.accessibility_button_disabled_button_mode_summary); - } else { - return "%s"; + int rId = R.string.accessibility_button_fade_summary; + if (mPreference != null && !mPreference.isEnabled()) { + rId = R.string.accessibility_button_disabled_button_mode_summary; } + return mContext.getString(rId); } @Override diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingLoadingStateDialogFragment.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingLoadingStateDialogFragment.java index b00f407030d..79cc56ea6dd 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingLoadingStateDialogFragment.java +++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingLoadingStateDialogFragment.java @@ -44,7 +44,7 @@ public class AudioSharingLoadingStateDialogFragment extends InstrumentedDialogFr private static final String TAG = "AudioSharingLoadingDlg"; private static final String BUNDLE_KEY_MESSAGE = "bundle_key_message"; - private static final long AUTO_DISMISS_TIME_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(10); + private static final long AUTO_DISMISS_TIME_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(15); private static final int AUTO_DISMISS_MESSAGE_ID = R.id.message; private static String sMessage = ""; @@ -74,13 +74,15 @@ public class AudioSharingLoadingStateDialogFragment extends InstrumentedDialogFr } AlertDialog dialog = AudioSharingDialogHelper.getDialogIfShowing(manager, TAG); if (dialog != null) { - if (sMessage.equals(message)) { - Log.d(TAG, "Dialog is showing with same message, return."); - return; - } else { - Log.d(TAG, "Dialog is showing with different message, dismiss and reshow."); - dialog.dismiss(); + if (!sMessage.equals(message)) { + Log.d(TAG, "Update dialog message."); + TextView messageView = dialog.findViewById(R.id.message); + if (messageView != null) { + messageView.setText(message); + } } + Log.d(TAG, "Dialog is showing, return."); + return; } sMessage = message; Log.d(TAG, "Show up the loading dialog."); @@ -113,8 +115,10 @@ public class AudioSharingLoadingStateDialogFragment extends InstrumentedDialogFr @NonNull public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { mHandler = new Handler(Looper.getMainLooper()); - mHandler.postDelayed(() -> dismiss(), AUTO_DISMISS_MESSAGE_ID, - AUTO_DISMISS_TIME_THRESHOLD_MS); + mHandler.postDelayed(() -> { + Log.d(TAG, "Auto dismiss dialog after timeout"); + dismiss(); + }, AUTO_DISMISS_MESSAGE_ID, AUTO_DISMISS_TIME_THRESHOLD_MS); Bundle args = requireArguments(); String message = args.getString(BUNDLE_KEY_MESSAGE, ""); AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); @@ -132,6 +136,7 @@ public class AudioSharingLoadingStateDialogFragment extends InstrumentedDialogFr public void onDismiss(@NonNull DialogInterface dialog) { super.onDismiss(dialog); if (mHandler != null) { + Log.d(TAG, "Dialog dismissed, remove auto dismiss task"); mHandler.removeMessages(AUTO_DISMISS_MESSAGE_ID); } } diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java index 2040694c935..c0f463d863f 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java +++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java @@ -70,6 +70,7 @@ import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicBoolean; @@ -112,12 +113,13 @@ public class AudioSharingSwitchBarController extends BasePreferenceController private final MetricsFeatureProvider mMetricsFeatureProvider; private final OnAudioSharingStateChangedListener mListener; private Map> mGroupedConnectedDevices = new HashMap<>(); - private List mTargetActiveSinks = new ArrayList<>(); + @Nullable private AudioSharingDeviceItem mTargetActiveItem; private List mDeviceItemsForSharing = new ArrayList<>(); @VisibleForTesting IntentFilter mIntentFilter; private final AtomicBoolean mCallbacksRegistered = new AtomicBoolean(false); private AtomicInteger mIntentHandleStage = new AtomicInteger(StartIntentHandleStage.TO_HANDLE.ordinal()); + private CopyOnWriteArrayList mSinksInAdding = new CopyOnWriteArrayList<>(); @VisibleForTesting BroadcastReceiver mReceiver = @@ -294,7 +296,16 @@ public class AudioSharingSwitchBarController extends BasePreferenceController public void onReceiveStateChanged( @NonNull BluetoothDevice sink, int sourceId, - @NonNull BluetoothLeBroadcastReceiveState state) {} + @NonNull BluetoothLeBroadcastReceiveState state) { + if (BluetoothUtils.isConnected(state)) { + if (mSinksInAdding.contains(sink)) { + mSinksInAdding.remove(sink); + } + dismissLoadingStateDialogIfNeeded(); + Log.d(TAG, "onReceiveStateChanged() connected, sink = " + sink + + ", remaining sinks = " + mSinksInAdding); + } + } }; AudioSharingSwitchBarController( @@ -506,17 +517,20 @@ public class AudioSharingSwitchBarController extends BasePreferenceController mBtManager, mGroupedConnectedDevices, /* filterByInSharing= */ false); // deviceItems is ordered. The active device is the first place if exits. mDeviceItemsForSharing = new ArrayList<>(deviceItems); - mTargetActiveSinks = new ArrayList<>(); + mTargetActiveItem = null; if (!deviceItems.isEmpty() && deviceItems.get(0).isActive()) { // If active device exists for audio sharing, share to it // automatically once the broadcast is started. - mTargetActiveSinks = - mGroupedConnectedDevices.getOrDefault( - deviceItems.get(0).getGroupId(), ImmutableList.of()); + mTargetActiveItem = deviceItems.get(0); mDeviceItemsForSharing.remove(0); } if (mBroadcast != null) { mBroadcast.startPrivateBroadcast(); + mSinksInAdding.clear(); + // TODO: use string res once finalized. + AudioSharingUtils.postOnMainThread(mContext, + () -> AudioSharingLoadingStateDialogFragment.show(mFragment, + "Starting audio stream...")); mMetricsFeatureProvider.action( mContext, SettingsEnums.ACTION_AUDIO_SHARING_MAIN_SWITCH_ON, @@ -580,27 +594,30 @@ public class AudioSharingSwitchBarController extends BasePreferenceController } private void handleOnBroadcastReady() { + List targetActiveSinks = mTargetActiveItem == null ? ImmutableList.of() + : mGroupedConnectedDevices.getOrDefault( + mTargetActiveItem.getGroupId(), ImmutableList.of()); Pair[] eventData = AudioSharingUtils.buildAudioSharingDialogEventData( SettingsEnums.AUDIO_SHARING_SETTINGS, SettingsEnums.DIALOG_AUDIO_SHARING_ADD_DEVICE, /* userTriggered= */ false, - /* deviceCountInSharing= */ mTargetActiveSinks.isEmpty() ? 0 : 1, + /* deviceCountInSharing= */ targetActiveSinks.isEmpty() ? 0 : 1, /* candidateDeviceCount= */ mDeviceItemsForSharing.size()); - if (!mTargetActiveSinks.isEmpty()) { + if (!targetActiveSinks.isEmpty() && mTargetActiveItem != null) { Log.d(TAG, "handleOnBroadcastReady: automatically add source to active sinks."); - AudioSharingUtils.addSourceToTargetSinks(mTargetActiveSinks, mBtManager); + addSourceToTargetSinks(targetActiveSinks, mTargetActiveItem.getName()); mMetricsFeatureProvider.action(mContext, SettingsEnums.ACTION_AUTO_JOIN_AUDIO_SHARING); - mTargetActiveSinks.clear(); + mTargetActiveItem = null; if (mIntentHandleStage.compareAndSet( StartIntentHandleStage.HANDLE_AUTO_ADD.ordinal(), StartIntentHandleStage.HANDLED.ordinal()) && mDeviceItemsForSharing.size() == 1) { Log.d(TAG, "handleOnBroadcastReady: auto add source to the second device"); - AudioSharingUtils.addSourceToTargetSinks( - mGroupedConnectedDevices.getOrDefault( - mDeviceItemsForSharing.get(0).getGroupId(), ImmutableList.of()), - mBtManager); + AudioSharingDeviceItem target = mDeviceItemsForSharing.get(0); + List targetSinks = mGroupedConnectedDevices.getOrDefault( + target.getGroupId(), ImmutableList.of()); + addSourceToTargetSinks(targetSinks, target.getName()); cleanUp(); // TODO: Add metric for auto add by intent return; @@ -611,6 +628,7 @@ public class AudioSharingSwitchBarController extends BasePreferenceController StartIntentHandleStage.HANDLED.ordinal()); if (mFragment == null) { Log.d(TAG, "handleOnBroadcastReady: dialog fail to show due to null fragment."); + dismissLoadingStateDialogIfNeeded(); cleanUp(); return; } @@ -622,15 +640,15 @@ public class AudioSharingSwitchBarController extends BasePreferenceController new AudioSharingDialogFragment.DialogEventListener() { @Override public void onItemClick(@NonNull AudioSharingDeviceItem item) { - AudioSharingUtils.addSourceToTargetSinks( - mGroupedConnectedDevices.getOrDefault( - item.getGroupId(), ImmutableList.of()), - mBtManager); + List targetSinks = mGroupedConnectedDevices.getOrDefault( + item.getGroupId(), ImmutableList.of()); + addSourceToTargetSinks(targetSinks, item.getName()); cleanUp(); } @Override public void onCancelClick() { + dismissLoadingStateDialogIfNeeded(); cleanUp(); } }; @@ -700,6 +718,27 @@ public class AudioSharingSwitchBarController extends BasePreferenceController }); } + private void addSourceToTargetSinks(List targetActiveSinks, + @NonNull String sinkName) { + mSinksInAdding.addAll(targetActiveSinks); + AudioSharingUtils.addSourceToTargetSinks(targetActiveSinks, mBtManager); + // TODO: move to res once finalized + String loadingMessage = "Sharing with " + sinkName + "..."; + showLoadingStateDialog(loadingMessage); + } + + private void showLoadingStateDialog(@NonNull String loadingMessage) { + AudioSharingUtils.postOnMainThread(mContext, + () -> AudioSharingLoadingStateDialogFragment.show(mFragment, loadingMessage)); + } + + private void dismissLoadingStateDialogIfNeeded() { + if (mSinksInAdding.isEmpty()) { + AudioSharingUtils.postOnMainThread(mContext, + () -> AudioSharingLoadingStateDialogFragment.dismiss(mFragment)); + } + } + private void cleanUp() { mGroupedConnectedDevices.clear(); mDeviceItemsForSharing.clear(); diff --git a/src/com/android/settings/inputmethod/PointerColorCustomizationFragment.java b/src/com/android/settings/inputmethod/PointerColorCustomizationFragment.java new file mode 100644 index 00000000000..2324c5ec2a6 --- /dev/null +++ b/src/com/android/settings/inputmethod/PointerColorCustomizationFragment.java @@ -0,0 +1,59 @@ +/* + * Copyright 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.inputmethod; + +import static com.android.settings.inputmethod.NewKeyboardSettingsUtils.isMouse; +import static com.android.settings.inputmethod.NewKeyboardSettingsUtils.isTouchpad; + +import android.app.settings.SettingsEnums; +import android.content.Context; + +import com.android.settings.R; +import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settingslib.search.SearchIndexable; + +/** Settings for pointer and touchpad. */ +@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC) +public class PointerColorCustomizationFragment extends DashboardFragment { + + private static final String TAG = "PointerColorCustomizationFragment"; + + @Override + public int getMetricsCategory() { + return SettingsEnums.ACCESSIBILITY_POINTER_COLOR_CUSTOMIZATION; + } + + @Override + protected int getPreferenceScreenResId() { + return R.xml.accessibility_pointer_color_customization; + } + + @Override + protected String getLogTag() { + return TAG; + } + + + public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new BaseSearchIndexProvider(R.xml.accessibility_pointer_color_customization) { + @Override + protected boolean isPageSearchEnabled(Context context) { + return isTouchpad() || isMouse(); + } + }; +} diff --git a/src/com/android/settings/inputmethod/PointerTouchpadFragment.java b/src/com/android/settings/inputmethod/PointerTouchpadFragment.java new file mode 100644 index 00000000000..fc069caf604 --- /dev/null +++ b/src/com/android/settings/inputmethod/PointerTouchpadFragment.java @@ -0,0 +1,58 @@ +/* + * Copyright 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.inputmethod; + +import static com.android.settings.inputmethod.NewKeyboardSettingsUtils.isMouse; +import static com.android.settings.inputmethod.NewKeyboardSettingsUtils.isTouchpad; + +import android.app.settings.SettingsEnums; +import android.content.Context; + +import com.android.settings.R; +import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settingslib.search.SearchIndexable; + +/** Accessibility settings for pointer and touchpad. */ +@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC) +public class PointerTouchpadFragment extends DashboardFragment { + + private static final String TAG = "PointerTouchpadFragment"; + + @Override + public int getMetricsCategory() { + return SettingsEnums.ACCESSIBILITY_POINTER_TOUCHPAD; + } + + @Override + protected int getPreferenceScreenResId() { + return R.xml.accessibility_pointer_and_touchpad; + } + + @Override + protected String getLogTag() { + return TAG; + } + + public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new BaseSearchIndexProvider(R.xml.accessibility_pointer_and_touchpad) { + @Override + protected boolean isPageSearchEnabled(Context context) { + return isTouchpad() || isMouse(); + } + }; +} diff --git a/src/com/android/settings/inputmethod/PointerTouchpadPreferenceController.java b/src/com/android/settings/inputmethod/PointerTouchpadPreferenceController.java new file mode 100644 index 00000000000..b6ea063237a --- /dev/null +++ b/src/com/android/settings/inputmethod/PointerTouchpadPreferenceController.java @@ -0,0 +1,39 @@ +/* + * Copyright 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.inputmethod; + +import android.content.Context; + +import androidx.annotation.NonNull; + +import com.android.settings.core.BasePreferenceController; + +/** Controller that shows and updates the pointer touchpad preference. */ +public class PointerTouchpadPreferenceController extends BasePreferenceController { + + public PointerTouchpadPreferenceController(@NonNull Context context, + @NonNull String preferenceKey) { + super(context, preferenceKey); + } + + @Override + public int getAvailabilityStatus() { + boolean isTouchpad = NewKeyboardSettingsUtils.isTouchpad(); + boolean isMouse = NewKeyboardSettingsUtils.isMouse(); + return (isTouchpad || isMouse) ? AVAILABLE : CONDITIONALLY_UNAVAILABLE; + } +} diff --git a/src/com/android/settings/network/SubscriptionInfoListViewModel.kt b/src/com/android/settings/network/SubscriptionInfoListViewModel.kt index df3b8bad278..fe505224e30 100644 --- a/src/com/android/settings/network/SubscriptionInfoListViewModel.kt +++ b/src/com/android/settings/network/SubscriptionInfoListViewModel.kt @@ -20,7 +20,7 @@ import android.app.Application import android.telephony.SubscriptionManager import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope -import com.android.settings.network.telephony.getSelectableSubscriptionInfoList +import com.android.settings.network.telephony.SubscriptionRepository import com.android.settings.network.telephony.subscriptionsChangedFlow import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.SharingStarted @@ -45,7 +45,8 @@ class SubscriptionInfoListViewModel(application: Application) : AndroidViewModel * Getting the Selectable SubscriptionInfo List from the SubscriptionRepository's * getAvailableSubscriptionInfoList */ - val selectableSubscriptionInfoListFlow = application.subscriptionsChangedFlow().map { - application.getSelectableSubscriptionInfoList() - }.stateIn(scope, SharingStarted.Eagerly, initialValue = emptyList()) + val selectableSubscriptionInfoListFlow = + SubscriptionRepository(application) + .selectableSubscriptionInfoListFlow() + .stateIn(scope, SharingStarted.Eagerly, initialValue = emptyList()) } diff --git a/src/com/android/settings/network/SubscriptionUtil.java b/src/com/android/settings/network/SubscriptionUtil.java index fcf15991306..aca2fc3b2e9 100644 --- a/src/com/android/settings/network/SubscriptionUtil.java +++ b/src/com/android/settings/network/SubscriptionUtil.java @@ -52,7 +52,7 @@ import com.android.settings.network.helper.SelectableSubscriptions; import com.android.settings.network.helper.SubscriptionAnnotation; import com.android.settings.network.telephony.DeleteEuiccSubscriptionDialogActivity; import com.android.settings.network.telephony.EuiccRacConnectivityDialogActivity; -import com.android.settings.network.telephony.SubscriptionRepositoryKt; +import com.android.settings.network.telephony.SubscriptionRepository; import com.android.settings.network.telephony.ToggleSubscriptionDialogActivity; import java.util.ArrayList; @@ -508,7 +508,7 @@ public class SubscriptionUtil { * @return list of user selectable subscriptions. */ public static List getSelectableSubscriptionInfoList(Context context) { - return SubscriptionRepositoryKt.getSelectableSubscriptionInfoList(context); + return new SubscriptionRepository(context).getSelectableSubscriptionInfoList(); } /** diff --git a/src/com/android/settings/network/telephony/SubscriptionRepository.kt b/src/com/android/settings/network/telephony/SubscriptionRepository.kt index 26ea9b3fc42..6b5b4cb4d43 100644 --- a/src/com/android/settings/network/telephony/SubscriptionRepository.kt +++ b/src/com/android/settings/network/telephony/SubscriptionRepository.kt @@ -42,13 +42,48 @@ private const val TAG = "SubscriptionRepository" class SubscriptionRepository(private val context: Context) { private val subscriptionManager = context.requireSubscriptionManager() + /** A cold flow of a list of subscriptions that are available and visible to the user. */ + fun selectableSubscriptionInfoListFlow(): Flow> = + context + .subscriptionsChangedFlow() + .map { getSelectableSubscriptionInfoList() } + .conflate() + .flowOn(Dispatchers.Default) + /** * Return a list of subscriptions that are available and visible to the user. * * @return list of user selectable subscriptions. */ - fun getSelectableSubscriptionInfoList(): List = - context.getSelectableSubscriptionInfoList() + fun getSelectableSubscriptionInfoList(): List { + val availableList = + subscriptionManager.getAvailableSubscriptionInfoList() ?: return emptyList() + val visibleList = + availableList.filter { subInfo -> + // Opportunistic subscriptions are considered invisible to users so they should + // never be returned. + SubscriptionUtil.isSubscriptionVisible(subscriptionManager, context, subInfo) + } + return visibleList + .groupBy { it.groupUuid } + .flatMap { (groupUuid, subInfos) -> + if (groupUuid == null) { + subInfos + } else { + // Multiple subscriptions in a group should only have one representative. + // It should be the current active primary subscription if any, or the primary + // subscription with minimum subscription id. + subInfos + .filter { it.simSlotIndex != SubscriptionManager.INVALID_SIM_SLOT_INDEX } + .ifEmpty { subInfos.sortedBy { it.subscriptionId } } + .take(1) + } + } + // Matching the sorting order in + // SubscriptionManagerService.getAvailableSubscriptionInfoList + .sortedWith(compareBy({ it.sortableSimSlotIndex }, { it.subscriptionId })) + .also { Log.d(TAG, "getSelectableSubscriptionInfoList: $it") } + } /** Flow of whether the subscription visible for the given [subId]. */ fun isSubscriptionVisibleFlow(subId: Int): Flow { @@ -154,38 +189,6 @@ fun Context.phoneNumberFlow(subscriptionInfo: SubscriptionInfo): Flow = fun Context.subscriptionsChangedFlow(): Flow = SubscriptionRepository(this).subscriptionsChangedFlow() -/** - * Return a list of subscriptions that are available and visible to the user. - * - * @return list of user selectable subscriptions. - */ -fun Context.getSelectableSubscriptionInfoList(): List { - val subscriptionManager = requireSubscriptionManager() - val availableList = subscriptionManager.getAvailableSubscriptionInfoList() ?: return emptyList() - val visibleList = availableList.filter { subInfo -> - // Opportunistic subscriptions are considered invisible - // to users so they should never be returned. - SubscriptionUtil.isSubscriptionVisible(subscriptionManager, this, subInfo) - } - return visibleList - .groupBy { it.groupUuid } - .flatMap { (groupUuid, subInfos) -> - if (groupUuid == null) { - subInfos - } else { - // Multiple subscriptions in a group should only have one representative. - // It should be the current active primary subscription if any, or the primary - // subscription with minimum subscription id. - subInfos.filter { it.simSlotIndex != SubscriptionManager.INVALID_SIM_SLOT_INDEX } - .ifEmpty { subInfos.sortedBy { it.subscriptionId } } - .take(1) - } - } - // Matching the sorting order in SubscriptionManagerService.getAvailableSubscriptionInfoList - .sortedWith(compareBy({ it.sortableSimSlotIndex }, { it.subscriptionId })) - .also { Log.d(TAG, "getSelectableSubscriptionInfoList: $it") } -} - /** Subscription with invalid sim slot index has lowest sort order. */ private val SubscriptionInfo.sortableSimSlotIndex: Int get() = if (simSlotIndex != SubscriptionManager.INVALID_SIM_SLOT_INDEX) { diff --git a/src/com/android/settings/notification/modes/ZenModeTriggerAddPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeTriggerAddPreferenceController.java index 68cc167ab6c..9355b1bba3c 100644 --- a/src/com/android/settings/notification/modes/ZenModeTriggerAddPreferenceController.java +++ b/src/com/android/settings/notification/modes/ZenModeTriggerAddPreferenceController.java @@ -58,6 +58,5 @@ class ZenModeTriggerAddPreferenceController extends AbstractZenModePreferenceCon conditionId -> saveMode(mode -> { mode.setCustomModeConditionId(mContext, conditionId); return mode; - // TODO: b/342156843 - Maybe jump to the corresponding schedule editing screen? }); } diff --git a/src/com/android/settings/notification/modes/ZenModesListFragment.java b/src/com/android/settings/notification/modes/ZenModesListFragment.java index 37772b38eed..89cb662fc78 100644 --- a/src/com/android/settings/notification/modes/ZenModesListFragment.java +++ b/src/com/android/settings/notification/modes/ZenModesListFragment.java @@ -142,9 +142,6 @@ public class ZenModesListFragment extends ZenModesFragmentBase { @Override public List getNonIndexableKeys(Context context) { final List keys = super.getNonIndexableKeys(context); - // TODO: b/332937523 - determine if this should be removed once the preference - // controller adds dynamic data to index - keys.add(ZenModesListPreferenceController.KEY); return keys; } diff --git a/src/com/android/settings/security/screenlock/AutoPinConfirmPreferenceController.java b/src/com/android/settings/security/screenlock/AutoPinConfirmPreferenceController.java index a41a0b2f7ee..560f3134d66 100644 --- a/src/com/android/settings/security/screenlock/AutoPinConfirmPreferenceController.java +++ b/src/com/android/settings/security/screenlock/AutoPinConfirmPreferenceController.java @@ -20,6 +20,7 @@ import static com.android.internal.widget.LockPatternUtils.MIN_AUTO_PIN_REQUIREM import android.content.Context; +import androidx.fragment.app.Fragment; import androidx.preference.Preference; import androidx.preference.TwoStatePreference; @@ -28,7 +29,6 @@ import com.android.settings.R; import com.android.settings.core.PreferenceControllerMixin; import com.android.settings.password.ChooseLockSettingsHelper; import com.android.settingslib.core.AbstractPreferenceController; -import com.android.settingslib.core.lifecycle.ObservablePreferenceFragment; /** * Preference controller for the pin_auto_confirm setting. @@ -40,11 +40,10 @@ public class AutoPinConfirmPreferenceController extends AbstractPreferenceContro private final int mUserId; private final LockPatternUtils mLockPatternUtils; - private final ObservablePreferenceFragment mParentFragment; + private final Fragment mParentFragment; public AutoPinConfirmPreferenceController(Context context, int userId, - LockPatternUtils lockPatternUtils, - ObservablePreferenceFragment parentFragment) { + LockPatternUtils lockPatternUtils, Fragment parentFragment) { super(context); mUserId = userId; mLockPatternUtils = lockPatternUtils; diff --git a/src/com/android/settings/support/actionbar/HelpMenuController.java b/src/com/android/settings/support/actionbar/HelpMenuController.java deleted file mode 100644 index 7e1f460e158..00000000000 --- a/src/com/android/settings/support/actionbar/HelpMenuController.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2017 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.support.actionbar; - -import static com.android.settings.support.actionbar.HelpResourceProvider.HELP_URI_RESOURCE_KEY; - -import android.app.Activity; -import android.os.Bundle; -import android.view.Menu; -import android.view.MenuInflater; - -import androidx.annotation.NonNull; -import androidx.fragment.app.Fragment; - -import com.android.settingslib.HelpUtils; -import com.android.settingslib.core.lifecycle.LifecycleObserver; -import com.android.settingslib.core.lifecycle.ObservableFragment; -import com.android.settingslib.core.lifecycle.ObservablePreferenceFragment; -import com.android.settingslib.core.lifecycle.events.OnCreateOptionsMenu; - -/** - * A controller that adds help menu to any Settings page. - */ -public class HelpMenuController implements LifecycleObserver, OnCreateOptionsMenu { - - private final Fragment mHost; - - public static void init(@NonNull ObservablePreferenceFragment host) { - host.getSettingsLifecycle().addObserver(new HelpMenuController(host)); - } - - public static void init(@NonNull ObservableFragment host) { - host.getSettingsLifecycle().addObserver(new HelpMenuController(host)); - } - - private HelpMenuController(@NonNull Fragment host) { - mHost = host; - } - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - final Bundle arguments = mHost.getArguments(); - int helpResourceId = 0; - if (arguments != null && arguments.containsKey(HELP_URI_RESOURCE_KEY)) { - helpResourceId = arguments.getInt(HELP_URI_RESOURCE_KEY); - } else if (mHost instanceof HelpResourceProvider) { - helpResourceId = ((HelpResourceProvider) mHost).getHelpResource(); - } - - String helpUri = null; - if (helpResourceId != 0) { - helpUri = mHost.getContext().getString(helpResourceId); - } - final Activity activity = mHost.getActivity(); - if (helpUri != null && activity != null) { - HelpUtils.prepareHelpMenuItem(activity, menu, helpUri, mHost.getClass().getName()); - } - } -} diff --git a/tests/robotests/src/com/android/settings/MainClearConfirmTest.java b/tests/robotests/src/com/android/settings/MainClearConfirmTest.java index f7711c81311..a102fdae9be 100644 --- a/tests/robotests/src/com/android/settings/MainClearConfirmTest.java +++ b/tests/robotests/src/com/android/settings/MainClearConfirmTest.java @@ -31,10 +31,11 @@ import android.platform.test.flag.junit.SetFlagsRule; import android.security.Flags; import android.service.persistentdata.PersistentDataBlockManager; import android.view.LayoutInflater; -import android.widget.TextView; import androidx.fragment.app.FragmentActivity; +import com.google.android.setupdesign.GlifLayout; + import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -85,12 +86,12 @@ public class MainClearConfirmTest { MainClearConfirm mainClearConfirm = new MainClearConfirm(); mainClearConfirm.mEraseEsims = true; mainClearConfirm.mContentView = - LayoutInflater.from(mActivity).inflate(R.layout.main_clear_confirm, null); + (GlifLayout) LayoutInflater.from(mActivity) + .inflate(R.layout.main_clear_confirm, null); mainClearConfirm.setSubtitle(); - assertThat(((TextView) mainClearConfirm.mContentView - .findViewById(R.id.sud_layout_description)).getText()) + assertThat(mainClearConfirm.mContentView.getDescriptionText()) .isEqualTo(mActivity.getString(R.string.main_clear_final_desc_esim)); } @@ -99,12 +100,12 @@ public class MainClearConfirmTest { MainClearConfirm mainClearConfirm = new MainClearConfirm(); mainClearConfirm.mEraseEsims = false; mainClearConfirm.mContentView = - LayoutInflater.from(mActivity).inflate(R.layout.main_clear_confirm, null); + (GlifLayout) LayoutInflater.from(mActivity) + .inflate(R.layout.main_clear_confirm, null); mainClearConfirm.setSubtitle(); - assertThat(((TextView) mainClearConfirm.mContentView - .findViewById(R.id.sud_layout_description)).getText()) + assertThat(mainClearConfirm.mContentView.getDescriptionText()) .isEqualTo(mActivity.getString(R.string.main_clear_final_desc)); } diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingLoadingStateDialogFragmentTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingLoadingStateDialogFragmentTest.java index b5da88cff1c..ff15f5269d4 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingLoadingStateDialogFragmentTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingLoadingStateDialogFragmentTest.java @@ -150,7 +150,7 @@ public class AudioSharingLoadingStateDialogFragmentTest { } @Test - public void showDialog_newMessage_dismissAndShowNewDialog() { + public void showDialog_newMessage_keepAndUpdateDialog() { mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); AudioSharingLoadingStateDialogFragment.show(mParent, TEST_MESSAGE1); shadowMainLooper().idle(); @@ -163,12 +163,7 @@ public class AudioSharingLoadingStateDialogFragmentTest { AudioSharingLoadingStateDialogFragment.show(mParent, TEST_MESSAGE2); shadowMainLooper().idle(); - assertThat(dialog.isShowing()).isFalse(); - AlertDialog newDialog = ShadowAlertDialogCompat.getLatestAlertDialog(); - assertThat(newDialog).isNotNull(); - assertThat(newDialog.isShowing()).isTrue(); - view = newDialog.findViewById(R.id.message); - assertThat(view).isNotNull(); + assertThat(dialog.isShowing()).isTrue(); assertThat(view.getText().toString()).isEqualTo(TEST_MESSAGE2); } } diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarControllerTest.java index 354c5c7f08e..0d21f18b821 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarControllerTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarControllerTest.java @@ -57,7 +57,9 @@ import android.util.Pair; import android.view.View; import android.view.accessibility.AccessibilityEvent; import android.widget.CompoundButton; +import android.widget.TextView; +import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.DialogFragment; import androidx.fragment.app.Fragment; @@ -235,6 +237,7 @@ public class AudioSharingSwitchBarControllerTest { @After public void tearDown() { + ShadowAlertDialogCompat.reset(); ShadowBluetoothUtils.reset(); ShadowThreadUtils.reset(); } @@ -426,6 +429,8 @@ public class AudioSharingSwitchBarControllerTest { assertThat(childFragments) .comparingElementsUsing(CLAZZNAME_EQUALS) .containsExactly(AudioSharingConfirmDialogFragment.class.getName()); + + childFragments.forEach(fragment -> ((DialogFragment) fragment).dismiss()); } @Test @@ -490,14 +495,21 @@ public class AudioSharingSwitchBarControllerTest { public void onAudioSharingProfilesConnected() {} }); mController.onCheckedChanged(mBtnView, /* isChecked= */ true); + shadowOf(Looper.getMainLooper()).idle(); + verify(mBroadcast).startPrivateBroadcast(); + List childFragments = mParentFragment.getChildFragmentManager().getFragments(); + // No loading state dialog. + assertThat(childFragments).isEmpty(); + mController.mBroadcastCallback.onPlaybackStarted(0, 0); shadowOf(Looper.getMainLooper()).idle(); verify(mFeatureFactory.metricsFeatureProvider) .action(any(Context.class), eq(SettingsEnums.ACTION_AUTO_JOIN_AUDIO_SHARING)); - List childFragments = mParentFragment.getChildFragmentManager().getFragments(); + childFragments = mParentFragment.getChildFragmentManager().getFragments(); + // No audio sharing dialog. assertThat(childFragments).isEmpty(); } @@ -514,7 +526,13 @@ public class AudioSharingSwitchBarControllerTest { when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(mMetadata); doNothing().when(mBroadcast).startPrivateBroadcast(); mController.onCheckedChanged(mBtnView, /* isChecked= */ true); + shadowOf(Looper.getMainLooper()).idle(); + verify(mBroadcast).startPrivateBroadcast(); + List childFragments = mParentFragment.getChildFragmentManager().getFragments(); + assertThat(childFragments).comparingElementsUsing(CLAZZNAME_EQUALS).containsExactly( + AudioSharingLoadingStateDialogFragment.class.getName()); + mController.mBroadcastCallback.onPlaybackStarted(0, 0); shadowOf(Looper.getMainLooper()).idle(); @@ -522,8 +540,12 @@ public class AudioSharingSwitchBarControllerTest { verify(mFeatureFactory.metricsFeatureProvider, never()) .action(any(Context.class), eq(SettingsEnums.ACTION_AUTO_JOIN_AUDIO_SHARING)); - List childFragments = mParentFragment.getChildFragmentManager().getFragments(); - assertThat(childFragments).isEmpty(); + childFragments = mParentFragment.getChildFragmentManager().getFragments(); + // No audio sharing dialog. + assertThat(childFragments).comparingElementsUsing(CLAZZNAME_EQUALS).doesNotContain( + AudioSharingDialogFragment.class.getName()); + + childFragments.forEach(fragment -> ((DialogFragment) fragment).dismiss()); } @Test @@ -534,23 +556,42 @@ public class AudioSharingSwitchBarControllerTest { when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(mDevice2, mDevice1)); when(mAssistant.getAllSources(any(BluetoothDevice.class))).thenReturn(ImmutableList.of()); doNothing().when(mBroadcast).startPrivateBroadcast(); - mController.onCheckedChanged(mBtnView, /* isChecked= */ true); when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(mMetadata); + mController.onCheckedChanged(mBtnView, /* isChecked= */ true); + shadowOf(Looper.getMainLooper()).idle(); + verify(mBroadcast).startPrivateBroadcast(); + List childFragments = mParentFragment.getChildFragmentManager().getFragments(); + assertThat(childFragments).comparingElementsUsing(CLAZZNAME_EQUALS).containsExactly( + AudioSharingLoadingStateDialogFragment.class.getName()); + AudioSharingLoadingStateDialogFragment loadingFragment = + (AudioSharingLoadingStateDialogFragment) Iterables.getOnlyElement(childFragments); + // TODO: use string res once finalized + String expectedMessage = "Starting audio stream..."; + checkLoadingStateDialogMessage(loadingFragment, expectedMessage); + mController.mBroadcastCallback.onPlaybackStarted(0, 0); shadowOf(Looper.getMainLooper()).idle(); verify(mFeatureFactory.metricsFeatureProvider) .action(any(Context.class), eq(SettingsEnums.ACTION_AUTO_JOIN_AUDIO_SHARING)); + // TODO: use string res once finalized + expectedMessage = "Sharing with " + TEST_DEVICE_NAME2 + "..."; + checkLoadingStateDialogMessage(loadingFragment, expectedMessage); - List childFragments = mParentFragment.getChildFragmentManager().getFragments(); + childFragments = mParentFragment.getChildFragmentManager().getFragments(); assertThat(childFragments) .comparingElementsUsing(CLAZZNAME_EQUALS) - .containsExactly(AudioSharingDialogFragment.class.getName()); + .containsExactly(AudioSharingDialogFragment.class.getName(), + AudioSharingLoadingStateDialogFragment.class.getName()); - AudioSharingDialogFragment fragment = - (AudioSharingDialogFragment) Iterables.getOnlyElement(childFragments); - Pair[] eventData = fragment.getEventData(); + Pair[] eventData = new Pair[0]; + for (Fragment fragment : childFragments) { + if (fragment instanceof AudioSharingDialogFragment) { + eventData = ((AudioSharingDialogFragment) fragment).getEventData(); + break; + } + } assertThat(eventData) .asList() .containsExactly( @@ -570,6 +611,8 @@ public class AudioSharingSwitchBarControllerTest { AudioSharingUtils.MetricKey.METRIC_KEY_CANDIDATE_DEVICE_COUNT .ordinal(), 1)); + + childFragments.forEach(fragment -> ((DialogFragment) fragment).dismiss()); } @Test @@ -582,6 +625,8 @@ public class AudioSharingSwitchBarControllerTest { when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(mMetadata); doNothing().when(mBroadcast).startPrivateBroadcast(); mController.onCheckedChanged(mBtnView, /* isChecked= */ true); + shadowOf(Looper.getMainLooper()).idle(); + verify(mBroadcast).startPrivateBroadcast(); mController.mBroadcastCallback.onPlaybackStarted(0, 0); shadowOf(Looper.getMainLooper()).idle(); @@ -597,6 +642,17 @@ public class AudioSharingSwitchBarControllerTest { verify(mAssistant).addSource(mDevice1, mMetadata, /* isGroupOp= */ false); assertThat(dialog.isShowing()).isFalse(); + // Loading state dialog shows sharing state for the user chosen sink. + List childFragments = mParentFragment.getChildFragmentManager().getFragments(); + assertThat(childFragments).comparingElementsUsing(CLAZZNAME_EQUALS).containsExactly( + AudioSharingLoadingStateDialogFragment.class.getName()); + AudioSharingLoadingStateDialogFragment loadingFragment = + (AudioSharingLoadingStateDialogFragment) Iterables.getOnlyElement(childFragments); + // TODO: use string res once finalized + String expectedMessage = "Sharing with " + TEST_DEVICE_NAME1 + "..."; + checkLoadingStateDialogMessage(loadingFragment, expectedMessage); + + childFragments.forEach(fragment -> ((DialogFragment) fragment).dismiss()); } @Test @@ -609,6 +665,8 @@ public class AudioSharingSwitchBarControllerTest { when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(mMetadata); doNothing().when(mBroadcast).startPrivateBroadcast(); mController.onCheckedChanged(mBtnView, /* isChecked= */ true); + shadowOf(Looper.getMainLooper()).idle(); + verify(mBroadcast).startPrivateBroadcast(); mController.mBroadcastCallback.onPlaybackStarted(0, 0); shadowOf(Looper.getMainLooper()).idle(); @@ -624,10 +682,21 @@ public class AudioSharingSwitchBarControllerTest { verify(mAssistant, never()).addSource(mDevice1, mMetadata, /* isGroupOp= */ false); assertThat(dialog.isShowing()).isFalse(); + // Loading state dialog shows sharing state for the auto add active sink. + List childFragments = mParentFragment.getChildFragmentManager().getFragments(); + assertThat(childFragments).comparingElementsUsing(CLAZZNAME_EQUALS).containsExactly( + AudioSharingLoadingStateDialogFragment.class.getName()); + AudioSharingLoadingStateDialogFragment loadingFragment = + (AudioSharingLoadingStateDialogFragment) Iterables.getOnlyElement(childFragments); + // TODO: use string res once finalized + String expectedMessage = "Sharing with " + TEST_DEVICE_NAME2 + "..."; + checkLoadingStateDialogMessage(loadingFragment, expectedMessage); + + childFragments.forEach(fragment -> ((DialogFragment) fragment).dismiss()); } @Test - public void testBluetoothLeBroadcastCallbacks_updateSwitch() { + public void testBroadcastCallbacks_updateSwitch() { mOnAudioSharingStateChanged = false; mSwitchBar.setChecked(false); when(mBroadcast.isEnabled(any())).thenReturn(false); @@ -673,7 +742,7 @@ public class AudioSharingSwitchBarControllerTest { } @Test - public void testBluetoothLeBroadcastCallbacks_doNothing() { + public void testBroadcastCallbacks_doNothing() { mController.mBroadcastCallback.onBroadcastMetadataChanged(/* reason= */ 1, mMetadata); mController.mBroadcastCallback.onBroadcastUpdated(/* reason= */ 1, /* broadcastId= */ 1); mController.mBroadcastCallback.onPlaybackStarted(/* reason= */ 1, /* broadcastId= */ 1); @@ -685,7 +754,7 @@ public class AudioSharingSwitchBarControllerTest { } @Test - public void testBluetoothLeBroadcastAssistantCallbacks_logAction() { + public void testAssistantCallbacks_onSourceAddFailed_logAction() { mController.mBroadcastAssistantCallback.onSourceAddFailed( mDevice1, mMetadata, /* reason= */ 1); verify(mFeatureFactory.metricsFeatureProvider) @@ -696,7 +765,24 @@ public class AudioSharingSwitchBarControllerTest { } @Test - public void testBluetoothLeBroadcastAssistantCallbacks_doNothing() { + public void testAssistantCallbacks_onReceiveStateChanged_dismissLoadingDialog() { + AudioSharingLoadingStateDialogFragment.show(mParentFragment, TEST_DEVICE_NAME1); + shadowOf(Looper.getMainLooper()).idle(); + List childFragments = mParentFragment.getChildFragmentManager().getFragments(); + assertThat(childFragments).comparingElementsUsing(CLAZZNAME_EQUALS).containsExactly( + AudioSharingLoadingStateDialogFragment.class.getName()); + + BluetoothLeBroadcastReceiveState state = mock(BluetoothLeBroadcastReceiveState.class); + when(state.getBisSyncState()).thenReturn(ImmutableList.of(1L)); + mController.mBroadcastAssistantCallback.onReceiveStateChanged(mDevice1, /* sourceId= */ 1, + state); + shadowOf(Looper.getMainLooper()).idle(); + childFragments = mParentFragment.getChildFragmentManager().getFragments(); + assertThat(childFragments).isEmpty(); + } + + @Test + public void testAssistantCallbacks_doNothing() { BluetoothLeBroadcastReceiveState state = mock(BluetoothLeBroadcastReceiveState.class); // Do nothing @@ -784,7 +870,7 @@ public class AudioSharingSwitchBarControllerTest { @Test public void handleStartAudioSharingFromIntent_flagOff_doNothing() { mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); - setUpStartSharingIntent(); + var unused = setUpFragmentWithStartSharingIntent(); mController.onStart(mLifecycleOwner); shadowOf(Looper.getMainLooper()).idle(); @@ -795,7 +881,7 @@ public class AudioSharingSwitchBarControllerTest { public void handleStartAudioSharingFromIntent_profileNotReady_doNothing() { mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); when(mAssistant.isProfileReady()).thenReturn(false); - setUpStartSharingIntent(); + var unused = setUpFragmentWithStartSharingIntent(); mController.onServiceConnected(); shadowOf(Looper.getMainLooper()).idle(); @@ -817,13 +903,16 @@ public class AudioSharingSwitchBarControllerTest { when(mBtnView.isEnabled()).thenReturn(true); when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(mDevice2, mDevice1)); when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(mMetadata); - setUpStartSharingIntent(); + Fragment parentFragment = setUpFragmentWithStartSharingIntent(); mController.onServiceConnected(); shadowOf(Looper.getMainLooper()).idle(); verify(mSwitchBar).setChecked(true); doNothing().when(mBroadcast).startPrivateBroadcast(); mController.onCheckedChanged(mBtnView, /* isChecked= */ true); + shadowOf(Looper.getMainLooper()).idle(); + + verify(mBroadcast).startPrivateBroadcast(); mController.mBroadcastCallback.onPlaybackStarted(0, 0); shadowOf(Looper.getMainLooper()).idle(); @@ -831,11 +920,21 @@ public class AudioSharingSwitchBarControllerTest { .action(any(Context.class), eq(SettingsEnums.ACTION_AUTO_JOIN_AUDIO_SHARING)); verify(mAssistant).addSource(mDevice1, mMetadata, /* isGroupOp= */ false); verify(mAssistant).addSource(mDevice2, mMetadata, /* isGroupOp= */ false); - List childFragments = mParentFragment.getChildFragmentManager().getFragments(); - assertThat(childFragments).isEmpty(); + List childFragments = parentFragment.getChildFragmentManager().getFragments(); + // Skip audio sharing dialog. + assertThat(childFragments).comparingElementsUsing(CLAZZNAME_EQUALS).containsExactly( + AudioSharingLoadingStateDialogFragment.class.getName()); + // The loading state dialog shows sharing state for the auto add second sink. + AudioSharingLoadingStateDialogFragment loadingFragment = + (AudioSharingLoadingStateDialogFragment) Iterables.getOnlyElement(childFragments); + // TODO: use string res once finalized + String expectedMessage = "Sharing with " + TEST_DEVICE_NAME1 + "..."; + checkLoadingStateDialogMessage(loadingFragment, expectedMessage); + + childFragments.forEach(fragment -> ((DialogFragment) fragment).dismiss()); } - private void setUpStartSharingIntent() { + private Fragment setUpFragmentWithStartSharingIntent() { Bundle args = new Bundle(); args.putBoolean(EXTRA_START_LE_AUDIO_SHARING, true); Intent intent = new Intent(); @@ -849,5 +948,15 @@ public class AudioSharingSwitchBarControllerTest { .get(); shadowOf(Looper.getMainLooper()).idle(); mController.init(fragment); + return fragment; + } + + private void checkLoadingStateDialogMessage( + @NonNull AudioSharingLoadingStateDialogFragment fragment, + @NonNull String expectedMessage) { + TextView loadingMessage = fragment.getDialog() == null ? null + : fragment.getDialog().findViewById(R.id.message); + assertThat(loadingMessage).isNotNull(); + assertThat(loadingMessage.getText().toString()).isEqualTo(expectedMessage); } } diff --git a/tests/robotests/src/com/android/settings/inputmethod/PointerTouchpadFragmentTest.java b/tests/robotests/src/com/android/settings/inputmethod/PointerTouchpadFragmentTest.java new file mode 100644 index 00000000000..68cd76893e9 --- /dev/null +++ b/tests/robotests/src/com/android/settings/inputmethod/PointerTouchpadFragmentTest.java @@ -0,0 +1,51 @@ +/* + * Copyright 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.inputmethod; + +import static com.google.common.truth.Truth.assertThat; + +import android.app.settings.SettingsEnums; + +import com.android.settings.R; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +@RunWith(RobolectricTestRunner.class) +public class PointerTouchpadFragmentTest { + + private PointerTouchpadFragment mFragment; + + @Before + public void setUp() { + mFragment = new PointerTouchpadFragment(); + } + + @Test + public void getPreferenceScreenResId_isPointerTouchpad() { + assertThat(mFragment.getPreferenceScreenResId()) + .isEqualTo(R.xml.accessibility_pointer_and_touchpad); + } + + @Test + public void getMetricsCategory_returnsCorrectCategory() { + assertThat(mFragment.getMetricsCategory()).isEqualTo( + SettingsEnums.ACCESSIBILITY_POINTER_TOUCHPAD); + } +} diff --git a/tests/robotests/src/com/android/settings/inputmethod/PointerTouchpadPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/inputmethod/PointerTouchpadPreferenceControllerTest.java new file mode 100644 index 00000000000..6fceebaef0a --- /dev/null +++ b/tests/robotests/src/com/android/settings/inputmethod/PointerTouchpadPreferenceControllerTest.java @@ -0,0 +1,91 @@ +/* + * Copyright 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.inputmethod; + +import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.view.InputDevice; + +import androidx.test.core.app.ApplicationProvider; + +import com.android.settings.core.BasePreferenceController; +import com.android.settings.testutils.shadow.ShadowInputDevice; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +/** Tests for {@link PointerTouchpadPreferenceController}. */ +@RunWith(RobolectricTestRunner.class) +@Config(shadows = { + ShadowInputDevice.class, +}) +public final class PointerTouchpadPreferenceControllerTest { + @Rule public MockitoRule rule = MockitoJUnit.rule(); + + private PointerTouchpadPreferenceController mController; + + @Before + public void initObjects() { + Context context = ApplicationProvider.getApplicationContext(); + mController = new PointerTouchpadPreferenceController(context, "pointer_touchpad"); + ShadowInputDevice.reset(); + } + + @Test + public void getAvailableStatus_noTouchpadOrMouseConditionallyUnavailable() { + int deviceId = 1; + ShadowInputDevice.sDeviceIds = new int[]{deviceId}; + InputDevice device = ShadowInputDevice.makeInputDevicebyIdWithSources(deviceId, + InputDevice.SOURCE_BLUETOOTH_STYLUS); + ShadowInputDevice.addDevice(deviceId, device); + + assertThat(mController.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE); + } + + @Test + public void getAvailabilityStatus_isTouchpadAvailable() { + int deviceId = 1; + ShadowInputDevice.sDeviceIds = new int[]{deviceId}; + InputDevice device = ShadowInputDevice.makeInputDevicebyIdWithSources(deviceId, + InputDevice.SOURCE_TOUCHPAD); + ShadowInputDevice.addDevice(deviceId, device); + + assertThat(mController.getAvailabilityStatus()) + .isEqualTo(BasePreferenceController.AVAILABLE); + } + + @Test + public void getAvailabilityStatus_isMouseAvailable() { + int deviceId = 1; + ShadowInputDevice.sDeviceIds = new int[]{deviceId}; + InputDevice device = ShadowInputDevice.makeInputDevicebyIdWithSources(deviceId, + InputDevice.SOURCE_MOUSE); + ShadowInputDevice.addDevice(deviceId, device); + + assertThat(mController.getAvailabilityStatus()) + .isEqualTo(BasePreferenceController.AVAILABLE); + } +} diff --git a/tests/robotests/src/com/android/settings/security/screenlock/AutoPinConfirmPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/security/screenlock/AutoPinConfirmPreferenceControllerTest.java index 86c1244c8b0..0e0ed6925e9 100644 --- a/tests/robotests/src/com/android/settings/security/screenlock/AutoPinConfirmPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/security/screenlock/AutoPinConfirmPreferenceControllerTest.java @@ -22,11 +22,11 @@ import static org.mockito.Mockito.when; import android.content.Context; +import androidx.fragment.app.Fragment; import androidx.preference.SwitchPreference; import androidx.test.core.app.ApplicationProvider; import com.android.internal.widget.LockPatternUtils; -import com.android.settingslib.core.lifecycle.ObservablePreferenceFragment; import org.junit.Before; import org.junit.Test; @@ -41,7 +41,7 @@ public class AutoPinConfirmPreferenceControllerTest { @Mock private LockPatternUtils mLockPatternUtils; @Mock - private ObservablePreferenceFragment mParentFragment; + private Fragment mParentFragment; private AutoPinConfirmPreferenceController mController; private SwitchPreference mPreference; diff --git a/tests/robotests/src/com/android/settings/support/actionbar/HelpMenuControllerTest.java b/tests/robotests/src/com/android/settings/support/actionbar/HelpMenuControllerTest.java deleted file mode 100644 index 8cd26da1ffe..00000000000 --- a/tests/robotests/src/com/android/settings/support/actionbar/HelpMenuControllerTest.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (C) 2017 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.support.actionbar; - -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; - -import android.content.Context; -import android.os.Bundle; - -import com.android.settingslib.core.lifecycle.ObservablePreferenceFragment; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.robolectric.RobolectricTestRunner; - -@RunWith(RobolectricTestRunner.class) -public class HelpMenuControllerTest { - - @Mock - private Context mContext; - private TestFragment mHost; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - mHost = spy(new TestFragment()); - doReturn(mContext).when(mHost).getContext(); - } - - @Test - public void onCreateOptionsMenu_withArgumentOverride_shouldPrepareHelpUsingOverride() { - final Bundle bundle = new Bundle(); - bundle.putInt(HelpResourceProvider.HELP_URI_RESOURCE_KEY, 123); - mHost.setArguments(bundle); - - HelpMenuController.init(mHost); - - mHost.getSettingsLifecycle().onCreateOptionsMenu(null /* menu */, null /* inflater */); - - verify(mContext).getString(123); - } - - @Test - public void onCreateOptionsMenu_noArgumentOverride_shouldPrepareHelpUsingProvider() { - HelpMenuController.init(mHost); - - mHost.getSettingsLifecycle().onCreateOptionsMenu(null /* menu */, null /* inflater */); - - verify(mContext).getString(mHost.getHelpResource()); - } - - private static class TestFragment extends ObservablePreferenceFragment - implements HelpResourceProvider { - - @Override - public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { - } - } -} diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/SubscriptionRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/SubscriptionRepositoryTest.kt index 5dbc5340745..5052f57c588 100644 --- a/tests/spa_unit/src/com/android/settings/network/telephony/SubscriptionRepositoryTest.kt +++ b/tests/spa_unit/src/com/android/settings/network/telephony/SubscriptionRepositoryTest.kt @@ -120,7 +120,7 @@ class SubscriptionRepositoryTest { ) } - val subInfos = context.getSelectableSubscriptionInfoList() + val subInfos = repository.getSelectableSubscriptionInfoList() assertThat(subInfos.map { it.simSlotIndex }) .containsExactly(SIM_SLOT_INDEX_0, SIM_SLOT_INDEX_1).inOrder() @@ -141,7 +141,7 @@ class SubscriptionRepositoryTest { ) } - val subInfos = context.getSelectableSubscriptionInfoList() + val subInfos = repository.getSelectableSubscriptionInfoList() assertThat(subInfos.map { it.simSlotIndex }) .containsExactly(SIM_SLOT_INDEX_1, SubscriptionManager.INVALID_SIM_SLOT_INDEX).inOrder() @@ -164,7 +164,7 @@ class SubscriptionRepositoryTest { ) } - val subInfos = context.getSelectableSubscriptionInfoList() + val subInfos = repository.getSelectableSubscriptionInfoList() assertThat(subInfos.map { it.subscriptionId }).containsExactly(SUB_ID_IN_SLOT_0) } @@ -184,7 +184,7 @@ class SubscriptionRepositoryTest { ) } - val subInfos = context.getSelectableSubscriptionInfoList() + val subInfos = repository.getSelectableSubscriptionInfoList() assertThat(subInfos.map { it.subscriptionId }).containsExactly(SUB_ID_3_NOT_IN_SLOT) }