diff --git a/AndroidManifest.xml b/AndroidManifest.xml index c001e03d094..e204dd92154 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -3227,6 +3227,19 @@ + + + + + + + + + + + + + diff --git a/aconfig/accessibility/accessibility_flags.aconfig b/aconfig/accessibility/accessibility_flags.aconfig index 5c81cc9a671..1871172fc5a 100644 --- a/aconfig/accessibility/accessibility_flags.aconfig +++ b/aconfig/accessibility/accessibility_flags.aconfig @@ -37,6 +37,13 @@ flag { bug: "300302098" } +flag { + name: "enable_color_contrast_control" + namespace: "accessibility" + description: "Allows users to control color contrast in the Accessibility settings page." + bug: "246577325" +} + flag { name: "enable_hearing_aid_preset_control" namespace: "accessibility" @@ -89,8 +96,11 @@ flag { } flag { - name: "enable_color_contrast_control" + name: "toggle_feature_fragment_collection_info" namespace: "accessibility" - description: "Allows users to control color contrast in the Accessibility settings page." - bug: "246577325" + description: "Provides custom CollectionInfo for ToggleFeaturePreferenceFragment" + bug: "318607873" + metadata { + purpose: PURPOSE_BUGFIX + } } diff --git a/aconfig/settings_bluetooth_declarations.aconfig b/aconfig/settings_bluetooth_declarations.aconfig index 3d14288fcd2..0c423b5a1b7 100644 --- a/aconfig/settings_bluetooth_declarations.aconfig +++ b/aconfig/settings_bluetooth_declarations.aconfig @@ -34,3 +34,13 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "enable_bluetooth_key_missing_dialog" + namespace: "cross_device_experiences" + description: "Show a dialog if the bluetooth key is missing when reconnecting" + bug: "360031750" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/res/layout/bluetooth_key_missing.xml b/res/layout/bluetooth_key_missing.xml new file mode 100644 index 00000000000..b9f8d866bd3 --- /dev/null +++ b/res/layout/bluetooth_key_missing.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml index 3cfe0686db2..c63e5f4fcf2 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1860,6 +1860,15 @@ Change + + %1$s not connected + + For your security, forget this device, then pair it again + + Forget device + + Cancel + Device details diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java index 493a27b6f26..7678338976a 100644 --- a/src/com/android/settings/Settings.java +++ b/src/com/android/settings/Settings.java @@ -447,7 +447,7 @@ public class Settings extends SettingsActivity { super.onNewIntent(intent); Log.d(TAG, "Starting onNewIntent"); - + setIntent(intent); createUiFromIntent(null /* savedState */, convertIntent(intent)); } diff --git a/src/com/android/settings/accessibility/AccessibilityFragmentUtils.java b/src/com/android/settings/accessibility/AccessibilityFragmentUtils.java new file mode 100644 index 00000000000..34e17c01335 --- /dev/null +++ b/src/com/android/settings/accessibility/AccessibilityFragmentUtils.java @@ -0,0 +1,152 @@ +/* + * 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 android.os.Bundle; +import android.text.TextUtils; +import android.view.View; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; + +import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; +import androidx.core.view.AccessibilityDelegateCompat; +import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; +import androidx.preference.Preference; +import androidx.preference.PreferenceGroupAdapter; +import androidx.recyclerview.widget.RecyclerView; +import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate; + +import com.android.settingslib.widget.IllustrationPreference; + +/** Utilities for {@code Settings > Accessibility} fragments. */ +public class AccessibilityFragmentUtils { + // TODO: b/350782252 - Replace with an official library-provided solution when available. + /** + * Modifies the existing {@link RecyclerViewAccessibilityDelegate} of the provided + * {@link RecyclerView} for this fragment to report the number of visible and important + * items on this page via the RecyclerView's {@link AccessibilityNodeInfo}. + * + *

Note: This is special-cased to the structure of these fragments: + * one column, N rows (one per preference, including category titles and header+footer + * preferences), <=N 'important' rows (image prefs without content descriptions). This + * is not intended for use with generic {@link RecyclerView}s. + */ + public static RecyclerView addCollectionInfoToAccessibilityDelegate(RecyclerView recyclerView) { + if (!Flags.toggleFeatureFragmentCollectionInfo()) { + return recyclerView; + } + final RecyclerViewAccessibilityDelegate delegate = + recyclerView.getCompatAccessibilityDelegate(); + if (delegate == null) { + // No delegate, so do nothing. This should not occur for real RecyclerViews. + return recyclerView; + } + recyclerView.setAccessibilityDelegateCompat( + new RvAccessibilityDelegateWrapper(recyclerView, delegate) { + @Override + public void onInitializeAccessibilityNodeInfo(@NonNull View host, + @NonNull AccessibilityNodeInfoCompat info) { + super.onInitializeAccessibilityNodeInfo(host, info); + if (!(recyclerView.getAdapter() + instanceof final PreferenceGroupAdapter preferenceGroupAdapter)) { + return; + } + final int visibleCount = preferenceGroupAdapter.getItemCount(); + int importantCount = 0; + for (int i = 0; i < visibleCount; i++) { + if (isPreferenceImportantToA11y(preferenceGroupAdapter.getItem(i))) { + importantCount++; + } + } + info.unwrap().setCollectionInfo( + new AccessibilityNodeInfo.CollectionInfo( + /*rowCount=*/visibleCount, + /*columnCount=*/1, + /*hierarchical=*/false, + AccessibilityNodeInfo.CollectionInfo.SELECTION_MODE_SINGLE, + /*itemCount=*/visibleCount, + /*importantForAccessibilityItemCount=*/importantCount)); + } + }); + return recyclerView; + } + + /** + * Returns whether the preference will be marked as important to accessibility for the sake + * of calculating {@link AccessibilityNodeInfo.CollectionInfo} counts. + * + *

The accessibility service itself knows this information for an individual preference + * on the screen, but it expects the preference's {@link RecyclerView} to also provide the + * same information for its entire set of adapter items. + */ + @VisibleForTesting + static boolean isPreferenceImportantToA11y(Preference pref) { + if ((pref instanceof IllustrationPreference illustrationPref + && TextUtils.isEmpty(illustrationPref.getContentDescription())) + || pref instanceof PaletteListPreference) { + // Illustration preference that is visible but unannounced by accessibility services. + return false; + } + // All other preferences from the PreferenceGroupAdapter are important. + return true; + } + + /** + * Wrapper around a {@link RecyclerViewAccessibilityDelegate} that allows customizing + * a subset of methods and while also deferring to the original. All overridden methods + * in instantiations of this class should call {@code super}. + */ + private static class RvAccessibilityDelegateWrapper extends RecyclerViewAccessibilityDelegate { + private final RecyclerViewAccessibilityDelegate mOriginal; + + RvAccessibilityDelegateWrapper(RecyclerView recyclerView, + RecyclerViewAccessibilityDelegate original) { + super(recyclerView); + mOriginal = original; + } + + @Override + public boolean performAccessibilityAction(@NonNull View host, int action, Bundle args) { + return mOriginal.performAccessibilityAction(host, action, args); + } + + @Override + public void onInitializeAccessibilityNodeInfo(@NonNull View host, + @NonNull AccessibilityNodeInfoCompat info) { + mOriginal.onInitializeAccessibilityNodeInfo(host, info); + } + + @Override + public void onInitializeAccessibilityEvent(@NonNull View host, + @NonNull AccessibilityEvent event) { + mOriginal.onInitializeAccessibilityEvent(host, event); + } + + @Override + @NonNull + public AccessibilityDelegateCompat getItemDelegate() { + if (mOriginal == null) { + // Needed for super constructor which calls getItemDelegate before mOriginal is + // defined, but unused by actual clients of this RecyclerViewAccessibilityDelegate + // which invoke getItemDelegate() after the constructor finishes. + return new ItemDelegate(this); + } + return mOriginal.getItemDelegate(); + } + } +} diff --git a/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java b/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java index 0ac29bc6ba5..9c61e5c3305 100644 --- a/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java +++ b/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java @@ -56,6 +56,7 @@ import androidx.annotation.VisibleForTesting; import androidx.preference.Preference; import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceScreen; +import androidx.recyclerview.widget.RecyclerView; import com.android.internal.accessibility.common.ShortcutConstants; import com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType; @@ -871,4 +872,12 @@ public abstract class ToggleFeaturePreferenceFragment extends DashboardFragment return PreferredShortcuts.retrieveUserShortcutType( getPrefContext(), mComponentName.flattenToString(), getDefaultShortcutTypes()); } + + @Override + public RecyclerView onCreateRecyclerView(LayoutInflater inflater, ViewGroup parent, + Bundle savedInstanceState) { + RecyclerView recyclerView = + super.onCreateRecyclerView(inflater, parent, savedInstanceState); + return AccessibilityFragmentUtils.addCollectionInfoToAccessibilityDelegate(recyclerView); + } } diff --git a/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragmentForSetupWizard.java b/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragmentForSetupWizard.java index 97405d24e9f..52d75c19ed4 100644 --- a/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragmentForSetupWizard.java +++ b/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragmentForSetupWizard.java @@ -79,7 +79,8 @@ public class ToggleScreenMagnificationPreferenceFragmentForSetupWizard Bundle savedInstanceState) { if (parent instanceof GlifPreferenceLayout) { final GlifPreferenceLayout layout = (GlifPreferenceLayout) parent; - return layout.onCreateRecyclerView(inflater, parent, savedInstanceState); + return AccessibilityFragmentUtils.addCollectionInfoToAccessibilityDelegate( + layout.onCreateRecyclerView(inflater, parent, savedInstanceState)); } return super.onCreateRecyclerView(inflater, parent, savedInstanceState); } diff --git a/src/com/android/settings/accessibility/ToggleScreenReaderPreferenceFragmentForSetupWizard.java b/src/com/android/settings/accessibility/ToggleScreenReaderPreferenceFragmentForSetupWizard.java index 4309b1d9038..10813a7e262 100644 --- a/src/com/android/settings/accessibility/ToggleScreenReaderPreferenceFragmentForSetupWizard.java +++ b/src/com/android/settings/accessibility/ToggleScreenReaderPreferenceFragmentForSetupWizard.java @@ -68,7 +68,8 @@ public class ToggleScreenReaderPreferenceFragmentForSetupWizard Bundle savedInstanceState) { if (parent instanceof GlifPreferenceLayout) { final GlifPreferenceLayout layout = (GlifPreferenceLayout) parent; - return layout.onCreateRecyclerView(inflater, parent, savedInstanceState); + return AccessibilityFragmentUtils.addCollectionInfoToAccessibilityDelegate( + layout.onCreateRecyclerView(inflater, parent, savedInstanceState)); } return super.onCreateRecyclerView(inflater, parent, savedInstanceState); } diff --git a/src/com/android/settings/accessibility/ToggleSelectToSpeakPreferenceFragmentForSetupWizard.java b/src/com/android/settings/accessibility/ToggleSelectToSpeakPreferenceFragmentForSetupWizard.java index 8d26785d021..10796b5d218 100644 --- a/src/com/android/settings/accessibility/ToggleSelectToSpeakPreferenceFragmentForSetupWizard.java +++ b/src/com/android/settings/accessibility/ToggleSelectToSpeakPreferenceFragmentForSetupWizard.java @@ -68,7 +68,8 @@ public class ToggleSelectToSpeakPreferenceFragmentForSetupWizard Bundle savedInstanceState) { if (parent instanceof GlifPreferenceLayout) { final GlifPreferenceLayout layout = (GlifPreferenceLayout) parent; - return layout.onCreateRecyclerView(inflater, parent, savedInstanceState); + return AccessibilityFragmentUtils.addCollectionInfoToAccessibilityDelegate( + layout.onCreateRecyclerView(inflater, parent, savedInstanceState)); } return super.onCreateRecyclerView(inflater, parent, savedInstanceState); } diff --git a/src/com/android/settings/bluetooth/BluetoothKeyMissingDialog.java b/src/com/android/settings/bluetooth/BluetoothKeyMissingDialog.java new file mode 100644 index 00000000000..46975f77726 --- /dev/null +++ b/src/com/android/settings/bluetooth/BluetoothKeyMissingDialog.java @@ -0,0 +1,47 @@ +/* + * 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.bluetooth; + +import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; + +import android.bluetooth.BluetoothDevice; +import android.content.Intent; +import android.os.Bundle; + +import androidx.annotation.Nullable; +import androidx.fragment.app.FragmentActivity; + +/** A dialog to ask the user to forget a bluetooth device when the key is missing. */ +public class BluetoothKeyMissingDialog extends FragmentActivity { + public static final String FRAGMENT_TAG = "BtKeyMissingFrg"; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); + Intent intent = getIntent(); + BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + if (device == null) { + finish(); + return; + } + BluetoothKeyMissingDialogFragment fragment = new BluetoothKeyMissingDialogFragment(device); + fragment.show(getSupportFragmentManager(), FRAGMENT_TAG); + closeSystemDialogs(); + } +} diff --git a/src/com/android/settings/bluetooth/BluetoothKeyMissingDialogFragment.java b/src/com/android/settings/bluetooth/BluetoothKeyMissingDialogFragment.java new file mode 100644 index 00000000000..a8e3aae175a --- /dev/null +++ b/src/com/android/settings/bluetooth/BluetoothKeyMissingDialogFragment.java @@ -0,0 +1,94 @@ +/* + * 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.bluetooth; + +import android.app.Dialog; +import android.app.settings.SettingsEnums; +import android.bluetooth.BluetoothDevice; +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; + +import com.android.settings.R; +import com.android.settings.core.instrumentation.InstrumentedDialogFragment; + +/** + * A dialogFragment used by {@link BluetoothKeyMissingDialog} to create a dialog for the + * bluetooth device. + */ +public class BluetoothKeyMissingDialogFragment extends InstrumentedDialogFragment + implements OnClickListener { + + private static final String TAG = "BTKeyMissingDialogFragment"; + + private BluetoothDevice mBluetoothDevice; + + public BluetoothKeyMissingDialogFragment(@NonNull BluetoothDevice bluetoothDevice) { + mBluetoothDevice = bluetoothDevice; + } + + @NonNull + @Override + public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + View view = getActivity().getLayoutInflater().inflate(R.layout.bluetooth_key_missing, null); + TextView keyMissingTitle = view.findViewById(R.id.bluetooth_key_missing_title); + keyMissingTitle.setText( + getString(R.string.bluetooth_key_missing_title, mBluetoothDevice.getName())); + builder.setView(view); + builder.setPositiveButton(getString(R.string.bluetooth_key_missing_forget), this); + builder.setNegativeButton(getString(R.string.bluetooth_key_missing_cancel), this); + AlertDialog dialog = builder.create(); + dialog.setCanceledOnTouchOutside(false); + return dialog; + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (!getActivity().isFinishing()) { + getActivity().finish(); + } + } + + @Override + public void onClick(DialogInterface dialog, int which) { + if (which == DialogInterface.BUTTON_POSITIVE) { + Log.i( + TAG, + "Positive button clicked, remove bond for " + + mBluetoothDevice.getAnonymizedAddress()); + mBluetoothDevice.removeBond(); + } else if (which == DialogInterface.BUTTON_NEGATIVE) { + Log.i(TAG, "Negative button clicked for " + mBluetoothDevice.getAnonymizedAddress()); + } + if (!getActivity().isFinishing()) { + getActivity().finish(); + } + } + + @Override + public int getMetricsCategory() { + return SettingsEnums.BLUETOOTH_KEY_MISSING_DIALOG_FRAGMENT; + } +} diff --git a/src/com/android/settings/bluetooth/BluetoothKeyMissingReceiver.java b/src/com/android/settings/bluetooth/BluetoothKeyMissingReceiver.java new file mode 100644 index 00000000000..d7a5343d694 --- /dev/null +++ b/src/com/android/settings/bluetooth/BluetoothKeyMissingReceiver.java @@ -0,0 +1,122 @@ +/* + * 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.bluetooth; + +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.bluetooth.BluetoothDevice; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.os.PowerManager; +import android.os.UserHandle; +import android.text.TextUtils; +import android.util.Log; + +import androidx.core.app.NotificationCompat; + +import com.android.settings.R; +import com.android.settings.flags.Flags; + +/** + * BluetoothKeyMissingReceiver is a receiver for Bluetooth key missing error when reconnecting to a + * bonded bluetooth device. + */ +public final class BluetoothKeyMissingReceiver extends BroadcastReceiver { + private static final String TAG = "BtKeyMissingReceiver"; + private static final String CHANNEL_ID = "bluetooth_notification_channel"; + private static final int NOTIFICATION_ID = android.R.drawable.stat_sys_data_bluetooth; + + @Override + public void onReceive(Context context, Intent intent) { + if (!Flags.enableBluetoothKeyMissingDialog()) { + return; + } + String action = intent.getAction(); + if (action == null) { + return; + } + + BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + PowerManager powerManager = context.getSystemService(PowerManager.class); + if (TextUtils.equals(action, BluetoothDevice.ACTION_KEY_MISSING)) { + Log.d(TAG, "Receive ACTION_KEY_MISSING"); + if (shouldShowDialog(context, device, powerManager)) { + Intent pairingIntent = getKeyMissingDialogIntent(context, device); + Log.d(TAG, "Show key missing dialog:" + device); + context.startActivityAsUser(pairingIntent, UserHandle.CURRENT); + } else { + Log.d(TAG, "Show key missing notification: " + device); + showNotification(context, device); + } + } + } + + private Intent getKeyMissingDialogIntent(Context context, BluetoothDevice device) { + Intent pairingIntent = new Intent(); + pairingIntent.setClass(context, BluetoothKeyMissingDialog.class); + pairingIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); + pairingIntent.setAction(BluetoothDevice.ACTION_KEY_MISSING); + pairingIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + return pairingIntent; + } + + private boolean shouldShowDialog( + Context context, BluetoothDevice device, PowerManager powerManager) { + return LocalBluetoothPreferences.shouldShowDialogInForeground(context, device) + && powerManager.isInteractive(); + } + + private void showNotification(Context context, BluetoothDevice bluetoothDevice) { + NotificationManager nm = context.getSystemService(NotificationManager.class); + NotificationChannel notificationChannel = + new NotificationChannel( + CHANNEL_ID, + context.getString(R.string.bluetooth), + NotificationManager.IMPORTANCE_HIGH); + nm.createNotificationChannel(notificationChannel); + + PendingIntent pairIntent = + PendingIntent.getActivity( + context, + 0, + getKeyMissingDialogIntent(context, bluetoothDevice), + PendingIntent.FLAG_ONE_SHOT + | PendingIntent.FLAG_UPDATE_CURRENT + | PendingIntent.FLAG_IMMUTABLE); + + NotificationCompat.Builder builder = new NotificationCompat.Builder(context, + CHANNEL_ID) + .setSmallIcon(android.R.drawable.stat_sys_data_bluetooth) + .setTicker(context.getString(R.string.bluetooth_notif_ticker)) + .setLocalOnly(true); + builder.setContentTitle( + context.getString( + R.string.bluetooth_key_missing_title, bluetoothDevice.getName())) + .setContentText(context.getString(R.string.bluetooth_key_missing_message)) + .setContentIntent(pairIntent) + .setAutoCancel(true) + .setDefaults(Notification.DEFAULT_SOUND) + .setColor( + context.getColor( + com.android.internal.R.color.system_notification_accent_color)); + + nm.notify(NOTIFICATION_ID, builder.build()); + } +} diff --git a/src/com/android/settings/gestures/OneHandedSettings.java b/src/com/android/settings/gestures/OneHandedSettings.java index c84b9ea6934..0a1ab64360c 100644 --- a/src/com/android/settings/gestures/OneHandedSettings.java +++ b/src/com/android/settings/gestures/OneHandedSettings.java @@ -23,9 +23,14 @@ import android.content.Context; import android.os.Bundle; import android.os.UserHandle; import android.util.Log; +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.recyclerview.widget.RecyclerView; import com.android.internal.accessibility.AccessibilityShortcutController; import com.android.settings.R; +import com.android.settings.accessibility.AccessibilityFragmentUtils; import com.android.settings.accessibility.AccessibilityShortcutPreferenceFragment; import com.android.settings.accessibility.AccessibilityUtil.QuickSettingsTooltipType; import com.android.settings.search.BaseSearchIndexProvider; @@ -176,4 +181,12 @@ public class OneHandedSettings extends AccessibilityShortcutPreferenceFragment { return OneHandedSettingsUtils.isSupportOneHandedMode(); } }; + + @Override + public RecyclerView onCreateRecyclerView(LayoutInflater inflater, ViewGroup parent, + Bundle savedInstanceState) { + RecyclerView recyclerView = + super.onCreateRecyclerView(inflater, parent, savedInstanceState); + return AccessibilityFragmentUtils.addCollectionInfoToAccessibilityDelegate(recyclerView); + } } diff --git a/src/com/android/settings/network/NetworkProviderSettings.java b/src/com/android/settings/network/NetworkProviderSettings.java index e2406826320..69183ff25c0 100644 --- a/src/com/android/settings/network/NetworkProviderSettings.java +++ b/src/com/android/settings/network/NetworkProviderSettings.java @@ -19,6 +19,8 @@ package com.android.settings.network; import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLED; import static android.os.UserManager.DISALLOW_CONFIG_WIFI; +import static com.android.wifitrackerlib.WifiEntry.CONNECTED_STATE_CONNECTED; + import android.app.Activity; import android.app.Dialog; import android.app.settings.SettingsEnums; @@ -669,7 +671,7 @@ public class NetworkProviderSettings extends RestrictedSettingsFragment @VisibleForTesting void addModifyMenuIfSuitable(ContextMenu menu, WifiEntry wifiEntry) { if (mIsAdmin && wifiEntry.isSaved() - && wifiEntry.getConnectedState() != WifiEntry.CONNECTED_STATE_CONNECTED) { + && wifiEntry.getConnectedState() != CONNECTED_STATE_CONNECTED) { menu.add(Menu.NONE, MENU_ID_MODIFY, 0 /* order */, R.string.wifi_modify); } } @@ -765,7 +767,7 @@ public class NetworkProviderSettings extends RestrictedSettingsFragment private void showDialog(WifiEntry wifiEntry, int dialogMode) { if (WifiUtils.isNetworkLockedDown(getActivity(), wifiEntry.getWifiConfiguration()) - && wifiEntry.getConnectedState() == WifiEntry.CONNECTED_STATE_CONNECTED) { + && wifiEntry.getConnectedState() == CONNECTED_STATE_CONNECTED) { RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getActivity(), RestrictedLockUtilsInternal.getDeviceOwner(getActivity())); return; @@ -1068,8 +1070,8 @@ public class NetworkProviderSettings extends RestrictedSettingsFragment @VisibleForTesting void launchNetworkDetailsFragment(LongPressWifiEntryPreference pref) { final WifiEntry wifiEntry = pref.getWifiEntry(); - if (!wifiEntry.isSaved()) { - Log.w(TAG, "launchNetworkDetailsFragment: Don't launch because WifiEntry isn't saved!"); + if (!wifiEntry.isSaved() && wifiEntry.getConnectedState() != CONNECTED_STATE_CONNECTED) { + Log.w(TAG, "Don't launch Wi-Fi details because WifiEntry is not saved or connected!"); return; } final Context context = requireContext(); diff --git a/src/com/android/settings/network/SimOnboardingActivity.kt b/src/com/android/settings/network/SimOnboardingActivity.kt index a5d4ade6992..25afb661e8b 100644 --- a/src/com/android/settings/network/SimOnboardingActivity.kt +++ b/src/com/android/settings/network/SimOnboardingActivity.kt @@ -221,6 +221,7 @@ class SimOnboardingActivity : SpaBaseDialogActivity() { "showRestartDialog:${showRestartDialog.value}") showStartingDialog.value = false } else if (onboardingService.activeSubInfoList.isNotEmpty()) { + Log.d(TAG, "status: showStartingDialog.value:${showStartingDialog.value}") showStartingDialog.value = true } } @@ -468,11 +469,11 @@ class SimOnboardingActivity : SpaBaseDialogActivity() { } fun handleEnableMultiSimSidecarStateChange() { - showDsdsProgressDialog.value = false when (enableMultiSimSidecar!!.state) { SidecarFragment.State.SUCCESS -> { enableMultiSimSidecar!!.reset() Log.i(TAG, "Successfully switched to DSDS without reboot.") + showDsdsProgressDialog.value = false // refresh data initServiceData(this, onboardingService.targetSubId, callbackListener) startSimOnboardingProvider() @@ -480,6 +481,7 @@ class SimOnboardingActivity : SpaBaseDialogActivity() { SidecarFragment.State.ERROR -> { enableMultiSimSidecar!!.reset() + showDsdsProgressDialog.value = false Log.i(TAG, "Failed to switch to DSDS without rebooting.") showError.value = ErrorType.ERROR_ENABLE_DSDS callbackListener(CallbackType.CALLBACK_ERROR) diff --git a/src/com/android/settings/users/MultiUserSwitchBarController.java b/src/com/android/settings/users/MultiUserSwitchBarController.java index 07c03d716c3..1d641418714 100644 --- a/src/com/android/settings/users/MultiUserSwitchBarController.java +++ b/src/com/android/settings/users/MultiUserSwitchBarController.java @@ -53,6 +53,12 @@ public class MultiUserSwitchBarController implements SwitchWidgetController.OnSw mSwitchBar = switchBar; mListener = listener; mUserCapabilities = UserCapabilities.create(context); + updateState(); + mSwitchBar.setListener(this); + } + + void updateState() { + mUserCapabilities.updateAddUserCapabilities(mContext); mSwitchBar.setChecked(mUserCapabilities.mUserSwitcherEnabled); if (Flags.fixDisablingOfMuToggleWhenRestrictionApplied()) { @@ -74,7 +80,6 @@ public class MultiUserSwitchBarController implements SwitchWidgetController.OnSw mSwitchBar.setEnabled(mUserCapabilities.mIsMain); } } - mSwitchBar.setListener(this); } @Override @@ -92,7 +97,7 @@ public class MultiUserSwitchBarController implements SwitchWidgetController.OnSw Log.d(TAG, "Toggling multi-user feature enabled state to: " + isChecked); final boolean success = Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.USER_SWITCHER_ENABLED, isChecked ? 1 : 0); - if (success && mListener != null) { + if (success && mListener != null && !Flags.newMultiuserSettingsUx()) { mListener.onMultiUserSwitchChanged(isChecked); } return success; diff --git a/src/com/android/settings/users/UserSettings.java b/src/com/android/settings/users/UserSettings.java index c387d9e461d..a0137df728f 100644 --- a/src/com/android/settings/users/UserSettings.java +++ b/src/com/android/settings/users/UserSettings.java @@ -419,6 +419,7 @@ public class UserSettings extends SettingsPreferenceFragment mTimeoutToDockUserPreferenceController.getPreferenceKey())); mRemoveGuestOnExitPreferenceController.updateState(screen.findPreference( mRemoveGuestOnExitPreferenceController.getPreferenceKey())); + mSwitchBarController.updateState(); if (mShouldUpdateUserList) { updateUI(); } diff --git a/tests/robotests/src/com/android/settings/accessibility/AccessibilityFragmentUtilsTest.java b/tests/robotests/src/com/android/settings/accessibility/AccessibilityFragmentUtilsTest.java new file mode 100644 index 00000000000..cd4ee89aaaf --- /dev/null +++ b/tests/robotests/src/com/android/settings/accessibility/AccessibilityFragmentUtilsTest.java @@ -0,0 +1,70 @@ +/* + * 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.google.common.truth.Truth.assertThat; + +import android.content.Context; + +import androidx.preference.Preference; +import androidx.test.core.app.ApplicationProvider; + +import com.android.settingslib.widget.IllustrationPreference; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +/** Tests for {@link AccessibilityFragmentUtils} */ +@RunWith(RobolectricTestRunner.class) +public class AccessibilityFragmentUtilsTest { + + private final Context mContext = ApplicationProvider.getApplicationContext(); + + @Test + public void isPreferenceImportantToA11y_basicPreference_isImportant() { + final Preference pref = new ShortcutPreference(mContext, /* attrs= */ null); + + assertThat(AccessibilityFragmentUtils.isPreferenceImportantToA11y(pref)).isTrue(); + } + + @Test + public void isPreferenceImportantToA11y_illustrationPreference_hasContentDesc_isImportant() { + final IllustrationPreference pref = + new IllustrationPreference(mContext, /* attrs= */ null); + pref.setContentDescription("content desc"); + + assertThat(AccessibilityFragmentUtils.isPreferenceImportantToA11y(pref)).isTrue(); + } + + @Test + public void isPreferenceImportantToA11y_illustrationPreference_noContentDesc_notImportant() { + final IllustrationPreference pref = + new IllustrationPreference(mContext, /* attrs= */ null); + pref.setContentDescription(null); + + assertThat(AccessibilityFragmentUtils.isPreferenceImportantToA11y(pref)).isFalse(); + } + + @Test + public void isPreferenceImportantToA11y_paletteListPreference_notImportant() { + final PaletteListPreference pref = + new PaletteListPreference(mContext, /* attrs= */ null); + + assertThat(AccessibilityFragmentUtils.isPreferenceImportantToA11y(pref)).isFalse(); + } +} diff --git a/tests/robotests/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragmentTest.java b/tests/robotests/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragmentTest.java index 22bb2669bb5..038672fc198 100644 --- a/tests/robotests/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragmentTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragmentTest.java @@ -53,9 +53,12 @@ import android.platform.test.flag.junit.SetFlagsRule; import android.provider.DeviceConfig; import android.provider.Settings; import android.view.accessibility.AccessibilityManager; +import android.view.accessibility.AccessibilityNodeInfo; +import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; import androidx.preference.Preference; import androidx.preference.TwoStatePreference; +import androidx.recyclerview.widget.RecyclerView; import androidx.test.core.app.ApplicationProvider; import com.android.server.accessibility.Flags; @@ -1000,6 +1003,28 @@ public class ToggleScreenMagnificationPreferenceFragmentTest { assertThat(summary).isEqualTo(expected); } + @Test + @EnableFlags( + com.android.settings.accessibility.Flags.FLAG_TOGGLE_FEATURE_FRAGMENT_COLLECTION_INFO) + public void fragmentRecyclerView_getCollectionInfo_hasCorrectCounts() { + ToggleScreenMagnificationPreferenceFragment fragment = + mFragController.create(R.id.main_content, /* bundle= */ + null).start().resume().get(); + RecyclerView rv = fragment.getListView(); + + AccessibilityNodeInfoCompat node = AccessibilityNodeInfoCompat.obtain(); + rv.getCompatAccessibilityDelegate().onInitializeAccessibilityNodeInfo(rv, node); + AccessibilityNodeInfo.CollectionInfo collectionInfo = node.unwrap().getCollectionInfo(); + + // Asserting against specific item counts will be brittle to changes to the preferences + // included on this page, so instead just check some properties of these counts. + assertThat(collectionInfo.getColumnCount()).isEqualTo(1); + assertThat(collectionInfo.getRowCount()).isEqualTo(collectionInfo.getItemCount()); + assertThat(collectionInfo.getItemCount()) + // One unimportant item: the illustration preference + .isEqualTo(collectionInfo.getImportantForAccessibilityItemCount() + 1); + } + private void putStringIntoSettings(String key, String componentName) { Settings.Secure.putString(mContext.getContentResolver(), key, componentName); } diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothKeyMissingDialogTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothKeyMissingDialogTest.java new file mode 100644 index 00000000000..a47101e7b79 --- /dev/null +++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothKeyMissingDialogTest.java @@ -0,0 +1,76 @@ +/* + * 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.bluetooth; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.robolectric.shadows.ShadowLooper.shadowMainLooper; + +import android.bluetooth.BluetoothDevice; + +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.FragmentActivity; + +import com.android.settings.testutils.shadow.ShadowAlertDialogCompat; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +@RunWith(RobolectricTestRunner.class) +@Config(shadows = ShadowAlertDialogCompat.class) +public class BluetoothKeyMissingDialogTest { + @Mock private BluetoothDevice mBluetoothDevice; + + private BluetoothKeyMissingDialogFragment mFragment = null; + private FragmentActivity mActivity = null; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mActivity = Robolectric.setupActivity(FragmentActivity.class); + mFragment = new BluetoothKeyMissingDialogFragment(mBluetoothDevice); + mActivity + .getSupportFragmentManager() + .beginTransaction() + .add(mFragment, null) + .commit(); + shadowMainLooper().idle(); + } + + @Test + public void clickForgetDevice_removeBond() { + mFragment.onClick(mFragment.getDialog(), AlertDialog.BUTTON_POSITIVE); + + verify(mBluetoothDevice).removeBond(); + assertThat(mActivity.isFinishing()).isTrue(); + } + + @Test + public void clickCancel_notRemoveBond() { + mFragment.onClick(mFragment.getDialog(), AlertDialog.BUTTON_NEGATIVE); + + verify(mBluetoothDevice, never()).removeBond(); + assertThat(mActivity.isFinishing()).isTrue(); + } +} diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothKeyMissingReceiverTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothKeyMissingReceiverTest.java new file mode 100644 index 00000000000..c764ed6cd97 --- /dev/null +++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothKeyMissingReceiverTest.java @@ -0,0 +1,160 @@ +/* + * 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.bluetooth; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +import android.app.Notification; +import android.app.NotificationManager; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.os.UserHandle; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; + +import com.android.settings.flags.Flags; +import com.android.settings.testutils.shadow.ShadowBluetoothAdapter; +import com.android.settings.testutils.shadow.ShadowBluetoothUtils; +import com.android.settingslib.bluetooth.LocalBluetoothManager; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.shadow.api.Shadow; +import org.robolectric.shadows.ShadowApplication; + +import java.util.List; +import java.util.stream.Collectors; + +@RunWith(RobolectricTestRunner.class) +@Config(shadows = {ShadowBluetoothAdapter.class, ShadowBluetoothUtils.class}) +public class BluetoothKeyMissingReceiverTest { + @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + + private Context mContext; + private ShadowApplication mShadowApplication; + private ShadowBluetoothAdapter mShadowBluetoothAdapter; + @Mock private LocalBluetoothManager mLocalBtManager; + @Mock private NotificationManager mNm; + @Mock private BluetoothDevice mBluetoothDevice; + + @Before + public void setUp() { + mContext = spy(RuntimeEnvironment.getApplication()); + mShadowApplication = Shadow.extract(mContext); + mShadowApplication.setSystemService(Context.NOTIFICATION_SERVICE, mNm); + mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter()); + mShadowBluetoothAdapter.setEnabled(true); + ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBtManager; + } + + @After + public void tearDown() { + ShadowBluetoothUtils.reset(); + } + + @Test + public void broadcastReceiver_isRegistered() { + List registeredReceivers = + mShadowApplication.getRegisteredReceivers(); + + int matchedCount = + registeredReceivers.stream() + .filter( + receiver -> + BluetoothKeyMissingReceiver.class + .getSimpleName() + .equals( + receiver.broadcastReceiver + .getClass() + .getSimpleName())) + .collect(Collectors.toList()) + .size(); + assertThat(matchedCount).isEqualTo(1); + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_BLUETOOTH_KEY_MISSING_DIALOG) + public void broadcastReceiver_receiveKeyMissingIntentFlagOff_doNothing() { + Intent intent = spy(new Intent(BluetoothDevice.ACTION_KEY_MISSING)); + when(intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)).thenReturn(mBluetoothDevice); + BluetoothKeyMissingReceiver bluetoothKeyMissingReceiver = getReceiver(intent); + bluetoothKeyMissingReceiver.onReceive(mContext, intent); + + verifyNoInteractions(mNm); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_BLUETOOTH_KEY_MISSING_DIALOG) + public void broadcastReceiver_background_showNotification() { + Intent intent = spy(new Intent(BluetoothDevice.ACTION_KEY_MISSING)); + when(intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)).thenReturn(mBluetoothDevice); + BluetoothKeyMissingReceiver bluetoothKeyMissingReceiver = getReceiver(intent); + bluetoothKeyMissingReceiver.onReceive(mContext, intent); + + verify(mNm).notify(eq(android.R.drawable.stat_sys_data_bluetooth), any(Notification.class)); + verify(mContext, never()).startActivityAsUser(any(), any()); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_BLUETOOTH_KEY_MISSING_DIALOG) + public void broadcastReceiver_foreground_receiveKeyMissingIntent_showDialog() { + when(mLocalBtManager.isForegroundActivity()).thenReturn(true); + Intent intent = spy(new Intent(BluetoothDevice.ACTION_KEY_MISSING)); + when(intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)).thenReturn(mBluetoothDevice); + BluetoothKeyMissingReceiver bluetoothKeyMissingReceiver = getReceiver(intent); + bluetoothKeyMissingReceiver.onReceive(mContext, intent); + + verifyNoInteractions(mNm); + ArgumentCaptor captor = ArgumentCaptor.forClass(Intent.class); + verify(mContext).startActivityAsUser(captor.capture(), eq(UserHandle.CURRENT)); + assertThat(captor.getValue().getComponent().getClassName()) + .isEqualTo(BluetoothKeyMissingDialog.class.getName()); + } + + private BluetoothKeyMissingReceiver getReceiver(Intent intent) { + assertThat(mShadowApplication.hasReceiverForIntent(intent)).isTrue(); + List receiversForIntent = + mShadowApplication.getReceiversForIntent(intent); + assertThat(receiversForIntent).hasSize(1); + BroadcastReceiver broadcastReceiver = receiversForIntent.get(0); + assertThat(broadcastReceiver).isInstanceOf(BluetoothKeyMissingReceiver.class); + return (BluetoothKeyMissingReceiver) broadcastReceiver; + } +} diff --git a/tests/robotests/src/com/android/settings/network/NetworkProviderSettingsTest.java b/tests/robotests/src/com/android/settings/network/NetworkProviderSettingsTest.java index 1bed8a82172..400f73f7f56 100644 --- a/tests/robotests/src/com/android/settings/network/NetworkProviderSettingsTest.java +++ b/tests/robotests/src/com/android/settings/network/NetworkProviderSettingsTest.java @@ -22,6 +22,7 @@ import static com.android.settings.network.NetworkProviderSettings.MENU_ID_MODIF import static com.android.settings.network.NetworkProviderSettings.MENU_ID_SHARE; import static com.android.settings.wifi.WifiConfigUiBase2.MODE_CONNECT; import static com.android.settings.wifi.WifiConfigUiBase2.MODE_MODIFY; +import static com.android.wifitrackerlib.WifiEntry.CONNECTED_STATE_CONNECTED; import static com.android.wifitrackerlib.WifiEntry.CONNECTED_STATE_DISCONNECTED; import static com.android.wifitrackerlib.WifiPickerTracker.WIFI_ENTRIES_CHANGED_REASON_GENERAL; @@ -343,7 +344,7 @@ public class NetworkProviderSettingsTest { when(mWifiEntry.canDisconnect()).thenReturn(true); when(mWifiEntry.canForget()).thenReturn(true); when(mWifiEntry.isSaved()).thenReturn(true); - when(mWifiEntry.getConnectedState()).thenReturn(WifiEntry.CONNECTED_STATE_CONNECTED); + when(mWifiEntry.getConnectedState()).thenReturn(CONNECTED_STATE_CONNECTED); final LongPressWifiEntryPreference connectedWifiEntryPreference = mNetworkProviderSettings.createLongPressWifiEntryPreference(mWifiEntry); @@ -366,7 +367,7 @@ public class NetworkProviderSettingsTest { when(mWifiEntry.canShare()).thenReturn(true); when(mWifiEntry.canForget()).thenReturn(true); when(mWifiEntry.isSaved()).thenReturn(true); - when(mWifiEntry.getConnectedState()).thenReturn(WifiEntry.CONNECTED_STATE_CONNECTED); + when(mWifiEntry.getConnectedState()).thenReturn(CONNECTED_STATE_CONNECTED); final LongPressWifiEntryPreference connectedWifiEntryPreference = mNetworkProviderSettings.createLongPressWifiEntryPreference(mWifiEntry); @@ -388,7 +389,7 @@ public class NetworkProviderSettingsTest { when(mWifiEntry.canShare()).thenReturn(false); when(mWifiEntry.canForget()).thenReturn(true); when(mWifiEntry.isSaved()).thenReturn(true); - when(mWifiEntry.getConnectedState()).thenReturn(WifiEntry.CONNECTED_STATE_CONNECTED); + when(mWifiEntry.getConnectedState()).thenReturn(CONNECTED_STATE_CONNECTED); final LongPressWifiEntryPreference connectedWifiEntryPreference = mNetworkProviderSettings.createLongPressWifiEntryPreference(mWifiEntry); @@ -872,14 +873,54 @@ public class NetworkProviderSettingsTest { } @Test - public void launchNetworkDetailsFragment_wifiEntryIsNotSaved_ignoreWifiEntry() { + public void launchNetworkDetailsFragment_entryDisconnectedNotSaved_ignore() { + when(mWifiEntry.getConnectedState()).thenReturn(CONNECTED_STATE_DISCONNECTED); when(mWifiEntry.isSaved()).thenReturn(false); LongPressWifiEntryPreference preference = mNetworkProviderSettings.createLongPressWifiEntryPreference(mWifiEntry); mNetworkProviderSettings.launchNetworkDetailsFragment(preference); - verify(mWifiEntry, never()).getKey(); + verify(mContext, never()).startActivity(any()); + } + + @Test + public void launchNetworkDetailsFragment_entryConnectedNotSaved_launch() { + doNothing().when(mContext).startActivity(any()); + when(mWifiEntry.getConnectedState()).thenReturn(CONNECTED_STATE_CONNECTED); + when(mWifiEntry.isSaved()).thenReturn(false); + LongPressWifiEntryPreference preference = + mNetworkProviderSettings.createLongPressWifiEntryPreference(mWifiEntry); + + mNetworkProviderSettings.launchNetworkDetailsFragment(preference); + + verify(mContext).startActivity(any()); + } + + @Test + public void launchNetworkDetailsFragment_entryDisconnectedSaved_launch() { + doNothing().when(mContext).startActivity(any()); + when(mWifiEntry.getConnectedState()).thenReturn(CONNECTED_STATE_DISCONNECTED); + when(mWifiEntry.isSaved()).thenReturn(true); + LongPressWifiEntryPreference preference = + mNetworkProviderSettings.createLongPressWifiEntryPreference(mWifiEntry); + + mNetworkProviderSettings.launchNetworkDetailsFragment(preference); + + verify(mContext).startActivity(any()); + } + + @Test + public void launchNetworkDetailsFragment_entryConnectedSaved_launch() { + doNothing().when(mContext).startActivity(any()); + when(mWifiEntry.getConnectedState()).thenReturn(CONNECTED_STATE_CONNECTED); + when(mWifiEntry.isSaved()).thenReturn(true); + LongPressWifiEntryPreference preference = + mNetworkProviderSettings.createLongPressWifiEntryPreference(mWifiEntry); + + mNetworkProviderSettings.launchNetworkDetailsFragment(preference); + + verify(mContext).startActivity(any()); } @Implements(PreferenceFragmentCompat.class)