diff --git a/res/drawable/ic_check_circle_filled_24dp.xml b/res/drawable/ic_check_circle_filled_24dp.xml index 9d2e296ef2f..507bf678ae2 100644 --- a/res/drawable/ic_check_circle_filled_24dp.xml +++ b/res/drawable/ic_check_circle_filled_24dp.xml @@ -20,7 +20,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24" - android:tint="?androidprv:attr/materialColorPrimaryContainer"> + android:tint="?android:attr/colorPrimary"> diff --git a/res/layout-land/udfps_enroll_enrolling.xml b/res/layout-land/udfps_enroll_enrolling.xml index f3237887e9a..743684fb02e 100644 --- a/res/layout-land/udfps_enroll_enrolling.xml +++ b/res/layout-land/udfps_enroll_enrolling.xml @@ -96,4 +96,6 @@ + + \ No newline at end of file diff --git a/res/layout/udfps_enroll_enrolling.xml b/res/layout/udfps_enroll_enrolling.xml index c97591d6d52..05556ffe46c 100644 --- a/res/layout/udfps_enroll_enrolling.xml +++ b/res/layout/udfps_enroll_enrolling.xml @@ -18,6 +18,7 @@ + android:layout_gravity="center_horizontal|bottom" + tools:ignore="Suspicious0dp"> + + + For all available hearing devices - Shortcuts & hearing aid compatibility + Hearing device settings + + Audio output, shortcut, hearing aid compatibility For this device @@ -1182,20 +1184,20 @@ Current screen lock - Fingerprint + Pattern + Pattern \u2022 Fingerprint - Fingerprint + PIN + PIN \u2022 Fingerprint - Fingerprint + Password + Password \u2022 Fingerprint Continue without fingerprint - Face Unlock + Pattern + Pattern \u2022 Face - Face Unlock + PIN + PIN \u2022 Face - Face Unlock + Password + Password \u2022 Face Continue without Face Unlock @@ -5877,12 +5879,8 @@ Add account Work profile isn\u2019t available yet - - Work profile - - Managed by your organization - - Apps and notifications are off + + Work apps Remove work profile @@ -7175,7 +7173,7 @@ Docking sounds - Touch sounds + Tap & click sounds Always show icon when in vibrate mode @@ -9783,6 +9781,8 @@ Cross-profile calendar Show work events on your personal calendar + + When work apps are off, they’re paused and can’t be accessed or send you notifications Manage storage @@ -10313,7 +10313,7 @@ Turn off this service?

- Save info like passwords, passkeys, payment methods, and other info won\'t be filled + Saved info like passwords, passkeys, payment methods, and other info won\'t be filled in when you sign in. To use your saved info, choose a password, passkey, or data service. ]]> diff --git a/res/xml/bluetooth_audio_routing_fragment.xml b/res/xml/accessibility_audio_routing_fragment.xml similarity index 85% rename from res/xml/bluetooth_audio_routing_fragment.xml rename to res/xml/accessibility_audio_routing_fragment.xml index 5edf6cdca41..12e75f22ab1 100644 --- a/res/xml/bluetooth_audio_routing_fragment.xml +++ b/res/xml/accessibility_audio_routing_fragment.xml @@ -31,7 +31,7 @@ android:key="audio_routing_ringtone" android:persistent="false" android:title="@string/bluetooth_ringtone_title" - settings:controller="com.android.settings.bluetooth.HearingDeviceRingtoneRoutingPreferenceController" /> + settings:controller="com.android.settings.accessibility.HearingDeviceRingtoneRoutingPreferenceController" /> + settings:controller="com.android.settings.accessibility.HearingDeviceCallRoutingPreferenceController" /> + settings:controller="com.android.settings.accessibility.HearingDeviceMediaRoutingPreferenceController" /> + settings:controller="com.android.settings.accessibility.HearingDeviceSystemSoundsRoutingPreferenceController" /> + + + - - + android:key="device_controls_general" /> diff --git a/res/xml/managed_profile_settings.xml b/res/xml/managed_profile_settings.xml index 20f6d3dc3f3..ddfb86987ca 100644 --- a/res/xml/managed_profile_settings.xml +++ b/res/xml/managed_profile_settings.xml @@ -19,10 +19,9 @@ android:key="managed_profile_settings_screen" android:title="@string/managed_profile_settings_title"> - + + \ No newline at end of file diff --git a/res/xml/reset_dashboard_fragment.xml b/res/xml/reset_dashboard_fragment.xml index 98f6c02a8f2..3bd7a136760 100644 --- a/res/xml/reset_dashboard_fragment.xml +++ b/res/xml/reset_dashboard_fragment.xml @@ -38,9 +38,10 @@ settings:controller="com.android.settings.network.BluetoothWiFiResetPreferenceController" /> - + android:title="@string/reset_app_preferences" + settings:userRestriction="no_control_apps" /> deviceList = getConnectedHearingAidDeviceList(); - return deviceList.isEmpty() ? null : mCachedDeviceManager.findDevice(deviceList.get(0)); - } - private int getConnectedHearingAidDeviceNum() { - return getConnectedHearingAidDeviceList().size(); - } - - private List getConnectedHearingAidDeviceList() { - if (!isHearingAidSupported()) { - return new ArrayList<>(); - } - final List deviceList = new ArrayList<>(); - final HapClientProfile hapClientProfile = mProfileManager.getHapClientProfile(); - if (hapClientProfile != null) { - deviceList.addAll(hapClientProfile.getConnectedDevices()); - } - final HearingAidProfile hearingAidProfile = mProfileManager.getHearingAidProfile(); - if (hearingAidProfile != null) { - deviceList.addAll(hearingAidProfile.getConnectedDevices()); - } - return deviceList.stream() - .distinct() - .filter(d -> !mCachedDeviceManager.isSubDevice(d)).collect(Collectors.toList()); - } - - private boolean isHearingAidSupported() { - if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) { - return false; - } - final List supportedList = mBluetoothAdapter.getSupportedProfiles(); - return supportedList.contains(BluetoothProfile.HEARING_AID) - || supportedList.contains(BluetoothProfile.HAP_CLIENT); - } - - private boolean isAnyHearingAidRelatedProfilesNotReady() { - HearingAidProfile hearingAidProfile = mProfileManager.getHearingAidProfile(); - if (hearingAidProfile != null && !hearingAidProfile.isProfileReady()) { - return true; - } - HapClientProfile hapClientProfile = mProfileManager.getHapClientProfile(); - if (hapClientProfile != null && !hapClientProfile.isProfileReady()) { - return true; - } - return false; + return mHelper.getConnectedHearingAidDeviceList().size(); } @VisibleForTesting(otherwise = VisibleForTesting.NONE) diff --git a/src/com/android/settings/accessibility/AccessibilityHearingAidsFragment.java b/src/com/android/settings/accessibility/AccessibilityHearingAidsFragment.java index 547c4568991..33fef62f205 100644 --- a/src/com/android/settings/accessibility/AccessibilityHearingAidsFragment.java +++ b/src/com/android/settings/accessibility/AccessibilityHearingAidsFragment.java @@ -39,7 +39,7 @@ public class AccessibilityHearingAidsFragment extends AccessibilityShortcutPrefe private static final String TAG = "AccessibilityHearingAidsFragment"; private static final String KEY_HEARING_OPTIONS_CATEGORY = "hearing_options_category"; - private static final int FIRST_PREFERENCE_IN_CATEGORY_INDEX = -1; + private static final int SHORTCUT_PREFERENCE_IN_CATEGORY_INDEX = 20; private String mFeatureName; public AccessibilityHearingAidsFragment() { @@ -65,7 +65,7 @@ public class AccessibilityHearingAidsFragment extends AccessibilityShortcutPrefe final View view = super.onCreateView(inflater, container, savedInstanceState); final PreferenceCategory controlCategory = findPreference(KEY_HEARING_OPTIONS_CATEGORY); // To move the shortcut preference under controlCategory need to remove the original added. - mShortcutPreference.setOrder(FIRST_PREFERENCE_IN_CATEGORY_INDEX); + mShortcutPreference.setOrder(SHORTCUT_PREFERENCE_IN_CATEGORY_INDEX); getPreferenceScreen().removePreference(mShortcutPreference); controlCategory.addPreference(mShortcutPreference); return view; diff --git a/src/com/android/settings/accessibility/AccessibilityShortcutPreferenceFragment.java b/src/com/android/settings/accessibility/AccessibilityShortcutPreferenceFragment.java index 01158bf1e5b..b414addc41f 100644 --- a/src/com/android/settings/accessibility/AccessibilityShortcutPreferenceFragment.java +++ b/src/com/android/settings/accessibility/AccessibilityShortcutPreferenceFragment.java @@ -322,6 +322,18 @@ public abstract class AccessibilityShortcutPreferenceFragment extends Restricted getComponentName()); }; + private static CharSequence getSoftwareShortcutTypeSummary(Context context) { + int resId; + if (AccessibilityUtil.isFloatingMenuEnabled(context)) { + resId = R.string.accessibility_shortcut_edit_summary_software; + } else if (AccessibilityUtil.isGestureNavigateEnabled(context)) { + resId = R.string.accessibility_shortcut_edit_summary_software_gesture; + } else { + resId = R.string.accessibility_shortcut_edit_summary_software; + } + return context.getText(resId); + } + /** * This method will be invoked when a button in the tutorial dialog is clicked. * @@ -429,11 +441,9 @@ public abstract class AccessibilityShortcutPreferenceFragment extends Restricted getComponentName().flattenToString(), AccessibilityUtil.UserShortcutType.SOFTWARE); final List list = new ArrayList<>(); - final CharSequence softwareTitle = context.getText( - R.string.accessibility_shortcut_edit_summary_software); if (hasShortcutType(shortcutTypes, AccessibilityUtil.UserShortcutType.SOFTWARE)) { - list.add(softwareTitle); + list.add(getSoftwareShortcutTypeSummary(context)); } if (hasShortcutType(shortcutTypes, AccessibilityUtil.UserShortcutType.HARDWARE)) { final CharSequence hardwareTitle = context.getText( @@ -443,7 +453,7 @@ public abstract class AccessibilityShortcutPreferenceFragment extends Restricted // Show software shortcut if first time to use. if (list.isEmpty()) { - list.add(softwareTitle); + list.add(getSoftwareShortcutTypeSummary(context)); } return CaseMap.toTitle().wholeString().noLowercase().apply(Locale.getDefault(), /* iter= */ diff --git a/src/com/android/settings/accessibility/HearingAidAudioRoutingPreferenceController.java b/src/com/android/settings/accessibility/HearingAidAudioRoutingPreferenceController.java new file mode 100644 index 00000000000..54022ef7f9e --- /dev/null +++ b/src/com/android/settings/accessibility/HearingAidAudioRoutingPreferenceController.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2023 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.content.Context; +import android.util.FeatureFlagUtils; + +import com.android.settings.core.BasePreferenceController; + +/** + * The controller of the audio routing. + */ +public class HearingAidAudioRoutingPreferenceController extends BasePreferenceController { + public HearingAidAudioRoutingPreferenceController(Context context, + String preferenceKey) { + super(context, preferenceKey); + } + + @Override + public int getAvailabilityStatus() { + return FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_AUDIO_ROUTING) + ? AVAILABLE : UNSUPPORTED_ON_DEVICE; + } +} diff --git a/src/com/android/settings/accessibility/HearingAidHelper.java b/src/com/android/settings/accessibility/HearingAidHelper.java new file mode 100644 index 00000000000..66a37f8dd58 --- /dev/null +++ b/src/com/android/settings/accessibility/HearingAidHelper.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2023 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.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; +import android.content.Context; + +import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; +import com.android.settingslib.bluetooth.HapClientProfile; +import com.android.settingslib.bluetooth.HearingAidProfile; +import com.android.settingslib.bluetooth.LocalBluetoothManager; +import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * A helper class to get and check hearing aids and its status. + */ +public class HearingAidHelper { + + private final BluetoothAdapter mBluetoothAdapter; + private final LocalBluetoothProfileManager mProfileManager; + private final CachedBluetoothDeviceManager mCachedDeviceManager; + + public HearingAidHelper(Context context) { + final LocalBluetoothManager localBluetoothManager = + com.android.settings.bluetooth.Utils.getLocalBluetoothManager(context); + mProfileManager = localBluetoothManager.getProfileManager(); + mCachedDeviceManager = localBluetoothManager.getCachedDeviceManager(); + mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + } + + /** + * Gets the connected hearing aids device whose profiles are + * {@link BluetoothProfile#HEARING_AID} or {@link BluetoothProfile#HAP_CLIENT}. + * + * @return a list of hearing aids {@link BluetoothDevice} objects + */ + public List getConnectedHearingAidDeviceList() { + if (!isHearingAidSupported()) { + return new ArrayList<>(); + } + final List deviceList = new ArrayList<>(); + final HapClientProfile hapClientProfile = mProfileManager.getHapClientProfile(); + if (hapClientProfile != null) { + deviceList.addAll(hapClientProfile.getConnectedDevices()); + } + final HearingAidProfile hearingAidProfile = mProfileManager.getHearingAidProfile(); + if (hearingAidProfile != null) { + deviceList.addAll(hearingAidProfile.getConnectedDevices()); + } + return deviceList.stream() + .distinct() + .filter(d -> !mCachedDeviceManager.isSubDevice(d)).collect(Collectors.toList()); + } + + /** + * Gets the first connected hearing aids device. + * + * @return a {@link CachedBluetoothDevice} that is hearing aids device + */ + public CachedBluetoothDevice getConnectedHearingAidDevice() { + final List deviceList = getConnectedHearingAidDeviceList(); + return deviceList.isEmpty() ? null : mCachedDeviceManager.findDevice(deviceList.get(0)); + } + + /** + * Checks if {@link BluetoothProfile#HEARING_AID} or {@link BluetoothProfile#HAP_CLIENT} + * supported. + */ + public boolean isHearingAidSupported() { + if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) { + return false; + } + final List supportedList = mBluetoothAdapter.getSupportedProfiles(); + return supportedList.contains(BluetoothProfile.HEARING_AID) + || supportedList.contains(BluetoothProfile.HAP_CLIENT); + } + + /** + * Checks if {@link BluetoothProfile#HEARING_AID} or {@link BluetoothProfile#HAP_CLIENT} + * profiles all ready. + */ + public boolean isAllHearingAidRelatedProfilesReady() { + HearingAidProfile hearingAidProfile = mProfileManager.getHearingAidProfile(); + if (hearingAidProfile != null && !hearingAidProfile.isProfileReady()) { + return false; + } + HapClientProfile hapClientProfile = mProfileManager.getHapClientProfile(); + if (hapClientProfile != null && !hapClientProfile.isProfileReady()) { + return false; + } + return true; + } +} diff --git a/src/com/android/settings/bluetooth/HearingDeviceAudioRoutingBasePreferenceController.java b/src/com/android/settings/accessibility/HearingDeviceAudioRoutingBasePreferenceController.java similarity index 79% rename from src/com/android/settings/bluetooth/HearingDeviceAudioRoutingBasePreferenceController.java rename to src/com/android/settings/accessibility/HearingDeviceAudioRoutingBasePreferenceController.java index e93863ae064..3599f4874c4 100644 --- a/src/com/android/settings/bluetooth/HearingDeviceAudioRoutingBasePreferenceController.java +++ b/src/com/android/settings/accessibility/HearingDeviceAudioRoutingBasePreferenceController.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.settings.bluetooth; +package com.android.settings.accessibility; import android.content.ContentResolver; import android.content.Context; @@ -47,19 +47,24 @@ public abstract class HearingDeviceAudioRoutingBasePreferenceController extends private static final String TAG = "HARoutingBasePreferenceController"; private static final boolean DEBUG = false; - private final HearingAidAudioRoutingHelper mHelper; + private final HearingAidAudioRoutingHelper mAudioRoutingHelper; + private final HearingAidHelper mHearingAidHelper; public HearingDeviceAudioRoutingBasePreferenceController(Context context, String preferenceKey) { - super(context, preferenceKey); - mHelper = new HearingAidAudioRoutingHelper(context); + this(context, preferenceKey, + new HearingAidAudioRoutingHelper(context), + new HearingAidHelper(context)); } @VisibleForTesting public HearingDeviceAudioRoutingBasePreferenceController(Context context, - String preferenceKey, HearingAidAudioRoutingHelper helper) { + String preferenceKey, HearingAidAudioRoutingHelper audioRoutingHelper, + HearingAidHelper hearingAidHelper) { super(context, preferenceKey); - mHelper = helper; + + mAudioRoutingHelper = audioRoutingHelper; + mHearingAidHelper = hearingAidHelper; } @Override @@ -81,7 +86,11 @@ public abstract class HearingDeviceAudioRoutingBasePreferenceController extends final Integer routingValue = Ints.tryParse((String) newValue); saveRoutingValue(mContext, routingValue); - trySetAudioRoutingConfig(getSupportedAttributeList(), getHearingDevice(), routingValue); + final CachedBluetoothDevice device = mHearingAidHelper.getConnectedHearingAidDevice(); + if (device != null) { + trySetAudioRoutingConfig(getSupportedAttributeList(), + mHearingAidHelper.getConnectedHearingAidDevice(), routingValue); + } return true; } @@ -89,10 +98,10 @@ public abstract class HearingDeviceAudioRoutingBasePreferenceController extends private void trySetAudioRoutingConfig(int[] audioAttributes, CachedBluetoothDevice hearingDevice, @HearingAidAudioRoutingConstants.RoutingValue int routingValue) { - final List supportedStrategies = mHelper.getSupportedStrategies( - audioAttributes); + final List supportedStrategies = + mAudioRoutingHelper.getSupportedStrategies(audioAttributes); final AudioDeviceAttributes hearingDeviceAttributes = - mHelper.getMatchedHearingDeviceAttributes(hearingDevice); + mAudioRoutingHelper.getMatchedHearingDeviceAttributes(hearingDevice); if (hearingDeviceAttributes == null) { if (DEBUG) { Log.d(TAG, @@ -103,8 +112,8 @@ public abstract class HearingDeviceAudioRoutingBasePreferenceController extends return; } - final boolean status = mHelper.setPreferredDeviceRoutingStrategies(supportedStrategies, - hearingDeviceAttributes, routingValue); + final boolean status = mAudioRoutingHelper.setPreferredDeviceRoutingStrategies( + supportedStrategies, hearingDeviceAttributes, routingValue); if (!status) { final List strategiesName = supportedStrategies.stream() @@ -121,12 +130,6 @@ public abstract class HearingDeviceAudioRoutingBasePreferenceController extends */ protected abstract int[] getSupportedAttributeList(); - /** - * Gets the {@link CachedBluetoothDevice} hearing device that is used to configure audio - * routing. - */ - protected abstract CachedBluetoothDevice getHearingDevice(); - /** * Saves the routing value. * diff --git a/src/com/android/settings/bluetooth/HearingDeviceCallRoutingPreferenceController.java b/src/com/android/settings/accessibility/HearingDeviceCallRoutingPreferenceController.java similarity index 81% rename from src/com/android/settings/bluetooth/HearingDeviceCallRoutingPreferenceController.java rename to src/com/android/settings/accessibility/HearingDeviceCallRoutingPreferenceController.java index 4e5e1937b11..8ca266359c2 100644 --- a/src/com/android/settings/bluetooth/HearingDeviceCallRoutingPreferenceController.java +++ b/src/com/android/settings/accessibility/HearingDeviceCallRoutingPreferenceController.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.settings.bluetooth; +package com.android.settings.accessibility; import android.content.Context; import android.provider.Settings; @@ -35,15 +35,6 @@ public class HearingDeviceCallRoutingPreferenceController extends super(context, preferenceKey); } - /** - * Initializes objects in this controller. Need to call this before using the controller. - * - * @param cachedBluetoothDevice the hearing device to configure audio routing - */ - public void init(CachedBluetoothDevice cachedBluetoothDevice) { - mHearingDevice = cachedBluetoothDevice; - } - @Override public int getAvailabilityStatus() { return Utils.isVoiceCapable(mContext) ? AVAILABLE : UNSUPPORTED_ON_DEVICE; @@ -54,11 +45,6 @@ public class HearingDeviceCallRoutingPreferenceController extends return HearingAidAudioRoutingConstants.CALL_ROUTING_ATTRIBUTES; } - @Override - protected CachedBluetoothDevice getHearingDevice() { - return mHearingDevice; - } - @Override protected void saveRoutingValue(Context context, int routingValue) { Settings.Secure.putInt(context.getContentResolver(), diff --git a/src/com/android/settings/bluetooth/HearingDeviceMediaRoutingPreferenceController.java b/src/com/android/settings/accessibility/HearingDeviceMediaRoutingPreferenceController.java similarity index 79% rename from src/com/android/settings/bluetooth/HearingDeviceMediaRoutingPreferenceController.java rename to src/com/android/settings/accessibility/HearingDeviceMediaRoutingPreferenceController.java index 1ea36c783e2..cd99ce0c0d0 100644 --- a/src/com/android/settings/bluetooth/HearingDeviceMediaRoutingPreferenceController.java +++ b/src/com/android/settings/accessibility/HearingDeviceMediaRoutingPreferenceController.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.settings.bluetooth; +package com.android.settings.accessibility; import android.content.Context; import android.provider.Settings; @@ -34,26 +34,12 @@ public class HearingDeviceMediaRoutingPreferenceController extends super(context, preferenceKey); } - /** - * Initializes objects in this controller. Need to call this before using the controller. - * - * @param cachedBluetoothDevice the hearing device to configure audio routing - */ - public void init(CachedBluetoothDevice cachedBluetoothDevice) { - mHearingDevice = cachedBluetoothDevice; - } - @Override protected int[] getSupportedAttributeList() { return HearingAidAudioRoutingConstants.MEDIA_ROUTING_ATTRIBUTES; } - @Override - protected CachedBluetoothDevice getHearingDevice() { - return mHearingDevice; - } - @Override protected void saveRoutingValue(Context context, int routingValue) { Settings.Secure.putInt(context.getContentResolver(), diff --git a/src/com/android/settings/bluetooth/HearingDeviceRingtoneRoutingPreferenceController.java b/src/com/android/settings/accessibility/HearingDeviceRingtoneRoutingPreferenceController.java similarity index 80% rename from src/com/android/settings/bluetooth/HearingDeviceRingtoneRoutingPreferenceController.java rename to src/com/android/settings/accessibility/HearingDeviceRingtoneRoutingPreferenceController.java index 006cb6b217a..3e8f79cec29 100644 --- a/src/com/android/settings/bluetooth/HearingDeviceRingtoneRoutingPreferenceController.java +++ b/src/com/android/settings/accessibility/HearingDeviceRingtoneRoutingPreferenceController.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.settings.bluetooth; +package com.android.settings.accessibility; import android.content.Context; import android.provider.Settings; @@ -34,26 +34,12 @@ public class HearingDeviceRingtoneRoutingPreferenceController extends super(context, preferenceKey); } - /** - * Initializes objects in this controller. Need to call this before using the controller. - * - * @param cachedBluetoothDevice the hearing device to configure audio routing - */ - public void init(CachedBluetoothDevice cachedBluetoothDevice) { - mHearingDevice = cachedBluetoothDevice; - } - @Override protected int[] getSupportedAttributeList() { return HearingAidAudioRoutingConstants.RINGTONE_ROUTING_ATTRIBUTE; } - @Override - protected CachedBluetoothDevice getHearingDevice() { - return mHearingDevice; - } - @Override protected void saveRoutingValue(Context context, int routingValue) { Settings.Secure.putInt(context.getContentResolver(), diff --git a/src/com/android/settings/bluetooth/HearingDeviceSystemSoundsRoutingPreferenceController.java b/src/com/android/settings/accessibility/HearingDeviceSystemSoundsRoutingPreferenceController.java similarity index 80% rename from src/com/android/settings/bluetooth/HearingDeviceSystemSoundsRoutingPreferenceController.java rename to src/com/android/settings/accessibility/HearingDeviceSystemSoundsRoutingPreferenceController.java index a66cfab93a0..1bc0090c7e9 100644 --- a/src/com/android/settings/bluetooth/HearingDeviceSystemSoundsRoutingPreferenceController.java +++ b/src/com/android/settings/accessibility/HearingDeviceSystemSoundsRoutingPreferenceController.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.settings.bluetooth; +package com.android.settings.accessibility; import android.content.Context; import android.provider.Settings; @@ -35,26 +35,12 @@ public class HearingDeviceSystemSoundsRoutingPreferenceController extends super(context, preferenceKey); } - /** - * Initializes objects in this controller. Need to call this before using the controller. - * - * @param cachedBluetoothDevice the hearing device to configure audio routing - */ - public void init(CachedBluetoothDevice cachedBluetoothDevice) { - mHearingDevice = cachedBluetoothDevice; - } - @Override protected int[] getSupportedAttributeList() { return HearingAidAudioRoutingConstants.SYSTEM_SOUNDS_ROUTING_ATTRIBUTES; } - @Override - protected CachedBluetoothDevice getHearingDevice() { - return mHearingDevice; - } - @Override protected void saveRoutingValue(Context context, int routingValue) { Settings.Secure.putInt(context.getContentResolver(), diff --git a/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java b/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java index 201172575ed..12706712427 100644 --- a/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java +++ b/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java @@ -249,12 +249,18 @@ public class ToggleScreenMagnificationPreferenceFragment extends super.onProcessArguments(arguments); } - private void addAlwaysOnSetting(PreferenceCategory generalCategory) { - if (!DeviceConfig.getBoolean( + private boolean isAlwaysOnSettingEnabled() { + final boolean defaultValue = getContext().getResources().getBoolean( + com.android.internal.R.bool.config_magnification_always_on_enabled); + + return DeviceConfig.getBoolean( DeviceConfig.NAMESPACE_WINDOW_MANAGER, "AlwaysOnMagnifier__enable_always_on_magnifier", - false - )) { + defaultValue + ); + } + private void addAlwaysOnSetting(PreferenceCategory generalCategory) { + if (!isAlwaysOnSettingEnabled()) { return; } diff --git a/src/com/android/settings/accounts/ContactSearchPreferenceController.java b/src/com/android/settings/accounts/ContactSearchPreferenceController.java index 5e4ef48d7c2..87dabd8d9a4 100644 --- a/src/com/android/settings/accounts/ContactSearchPreferenceController.java +++ b/src/com/android/settings/accounts/ContactSearchPreferenceController.java @@ -20,37 +20,38 @@ import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; -import androidx.annotation.VisibleForTesting; +import androidx.lifecycle.DefaultLifecycleObserver; +import androidx.lifecycle.LifecycleOwner; import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; +import com.android.settings.R; import com.android.settings.Utils; -import com.android.settings.core.BasePreferenceController; +import com.android.settings.core.TogglePreferenceController; import com.android.settings.slices.SliceData; import com.android.settingslib.RestrictedLockUtils; import com.android.settingslib.RestrictedLockUtilsInternal; import com.android.settingslib.RestrictedSwitchPreference; -public class ContactSearchPreferenceController extends BasePreferenceController implements - Preference.OnPreferenceChangeListener { +import org.jetbrains.annotations.NotNull; - private UserHandle mManagedUser; +public class ContactSearchPreferenceController extends TogglePreferenceController implements + Preference.OnPreferenceChangeListener, DefaultLifecycleObserver, + ManagedProfileQuietModeEnabler.QuietModeChangeListener { + + private final ManagedProfileQuietModeEnabler mQuietModeEnabler; + private final UserHandle mManagedUser; + private Preference mPreference; public ContactSearchPreferenceController(Context context, String key) { super(context, key); - // Set default managed profile for the current user, otherwise isAvailable will be false and - // the setting won't be searchable. - UserManager userManager = context.getSystemService(UserManager.class); - mManagedUser = Utils.getManagedProfile(userManager); - } - - @VisibleForTesting - void setManagedUser(UserHandle managedUser) { - mManagedUser = managedUser; + mManagedUser = Utils.getManagedProfile(context.getSystemService(UserManager.class)); + mQuietModeEnabler = new ManagedProfileQuietModeEnabler(context, this); } @Override public int getAvailabilityStatus() { - return (mManagedUser != null) ? AVAILABLE : DISABLED_FOR_USER; + return mQuietModeEnabler.isAvailable() ? AVAILABLE : DISABLED_FOR_USER; } @Override @@ -59,6 +60,7 @@ public class ContactSearchPreferenceController extends BasePreferenceController if (preference instanceof RestrictedSwitchPreference) { final RestrictedSwitchPreference pref = (RestrictedSwitchPreference) preference; pref.setChecked(isChecked()); + pref.setEnabled(!mQuietModeEnabler.isQuietModeEnabled()); if (mManagedUser != null) { final RestrictedLockUtils.EnforcedAdmin enforcedAdmin = RestrictedLockUtilsInternal.checkIfRemoteContactSearchDisallowed( @@ -68,26 +70,48 @@ public class ContactSearchPreferenceController extends BasePreferenceController } } - private boolean isChecked() { - if (mManagedUser == null) { + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mPreference = screen.findPreference(getPreferenceKey()); + updateState(mPreference); + } + + @Override + public void onStart(@NotNull LifecycleOwner lifecycleOwner) { + lifecycleOwner.getLifecycle().addObserver(mQuietModeEnabler); + } + + @Override + public void onStop(@NotNull LifecycleOwner lifecycleOwner) { + lifecycleOwner.getLifecycle().removeObserver(mQuietModeEnabler); + } + + @Override + public boolean isChecked() { + if (mManagedUser == null || mQuietModeEnabler.isQuietModeEnabled()) { return false; } return 0 != Settings.Secure.getIntForUser(mContext.getContentResolver(), MANAGED_PROFILE_CONTACT_REMOTE_SEARCH, 0, mManagedUser.getIdentifier()); } - private boolean setChecked(boolean isChecked) { - if (mManagedUser != null) { - final int value = isChecked ? 1 : 0; - Settings.Secure.putIntForUser(mContext.getContentResolver(), - MANAGED_PROFILE_CONTACT_REMOTE_SEARCH, value, mManagedUser.getIdentifier()); + @Override + public boolean setChecked(boolean isChecked) { + if (mManagedUser == null || mQuietModeEnabler.isQuietModeEnabled()) { + return false; } + final int value = isChecked ? 1 : 0; + Settings.Secure.putIntForUser(mContext.getContentResolver(), + MANAGED_PROFILE_CONTACT_REMOTE_SEARCH, value, mManagedUser.getIdentifier()); return true; } @Override - public final boolean onPreferenceChange(Preference preference, Object newValue) { - return setChecked((boolean) newValue); + public void onQuietModeChanged() { + if (mPreference != null) { + updateState(mPreference); + } } @Override @@ -95,4 +119,9 @@ public class ContactSearchPreferenceController extends BasePreferenceController public int getSliceType() { return SliceData.SliceType.SWITCH; } -} \ No newline at end of file + + @Override + public int getSliceHighlightMenuRes() { + return R.string.menu_key_accounts; + } +} diff --git a/src/com/android/settings/accounts/ManagedProfileQuietModeEnabler.java b/src/com/android/settings/accounts/ManagedProfileQuietModeEnabler.java new file mode 100644 index 00000000000..989be09565c --- /dev/null +++ b/src/com/android/settings/accounts/ManagedProfileQuietModeEnabler.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2023 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.accounts; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.UserHandle; +import android.os.UserManager; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; +import androidx.lifecycle.DefaultLifecycleObserver; +import androidx.lifecycle.LifecycleOwner; + +import com.android.settings.Utils; + +import javax.annotation.Nullable; + +/** + * A class that controls the managed profile's quiet mode, listens to quiet mode changes and + * modifies managed profile settings. The user facing term for quiet mode is "work apps". + */ +final class ManagedProfileQuietModeEnabler implements DefaultLifecycleObserver { + + private static final String TAG = "QuietModeEnabler"; + private final Context mContext; + private final QuietModeChangeListener mListener; + @Nullable private final UserHandle mManagedProfile; + private final UserManager mUserManager; + + public interface QuietModeChangeListener { + /** Called when quiet mode has changed. */ + void onQuietModeChanged(); + } + + ManagedProfileQuietModeEnabler(Context context, QuietModeChangeListener listener) { + mContext = context; + mListener = listener; + mUserManager = context.getSystemService(UserManager.class); + mManagedProfile = Utils.getManagedProfile(mUserManager); + } + + public void setQuietModeEnabled(boolean enabled) { + if (mManagedProfile != null) { + mUserManager.requestQuietModeEnabled(enabled, mManagedProfile); + } + } + + public boolean isQuietModeEnabled() { + return mManagedProfile != null && mUserManager.isQuietModeEnabled(mManagedProfile); + } + + @Override + public void onStart(@NonNull LifecycleOwner owner) { + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE); + intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE); + mContext.registerReceiver(mReceiver, intentFilter, Context.RECEIVER_NOT_EXPORTED); + } + + @Override + public void onStop(@NonNull LifecycleOwner owner) { + mContext.unregisterReceiver(mReceiver); + } + + public boolean isAvailable() { + return (mManagedProfile != null); + } + + private void refreshQuietMode() { + if (mListener != null) { + mListener.onQuietModeChanged(); + } + } + + /** + * Receiver that listens to {@link Intent#ACTION_MANAGED_PROFILE_AVAILABLE} and + * {@link Intent#ACTION_MANAGED_PROFILE_UNAVAILABLE}, and updates the work mode + */ + @VisibleForTesting + final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent == null) { + return; + } + String action = intent.getAction(); + Log.v(TAG, "Received broadcast: " + action); + + if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action) + || Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action)) { + int intentUserIdentifier = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, + UserHandle.USER_NULL); + if (intentUserIdentifier == mManagedProfile.getIdentifier()) { + refreshQuietMode(); + } else { + Log.w(TAG, "Managed profile broadcast ID: " + intentUserIdentifier + + " does not match managed user: " + mManagedProfile); + } + } else { + Log.w(TAG, "Cannot handle received broadcast: " + intent.getAction()); + } + } + }; +} diff --git a/src/com/android/settings/accounts/WorkModePreferenceController.java b/src/com/android/settings/accounts/WorkModePreferenceController.java index 1941de45c4d..a261afccaa6 100644 --- a/src/com/android/settings/accounts/WorkModePreferenceController.java +++ b/src/com/android/settings/accounts/WorkModePreferenceController.java @@ -1,165 +1,90 @@ /* * Copyright (C) 2018 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 + * 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. + * 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.accounts; -import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_SETTING_OFF_SUMMARY; -import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_SETTING_ON_SUMMARY; - -import android.app.admin.DevicePolicyManager; -import android.content.BroadcastReceiver; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.os.UserHandle; -import android.os.UserManager; -import android.util.Log; -import androidx.annotation.VisibleForTesting; +import androidx.lifecycle.DefaultLifecycleObserver; +import androidx.lifecycle.LifecycleOwner; import androidx.preference.Preference; -import androidx.preference.PreferenceScreen; -import androidx.preference.TwoStatePreference; +import com.android.internal.annotations.VisibleForTesting; import com.android.settings.R; -import com.android.settings.Utils; -import com.android.settings.core.BasePreferenceController; import com.android.settings.slices.SliceData; -import com.android.settingslib.core.lifecycle.LifecycleObserver; -import com.android.settingslib.core.lifecycle.events.OnStart; -import com.android.settingslib.core.lifecycle.events.OnStop; +import com.android.settings.widget.SettingsMainSwitchPreferenceController; +import com.android.settingslib.widget.MainSwitchPreference; -public class WorkModePreferenceController extends BasePreferenceController implements - Preference.OnPreferenceChangeListener, LifecycleObserver, OnStart, OnStop { +import org.jetbrains.annotations.NotNull; - private static final String TAG = "WorkModeController"; - private UserManager mUserManager; - private UserHandle mManagedUser; - private DevicePolicyManager mDevicePolicyManager; +public class WorkModePreferenceController extends SettingsMainSwitchPreferenceController + implements Preference.OnPreferenceChangeListener, DefaultLifecycleObserver, + ManagedProfileQuietModeEnabler.QuietModeChangeListener { - private Preference mPreference; - private IntentFilter mIntentFilter; + private final ManagedProfileQuietModeEnabler mQuietModeEnabler; public WorkModePreferenceController(Context context, String key) { super(context, key); - mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE); - mDevicePolicyManager = mContext.getSystemService(DevicePolicyManager.class); - mIntentFilter = new IntentFilter(); - mIntentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE); - mIntentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE); - // Set default managed profile for the current user, otherwise isAvailable will be false and - // the setting won't be searchable. - mManagedUser = Utils.getManagedProfile(mUserManager); - } - - @VisibleForTesting - void setManagedUser(UserHandle managedUser) { - mManagedUser = managedUser; - } - - @Override - public void onStart() { - mContext.registerReceiver(mReceiver, mIntentFilter, - Context.RECEIVER_EXPORTED_UNAUDITED); - } - - @Override - public void onStop() { - mContext.unregisterReceiver(mReceiver); + mQuietModeEnabler = new ManagedProfileQuietModeEnabler(context, this); } @Override public int getAvailabilityStatus() { - return (mManagedUser != null) ? AVAILABLE : DISABLED_FOR_USER; + return (mQuietModeEnabler.isAvailable()) ? AVAILABLE : DISABLED_FOR_USER; } @Override - public void displayPreference(PreferenceScreen screen) { - super.displayPreference(screen); - mPreference = screen.findPreference(getPreferenceKey()); + public void onStart(@NotNull LifecycleOwner lifecycleOwner) { + lifecycleOwner.getLifecycle().addObserver(mQuietModeEnabler); } @Override - public CharSequence getSummary() { - if (isChecked()) { - return mDevicePolicyManager.getResources().getString( - WORK_PROFILE_SETTING_ON_SUMMARY, - () -> mContext.getString(R.string.work_mode_on_summary)); - } - - return mDevicePolicyManager.getResources().getString( - WORK_PROFILE_SETTING_OFF_SUMMARY, - () -> mContext.getString(R.string.work_mode_off_summary)); + public void onStop(@NotNull LifecycleOwner lifecycleOwner) { + lifecycleOwner.getLifecycle().removeObserver(mQuietModeEnabler); } - private boolean isChecked() { - boolean isWorkModeOn = false; - if (mUserManager != null && mManagedUser != null) { - isWorkModeOn = !mUserManager.isQuietModeEnabled(mManagedUser); - } - return isWorkModeOn; + @Override + public boolean isChecked() { + return !mQuietModeEnabler.isQuietModeEnabled(); } - private boolean setChecked(boolean isChecked) { - if (mUserManager != null && mManagedUser != null) { - final boolean quietModeEnabled = !isChecked; - mUserManager.requestQuietModeEnabled(quietModeEnabled, mManagedUser); - } + @Override + public boolean setChecked(boolean isChecked) { + mQuietModeEnabler.setQuietModeEnabled(!isChecked); return true; } @Override - public void updateState(Preference preference) { - super.updateState(preference); - if (preference instanceof TwoStatePreference) { - ((TwoStatePreference) preference).setChecked(isChecked()); - } + public void onQuietModeChanged() { + updateState(mSwitchPreference); } - @Override - public final boolean onPreferenceChange(Preference preference, Object newValue) { - return setChecked((boolean) newValue); - } - - /** - * Receiver that listens to {@link Intent#ACTION_MANAGED_PROFILE_AVAILABLE} and - * {@link Intent#ACTION_MANAGED_PROFILE_UNAVAILABLE}, and updates the work mode - */ - @VisibleForTesting - final BroadcastReceiver mReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (intent == null) { - return; - } - final String action = intent.getAction(); - Log.v(TAG, "Received broadcast: " + action); - - if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action) - || Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action)) { - if (intent.getIntExtra(Intent.EXTRA_USER_HANDLE, - UserHandle.USER_NULL) == mManagedUser.getIdentifier()) { - updateState(mPreference); - } - return; - } - Log.w(TAG, "Cannot handle received broadcast: " + intent.getAction()); - } - }; - @Override @SliceData.SliceType public int getSliceType() { return SliceData.SliceType.SWITCH; } -} \ No newline at end of file + + @Override + public int getSliceHighlightMenuRes() { + return R.string.menu_key_accounts; + } + + @VisibleForTesting + void setPreference(MainSwitchPreference preference) { + mSwitchPreference = preference; + } +} diff --git a/src/com/android/settings/activityembedding/ActivityEmbeddingUtils.java b/src/com/android/settings/activityembedding/ActivityEmbeddingUtils.java index ae890f87632..67d56e0393a 100644 --- a/src/com/android/settings/activityembedding/ActivityEmbeddingUtils.java +++ b/src/com/android/settings/activityembedding/ActivityEmbeddingUtils.java @@ -97,16 +97,24 @@ public class ActivityEmbeddingUtils { * */ public static boolean isEmbeddingActivityEnabled(Context context) { - boolean isFlagEnabled = FeatureFlagUtils.isEnabled(context, - FeatureFlagUtils.SETTINGS_SUPPORT_LARGE_SCREEN); - boolean isSettingsSplitSupported = isSettingsSplitEnabled(context); - boolean isUserSetupComplete = WizardManagerHelper.isUserSetupComplete(context); - - Log.d(TAG, "isFlagEnabled = " + isFlagEnabled); - Log.d(TAG, "isSettingsSplitSupported = " + isSettingsSplitSupported); - Log.d(TAG, "isUserSetupComplete = " + isUserSetupComplete); - - return isFlagEnabled && isSettingsSplitSupported && isUserSetupComplete; + // Activity Embedding feature is not enabled if Settings doesn't enable large screen + // optimization or the device is not supported. + if (!isSettingsSplitEnabled(context)) { + Log.d(TAG, "isSettingsSplitSupported = false"); + return false; + } + // Activity Embedding feature is not enabled if a user chooses to disable the feature. + if (!FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_SUPPORT_LARGE_SCREEN)) { + Log.d(TAG, "isFlagEnabled = false"); + return false; + } + // Don't enable Activity embedding for setup wizard. + if (!WizardManagerHelper.isUserSetupComplete(context)) { + Log.d(TAG, "isUserSetupComplete = false"); + return false; + } + Log.d(TAG, "isEmbeddingActivityEnabled = true"); + return true; } /** Whether to show the regular or simplified homepage layout. */ @@ -120,8 +128,7 @@ public class ActivityEmbeddingUtils { * Check if activity is already embedded */ public static boolean isAlreadyEmbedded(Activity activity) { - return ActivityEmbeddingController - .getInstance(activity) - .isActivityEmbedded(activity); + return isEmbeddingActivityEnabled(activity) && ActivityEmbeddingController.getInstance( + activity).isActivityEmbedded(activity); } } diff --git a/src/com/android/settings/applications/manageapplications/ManageApplications.java b/src/com/android/settings/applications/manageapplications/ManageApplications.java index a6e6ac47673..e0f1b5f702d 100644 --- a/src/com/android/settings/applications/manageapplications/ManageApplications.java +++ b/src/com/android/settings/applications/manageapplications/ManageApplications.java @@ -16,8 +16,6 @@ package com.android.settings.applications.manageapplications; -import static android.util.FeatureFlagUtils.SETTINGS_ENABLE_SPA; - import static androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_DRAGGING; import static androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_IDLE; @@ -67,7 +65,6 @@ import android.provider.DeviceConfig; import android.provider.Settings; import android.text.TextUtils; import android.util.ArraySet; -import android.util.FeatureFlagUtils; import android.util.IconDrawableFactory; import android.util.Log; import android.view.LayoutInflater; @@ -728,9 +725,6 @@ public class ManageApplications extends InstrumentedFragment R.string.long_background_tasks_label); break; case LIST_TYPE_CLONED_APPS: - if (!FeatureFlagUtils.isEnabled(getContext(), SETTINGS_ENABLE_SPA)) { - return; - } int userId = UserHandle.getUserId(mCurrentUid); UserInfo userInfo = mUserManager.getUserInfo(userId); if (userInfo != null && !userInfo.isCloneProfile()) { diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java index bf83cb15f21..938c075cb70 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java @@ -261,6 +261,8 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling { int rotation = getApplicationContext().getDisplay().getRotation(); final GlifLayout layout = (GlifLayout) getLayoutInflater().inflate( R.layout.udfps_enroll_enrolling, null, false); + final UdfpsEnrollView udfpsEnrollView = layout.findViewById(R.id.udfps_animation_view); + updateUdfpsEnrollView(udfpsEnrollView, props.get(0)); switch (rotation) { case Surface.ROTATION_90: final LinearLayout layoutContainer = layout.findViewById( @@ -276,66 +278,52 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling { ? 0 : (int) getResources().getDimension( R.dimen.rotation_90_enroll_padding_end), 0); layoutContainer.setLayoutParams(lp); - if (FeatureFlagUtils.isEnabled(getApplicationContext(), - FeatureFlagUtils.SETTINGS_SHOW_UDFPS_ENROLL_IN_SETTINGS)) { - final UdfpsEnrollView udfpsEnrollView = addUdfpsEnrollView(props.get(0)); - layout.addView(udfpsEnrollView); - setOnHoverListener(true, layout, udfpsEnrollView); - } + + setOnHoverListener(true, layout, udfpsEnrollView); setContentView(layout, lp); break; case Surface.ROTATION_0: case Surface.ROTATION_180: - if (FeatureFlagUtils.isEnabled(getApplicationContext(), - FeatureFlagUtils.SETTINGS_SHOW_UDFPS_ENROLL_IN_SETTINGS)) { - final UdfpsEnrollView udfpsEnrollView = addUdfpsEnrollView(props.get(0)); - // In the portrait mode, set layout_container's height 0, so it's - // always shown at the bottom of the screen. - // Add udfps enroll view into layout_container instead of - // udfps_enroll_enrolling, so that when the content is too long to - // make udfps_enroll_enrolling larger than the screen, udfps enroll - // view could still be set to right position by setting bottom margin to - // its parent view (layout_container) because it's always at the - // bottom of the screen. - final FrameLayout portraitLayoutContainer = layout.findViewById( - R.id.layout_container); - final ViewGroup.LayoutParams containerLp = - portraitLayoutContainer.getLayoutParams(); - containerLp.height = 0; + // In the portrait mode, layout_container's height is 0, so it's + // always shown at the bottom of the screen. + final FrameLayout portraitLayoutContainer = layout.findViewById( + R.id.layout_container); - // In the portrait mode, the title and lottie animation view may - // overlap when title needs three lines, so adding some paddings - // between them, and adjusting the fp progress view here accordingly. - final int layoutLottieAnimationPadding = (int) getResources() - .getDimension(R.dimen.udfps_lottie_padding_top); - portraitLayoutContainer.setPadding(0, - layoutLottieAnimationPadding, 0, 0); - final ImageView progressView = udfpsEnrollView.findViewById( - R.id.udfps_enroll_animation_fp_progress_view); - progressView.setPadding(0, -(layoutLottieAnimationPadding), - 0, layoutLottieAnimationPadding); - final ImageView fingerprintView = udfpsEnrollView.findViewById( - R.id.udfps_enroll_animation_fp_view); - fingerprintView.setPadding(0, -layoutLottieAnimationPadding, - 0, layoutLottieAnimationPadding); + // In the portrait mode, the title and lottie animation view may + // overlap when title needs three lines, so adding some paddings + // between them, and adjusting the fp progress view here accordingly. + final int layoutLottieAnimationPadding = (int) getResources() + .getDimension(R.dimen.udfps_lottie_padding_top); + portraitLayoutContainer.setPadding(0, + layoutLottieAnimationPadding, 0, 0); + final ImageView progressView = udfpsEnrollView.findViewById( + R.id.udfps_enroll_animation_fp_progress_view); + progressView.setPadding(0, -(layoutLottieAnimationPadding), + 0, layoutLottieAnimationPadding); + final ImageView fingerprintView = udfpsEnrollView.findViewById( + R.id.udfps_enroll_animation_fp_view); + fingerprintView.setPadding(0, -layoutLottieAnimationPadding, + 0, layoutLottieAnimationPadding); - portraitLayoutContainer.addView(udfpsEnrollView); - setOnHoverListener(false, layout, udfpsEnrollView); - } + // TODO(b/260970216) Instead of hiding the description text view, we should + // make the header view scrollable if the text is too long. + // If description text view has overlap with udfps progress view, hide it. + View view = layout.getDescriptionTextView(); + layout.getViewTreeObserver().addOnDrawListener(() -> { + if (view.getVisibility() == View.VISIBLE + && hasOverlap(view, udfpsEnrollView)) { + view.setVisibility(View.GONE); + } + }); + setOnHoverListener(false, layout, udfpsEnrollView); setContentView(layout); break; case Surface.ROTATION_270: default: - if (FeatureFlagUtils.isEnabled(getApplicationContext(), - FeatureFlagUtils.SETTINGS_SHOW_UDFPS_ENROLL_IN_SETTINGS)) { - final UdfpsEnrollView udfpsEnrollView = addUdfpsEnrollView(props.get(0)); - layout.addView(udfpsEnrollView); - setOnHoverListener(true, layout, udfpsEnrollView); - } - + setOnHoverListener(true, layout, udfpsEnrollView); setContentView(layout); break; } @@ -1235,10 +1223,8 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling { } } - private UdfpsEnrollView addUdfpsEnrollView(FingerprintSensorPropertiesInternal udfpsProps) { - UdfpsEnrollView udfpsEnrollView = (UdfpsEnrollView) getLayoutInflater().inflate( - R.layout.udfps_enroll_view, null, false); - + private UdfpsEnrollView updateUdfpsEnrollView(UdfpsEnrollView udfpsEnrollView, + FingerprintSensorPropertiesInternal udfpsProps) { DisplayInfo displayInfo = new DisplayInfo(); getDisplay().getDisplayInfo(displayInfo); mScaleFactor = mUdfpsUtils.getScaleFactor(displayInfo); @@ -1305,6 +1291,24 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling { : R.id.sud_layout_content).setOnHoverListener(onHoverListener); } + + @VisibleForTesting boolean hasOverlap(View view1, View view2) { + int[] firstPosition = new int[2]; + int[] secondPosition = new int[2]; + + view1.getLocationOnScreen(firstPosition); + view2.getLocationOnScreen(secondPosition); + + // Rect constructor parameters: left, top, right, bottom + Rect rectView1 = new Rect(firstPosition[0], firstPosition[1], + firstPosition[0] + view1.getMeasuredWidth(), + firstPosition[1] + view1.getMeasuredHeight()); + Rect rectView2 = new Rect(secondPosition[0], secondPosition[1], + secondPosition[0] + view2.getMeasuredWidth(), + secondPosition[1] + view2.getMeasuredHeight()); + return rectView1.intersect(rectView2); + } + public static class IconTouchDialog extends InstrumentedDialogFragment { @Override diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java b/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java index e0f402bdc02..be090e33366 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java @@ -185,7 +185,8 @@ public class FingerprintSettings extends SubSettings { private static final int MSG_FINGER_AUTH_HELP = 1004; private static final int CONFIRM_REQUEST = 101; - private static final int CHOOSE_LOCK_GENERIC_REQUEST = 102; + @VisibleForTesting + static final int CHOOSE_LOCK_GENERIC_REQUEST = 102; @VisibleForTesting static final int ADD_FINGERPRINT_REQUEST = 10; private static final int AUTO_ADD_FIRST_FINGERPRINT_REQUEST = 11; @@ -1014,7 +1015,7 @@ public class FingerprintSettings extends SubSettings { true); intent.putExtra(Intent.EXTRA_USER_ID, mUserId); intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE, true); - intent.putExtra(Intent.EXTRA_USER_ID, mUserId); + intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, true); startActivityForResult(intent, CHOOSE_LOCK_GENERIC_REQUEST); } } diff --git a/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollView.java b/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollView.java index 6e42059395f..5ded91ec68c 100644 --- a/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollView.java +++ b/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollView.java @@ -161,22 +161,20 @@ public class UdfpsEnrollView extends FrameLayout implements UdfpsEnrollHelper.Li MarginLayoutParams marginLayoutParams = (MarginLayoutParams) getLayoutParams(); FrameLayout.LayoutParams params = (LayoutParams) getLayoutParams(); if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) { - parentView.getViewTreeObserver().addOnDrawListener(() -> { - final int[] coords = parentView.getLocationOnScreen(); - final int parentLeft = coords[0]; - final int parentTop = coords[1]; - final int parentRight = parentLeft + parentView.getWidth(); - params.gravity = Gravity.RIGHT | Gravity.TOP; - final int rightMargin = parentRight - rotatedBounds.right - getPaddingX(); - final int topMargin = rotatedBounds.top - parentTop - getPaddingY(); - if (marginLayoutParams.rightMargin == rightMargin - && marginLayoutParams.topMargin == topMargin) { - return; - } - marginLayoutParams.rightMargin = rightMargin; - marginLayoutParams.topMargin = topMargin; - setLayoutParams(params); - }); + final int[] coords = parentView.getLocationOnScreen(); + final int parentLeft = coords[0]; + final int parentTop = coords[1]; + final int parentRight = parentLeft + parentView.getWidth(); + params.gravity = Gravity.RIGHT | Gravity.TOP; + final int rightMargin = parentRight - rotatedBounds.right - getPaddingX(); + final int topMargin = rotatedBounds.top - parentTop - getPaddingY(); + if (marginLayoutParams.rightMargin == rightMargin + && marginLayoutParams.topMargin == topMargin) { + return; + } + marginLayoutParams.rightMargin = rightMargin; + marginLayoutParams.topMargin = topMargin; + setLayoutParams(params); } else { final int[] coords = parentView.getLocationOnScreen(); final int parentLeft = coords[0]; diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsAudioRoutingController.java b/src/com/android/settings/bluetooth/BluetoothDetailsAudioRoutingController.java deleted file mode 100644 index 91221a33ba9..00000000000 --- a/src/com/android/settings/bluetooth/BluetoothDetailsAudioRoutingController.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (C) 2022 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.android.settings.bluetooth.BluetoothDeviceDetailsFragment.KEY_DEVICE_ADDRESS; - -import android.content.Context; -import android.os.Bundle; -import android.util.FeatureFlagUtils; - -import androidx.annotation.VisibleForTesting; -import androidx.preference.Preference; -import androidx.preference.PreferenceCategory; -import androidx.preference.PreferenceFragmentCompat; -import androidx.preference.PreferenceScreen; - -import com.android.settings.R; -import com.android.settingslib.bluetooth.CachedBluetoothDevice; -import com.android.settingslib.core.lifecycle.Lifecycle; - -/** - * The controller of the audio routing in the bluetooth detail settings. - */ -public class BluetoothDetailsAudioRoutingController extends BluetoothDetailsController { - - private static final String KEY_DEVICE_CONTROLS_SPECIFIC_GROUP = "device_controls_specific"; - @VisibleForTesting - static final String KEY_AUDIO_ROUTING = "audio_routing"; - - public BluetoothDetailsAudioRoutingController(Context context, - PreferenceFragmentCompat fragment, CachedBluetoothDevice device, Lifecycle lifecycle) { - super(context, fragment, device, lifecycle); - } - - @Override - public boolean isAvailable() { - return mCachedDevice.isHearingAidDevice() && FeatureFlagUtils.isEnabled(mContext, - FeatureFlagUtils.SETTINGS_AUDIO_ROUTING); - } - - @Override - protected void init(PreferenceScreen screen) { - if (!mCachedDevice.isHearingAidDevice()) { - return; - } - - final PreferenceCategory prefCategory = screen.findPreference(getPreferenceKey()); - final Preference pref = createAudioRoutingPreference(prefCategory.getContext()); - prefCategory.addPreference(pref); - } - - @Override - protected void refresh() {} - - @Override - public String getPreferenceKey() { - return KEY_DEVICE_CONTROLS_SPECIFIC_GROUP; - } - - private Preference createAudioRoutingPreference(Context context) { - final Preference preference = new Preference(context); - - preference.setKey(KEY_AUDIO_ROUTING); - preference.setTitle(context.getString(R.string.bluetooth_audio_routing_title)); - preference.setSummary(context.getString(R.string.bluetooth_audio_routing_summary)); - final Bundle extras = preference.getExtras(); - extras.putString(KEY_DEVICE_ADDRESS, mCachedDevice.getAddress()); - preference.setFragment(BluetoothDetailsAudioRoutingFragment.class.getName()); - - return preference; - } -} diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsAudioRoutingFragment.java b/src/com/android/settings/bluetooth/BluetoothDetailsAudioRoutingFragment.java deleted file mode 100644 index 6c435a27cd2..00000000000 --- a/src/com/android/settings/bluetooth/BluetoothDetailsAudioRoutingFragment.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (C) 2023 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.os.UserManager.DISALLOW_CONFIG_BLUETOOTH; - -import static com.android.settings.bluetooth.BluetoothDeviceDetailsFragment.KEY_DEVICE_ADDRESS; - -import android.app.settings.SettingsEnums; -import android.bluetooth.BluetoothDevice; -import android.content.Context; -import android.util.Log; - -import androidx.annotation.VisibleForTesting; - -import com.android.settings.R; -import com.android.settings.dashboard.RestrictedDashboardFragment; -import com.android.settings.search.BaseSearchIndexProvider; -import com.android.settingslib.bluetooth.CachedBluetoothDevice; -import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; -import com.android.settingslib.bluetooth.LocalBluetoothManager; - -/** Settings fragment containing bluetooth audio routing. */ -public class BluetoothDetailsAudioRoutingFragment extends RestrictedDashboardFragment { - - public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = - new BaseSearchIndexProvider(R.xml.bluetooth_audio_routing_fragment); - private static final String TAG = "BluetoothDetailsAudioRoutingFragment"; - @VisibleForTesting - CachedBluetoothDevice mCachedDevice; - - public BluetoothDetailsAudioRoutingFragment() { - super(DISALLOW_CONFIG_BLUETOOTH); - } - - @Override - public void onAttach(Context context) { - super.onAttach(context); - final LocalBluetoothManager localBtMgr = Utils.getLocalBtManager(context); - final CachedBluetoothDeviceManager cachedDeviceMgr = localBtMgr.getCachedDeviceManager(); - final BluetoothDevice bluetoothDevice = localBtMgr.getBluetoothAdapter().getRemoteDevice( - getArguments().getString(KEY_DEVICE_ADDRESS)); - - mCachedDevice = cachedDeviceMgr.findDevice(bluetoothDevice); - if (mCachedDevice == null) { - // Close this page if device is null with invalid device mac address - Log.w(TAG, "onAttach() CachedDevice is null! Can not find address: " - + bluetoothDevice.getAnonymizedAddress()); - finish(); - return; - } - - use(HearingDeviceRingtoneRoutingPreferenceController.class).init(mCachedDevice); - use(HearingDeviceCallRoutingPreferenceController.class).init(mCachedDevice); - use(HearingDeviceMediaRoutingPreferenceController.class).init(mCachedDevice); - use(HearingDeviceSystemSoundsRoutingPreferenceController.class).init(mCachedDevice); - } - - @Override - public int getMetricsCategory() { - return SettingsEnums.BLUETOOTH_AUDIO_ROUTING; - } - - @Override - protected int getPreferenceScreenResId() { - return R.xml.bluetooth_audio_routing_fragment; - } - - @Override - protected String getLogTag() { - return TAG; - } -} diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceControlsController.java b/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceControlsController.java index a3b1105015d..c4a422186dd 100644 --- a/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceControlsController.java +++ b/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceControlsController.java @@ -34,7 +34,7 @@ import com.android.settingslib.core.lifecycle.Lifecycle; import com.google.common.annotations.VisibleForTesting; /** - * The controller of the hearing device controls in the bluetooth detail settings. + * The controller of the hearing device settings to launch Hearing device page. */ public class BluetoothDetailsHearingDeviceControlsController extends BluetoothDetailsController implements Preference.OnPreferenceClickListener { @@ -87,6 +87,7 @@ public class BluetoothDetailsHearingDeviceControlsController extends BluetoothDe final Preference preference = new Preference(context); preference.setKey(KEY_HEARING_DEVICE_CONTROLS); preference.setTitle(context.getString(R.string.bluetooth_device_controls_title)); + preference.setSummary(context.getString(R.string.bluetooth_device_controls_summary)); preference.setOnPreferenceClickListener(this); return preference; diff --git a/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java b/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java index 2a94b1e077e..99f3e3187cb 100644 --- a/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java +++ b/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java @@ -314,8 +314,6 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment lifecycle)); controllers.add(new BluetoothDetailsHearingDeviceControlsController(context, this, mCachedDevice, lifecycle)); - controllers.add(new BluetoothDetailsAudioRoutingController(context, this, mCachedDevice, - lifecycle)); } return controllers; } diff --git a/src/com/android/settings/bluetooth/BluetoothDeviceUpdater.java b/src/com/android/settings/bluetooth/BluetoothDeviceUpdater.java index 17c5f36d5c6..2935c6779dc 100644 --- a/src/com/android/settings/bluetooth/BluetoothDeviceUpdater.java +++ b/src/com/android/settings/bluetooth/BluetoothDeviceUpdater.java @@ -35,8 +35,10 @@ import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; +import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; +import java.util.List; import java.util.Map; /** @@ -125,7 +127,7 @@ public abstract class BluetoothDeviceUpdater implements BluetoothCallback, update(cachedBluetoothDevice); } } else { - removeAllDevicesFromPreference(); + removeAllDevicesFromPreference(); } } @@ -252,7 +254,7 @@ public abstract class BluetoothDeviceUpdater implements BluetoothCallback, btPreference.setOnGearClickListener(mDeviceProfilesListener); if (this instanceof Preference.OnPreferenceClickListener) { btPreference.setOnPreferenceClickListener( - (Preference.OnPreferenceClickListener)this); + (Preference.OnPreferenceClickListener) this); } mPreferenceMap.put(device, btPreference); mDevicePreferenceCallback.onDeviceAdded(btPreference); @@ -266,17 +268,20 @@ public abstract class BluetoothDeviceUpdater implements BluetoothCallback, final BluetoothDevice device = cachedDevice.getDevice(); final CachedBluetoothDevice subCachedDevice = cachedDevice.getSubDevice(); if (mPreferenceMap.containsKey(device)) { - mDevicePreferenceCallback.onDeviceRemoved(mPreferenceMap.get(device)); - mPreferenceMap.remove(device); + removePreference(device); } else if (subCachedDevice != null) { // When doing remove, to check if preference maps to sub device. // This would happen when connection state is changed in detail page that there is no // callback from SettingsLib. final BluetoothDevice subDevice = subCachedDevice.getDevice(); - if (mPreferenceMap.containsKey(subDevice)) { - mDevicePreferenceCallback.onDeviceRemoved(mPreferenceMap.get(subDevice)); - mPreferenceMap.remove(subDevice); - } + removePreference(subDevice); + } + } + + private void removePreference(BluetoothDevice device) { + if (mPreferenceMap.containsKey(device)) { + mDevicePreferenceCallback.onDeviceRemoved(mPreferenceMap.get(device)); + mPreferenceMap.remove(device); } } @@ -324,14 +329,38 @@ public abstract class BluetoothDeviceUpdater implements BluetoothCallback, * Update the attributes of {@link Preference}. */ public void refreshPreference() { - for (Preference preference : mPreferenceMap.values()) { - ((BluetoothDevicePreference) preference).onPreferenceAttributesChanged(); + List removeList = new ArrayList<>(); + mPreferenceMap.forEach((key, preference) -> { + if (isDeviceOfMapInCachedDevicesList(key)) { + ((BluetoothDevicePreference) preference).onPreferenceAttributesChanged(); + } else { + // If the BluetoothDevice of preference is not in the CachedDevices List, then + // remove this preference. + removeList.add(key); + } + }); + + for (BluetoothDevice bluetoothDevice : removeList) { + Log.d(getLogTag(), "removePreference key: " + bluetoothDevice.getAnonymizedAddress()); + removePreference(bluetoothDevice); } } - protected boolean isDeviceInCachedDevicesList(CachedBluetoothDevice cachedDevice){ + protected boolean isDeviceInCachedDevicesList(CachedBluetoothDevice cachedDevice) { return mLocalManager.getCachedDeviceManager().getCachedDevicesCopy().contains(cachedDevice); } + + private boolean isDeviceOfMapInCachedDevicesList(BluetoothDevice inputBluetoothDevice) { + Collection cachedDevices = + mLocalManager.getCachedDeviceManager().getCachedDevicesCopy(); + if (cachedDevices == null || cachedDevices.isEmpty()) { + return false; + } + return cachedDevices.stream() + .anyMatch(cachedBluetoothDevice -> cachedBluetoothDevice.getDevice() != null + && cachedBluetoothDevice.getDevice().equals(inputBluetoothDevice)); + } + protected String getLogTag() { return TAG; } diff --git a/src/com/android/settings/development/graphicsdriver/GraphicsDriverEnableAngleAsSystemDriverController.java b/src/com/android/settings/development/graphicsdriver/GraphicsDriverEnableAngleAsSystemDriverController.java index b9f34137111..04252fa0d56 100644 --- a/src/com/android/settings/development/graphicsdriver/GraphicsDriverEnableAngleAsSystemDriverController.java +++ b/src/com/android/settings/development/graphicsdriver/GraphicsDriverEnableAngleAsSystemDriverController.java @@ -48,6 +48,8 @@ public class GraphicsDriverEnableAngleAsSystemDriverController private final DevelopmentSettingsDashboardFragment mFragment; + private final GraphicsDriverSystemPropertiesWrapper mSystemProperties; + @VisibleForTesting static final String PROPERTY_RO_GFX_ANGLE_SUPPORTED = "ro.gfx.angle.supported"; @@ -57,11 +59,34 @@ public class GraphicsDriverEnableAngleAsSystemDriverController @VisibleForTesting static final String ANGLE_DRIVER_SUFFIX = "angle"; + @VisibleForTesting + static class Injector { + public GraphicsDriverSystemPropertiesWrapper createSystemPropertiesWrapper() { + return new GraphicsDriverSystemPropertiesWrapper() { + @Override + public String get(String key, String def) { + return SystemProperties.get(key, def); + } + + @Override + public void set(String key, String val) { + SystemProperties.set(key, val); + } + }; + } + } public GraphicsDriverEnableAngleAsSystemDriverController( Context context, DevelopmentSettingsDashboardFragment fragment) { + this(context, fragment, new Injector()); + } + + @VisibleForTesting + GraphicsDriverEnableAngleAsSystemDriverController( + Context context, DevelopmentSettingsDashboardFragment fragment, Injector injector) { super(context); mFragment = fragment; + mSystemProperties = injector.createSystemPropertiesWrapper(); } @Override @@ -76,20 +101,27 @@ public class GraphicsDriverEnableAngleAsSystemDriverController // set "persist.graphics.egl" to "" if enableAngleAsSystemDriver is false GraphicsEnvironment.getInstance().toggleAngleAsSystemDriver(enableAngleAsSystemDriver); // pop up a window asking user to reboot to make the new "persist.graphics.egl" take effect + showRebootDialog(); + return true; + } + + @VisibleForTesting + void showRebootDialog() { RebootConfirmationDialogFragment.show( mFragment, R.string.reboot_dialog_enable_angle_as_system_driver, R.string.cancel, this); - return true; } + @Override public void updateState(Preference preference) { // set switch on if "persist.graphics.egl" is "angle" and angle is built in /vendor // set switch off otherwise. - final String currentGlesDriver = SystemProperties.get(PROPERTY_PERSISTENT_GRAPHICS_EGL); + final String currentGlesDriver = + mSystemProperties.get(PROPERTY_PERSISTENT_GRAPHICS_EGL, ""); final boolean isAngle = TextUtils.equals(ANGLE_DRIVER_SUFFIX, currentGlesDriver); - final boolean isAngleSupported = - TextUtils.equals(SystemProperties.get(PROPERTY_RO_GFX_ANGLE_SUPPORTED), "true"); + final boolean isAngleSupported = TextUtils + .equals(mSystemProperties.get(PROPERTY_RO_GFX_ANGLE_SUPPORTED, ""), "true"); ((SwitchPreference) mPreference).setChecked(isAngle && isAngleSupported); ((SwitchPreference) mPreference).setEnabled(isAngleSupported); } @@ -98,8 +130,8 @@ public class GraphicsDriverEnableAngleAsSystemDriverController protected void onDeveloperOptionsSwitchEnabled() { // only enable the switch if ro.gfx.angle.supported is true // we use ro.gfx.angle.supported to indicate if ANGLE libs are installed under /vendor - final boolean isAngleSupported = - TextUtils.equals(SystemProperties.get(PROPERTY_RO_GFX_ANGLE_SUPPORTED), "true"); + final boolean isAngleSupported = TextUtils + .equals(mSystemProperties.get(PROPERTY_RO_GFX_ANGLE_SUPPORTED, ""), "true"); ((SwitchPreference) mPreference).setEnabled(isAngleSupported); } @@ -116,7 +148,8 @@ public class GraphicsDriverEnableAngleAsSystemDriverController @Override public void onRebootCancelled() { // if user presses button "Cancel", do not reboot the device, and toggles switch back - final String currentGlesDriver = SystemProperties.get(PROPERTY_PERSISTENT_GRAPHICS_EGL); + final String currentGlesDriver = + mSystemProperties.get(PROPERTY_PERSISTENT_GRAPHICS_EGL, ""); if (TextUtils.equals(ANGLE_DRIVER_SUFFIX, currentGlesDriver)) { // if persist.graphics.egl = "angle", set the property value back to "" GraphicsEnvironment.getInstance().toggleAngleAsSystemDriver(false); diff --git a/src/com/android/settings/development/graphicsdriver/GraphicsDriverSystemPropertiesWrapper.java b/src/com/android/settings/development/graphicsdriver/GraphicsDriverSystemPropertiesWrapper.java new file mode 100644 index 00000000000..549cd81c565 --- /dev/null +++ b/src/com/android/settings/development/graphicsdriver/GraphicsDriverSystemPropertiesWrapper.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2023 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.development.graphicsdriver; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.SystemProperties; +/** + * Wrapper interface to access {@link SystemProperties}. + * + * @hide + */ +interface GraphicsDriverSystemPropertiesWrapper { + /** + * Get the String value for the given {@code key}. + * + * @param key the key to lookup + * @param def the default value in case the property is not set or empty + * @return if the {@code key} isn't found, return {@code def} if it isn't null, or an empty + * string otherwise + */ + @NonNull + String get(@NonNull String key, @Nullable String def); + /** + * Set the value for the given {@code key} to {@code val}. + * + * @throws IllegalArgumentException if the {@code val} exceeds 91 characters + * @throws RuntimeException if the property cannot be set, for example, if it was blocked by + * SELinux. libc will log the underlying reason. + */ + void set(@NonNull String key, @Nullable String val); +} diff --git a/src/com/android/settings/fuelgauge/BatteryInfo.java b/src/com/android/settings/fuelgauge/BatteryInfo.java index 98240447aeb..300db23007c 100644 --- a/src/com/android/settings/fuelgauge/BatteryInfo.java +++ b/src/com/android/settings/fuelgauge/BatteryInfo.java @@ -322,18 +322,11 @@ public class BatteryInfo { final long drainTimeUs = PowerUtil.convertMsToUs(estimate.getEstimateMillis()); if (drainTimeUs > 0) { info.remainingTimeUs = drainTimeUs; - info.remainingLabel = PowerUtil.getBatteryRemainingStringFormatted( + info.remainingLabel = PowerUtil.getBatteryRemainingShortStringFormatted( context, - PowerUtil.convertUsToMs(drainTimeUs), - null /* percentageString */, - false /* basedOnUsage */ - ); - info.chargeLabel = PowerUtil.getBatteryRemainingStringFormatted( - context, - PowerUtil.convertUsToMs(drainTimeUs), - info.batteryPercentString, - estimate.isBasedOnUsage() && !shortString + PowerUtil.convertUsToMs(drainTimeUs) ); + info.chargeLabel = info.remainingLabel; info.suggestionLabel = PowerUtil.getBatteryTipStringFormatted( context, PowerUtil.convertUsToMs(drainTimeUs)); } else { diff --git a/src/com/android/settings/homepage/SettingsHomepageActivity.java b/src/com/android/settings/homepage/SettingsHomepageActivity.java index 03bc1b32fb2..829a89c6f03 100644 --- a/src/com/android/settings/homepage/SettingsHomepageActivity.java +++ b/src/com/android/settings/homepage/SettingsHomepageActivity.java @@ -57,7 +57,6 @@ import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentTransaction; -import androidx.window.embedding.ActivityEmbeddingController; import androidx.window.embedding.SplitRule; import com.android.settings.R; @@ -108,7 +107,6 @@ public class SettingsHomepageActivity extends FragmentActivity implements private View mTwoPaneSuggestionView; private CategoryMixin mCategoryMixin; private Set mLoadedListeners; - private ActivityEmbeddingController mActivityEmbeddingController; private boolean mIsEmbeddingActivityEnabled; private boolean mIsTwoPane; // A regular layout shows icons on homepage, whereas a simplified layout doesn't. @@ -200,8 +198,7 @@ public class SettingsHomepageActivity extends FragmentActivity implements setupEdgeToEdge(); setContentView(R.layout.settings_homepage_container); - mActivityEmbeddingController = ActivityEmbeddingController.getInstance(this); - mIsTwoPane = mActivityEmbeddingController.isActivityEmbedded(this); + mIsTwoPane = ActivityEmbeddingUtils.isAlreadyEmbedded(this); updateAppBarMinHeight(); initHomepageContainer(); @@ -242,7 +239,7 @@ public class SettingsHomepageActivity extends FragmentActivity implements // Settings app may be launched on an existing task. Reset SplitPairRule of SubSettings here // to prevent SplitPairRule of an existing task applied on a new started Settings app. - if (ActivityEmbeddingUtils.isEmbeddingActivityEnabled(this) + if (mIsEmbeddingActivityEnabled && (getIntent().getFlags() & Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0) { initSplitPairRules(); } @@ -284,7 +281,7 @@ public class SettingsHomepageActivity extends FragmentActivity implements @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); - final boolean newTwoPaneState = mActivityEmbeddingController.isActivityEmbedded(this); + final boolean newTwoPaneState = ActivityEmbeddingUtils.isAlreadyEmbedded(this); if (mIsTwoPane != newTwoPaneState) { mIsTwoPane = newTwoPaneState; updateHomepageAppBar(); @@ -427,8 +424,9 @@ public class SettingsHomepageActivity extends FragmentActivity implements } private boolean shouldLaunchDeepLinkIntentToRight() { - if (!FeatureFlagUtils.isEnabled(this, FeatureFlagUtils.SETTINGS_SUPPORT_LARGE_SCREEN) - || !ActivityEmbeddingUtils.isSettingsSplitEnabled(this)) { + if (!ActivityEmbeddingUtils.isSettingsSplitEnabled(this) + || !FeatureFlagUtils.isEnabled(this, + FeatureFlagUtils.SETTINGS_SUPPORT_LARGE_SCREEN)) { return false; } diff --git a/src/com/android/settings/localepicker/LocaleDialogFragment.java b/src/com/android/settings/localepicker/LocaleDialogFragment.java index 2dc09bd9ee0..ad9e10f19ed 100644 --- a/src/com/android/settings/localepicker/LocaleDialogFragment.java +++ b/src/com/android/settings/localepicker/LocaleDialogFragment.java @@ -21,8 +21,8 @@ import android.app.Dialog; import android.app.settings.SettingsEnums; import android.content.Context; import android.content.DialogInterface; +import android.content.Intent; import android.os.Bundle; -import android.os.ResultReceiver; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -35,7 +35,6 @@ import androidx.fragment.app.FragmentManager; import com.android.internal.app.LocaleStore; import com.android.settings.R; -import com.android.settings.RestrictedSettingsFragment; import com.android.settings.core.instrumentation.InstrumentedDialogFragment; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; @@ -46,49 +45,19 @@ import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; public class LocaleDialogFragment extends InstrumentedDialogFragment { private static final String TAG = LocaleDialogFragment.class.getSimpleName(); - static final int DIALOG_CONFIRM_SYSTEM_DEFAULT = 0; - static final int DIALOG_NOT_AVAILABLE_LOCALE = 1; + static final int DIALOG_CONFIRM_SYSTEM_DEFAULT = 1; + static final int DIALOG_NOT_AVAILABLE_LOCALE = 2; static final String ARG_DIALOG_TYPE = "arg_dialog_type"; static final String ARG_TARGET_LOCALE = "arg_target_locale"; - static final String ARG_RESULT_RECEIVER = "arg_result_receiver"; + static final String ARG_SHOW_DIALOG = "arg_show_dialog"; + + private boolean mShouldKeepDialog; public static LocaleDialogFragment newInstance() { return new LocaleDialogFragment(); } - /** - * Show dialog - */ - public void show( - @NonNull RestrictedSettingsFragment fragment, - int dialogType, - LocaleStore.LocaleInfo localeInfo) { - if (!isAdded()) { - return; - } - show(fragment, dialogType, localeInfo, null); - } - - /** - * Show dialog - */ - public void show( - @NonNull RestrictedSettingsFragment fragment, - int dialogType, - LocaleStore.LocaleInfo localeInfo, - ResultReceiver resultReceiver) { - FragmentManager manager = fragment.getChildFragmentManager(); - Bundle args = new Bundle(); - args.putInt(ARG_DIALOG_TYPE, dialogType); - args.putSerializable(ARG_TARGET_LOCALE, localeInfo); - args.putParcelable(ARG_RESULT_RECEIVER, resultReceiver); - - LocaleDialogFragment localeDialogFragment = new LocaleDialogFragment(); - localeDialogFragment.setArguments(args); - localeDialogFragment.show(manager, TAG); - } - @Override public int getMetricsCategory() { int dialogType = getArguments().getInt(ARG_DIALOG_TYPE); @@ -102,9 +71,29 @@ public class LocaleDialogFragment extends InstrumentedDialogFragment { } } + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putBoolean(ARG_SHOW_DIALOG, mShouldKeepDialog); + } + @Override public Dialog onCreateDialog(Bundle savedInstanceState) { - LocaleDialogController controller = new LocaleDialogController(this); + if (savedInstanceState != null) { + Bundle arguments = getArguments(); + int type = arguments.getInt(ARG_DIALOG_TYPE); + mShouldKeepDialog = savedInstanceState.getBoolean(ARG_SHOW_DIALOG, false); + // Keep the dialog if user rotates the device, otherwise close the confirm system + // default dialog only when user changes the locale. + if (type == DIALOG_CONFIRM_SYSTEM_DEFAULT && !mShouldKeepDialog) { + dismiss(); + } + } + + mShouldKeepDialog = true; + LocaleListEditor parentFragment = (LocaleListEditor) getParentFragment(); + LocaleDialogController controller = getLocaleDialogController(getContext(), this, + parentFragment); LocaleDialogController.DialogContent dialogContent = controller.getDialogContent(); ViewGroup viewGroup = (ViewGroup) LayoutInflater.from(getContext()).inflate( R.layout.locale_dialog, null); @@ -140,47 +129,57 @@ public class LocaleDialogFragment extends InstrumentedDialogFragment { textView.setText(content); } - static class LocaleDialogController implements DialogInterface.OnClickListener { + @VisibleForTesting + LocaleDialogController getLocaleDialogController(Context context, + LocaleDialogFragment dialogFragment, LocaleListEditor parentFragment) { + return new LocaleDialogController(context, dialogFragment, parentFragment); + } + + class LocaleDialogController implements DialogInterface.OnClickListener { private final Context mContext; private final int mDialogType; private final LocaleStore.LocaleInfo mLocaleInfo; - private final ResultReceiver mResultReceiver; private final MetricsFeatureProvider mMetricsFeatureProvider; + private LocaleListEditor mParent; + LocaleDialogController( - @NonNull Context context, @NonNull LocaleDialogFragment dialogFragment) { + @NonNull Context context, @NonNull LocaleDialogFragment dialogFragment, + LocaleListEditor parentFragment) { mContext = context; Bundle arguments = dialogFragment.getArguments(); mDialogType = arguments.getInt(ARG_DIALOG_TYPE); - mLocaleInfo = (LocaleStore.LocaleInfo) arguments.getSerializable( - ARG_TARGET_LOCALE); - mResultReceiver = (ResultReceiver) arguments.getParcelable(ARG_RESULT_RECEIVER); + mLocaleInfo = (LocaleStore.LocaleInfo) arguments.getSerializable(ARG_TARGET_LOCALE); mMetricsFeatureProvider = FeatureFactory.getFactory( mContext).getMetricsFeatureProvider(); + mParent = parentFragment; } - LocaleDialogController(@NonNull LocaleDialogFragment dialogFragment) { - this(dialogFragment.getContext(), dialogFragment); + LocaleDialogController(@NonNull LocaleDialogFragment dialogFragment, + LocaleListEditor parent) { + this(dialogFragment.getContext(), dialogFragment, parent); } @Override public void onClick(DialogInterface dialog, int which) { - if (mResultReceiver != null && mDialogType == DIALOG_CONFIRM_SYSTEM_DEFAULT) { + if (mDialogType == DIALOG_CONFIRM_SYSTEM_DEFAULT) { + int result = Activity.RESULT_CANCELED; + if (which == DialogInterface.BUTTON_POSITIVE) { + result = Activity.RESULT_OK; + } + Intent intent = new Intent(); Bundle bundle = new Bundle(); bundle.putInt(ARG_DIALOG_TYPE, DIALOG_CONFIRM_SYSTEM_DEFAULT); - if (which == DialogInterface.BUTTON_POSITIVE) { - mResultReceiver.send(Activity.RESULT_OK, bundle); - } else if (which == DialogInterface.BUTTON_NEGATIVE) { - mResultReceiver.send(Activity.RESULT_CANCELED, bundle); - } + intent.putExtras(bundle); + mParent.onActivityResult(DIALOG_CONFIRM_SYSTEM_DEFAULT, result, intent); mMetricsFeatureProvider.action(mContext, SettingsEnums.ACTION_CHANGE_LANGUAGE); } + mShouldKeepDialog = false; } @VisibleForTesting DialogContent getDialogContent() { - DialogContent - dialogContent = new DialogContent(); + DialogContent dialogContent = new DialogContent(); switch (mDialogType) { case DIALOG_CONFIRM_SYSTEM_DEFAULT: dialogContent.mTitle = String.format(mContext.getString( diff --git a/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java b/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java index 2223db06562..edd302645e2 100644 --- a/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java +++ b/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java @@ -16,14 +16,11 @@ package com.android.settings.localepicker; -import android.app.Activity; import android.content.Context; import android.graphics.Canvas; import android.os.Bundle; -import android.os.Handler; import android.os.LocaleList; -import android.os.Looper; -import android.os.ResultReceiver; +import android.text.TextUtils; import android.util.Log; import android.util.TypedValue; import android.view.LayoutInflater; @@ -52,15 +49,20 @@ class LocaleDragAndDropAdapter private static final String TAG = "LocaleDragAndDropAdapter"; private static final String CFGKEY_SELECTED_LOCALES = "selectedLocales"; + private static final String CFGKEY_DRAG_LOCALE = "dragLocales"; + private static final String CFGKEY_DRAG_LOCALES_TO_POSITION = "dragLocales_end"; + private final Context mContext; + private final ItemTouchHelper mItemTouchHelper; + private List mFeedItemList; private List mCacheItemList; - private final ItemTouchHelper mItemTouchHelper; private RecyclerView mParentView = null; private LocaleListEditor mParent; private boolean mRemoveMode = false; private boolean mDragEnabled = true; private NumberFormat mNumberFormatter = NumberFormat.getNumberInstance(); + private LocaleStore.LocaleInfo mDragLocale; class CustomViewHolder extends RecyclerView.ViewHolder implements View.OnTouchListener { private final LocaleDragCell mLocaleDragCell; @@ -87,8 +89,7 @@ class LocaleDragAndDropAdapter } } - LocaleDragAndDropAdapter(LocaleListEditor parent, - List feedItemList) { + LocaleDragAndDropAdapter(LocaleListEditor parent, List feedItemList) { mFeedItemList = feedItemList; mParent = parent; mCacheItemList = new ArrayList<>(feedItemList); @@ -202,6 +203,7 @@ class LocaleDragAndDropAdapter final LocaleStore.LocaleInfo saved = mFeedItemList.get(fromPosition); mFeedItemList.remove(fromPosition); mFeedItemList.add(toPosition, saved); + mDragLocale = saved; } else { // TODO: It looks like sometimes the RecycleView tries to swap item -1 // I did not see it in a while, but if it happens, investigate and file a bug. @@ -317,43 +319,20 @@ class LocaleDragAndDropAdapter }); } - public void doTheUpdateWithMovingLocaleItem() { - LocaleStore.LocaleInfo localeInfo = mFeedItemList.get(0); - final LocaleDialogFragment fragment = LocaleDialogFragment.newInstance(); - if (!localeInfo.getLocale().equals(LocalePicker.getLocales().get(0))) { - fragment.show(mParent, - LocaleDialogFragment.DIALOG_CONFIRM_SYSTEM_DEFAULT, - localeInfo, - new ResultReceiver(new Handler(Looper.getMainLooper())) { - @Override - protected void onReceiveResult(int resultCode, Bundle resultData) { - super.onReceiveResult(resultCode, resultData); - int type = resultData.getInt(LocaleDialogFragment.ARG_DIALOG_TYPE); - if (type == LocaleDialogFragment.DIALOG_CONFIRM_SYSTEM_DEFAULT) { - if (resultCode == Activity.RESULT_OK) { - doTheUpdate(); - if (!localeInfo.isTranslated()) { - fragment.show(mParent, - LocaleDialogFragment - .DIALOG_NOT_AVAILABLE_LOCALE, - localeInfo); - } - } else { - if (!localeInfo.getLocale() - .equals(mCacheItemList.get(0).getLocale())) { - mFeedItemList = new ArrayList<>(mCacheItemList); - notifyDataSetChanged(); - } - } - mCacheItemList = new ArrayList<>(mFeedItemList); - } - } - }); - } else { - doTheUpdate(); + public void notifyListChanged(LocaleStore.LocaleInfo localeInfo) { + if (!localeInfo.getLocale().equals(mCacheItemList.get(0).getLocale())) { + mFeedItemList = new ArrayList<>(mCacheItemList); + notifyDataSetChanged(); } } + public void setCacheItemList() { + mCacheItemList = new ArrayList<>(mFeedItemList); + } + + public List getFeedItemList() { + return mFeedItemList; + } private void setDragEnabled(boolean enabled) { mDragEnabled = enabled; } @@ -373,6 +352,8 @@ class LocaleDragAndDropAdapter } } outInstanceState.putStringArrayList(CFGKEY_SELECTED_LOCALES, selectedLocales); + // Save the dragged locale before rotation + outInstanceState.putSerializable(CFGKEY_DRAG_LOCALE, mDragLocale); } } @@ -381,18 +362,30 @@ class LocaleDragAndDropAdapter * (for instance when the device is rotated) * * @param savedInstanceState Bundle with the data saved by {@link #saveState(Bundle)} + * @param isDialogShowing A flag indicating whether the dialog is showing or not. */ - public void restoreState(Bundle savedInstanceState) { - if (savedInstanceState != null && mRemoveMode) { - final ArrayList selectedLocales = - savedInstanceState.getStringArrayList(CFGKEY_SELECTED_LOCALES); - if (selectedLocales == null || selectedLocales.isEmpty()) { - return; + public void restoreState(Bundle savedInstanceState, boolean isDialogShowing) { + if (savedInstanceState != null) { + if (mRemoveMode) { + final ArrayList selectedLocales = + savedInstanceState.getStringArrayList(CFGKEY_SELECTED_LOCALES); + if (selectedLocales == null || selectedLocales.isEmpty()) { + return; + } + for (LocaleStore.LocaleInfo li : mFeedItemList) { + li.setChecked(selectedLocales.contains(li.getId())); + } + notifyItemRangeChanged(0, mFeedItemList.size()); + } else if (isDialogShowing) { + // After rotation, the dragged position will be restored to original. Restore the + // drag locale's original position to the top. + mDragLocale = (LocaleStore.LocaleInfo) savedInstanceState.getSerializable( + CFGKEY_DRAG_LOCALE); + mFeedItemList.removeIf( + localeInfo -> TextUtils.equals(localeInfo.getId(), mDragLocale.getId())); + mFeedItemList.add(0, mDragLocale); + notifyItemRangeChanged(0, mFeedItemList.size()); } - for (LocaleStore.LocaleInfo li : mFeedItemList) { - li.setChecked(selectedLocales.contains(li.getId())); - } - notifyItemRangeChanged(0, mFeedItemList.size()); } } } diff --git a/src/com/android/settings/localepicker/LocaleListEditor.java b/src/com/android/settings/localepicker/LocaleListEditor.java index 6317f24a304..7ec08f7300e 100644 --- a/src/com/android/settings/localepicker/LocaleListEditor.java +++ b/src/com/android/settings/localepicker/LocaleListEditor.java @@ -18,6 +18,8 @@ package com.android.settings.localepicker; import static android.os.UserManager.DISALLOW_CONFIG_LOCALE; +import static com.android.settings.localepicker.LocaleDialogFragment.DIALOG_CONFIRM_SYSTEM_DEFAULT; + import android.app.Activity; import android.app.settings.SettingsEnums; import android.content.Context; @@ -32,12 +34,14 @@ import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; +import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import androidx.annotation.VisibleForTesting; import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.FragmentManager; import androidx.preference.PreferenceScreen; import androidx.recyclerview.widget.RecyclerView; @@ -60,7 +64,7 @@ import java.util.Locale; * Drag-and-drop editor for the user-ordered locale lists. */ @SearchIndexable -public class LocaleListEditor extends RestrictedSettingsFragment { +public class LocaleListEditor extends RestrictedSettingsFragment implements View.OnTouchListener { protected static final String INTENT_LOCALE_KEY = "localeInfo"; private static final String CFGKEY_REMOVE_MODE = "localeRemoveMode"; @@ -70,6 +74,8 @@ public class LocaleListEditor extends RestrictedSettingsFragment { private static final String INDEX_KEY_ADD_LANGUAGE = "add_language"; private static final String KEY_LANGUAGES_PICKER = "languages_picker"; + private static final String TAG_DIALOG_CONFIRM_SYSTEM_DEFAULT = "dialog_confirm_system_default"; + private static final String TAG_DIALOG_NOT_AVAILABLE = "dialog_not_available_locale"; private LocaleDragAndDropAdapter mAdapter; private Menu mMenu; @@ -80,6 +86,7 @@ public class LocaleListEditor extends RestrictedSettingsFragment { private LayoutPreference mLocalePickerPreference; private LocaleHelperPreferenceController mLocaleHelperPreferenceController; + private FragmentManager mFragmentManager; public LocaleListEditor() { super(DISALLOW_CONFIG_LOCALE); @@ -106,6 +113,7 @@ public class LocaleListEditor extends RestrictedSettingsFragment { LocaleStore.fillCache(this.getContext()); final List feedsList = getUserLocaleList(); mAdapter = new LocaleDragAndDropAdapter(this, feedsList); + mFragmentManager = getChildFragmentManager(); } @Override @@ -141,7 +149,15 @@ public class LocaleListEditor extends RestrictedSettingsFragment { mShowingRemoveDialog = savedInstanceState.getBoolean(CFGKEY_REMOVE_DIALOG, false); } setRemoveMode(mRemoveMode); - mAdapter.restoreState(savedInstanceState); + + final LocaleDialogFragment dialogFragment = + (LocaleDialogFragment) mFragmentManager.findFragmentByTag( + TAG_DIALOG_CONFIRM_SYSTEM_DEFAULT); + boolean isDialogShowing = false; + if (dialogFragment != null && dialogFragment.isAdded()) { + isDialogShowing = true; + } + mAdapter.restoreState(savedInstanceState, isDialogShowing); if (mShowingRemoveDialog) { showRemoveLocaleWarningDialog(); @@ -178,17 +194,32 @@ public class LocaleListEditor extends RestrictedSettingsFragment { @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { + LocaleStore.LocaleInfo localeInfo; if (requestCode == REQUEST_LOCALE_PICKER && resultCode == Activity.RESULT_OK && data != null) { - final LocaleStore.LocaleInfo localeInfo = - (LocaleStore.LocaleInfo) data.getSerializableExtra( - INTENT_LOCALE_KEY); - + localeInfo = (LocaleStore.LocaleInfo) data.getSerializableExtra(INTENT_LOCALE_KEY); String preferencesTags = Settings.System.getString( getContext().getContentResolver(), Settings.System.LOCALE_PREFERENCES); mAdapter.addLocale(mayAppendUnicodeTags(localeInfo, preferencesTags)); updateVisibilityOfRemoveMenu(); + } else if (requestCode == DIALOG_CONFIRM_SYSTEM_DEFAULT) { + localeInfo = mAdapter.getFeedItemList().get(0); + if (resultCode == Activity.RESULT_OK) { + mAdapter.doTheUpdate(); + if (!localeInfo.isTranslated()) { + Bundle args = new Bundle(); + args.putInt(LocaleDialogFragment.ARG_DIALOG_TYPE, + LocaleDialogFragment.DIALOG_NOT_AVAILABLE_LOCALE); + args.putSerializable(LocaleDialogFragment.ARG_TARGET_LOCALE, localeInfo); + LocaleDialogFragment localeDialogFragment = LocaleDialogFragment.newInstance(); + localeDialogFragment.setArguments(args); + localeDialogFragment.show(mFragmentManager, TAG_DIALOG_NOT_AVAILABLE); + } + } else { + mAdapter.notifyListChanged(localeInfo); + } + mAdapter.setCacheItemList(); } super.onActivityResult(requestCode, resultCode, data); } @@ -332,6 +363,7 @@ public class LocaleListEditor extends RestrictedSettingsFragment { list.setNestedScrollingEnabled(false); mAdapter.setRecyclerView(list); list.setAdapter(mAdapter); + list.setOnTouchListener(this); mAddLanguage = layout.findViewById(R.id.add_language); mAddLanguage.setOnClickListener(new View.OnClickListener() { @@ -348,6 +380,26 @@ public class LocaleListEditor extends RestrictedSettingsFragment { }); } + @Override + public boolean onTouch(View v, MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_UP + || event.getAction() == MotionEvent.ACTION_CANCEL) { + LocaleStore.LocaleInfo localeInfo = mAdapter.getFeedItemList().get(0); + if (!localeInfo.getLocale().equals(LocalePicker.getLocales().get(0))) { + final LocaleDialogFragment localeDialogFragment = + LocaleDialogFragment.newInstance(); + Bundle args = new Bundle(); + args.putInt(LocaleDialogFragment.ARG_DIALOG_TYPE, DIALOG_CONFIRM_SYSTEM_DEFAULT); + args.putSerializable(LocaleDialogFragment.ARG_TARGET_LOCALE, localeInfo); + localeDialogFragment.setArguments(args); + localeDialogFragment.show(mFragmentManager, TAG_DIALOG_CONFIRM_SYSTEM_DEFAULT); + } else { + mAdapter.doTheUpdate(); + } + } + return false; + } + // Hide the "Remove" menu if there is only one locale in the list, show it otherwise // This is called when the menu is first created, and then one add / remove locale private void updateVisibilityOfRemoveMenu() { diff --git a/src/com/android/settings/localepicker/LocaleRecyclerView.java b/src/com/android/settings/localepicker/LocaleRecyclerView.java index 5d469bf7f10..4a5f28b8855 100644 --- a/src/com/android/settings/localepicker/LocaleRecyclerView.java +++ b/src/com/android/settings/localepicker/LocaleRecyclerView.java @@ -34,15 +34,4 @@ class LocaleRecyclerView extends RecyclerView { public LocaleRecyclerView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } - - @Override - public boolean onTouchEvent(MotionEvent e) { - if (e.getAction() == MotionEvent.ACTION_UP || e.getAction() == MotionEvent.ACTION_CANCEL) { - LocaleDragAndDropAdapter adapter = (LocaleDragAndDropAdapter) this.getAdapter(); - if (adapter != null) { - adapter.doTheUpdateWithMovingLocaleItem(); - } - } - return super.onTouchEvent(e); - } } diff --git a/src/com/android/settings/network/tether/TetherSettings.java b/src/com/android/settings/network/tether/TetherSettings.java index ba19d1c129b..9fa8730cf10 100644 --- a/src/com/android/settings/network/tether/TetherSettings.java +++ b/src/com/android/settings/network/tether/TetherSettings.java @@ -171,6 +171,8 @@ public class TetherSettings extends RestrictedSettingsFragment return; } + setupTetherPreference(); + final Activity activity = getActivity(); BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); if (adapter != null) { @@ -184,7 +186,6 @@ public class TetherSettings extends RestrictedSettingsFragment new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)); } - setupTetherPreference(); setTopIntroPreferenceTitle(); mDataSaverBackend.addListener(this); @@ -605,6 +606,7 @@ public class TetherSettings extends RestrictedSettingsFragment public void onServiceConnected(int profile, BluetoothProfile proxy) { if (mBluetoothPan.get() == null) { mBluetoothPan.set((BluetoothPan) proxy); + updateBluetoothState(); } } diff --git a/src/com/android/settings/slices/SlicePreferenceController.java b/src/com/android/settings/slices/SlicePreferenceController.java index df28304f738..eb10bd42be1 100644 --- a/src/com/android/settings/slices/SlicePreferenceController.java +++ b/src/com/android/settings/slices/SlicePreferenceController.java @@ -44,6 +44,7 @@ public class SlicePreferenceController extends BasePreferenceController implemen LiveData mLiveData; @VisibleForTesting SlicePreference mSlicePreference; + private boolean mIsObservering = false; private Uri mUri; public SlicePreferenceController(Context context, String preferenceKey) { @@ -68,25 +69,31 @@ public class SlicePreferenceController extends BasePreferenceController implemen }); //TODO(b/120803703): figure out why we need to remove observer first - mLiveData.removeObserver(this); + removeLiveDataObserver(); } @Override public void onStart() { - if (mLiveData != null) { + if (mLiveData != null && !mIsObservering) { + mIsObservering = true; mLiveData.observeForever(this); } } @Override public void onStop() { - if (mLiveData != null) { - mLiveData.removeObserver(this); - } + removeLiveDataObserver(); } @Override public void onChanged(Slice slice) { mSlicePreference.onSliceUpdated(slice); } + + private void removeLiveDataObserver() { + if (mLiveData != null && mIsObservering && mLiveData.hasActiveObservers()) { + mIsObservering = false; + mLiveData.removeObserver(this); + } + } } diff --git a/src/com/android/settings/users/UserSettings.java b/src/com/android/settings/users/UserSettings.java index d904ed0f55c..28e02ec16e2 100644 --- a/src/com/android/settings/users/UserSettings.java +++ b/src/com/android/settings/users/UserSettings.java @@ -28,7 +28,6 @@ import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; -import android.content.SharedPreferences; import android.content.pm.UserInfo; import android.content.res.Resources; import android.graphics.Bitmap; @@ -83,6 +82,7 @@ import com.android.settingslib.RestrictedPreference; import com.android.settingslib.drawable.CircleFramedDrawable; import com.android.settingslib.search.SearchIndexable; import com.android.settingslib.search.SearchIndexableRaw; +import com.android.settingslib.users.CreateUserDialogController; import com.android.settingslib.users.EditUserInfoController; import com.android.settingslib.users.GrantAdminDialogController; import com.android.settingslib.users.UserCreatingDialog; @@ -119,6 +119,7 @@ public class UserSettings extends SettingsPreferenceFragment /** UserId of the user being removed */ private static final String SAVE_REMOVING_USER = "removing_user"; + private static final String SAVE_CREATE_USER = "create_user"; private static final String KEY_USER_LIST = "user_list"; private static final String KEY_USER_ME = "user_me"; @@ -171,9 +172,6 @@ public class UserSettings extends SettingsPreferenceFragment static final int RESULT_GUEST_REMOVED = 100; - private static final String KEY_ADD_USER_LONG_MESSAGE_DISPLAYED = - "key_add_user_long_message_displayed"; - private static final String KEY_TITLE = "title"; private static final String KEY_SUMMARY = "summary"; @@ -222,6 +220,8 @@ public class UserSettings extends SettingsPreferenceFragment new GrantAdminDialogController(); private EditUserInfoController mEditUserInfoController = new EditUserInfoController(Utils.FILE_PROVIDER_AUTHORITY); + private CreateUserDialogController mCreateUserDialogController = + new CreateUserDialogController(Utils.FILE_PROVIDER_AUTHORITY); private AddUserWhenLockedPreferenceController mAddUserWhenLockedPreferenceController; private GuestTelephonyPreferenceController mGuestTelephonyPreferenceController; private RemoveGuestOnExitPreferenceController mRemoveGuestOnExitPreferenceController; @@ -233,7 +233,7 @@ public class UserSettings extends SettingsPreferenceFragment private CharSequence mPendingUserName; private Drawable mPendingUserIcon; - private boolean mGrantAdmin; + private boolean mPendingUserIsAdmin; // A place to cache the generated default avatar private Drawable mDefaultIconDrawable; @@ -348,7 +348,11 @@ public class UserSettings extends SettingsPreferenceFragment if (icicle.containsKey(SAVE_REMOVING_USER)) { mRemovingUserId = icicle.getInt(SAVE_REMOVING_USER); } - mEditUserInfoController.onRestoreInstanceState(icicle); + if (icicle.containsKey(SAVE_CREATE_USER)) { + mCreateUserDialogController.onRestoreInstanceState(icicle); + } else { + mEditUserInfoController.onRestoreInstanceState(icicle); + } } mUserCaps = UserCapabilities.create(activity); @@ -440,7 +444,12 @@ public class UserSettings extends SettingsPreferenceFragment @Override public void onSaveInstanceState(Bundle outState) { - mEditUserInfoController.onSaveInstanceState(outState); + if (mCreateUserDialogController.isActive()) { + outState.putBoolean(SAVE_CREATE_USER, mCreateUserDialogController.isActive()); + mCreateUserDialogController.onSaveInstanceState(outState); + } else { + mEditUserInfoController.onSaveInstanceState(outState); + } outState.putInt(SAVE_REMOVING_USER, mRemovingUserId); super.onSaveInstanceState(outState); } @@ -448,6 +457,7 @@ public class UserSettings extends SettingsPreferenceFragment @Override public void startActivityForResult(Intent intent, int requestCode) { mEditUserInfoController.startingActivityForResult(); + mCreateUserDialogController.startingActivityForResult(); super.startActivityForResult(intent, requestCode); } @@ -562,6 +572,7 @@ public class UserSettings extends SettingsPreferenceFragment && resultCode == RESULT_GUEST_REMOVED) { scheduleGuestCreation(); } else { + mCreateUserDialogController.onActivityResult(requestCode, resultCode, data); mEditUserInfoController.onActivityResult(requestCode, resultCode, data); } } @@ -704,37 +715,12 @@ public class UserSettings extends SettingsPreferenceFragment .setPositiveButton(android.R.string.ok, null) .create(); case DIALOG_ADD_USER: { - final SharedPreferences preferences = getActivity().getPreferences( - Context.MODE_PRIVATE); - final boolean longMessageDisplayed = preferences.getBoolean( - KEY_ADD_USER_LONG_MESSAGE_DISPLAYED, false); - final int messageResId = longMessageDisplayed - ? com.android.settingslib.R.string.user_add_user_message_short - : com.android.settingslib.R.string.user_add_user_message_long; - Dialog dlg = new AlertDialog.Builder(context) - .setTitle(com.android.settingslib.R.string.user_add_user_title) - .setMessage(messageResId) - .setPositiveButton(android.R.string.ok, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - if (!longMessageDisplayed) { - preferences.edit().putBoolean( - KEY_ADD_USER_LONG_MESSAGE_DISPLAYED, - true).apply(); - } - if (UserManager.isMultipleAdminEnabled()) { - showDialog(DIALOG_GRANT_ADMIN); - } else { - showDialog(DIALOG_USER_PROFILE_EDITOR_ADD_USER); - } - } - }) - .setNegativeButton(android.R.string.cancel, null) - .create(); - return dlg; - } - case DIALOG_GRANT_ADMIN: { - return buildGrantAdminDialog(); + synchronized (mUserLock) { + mPendingUserName = getString( + com.android.settingslib.R.string.user_new_user_name); + mPendingUserIcon = null; + } + return buildAddUserDialog(USER_TYPE_USER); } case DIALOG_CHOOSE_USER_TYPE: { List> data = new ArrayList>(); @@ -919,17 +905,14 @@ public class UserSettings extends SettingsPreferenceFragment private Dialog buildAddUserDialog(int userType) { Dialog d; synchronized (mUserLock) { - d = mEditUserInfoController.createDialog( + d = mCreateUserDialogController.createDialog( getActivity(), this::startActivityForResult, - null, - mPendingUserName.toString(), - getString(userType == USER_TYPE_USER - ? com.android.settingslib.R.string.user_info_settings_title - : com.android.settingslib.R.string.profile_info_settings_title), - (userName, userIcon) -> { + UserManager.isMultipleAdminEnabled(), + (userName, userIcon, isAdmin) -> { mPendingUserIcon = userIcon; mPendingUserName = userName; + mPendingUserIsAdmin = isAdmin; addUserNow(userType); }, () -> { @@ -943,26 +926,6 @@ public class UserSettings extends SettingsPreferenceFragment return d; } - private Dialog buildGrantAdminDialog() { - return mGrantAdminDialogController.createDialog( - getActivity(), - (grantAdmin) -> { - mGrantAdmin = grantAdmin; - if (mGrantAdmin) { - mMetricsFeatureProvider.action(getActivity(), - SettingsEnums.ACTION_GRANT_ADMIN_FROM_SETTINGS_CREATION_DIALOG); - } else { - mMetricsFeatureProvider.action(getActivity(), - SettingsEnums.ACTION_NOT_GRANT_ADMIN_FROM_SETTINGS_CREATION_DIALOG); - } - showDialog(DIALOG_USER_PROFILE_EDITOR_ADD_USER); - }, - () -> { - mGrantAdmin = false; - } - ); - } - @Override public int getDialogMetricsCategory(int dialogId) { switch (dialogId) { @@ -1065,7 +1028,7 @@ public class UserSettings extends SettingsPreferenceFragment userName, mUserManager.USER_TYPE_FULL_SECONDARY, 0); - if (mGrantAdmin) { + if (mPendingUserIsAdmin) { mUserManager.setUserAdmin(user.id); } } else { @@ -1665,6 +1628,9 @@ public class UserSettings extends SettingsPreferenceFragment synchronized (mUserLock) { mRemovingUserId = -1; updateUserList(); + if (mCreateUserDialogController.isActive()) { + mCreateUserDialogController.clear(); + } } } diff --git a/tests/robotests/src/com/android/settings/accessibility/AccessibilityHearingAidPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/AccessibilityHearingAidPreferenceControllerTest.java index 5ee7ab3d13c..c68e90bdc17 100644 --- a/tests/robotests/src/com/android/settings/accessibility/AccessibilityHearingAidPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/AccessibilityHearingAidPreferenceControllerTest.java @@ -24,25 +24,22 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.app.Activity; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHapClient; import android.bluetooth.BluetoothHearingAid; import android.bluetooth.BluetoothProfile; import android.content.BroadcastReceiver; +import android.content.Context; import android.content.Intent; -import androidx.appcompat.app.AlertDialog; -import androidx.fragment.app.FragmentActivity; import androidx.preference.Preference; +import androidx.test.core.app.ApplicationProvider; import com.android.settings.R; import com.android.settings.bluetooth.Utils; -import com.android.settings.testutils.shadow.ShadowAlertDialogCompat; import com.android.settings.testutils.shadow.ShadowBluetoothAdapter; import com.android.settings.testutils.shadow.ShadowBluetoothUtils; -import com.android.settings.utils.ActivityControllerWrapper; import com.android.settingslib.bluetooth.BluetoothEventManager; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; @@ -55,11 +52,12 @@ import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; import org.junit.After; import org.junit.Before; import org.junit.Ignore; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.robolectric.Robolectric; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; import org.robolectric.shadow.api.Shadow; @@ -74,6 +72,9 @@ import java.util.Set; @RunWith(RobolectricTestRunner.class) @Config(shadows = {ShadowBluetoothAdapter.class, ShadowBluetoothUtils.class}) public class AccessibilityHearingAidPreferenceControllerTest { + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + private static final String TEST_DEVICE_ADDRESS = "00:A1:A1:A1:A1:A1"; private static final String TEST_DEVICE_ADDRESS_2 = "00:A2:A2:A2:A2:A2"; private static final String TEST_DEVICE_NAME = "TEST_HEARING_AID_BT_DEVICE_NAME"; @@ -82,7 +83,8 @@ public class AccessibilityHearingAidPreferenceControllerTest { private BluetoothAdapter mBluetoothAdapter; private ShadowBluetoothAdapter mShadowBluetoothAdapter; private BluetoothDevice mBluetoothDevice; - private Activity mContext; + private final Context mContext = ApplicationProvider.getApplicationContext(); + private Preference mHearingAidPreference; private AccessibilityHearingAidPreferenceController mPreferenceController; private ShadowApplication mShadowApplication; @@ -106,11 +108,7 @@ public class AccessibilityHearingAidPreferenceControllerTest { @Before public void setUp() { - MockitoAnnotations.initMocks(this); mShadowApplication = ShadowApplication.getInstance(); - - mContext = spy((Activity) ActivityControllerWrapper.setup( - Robolectric.buildActivity(Activity.class)).get()); setupEnvironment(); mHearingAidPreference = new Preference(mContext); @@ -274,65 +272,6 @@ public class AccessibilityHearingAidPreferenceControllerTest { verify(mPreferenceController).launchBluetoothDeviceDetailSetting(mCachedBluetoothDevice); } - @Test - public void onSupportHearingAidProfile_isAvailable() { - mShadowBluetoothAdapter.clearSupportedProfiles(); - mShadowBluetoothAdapter.addSupportedProfiles(BluetoothProfile.HEARING_AID); - mPreferenceController = new AccessibilityHearingAidPreferenceController(mContext, - HEARING_AID_PREFERENCE); - mPreferenceController.setPreference(mHearingAidPreference); - - assertThat(mPreferenceController.isAvailable()).isTrue(); - } - - @Test - public void onSupportHapClientProfile_isAvailable() { - mShadowBluetoothAdapter.clearSupportedProfiles(); - mShadowBluetoothAdapter.addSupportedProfiles(BluetoothProfile.HAP_CLIENT); - mPreferenceController = new AccessibilityHearingAidPreferenceController(mContext, - HEARING_AID_PREFERENCE); - mPreferenceController.setPreference(mHearingAidPreference); - - assertThat(mPreferenceController.isAvailable()).isTrue(); - } - - @Test - public void onNotSupportAnyHearingAidRelatedProfile_isNotAvailable() { - mShadowBluetoothAdapter.clearSupportedProfiles(); - mPreferenceController = new AccessibilityHearingAidPreferenceController(mContext, - HEARING_AID_PREFERENCE); - mPreferenceController.setPreference(mHearingAidPreference); - - assertThat(mPreferenceController.isAvailable()).isFalse(); - } - - @Test - public void getConnectedHearingAidDevice_doNotReturnSubDevice() { - when(mHearingAidProfile.getConnectedDevices()).thenReturn(generateHearingAidDeviceList()); - when(mLocalBluetoothManager.getCachedDeviceManager().isSubDevice(mBluetoothDevice)) - .thenReturn(true); - - assertThat(mPreferenceController.getConnectedHearingAidDevice()).isNull(); - } - - @Test - @Config(shadows = ShadowAlertDialogCompat.class) - public void onActiveDeviceChanged_hearingAidProfile_launchHearingAidPairingDialog() { - final FragmentActivity mActivity = Robolectric.setupActivity(FragmentActivity.class); - when(mCachedBluetoothDevice.isConnectedAshaHearingAidDevice()).thenReturn(true); - when(mCachedBluetoothDevice.getDeviceMode()).thenReturn( - HearingAidInfo.DeviceMode.MODE_BINAURAL); - when(mCachedBluetoothDevice.getDeviceSide()).thenReturn( - HearingAidInfo.DeviceSide.SIDE_LEFT); - mPreferenceController.setFragmentManager(mActivity.getSupportFragmentManager()); - - mPreferenceController.onActiveDeviceChanged(mCachedBluetoothDevice, - BluetoothProfile.HEARING_AID); - - final AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog(); - assertThat(dialog.isShowing()).isTrue(); - } - @Test public void onServiceConnected_onHearingAidProfileConnected_updateSummary() { when(mCachedBluetoothDevice.getDeviceSide()).thenReturn( diff --git a/tests/robotests/src/com/android/settings/accessibility/HearingAidAudioRoutingPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/HearingAidAudioRoutingPreferenceControllerTest.java new file mode 100644 index 00000000000..d16bc4351c3 --- /dev/null +++ b/tests/robotests/src/com/android/settings/accessibility/HearingAidAudioRoutingPreferenceControllerTest.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.accessibility; + +import static com.android.settings.core.BasePreferenceController.AVAILABLE; +import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.util.FeatureFlagUtils; + +import androidx.test.core.app.ApplicationProvider; + +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; + +/** Tests for {@link HearingAidAudioRoutingPreferenceController}. */ +@RunWith(RobolectricTestRunner.class) +public class HearingAidAudioRoutingPreferenceControllerTest { + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + private final Context mContext = ApplicationProvider.getApplicationContext(); + + private HearingAidAudioRoutingPreferenceController mController; + + @Before + public void setUp() { + mController = new HearingAidAudioRoutingPreferenceController(mContext, "test_key"); + } + + @Test + public void getAvailabilityStatus_audioRoutingNotSupported_returnUnsupported() { + FeatureFlagUtils.setEnabled(mContext, + FeatureFlagUtils.SETTINGS_AUDIO_ROUTING, false); + + assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE); + } + + @Test + public void getAvailabilityStatus_audioRoutingNotSupported_available() { + FeatureFlagUtils.setEnabled(mContext, + FeatureFlagUtils.SETTINGS_AUDIO_ROUTING, true); + + assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE); + } +} diff --git a/tests/robotests/src/com/android/settings/accessibility/HearingAidHelperTest.java b/tests/robotests/src/com/android/settings/accessibility/HearingAidHelperTest.java new file mode 100644 index 00000000000..194b766dd75 --- /dev/null +++ b/tests/robotests/src/com/android/settings/accessibility/HearingAidHelperTest.java @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2023 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 static org.mockito.Mockito.when; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; +import android.content.Context; + +import androidx.test.core.app.ApplicationProvider; + +import com.android.settings.bluetooth.Utils; +import com.android.settings.testutils.shadow.ShadowBluetoothAdapter; +import com.android.settings.testutils.shadow.ShadowBluetoothUtils; +import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; +import com.android.settingslib.bluetooth.HapClientProfile; +import com.android.settingslib.bluetooth.HearingAidProfile; +import com.android.settingslib.bluetooth.LocalBluetoothManager; +import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; +import org.robolectric.shadow.api.Shadow; + +import java.util.ArrayList; +import java.util.Collections; + +/** Tests for {@link HearingAidHelper}. */ +@RunWith(RobolectricTestRunner.class) +@Config(shadows = {ShadowBluetoothAdapter.class, ShadowBluetoothUtils.class}) +public class HearingAidHelperTest { + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + private final Context mContext = ApplicationProvider.getApplicationContext(); + private static final String TEST_DEVICE_ADDRESS = "00:A1:A1:A1:A1:A1"; + + @Mock + private LocalBluetoothManager mLocalBluetoothManager; + @Mock + private LocalBluetoothProfileManager mLocalBluetoothProfileManager; + @Mock + private CachedBluetoothDeviceManager mCachedDeviceManager; + @Mock + private CachedBluetoothDevice mCachedBluetoothDevice; + @Mock + private HearingAidProfile mHearingAidProfile; + @Mock + private HapClientProfile mHapClientProfile; + private ShadowBluetoothAdapter mShadowBluetoothAdapter; + private BluetoothAdapter mBluetoothAdapter; + private BluetoothDevice mBluetoothDevice; + private HearingAidHelper mHelper; + + @Before + public void setUp() { + ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBluetoothManager; + mLocalBluetoothManager = Utils.getLocalBtManager(mContext); + mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + mShadowBluetoothAdapter = Shadow.extract(mBluetoothAdapter); + mBluetoothDevice = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS); + when(mCachedBluetoothDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS); + when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(mCachedDeviceManager); + when(mCachedDeviceManager.findDevice(mBluetoothDevice)).thenReturn(mCachedBluetoothDevice); + when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalBluetoothProfileManager); + when(mLocalBluetoothProfileManager.getHearingAidProfile()).thenReturn(mHearingAidProfile); + when(mLocalBluetoothProfileManager.getHapClientProfile()).thenReturn(mHapClientProfile); + + mHelper = new HearingAidHelper(mContext); + } + + @Test + public void isHearingAidSupported_supported_returnTrue() { + mBluetoothAdapter.enable(); + mShadowBluetoothAdapter.clearSupportedProfiles(); + mShadowBluetoothAdapter.addSupportedProfiles(BluetoothProfile.HEARING_AID); + + assertThat(mHelper.isHearingAidSupported()).isTrue(); + } + + @Test + public void isHearingAidSupported_bluetoothOff_returnFalse() { + mShadowBluetoothAdapter.clearSupportedProfiles(); + mShadowBluetoothAdapter.addSupportedProfiles(BluetoothProfile.HEARING_AID); + mBluetoothAdapter.disable(); + + assertThat(mHelper.isHearingAidSupported()).isFalse(); + } + + + @Test + public void isAllHearingAidRelatedProfilesReady_allReady_returnTrue() { + when(mHearingAidProfile.isProfileReady()).thenReturn(true); + when(mHapClientProfile.isProfileReady()).thenReturn(true); + + assertThat(mHelper.isAllHearingAidRelatedProfilesReady()).isTrue(); + } + + @Test + public void isAllHearingAidRelatedProfilesReady_notFullReady_returnFalse() { + when(mHearingAidProfile.isProfileReady()).thenReturn(false); + when(mHapClientProfile.isProfileReady()).thenReturn(true); + + assertThat(mHelper.isAllHearingAidRelatedProfilesReady()).isFalse(); + } + + @Test + public void getConnectedHearingAidDeviceList_oneDeviceAdded_getOneDevice() { + mBluetoothAdapter.enable(); + mShadowBluetoothAdapter.clearSupportedProfiles(); + mShadowBluetoothAdapter.addSupportedProfiles(BluetoothProfile.HEARING_AID); + when(mHearingAidProfile.getConnectedDevices()).thenReturn(new ArrayList<>( + Collections.singletonList(mBluetoothDevice))); + + assertThat(mHelper.getConnectedHearingAidDeviceList().size()).isEqualTo(1); + } + + @Test + public void getConnectedHearingAidDeviceList_oneSubDeviceAdded_getZeroDevice() { + mBluetoothAdapter.enable(); + mShadowBluetoothAdapter.clearSupportedProfiles(); + mShadowBluetoothAdapter.addSupportedProfiles(BluetoothProfile.HEARING_AID); + when(mHearingAidProfile.getConnectedDevices()).thenReturn(new ArrayList<>( + Collections.singletonList(mBluetoothDevice))); + when(mLocalBluetoothManager.getCachedDeviceManager().isSubDevice( + mBluetoothDevice)).thenReturn(true); + + assertThat(mHelper.getConnectedHearingAidDeviceList().size()).isEqualTo(0); + } + + @Test + public void getConnectedHearingAidDevice_getExpectedCachedBluetoothDevice() { + mBluetoothAdapter.enable(); + mShadowBluetoothAdapter.clearSupportedProfiles(); + mShadowBluetoothAdapter.addSupportedProfiles(BluetoothProfile.HEARING_AID); + when(mHearingAidProfile.getConnectedDevices()).thenReturn(new ArrayList<>( + Collections.singletonList(mBluetoothDevice))); + + assertThat(mHelper.getConnectedHearingAidDevice()).isEqualTo(mCachedBluetoothDevice); + assertThat(mCachedBluetoothDevice.getAddress()).isEqualTo(mBluetoothDevice.getAddress()); + } +} diff --git a/tests/robotests/src/com/android/settings/bluetooth/HearingDeviceAudioRoutingBasePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/HearingDeviceAudioRoutingBasePreferenceControllerTest.java similarity index 70% rename from tests/robotests/src/com/android/settings/bluetooth/HearingDeviceAudioRoutingBasePreferenceControllerTest.java rename to tests/robotests/src/com/android/settings/accessibility/HearingDeviceAudioRoutingBasePreferenceControllerTest.java index 105da6546ad..4decf68d68c 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/HearingDeviceAudioRoutingBasePreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/HearingDeviceAudioRoutingBasePreferenceControllerTest.java @@ -14,13 +14,14 @@ * limitations under the License. */ -package com.android.settings.bluetooth; +package com.android.settings.accessibility; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -37,9 +38,15 @@ import androidx.preference.ListPreference; import androidx.test.core.app.ApplicationProvider; import com.android.settings.R; +import com.android.settings.bluetooth.Utils; +import com.android.settings.testutils.shadow.ShadowBluetoothAdapter; +import com.android.settings.testutils.shadow.ShadowBluetoothUtils; import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; import com.android.settingslib.bluetooth.HearingAidAudioRoutingConstants; import com.android.settingslib.bluetooth.HearingAidAudioRoutingHelper; +import com.android.settingslib.bluetooth.LocalBluetoothManager; +import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; import org.junit.Before; import org.junit.Rule; @@ -50,11 +57,13 @@ import org.mockito.Spy; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; import java.util.List; /** Tests for {@link HearingDeviceAudioRoutingBasePreferenceController}. */ @RunWith(RobolectricTestRunner.class) +@Config(shadows = {ShadowBluetoothAdapter.class, ShadowBluetoothUtils.class}) public class HearingDeviceAudioRoutingBasePreferenceControllerTest { @Rule @@ -63,8 +72,14 @@ public class HearingDeviceAudioRoutingBasePreferenceControllerTest { @Spy private final Context mContext = ApplicationProvider.getApplicationContext(); private static final String TEST_DEVICE_ADDRESS = "00:A1:A1:A1:A1:A1"; - private static final String FAKE_KEY = "fake_key"; + private final ListPreference mListPreference = new ListPreference(mContext); + @Mock + private LocalBluetoothManager mLocalBluetoothManager; + @Mock + private LocalBluetoothProfileManager mLocalBluetoothProfileManager; + @Mock + private CachedBluetoothDeviceManager mCachedDeviceManager; @Mock private AudioProductStrategy mAudioProductStrategyMedia; @Mock @@ -72,8 +87,10 @@ public class HearingDeviceAudioRoutingBasePreferenceControllerTest { @Mock private BluetoothDevice mBluetoothDevice; @Spy - private HearingAidAudioRoutingHelper mHelper = new HearingAidAudioRoutingHelper(mContext); - private final ListPreference mListPreference = new ListPreference(mContext); + private HearingAidAudioRoutingHelper mAudioRoutingHelper = + new HearingAidAudioRoutingHelper(mContext); + @Mock + private HearingAidHelper mHearingAidHelper; private TestHearingDeviceAudioRoutingBasePreferenceController mController; @Before @@ -83,19 +100,23 @@ public class HearingDeviceAudioRoutingBasePreferenceControllerTest { AudioDeviceInfo.TYPE_HEARING_AID, TEST_DEVICE_ADDRESS); + ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBluetoothManager; + mLocalBluetoothManager = Utils.getLocalBtManager(mContext); + when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(mCachedDeviceManager); + when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalBluetoothProfileManager); when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice); when(mBluetoothDevice.getAnonymizedAddress()).thenReturn(TEST_DEVICE_ADDRESS); when(mCachedBluetoothDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS); - when(mHelper.getMatchedHearingDeviceAttributes(any())).thenReturn(hearingDeviceAttribute); + doReturn(hearingDeviceAttribute).when( + mAudioRoutingHelper).getMatchedHearingDeviceAttributes(any()); when(mAudioProductStrategyMedia.getAudioAttributesForLegacyStreamType( - AudioManager.STREAM_MUSIC)) - .thenReturn((new AudioAttributes.Builder()).build()); - when(mHelper.getAudioProductStrategies()).thenReturn(List.of(mAudioProductStrategyMedia)); + AudioManager.STREAM_MUSIC)).thenReturn((new AudioAttributes.Builder()).build()); + when(mAudioRoutingHelper.getAudioProductStrategies()).thenReturn( + List.of(mAudioProductStrategyMedia)); - mController = new TestHearingDeviceAudioRoutingBasePreferenceController(mContext, FAKE_KEY, - mHelper); - TestHearingDeviceAudioRoutingBasePreferenceController.setupForTesting( - mCachedBluetoothDevice); + mController = new TestHearingDeviceAudioRoutingBasePreferenceController(mContext, + "test_key", + mAudioRoutingHelper, mHearingAidHelper); mListPreference.setEntries(R.array.bluetooth_audio_routing_titles); mListPreference.setEntryValues(R.array.bluetooth_audio_routing_values); mListPreference.setSummary("%s"); @@ -122,20 +143,21 @@ public class HearingDeviceAudioRoutingBasePreferenceControllerTest { @Test public void onPreferenceChange_noMatchedDeviceAttributes_notCallSetStrategies() { - when(mHelper.getMatchedHearingDeviceAttributes(any())).thenReturn(null); + when(mAudioRoutingHelper.getMatchedHearingDeviceAttributes(any())).thenReturn(null); - verify(mHelper, never()).setPreferredDeviceRoutingStrategies(any(), isNull(), anyInt()); + verify(mAudioRoutingHelper, never()).setPreferredDeviceRoutingStrategies(any(), isNull(), + anyInt()); } private static class TestHearingDeviceAudioRoutingBasePreferenceController extends HearingDeviceAudioRoutingBasePreferenceController { - private static CachedBluetoothDevice sCachedBluetoothDevice; private static int sSavedRoutingValue; TestHearingDeviceAudioRoutingBasePreferenceController(Context context, - String preferenceKey, HearingAidAudioRoutingHelper helper) { - super(context, preferenceKey, helper); + String preferenceKey, HearingAidAudioRoutingHelper audioRoutingHelper, + HearingAidHelper hearingAidHelper) { + super(context, preferenceKey, audioRoutingHelper, hearingAidHelper); } @Override @@ -143,11 +165,6 @@ public class HearingDeviceAudioRoutingBasePreferenceControllerTest { return new int[]{AudioAttributes.USAGE_MEDIA}; } - @Override - protected CachedBluetoothDevice getHearingDevice() { - return sCachedBluetoothDevice; - } - @Override protected void saveRoutingValue(Context context, int routingValue) { sSavedRoutingValue = routingValue; @@ -157,9 +174,5 @@ public class HearingDeviceAudioRoutingBasePreferenceControllerTest { protected int restoreRoutingValue(Context context) { return sSavedRoutingValue; } - - public static void setupForTesting(CachedBluetoothDevice cachedBluetoothDevice) { - sCachedBluetoothDevice = cachedBluetoothDevice; - } } } diff --git a/tests/robotests/src/com/android/settings/bluetooth/HearingDeviceCallRoutingPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/HearingDeviceCallRoutingPreferenceControllerTest.java similarity index 98% rename from tests/robotests/src/com/android/settings/bluetooth/HearingDeviceCallRoutingPreferenceControllerTest.java rename to tests/robotests/src/com/android/settings/accessibility/HearingDeviceCallRoutingPreferenceControllerTest.java index dec4cc4bb01..8eed2940984 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/HearingDeviceCallRoutingPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/HearingDeviceCallRoutingPreferenceControllerTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.settings.bluetooth; +package com.android.settings.accessibility; import static com.android.settings.core.BasePreferenceController.AVAILABLE; import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE; diff --git a/tests/robotests/src/com/android/settings/accounts/ContactSearchPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accounts/ContactSearchPreferenceControllerTest.java index c8606e134a5..bc65563650f 100644 --- a/tests/robotests/src/com/android/settings/accounts/ContactSearchPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/accounts/ContactSearchPreferenceControllerTest.java @@ -20,13 +20,19 @@ import static android.provider.Settings.Secure.MANAGED_PROFILE_CONTACT_REMOTE_SE import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.content.Context; +import android.content.pm.UserInfo; import android.os.UserHandle; +import android.os.UserManager; import android.provider.Settings; +import androidx.test.core.app.ApplicationProvider; + import com.android.settingslib.RestrictedSwitchPreference; import org.junit.Before; @@ -35,39 +41,51 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; + +import java.util.Collections; @RunWith(RobolectricTestRunner.class) public class ContactSearchPreferenceControllerTest { private static final String PREF_KEY = "contacts_search"; - - @Mock - private UserHandle mManagedUser; + private static final int MANAGED_USER_ID = 10; private Context mContext; private ContactSearchPreferenceController mController; private RestrictedSwitchPreference mPreference; + @Mock + private UserHandle mManagedUser; + @Mock + private UserManager mUserManager; + @Mock + private UserInfo mUserInfo; + @Before public void setUp() { MockitoAnnotations.initMocks(this); - mContext = RuntimeEnvironment.application; - mController = new ContactSearchPreferenceController(mContext, PREF_KEY); - mController.setManagedUser(mManagedUser); + mContext = spy(ApplicationProvider.getApplicationContext()); + when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager); mPreference = spy(new RestrictedSwitchPreference(mContext)); + when(mUserInfo.isManagedProfile()).thenReturn(true); + when(mUserManager.getUserInfo(anyInt())).thenReturn(mUserInfo); + when(mUserManager.getProcessUserId()).thenReturn(0); + when(mUserManager.getUserProfiles()).thenReturn(Collections.singletonList(mManagedUser)); + when(mManagedUser.getIdentifier()).thenReturn(MANAGED_USER_ID); + mController = new ContactSearchPreferenceController(mContext, PREF_KEY); } @Test public void getAvailabilityStatus_noManagedUser_DISABLED() { - mController.setManagedUser(null); + when(mUserManager.getProcessUserId()).thenReturn(MANAGED_USER_ID); + mController = new ContactSearchPreferenceController(mContext, PREF_KEY); + assertThat(mController.getAvailabilityStatus()) .isNotEqualTo(ContactSearchPreferenceController.AVAILABLE); } @Test public void getAvailabilityStatus_hasManagedUser_AVAILABLE() { - mController.setManagedUser(mManagedUser); assertThat(mController.getAvailabilityStatus()) .isEqualTo(ContactSearchPreferenceController.AVAILABLE); } @@ -75,32 +93,96 @@ public class ContactSearchPreferenceControllerTest { @Test public void updateState_shouldRefreshContent() { Settings.Secure.putIntForUser(mContext.getContentResolver(), - MANAGED_PROFILE_CONTACT_REMOTE_SEARCH, 0, mManagedUser.getIdentifier()); + MANAGED_PROFILE_CONTACT_REMOTE_SEARCH, 0, MANAGED_USER_ID); + mController.updateState(mPreference); + assertThat(mPreference.isChecked()).isFalse(); Settings.Secure.putIntForUser(mContext.getContentResolver(), - MANAGED_PROFILE_CONTACT_REMOTE_SEARCH, 1, mManagedUser.getIdentifier()); + MANAGED_PROFILE_CONTACT_REMOTE_SEARCH, 1, MANAGED_USER_ID); + mController.updateState(mPreference); + assertThat(mPreference.isChecked()).isTrue(); } @Test public void updateState_preferenceShouldBeDisabled() { mController.updateState(mPreference); + verify(mPreference).setDisabledByAdmin(any()); } @Test public void onPreferenceChange_shouldUpdateProviderValue() { mController.onPreferenceChange(mPreference, false); + assertThat(Settings.Secure.getIntForUser(mContext.getContentResolver(), - MANAGED_PROFILE_CONTACT_REMOTE_SEARCH, 1, mManagedUser.getIdentifier())) + MANAGED_PROFILE_CONTACT_REMOTE_SEARCH, 1, MANAGED_USER_ID)) .isEqualTo(0); mController.onPreferenceChange(mPreference, true); + assertThat(Settings.Secure.getIntForUser(mContext.getContentResolver(), - MANAGED_PROFILE_CONTACT_REMOTE_SEARCH, 0, mManagedUser.getIdentifier())) + MANAGED_PROFILE_CONTACT_REMOTE_SEARCH, 0, MANAGED_USER_ID)) .isEqualTo(1); } -} \ No newline at end of file + + @Test + public void onQuietModeDisabled_preferenceEnabled() { + when(mUserManager.isQuietModeEnabled(any(UserHandle.class))).thenReturn(false); + + mController.updateState(mPreference); + + assertThat(mPreference.isEnabled()).isTrue(); + } + + @Test + public void onQuietModeEnabled_preferenceDisabledAndUnchecked() { + when(mUserManager.isQuietModeEnabled(any(UserHandle.class))).thenReturn(true); + + mController.updateState(mPreference); + + assertThat(mPreference.isEnabled()).isFalse(); + assertThat(mPreference.isChecked()).isFalse(); + } + + @Test + public void afterQuietModeTurnedOnAndOffWhenPreferenceChecked_toggleCheckedAndEnabled() { + Settings.Secure.putIntForUser(mContext.getContentResolver(), + MANAGED_PROFILE_CONTACT_REMOTE_SEARCH, 1, MANAGED_USER_ID); + when(mUserManager.isQuietModeEnabled(any(UserHandle.class))).thenReturn(true); + + mController.updateState(mPreference); + + assertThat(mPreference.isEnabled()).isFalse(); + assertThat(mPreference.isChecked()).isFalse(); + + when(mUserManager.isQuietModeEnabled(any(UserHandle.class))).thenReturn(false); + + mController.updateState(mPreference); + + assertThat(mPreference.isEnabled()).isTrue(); + assertThat(mPreference.isChecked()).isTrue(); + } + + @Test + public void afterQuietModeTurnedOnAndOffWhenPreferenceUnchecked_toggleUncheckedAndEnabled() { + Settings.Secure.putIntForUser(mContext.getContentResolver(), + MANAGED_PROFILE_CONTACT_REMOTE_SEARCH, 0, MANAGED_USER_ID); + when(mUserManager.isQuietModeEnabled(any(UserHandle.class))).thenReturn(true); + + mController.updateState(mPreference); + + assertThat(mPreference.isEnabled()).isFalse(); + assertThat(mPreference.isChecked()).isFalse(); + + when(mUserManager.isQuietModeEnabled(any(UserHandle.class))).thenReturn(false); + + mController.updateState(mPreference); + + assertThat(mPreference.isEnabled()).isTrue(); + assertThat(mPreference.isChecked()).isFalse(); + } +} diff --git a/tests/robotests/src/com/android/settings/accounts/ManagedProfileQuietModeEnablerTest.java b/tests/robotests/src/com/android/settings/accounts/ManagedProfileQuietModeEnablerTest.java new file mode 100644 index 00000000000..2698efa4bd8 --- /dev/null +++ b/tests/robotests/src/com/android/settings/accounts/ManagedProfileQuietModeEnablerTest.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2023 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.accounts; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.UserInfo; +import android.os.UserHandle; +import android.os.UserManager; + +import androidx.lifecycle.Lifecycle; +import androidx.lifecycle.LifecycleOwner; +import androidx.lifecycle.LifecycleRegistry; +import androidx.test.core.app.ApplicationProvider; + +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; + +import java.util.Collections; + + +@RunWith(RobolectricTestRunner.class) +public class ManagedProfileQuietModeEnablerTest { + private static final int MANAGED_USER_ID = 10; + private Context mContext; + private ManagedProfileQuietModeEnabler mQuietModeEnabler; + private LifecycleOwner mLifecycleOwner = new LifecycleOwner() { + public LifecycleRegistry registry = new LifecycleRegistry(this); + + @Override + public Lifecycle getLifecycle() { + return registry; + } + }; + + @Mock + private ManagedProfileQuietModeEnabler.QuietModeChangeListener mOnQuietModeChangeListener; + @Mock + private UserManager mUserManager; + @Mock + private UserHandle mManagedUser; + @Mock + private UserInfo mUserInfo; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = spy(ApplicationProvider.getApplicationContext()); + when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager); + when(mUserInfo.isManagedProfile()).thenReturn(true); + when(mUserManager.getUserInfo(anyInt())).thenReturn(mUserInfo); + when(mUserManager.getProcessUserId()).thenReturn(0); + when(mManagedUser.getIdentifier()).thenReturn(MANAGED_USER_ID); + when(mUserManager.getUserProfiles()).thenReturn(Collections.singletonList(mManagedUser)); + mQuietModeEnabler = new ManagedProfileQuietModeEnabler(mContext, + mOnQuietModeChangeListener); + } + + @Test + public void onSetQuietMode_shouldRequestQuietModeEnabled() { + mQuietModeEnabler.setQuietModeEnabled(false); + verify(mUserManager).requestQuietModeEnabled(false, mManagedUser); + mQuietModeEnabler.setQuietModeEnabled(true); + verify(mUserManager).requestQuietModeEnabled(true, mManagedUser); + } + + @Test + public void onIsQuietModeEnabled_shouldCallIsQuietModeEnabled() { + assertThat(mQuietModeEnabler.isQuietModeEnabled()).isEqualTo( + verify(mUserManager).isQuietModeEnabled(any())); + } + + @Test + public void onQuietModeChanged_listenerNotified() { + mQuietModeEnabler.onStart(mLifecycleOwner); + mContext.sendBroadcast(new Intent(Intent.ACTION_MANAGED_PROFILE_AVAILABLE).putExtra( + Intent.EXTRA_USER_HANDLE, MANAGED_USER_ID)); + mContext.sendBroadcast(new Intent(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE).putExtra( + Intent.EXTRA_USER_HANDLE, MANAGED_USER_ID)); + verify(mOnQuietModeChangeListener, times(2)).onQuietModeChanged(); + } + + @Test + public void onStart_shouldRegisterReceiver() { + mQuietModeEnabler.onStart(mLifecycleOwner); + verify(mContext).registerReceiver(eq(mQuietModeEnabler.mReceiver), any(), anyInt()); + } + + @Test + public void onStop_shouldUnregisterReceiver() { + // register it first + mContext.registerReceiver(mQuietModeEnabler.mReceiver, null, + Context.RECEIVER_EXPORTED/*UNAUDITED*/); + + mQuietModeEnabler.onStop(mLifecycleOwner); + verify(mContext).unregisterReceiver(mQuietModeEnabler.mReceiver); + } +} diff --git a/tests/robotests/src/com/android/settings/accounts/WorkModePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accounts/WorkModePreferenceControllerTest.java index 2a283181b1c..e862d108c7c 100644 --- a/tests/robotests/src/com/android/settings/accounts/WorkModePreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/accounts/WorkModePreferenceControllerTest.java @@ -19,18 +19,18 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; +import android.content.pm.UserInfo; import android.os.UserHandle; import android.os.UserManager; -import androidx.preference.SwitchPreference; +import androidx.test.core.app.ApplicationProvider; -import com.android.settings.R; +import com.android.settingslib.widget.MainSwitchPreference; import org.junit.Before; import org.junit.Test; @@ -38,43 +38,51 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; + +import java.util.Collections; @RunWith(RobolectricTestRunner.class) public class WorkModePreferenceControllerTest { private static final String PREF_KEY = "work_mode"; + private static final int MANAGED_USER_ID = 10; + + private Context mContext; + private WorkModePreferenceController mController; + private MainSwitchPreference mPreference; @Mock private UserManager mUserManager; @Mock private UserHandle mManagedUser; - - private Context mContext; - private WorkModePreferenceController mController; - private SwitchPreference mPreference; + @Mock + private UserInfo mUserInfo; @Before public void setUp() { MockitoAnnotations.initMocks(this); - mContext = spy(RuntimeEnvironment.application); - when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager); - + mContext = spy(ApplicationProvider.getApplicationContext()); + when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager); + mPreference = new MainSwitchPreference(mContext); + when(mUserInfo.isManagedProfile()).thenReturn(true); + when(mUserManager.getUserInfo(anyInt())).thenReturn(mUserInfo); + when(mUserManager.getProcessUserId()).thenReturn(0); + when(mUserManager.getUserProfiles()).thenReturn(Collections.singletonList(mManagedUser)); + when(mManagedUser.getIdentifier()).thenReturn(MANAGED_USER_ID); mController = new WorkModePreferenceController(mContext, PREF_KEY); - mController.setManagedUser(mManagedUser); - mPreference = new SwitchPreference(mContext); } @Test public void getAvailabilityStatus_noManagedUser_DISABLED() { - mController.setManagedUser(null); + when(mUserManager.getProcessUserId()).thenReturn(MANAGED_USER_ID); + mController = new WorkModePreferenceController(mContext, PREF_KEY); + assertThat(mController.getAvailabilityStatus()) .isNotEqualTo(WorkModePreferenceController.AVAILABLE); } @Test public void getAvailabilityStatus_hasManagedUser_AVAILABLE() { - mController.setManagedUser(mManagedUser); assertThat(mController.getAvailabilityStatus()) .isEqualTo(WorkModePreferenceController.AVAILABLE); } @@ -83,41 +91,29 @@ public class WorkModePreferenceControllerTest { public void updateState_shouldRefreshContent() { when(mUserManager.isQuietModeEnabled(any(UserHandle.class))) .thenReturn(false); + mController.updateState(mPreference); + assertThat(mPreference.isChecked()).isTrue(); - assertThat(mPreference.getSummary()) - .isEqualTo(mContext.getText(R.string.work_mode_on_summary)); when(mUserManager.isQuietModeEnabled(any(UserHandle.class))) .thenReturn(true); + mController.updateState(mPreference); + assertThat(mPreference.isChecked()).isFalse(); - assertThat(mPreference.getSummary()) - .isEqualTo(mContext.getText(R.string.work_mode_off_summary)); } @Test public void onPreferenceChange_shouldRequestQuietModeEnabled() { + mController.setPreference(mPreference); + mController.onPreferenceChange(mPreference, true); + verify(mUserManager).requestQuietModeEnabled(false, mManagedUser); mController.onPreferenceChange(mPreference, false); + verify(mUserManager).requestQuietModeEnabled(true, mManagedUser); } - - @Test - public void onStart_shouldRegisterReceiver() { - mController.onStart(); - verify(mContext).registerReceiver(eq(mController.mReceiver), any(), anyInt()); - } - - @Test - public void onStop_shouldUnregisterReceiver() { - // register it first - mContext.registerReceiver(mController.mReceiver, null, - Context.RECEIVER_EXPORTED/*UNAUDITED*/); - - mController.onStop(); - verify(mContext).unregisterReceiver(mController.mReceiver); - } } \ No newline at end of file diff --git a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrollingTest.java b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrollingTest.java index 7282be357c1..959c6426894 100644 --- a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrollingTest.java +++ b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrollingTest.java @@ -53,7 +53,6 @@ import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.os.Bundle; import android.os.CancellationSignal; import android.os.Vibrator; -import android.util.FeatureFlagUtils; import android.view.Display; import android.view.Surface; import android.view.View; @@ -203,8 +202,6 @@ public class FingerprintEnrollEnrollingTest { @Test public void fingerprintUdfpsOverlayEnrollment_showOverlayPortrait() { - FeatureFlagUtils.setEnabled(mContext, - FeatureFlagUtils.SETTINGS_SHOW_UDFPS_ENROLL_IN_SETTINGS, true); initializeActivityFor(TYPE_UDFPS_OPTICAL); when(mMockDisplay.getRotation()).thenReturn(Surface.ROTATION_0); @@ -216,8 +213,6 @@ public class FingerprintEnrollEnrollingTest { @Test public void fingerprintUdfpsOverlayEnrollment_showOverlayLandscape() { - FeatureFlagUtils.setEnabled(mContext, - FeatureFlagUtils.SETTINGS_SHOW_UDFPS_ENROLL_IN_SETTINGS, true); initializeActivityFor(TYPE_UDFPS_OPTICAL); when(mMockDisplay.getRotation()).thenReturn(Surface.ROTATION_90); @@ -229,8 +224,6 @@ public class FingerprintEnrollEnrollingTest { @Test public void fingerprintUdfpsOverlayEnrollment_usesCorrectProgressBarFillColor() { - FeatureFlagUtils.setEnabled(mContext, - FeatureFlagUtils.SETTINGS_SHOW_UDFPS_ENROLL_IN_SETTINGS, true); initializeActivityFor(TYPE_UDFPS_OPTICAL); final TypedArray ta = mActivity.obtainStyledAttributes(null, R.styleable.BiometricsEnrollView, R.attr.biometricsEnrollStyle, @@ -250,9 +243,7 @@ public class FingerprintEnrollEnrollingTest { @Test public void fingerprintUdfpsOverlayEnrollment_checkViewOverlapPortrait() { - FeatureFlagUtils.setEnabled(mContext, - FeatureFlagUtils.SETTINGS_SHOW_UDFPS_ENROLL_IN_SETTINGS, true); - when(mMockDisplay.getRotation()).thenReturn(Surface.ROTATION_90); + when(mMockDisplay.getRotation()).thenReturn(Surface.ROTATION_0); initializeActivityFor(TYPE_UDFPS_OPTICAL); final GlifLayout defaultLayout = mActivity.findViewById(R.id.setup_wizard_layout); @@ -294,9 +285,9 @@ public class FingerprintEnrollEnrollingTest { udfpsEnrollView.getViewTreeObserver().addOnDrawListener(() -> { udfpsEnrollView.getLocationOnScreen(udfpsEnrollViewPosition); rectUdfpsEnrollView.set(new Rect(udfpsEnrollViewPosition[0], - udfpsEnrollViewPosition[1], udfpsEnrollViewPosition[0] - + udfpsEnrollView.getWidth(), udfpsEnrollViewPosition[1] - + udfpsEnrollView.getHeight())); + udfpsEnrollViewPosition[1], udfpsEnrollViewPosition[0] + + udfpsEnrollView.getWidth(), udfpsEnrollViewPosition[1] + + udfpsEnrollView.getHeight())); }); lottieAnimationContainer.getViewTreeObserver().addOnDrawListener(() -> { @@ -320,10 +311,36 @@ public class FingerprintEnrollEnrollingTest { .intersect(rectUdfpsEnrollView.get())).isFalse(); } + @Test + public void fingerprintUdfpsOverlayEnrollment_descriptionViewGoneWithOverlap() { + initializeActivityWithoutCreate(TYPE_UDFPS_OPTICAL); + doReturn(true).when(mActivity).hasOverlap(any(), any()); + when(mMockDisplay.getRotation()).thenReturn(Surface.ROTATION_0); + createActivity(); + + final GlifLayout defaultLayout = spy(mActivity.findViewById(R.id.setup_wizard_layout)); + final TextView descriptionTextView = defaultLayout.getDescriptionTextView(); + + defaultLayout.getViewTreeObserver().dispatchOnDraw(); + assertThat(descriptionTextView.getVisibility()).isEqualTo(View.GONE); + } + + @Test + public void fingerprintUdfpsOverlayEnrollment_descriptionViewVisibleWithoutOverlap() { + initializeActivityWithoutCreate(TYPE_UDFPS_OPTICAL); + doReturn(false).when(mActivity).hasOverlap(any(), any()); + when(mMockDisplay.getRotation()).thenReturn(Surface.ROTATION_0); + createActivity(); + + final GlifLayout defaultLayout = spy(mActivity.findViewById(R.id.setup_wizard_layout)); + final TextView descriptionTextView = defaultLayout.getDescriptionTextView(); + + defaultLayout.getViewTreeObserver().dispatchOnDraw(); + assertThat(descriptionTextView.getVisibility()).isEqualTo(View.VISIBLE); + } + @Test public void forwardEnrollProgressEvents() { - FeatureFlagUtils.setEnabled(mContext, - FeatureFlagUtils.SETTINGS_SHOW_UDFPS_ENROLL_IN_SETTINGS, true); initializeActivityFor(TYPE_UDFPS_OPTICAL); EnrollListener listener = new EnrollListener(mActivity); @@ -337,8 +354,6 @@ public class FingerprintEnrollEnrollingTest { @Test public void forwardEnrollHelpEvents() { - FeatureFlagUtils.setEnabled(mContext, - FeatureFlagUtils.SETTINGS_SHOW_UDFPS_ENROLL_IN_SETTINGS, true); initializeActivityFor(TYPE_UDFPS_OPTICAL); EnrollListener listener = new EnrollListener(mActivity); @@ -352,8 +367,6 @@ public class FingerprintEnrollEnrollingTest { @Test public void forwardEnrollAcquiredEvents() { - FeatureFlagUtils.setEnabled(mContext, - FeatureFlagUtils.SETTINGS_SHOW_UDFPS_ENROLL_IN_SETTINGS, true); initializeActivityFor(TYPE_UDFPS_OPTICAL); EnrollListener listener = new EnrollListener(mActivity); @@ -368,8 +381,6 @@ public class FingerprintEnrollEnrollingTest { @Test public void forwardEnrollPointerDownEvents() { - FeatureFlagUtils.setEnabled(mContext, - FeatureFlagUtils.SETTINGS_SHOW_UDFPS_ENROLL_IN_SETTINGS, true); initializeActivityFor(TYPE_UDFPS_OPTICAL); EnrollListener listener = new EnrollListener(mActivity); @@ -383,8 +394,6 @@ public class FingerprintEnrollEnrollingTest { @Test public void forwardEnrollPointerUpEvents() { - FeatureFlagUtils.setEnabled(mContext, - FeatureFlagUtils.SETTINGS_SHOW_UDFPS_ENROLL_IN_SETTINGS, true); initializeActivityFor(TYPE_UDFPS_OPTICAL); EnrollListener listener = new EnrollListener(mActivity); diff --git a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsFragmentTest.java b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsFragmentTest.java index 2bc81e60a06..18b05add7ad 100644 --- a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsFragmentTest.java +++ b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsFragmentTest.java @@ -20,8 +20,11 @@ import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFP import static com.android.settings.biometrics.fingerprint.FingerprintSettings.FingerprintSettingsFragment; import static com.android.settings.biometrics.fingerprint.FingerprintSettings.FingerprintSettingsFragment.ADD_FINGERPRINT_REQUEST; +import static com.android.settings.biometrics.fingerprint.FingerprintSettings.FingerprintSettingsFragment.CHOOSE_LOCK_GENERIC_REQUEST; import static com.android.settings.biometrics.fingerprint.FingerprintSettings.FingerprintSettingsFragment.KEY_FINGERPRINT_ADD; +import static com.google.common.truth.Truth.assertThat; + import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; @@ -54,6 +57,7 @@ import com.android.settings.biometrics.BiometricsSplitScreenDialog; import com.android.settings.password.ChooseLockSettingsHelper; import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.shadow.ShadowFragment; +import com.android.settings.testutils.shadow.ShadowLockPatternUtils; import com.android.settings.testutils.shadow.ShadowSettingsPreferenceFragment; import com.android.settings.testutils.shadow.ShadowUserManager; import com.android.settings.testutils.shadow.ShadowUtils; @@ -63,6 +67,7 @@ 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; @@ -74,7 +79,7 @@ import java.util.ArrayList; @RunWith(RobolectricTestRunner.class) @Config(shadows = {ShadowSettingsPreferenceFragment.class, ShadowUtils.class, ShadowFragment.class, - ShadowUserManager.class}) + ShadowUserManager.class, ShadowLockPatternUtils.class}) public class FingerprintSettingsFragmentTest { private FingerprintSettingsFragment mFragment; private Context mContext; @@ -92,10 +97,62 @@ public class FingerprintSettingsFragmentTest { doReturn(true).when(mFingerprintManager).isHardwareDetected(); ShadowUtils.setFingerprintManager(mFingerprintManager); FakeFeatureFactory.setupForTest(); + } + @After + public void tearDown() { + ShadowUtils.reset(); + } + + @Test + public void testAddFingerprint_inFullScreen_noDialog() { + setUpFragment(false); + // Click "Add Fingerprint" + final Preference preference = new Preference(mContext); + preference.setKey(KEY_FINGERPRINT_ADD); + mFragment.onPreferenceTreeClick(preference); + + verify(mFragment).startActivityForResult(any(), eq(ADD_FINGERPRINT_REQUEST)); + verify(mFragmentTransaction, never()).add(any(), + eq(BiometricsSplitScreenDialog.class.getName())); + + } + + @Test + public void testAddFingerprint_inMultiWindow_showsDialog() { + setUpFragment(false); + + doReturn(true).when(mActivity).isInMultiWindowMode(); + + // Click "Add Fingerprint" + final Preference preference = new Preference(mContext); + preference.setKey(KEY_FINGERPRINT_ADD); + mFragment.onPreferenceTreeClick(preference); + + verify(mFragment, times(0)).startActivityForResult(any(), eq(ADD_FINGERPRINT_REQUEST)); + verify(mFragmentTransaction).add(any(), eq(BiometricsSplitScreenDialog.class.getName())); + } + + @Test + public void testChooseLockKeyForFingerprint() { + setUpFragment(true); + ArgumentCaptor intentArgumentCaptor = ArgumentCaptor.forClass( + Intent.class); + verify(mFragment).startActivityForResult(intentArgumentCaptor.capture(), + eq(CHOOSE_LOCK_GENERIC_REQUEST)); + + Intent intent = intentArgumentCaptor.getValue(); + assertThat(intent.getBooleanExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, + false)).isTrue(); + } + + private void setUpFragment(boolean showChooseLock) { Intent intent = new Intent(); - intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, new byte[0]); - intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, 1L); + if (!showChooseLock) { + intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, new byte[0]); + intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, 1L); + } + mActivity = spy(Robolectric.buildActivity(FragmentActivity.class, intent).get()); mContext = spy(ApplicationProvider.getApplicationContext()); @@ -112,49 +169,12 @@ public class FingerprintSettingsFragmentTest { doNothing().when(mFragment).startActivityForResult(any(Intent.class), anyInt()); setSensor(); - } - @After - public void tearDown() { - ShadowUtils.reset(); - } - - @Test - public void testAddFingerprint_inFullScreen_noDialog() { // Start fragment mFragment.onAttach(mContext); mFragment.onCreate(null); mFragment.onCreateView(LayoutInflater.from(mContext), mock(ViewGroup.class), Bundle.EMPTY); mFragment.onResume(); - - // Click "Add Fingerprint" - final Preference preference = new Preference(mContext); - preference.setKey(KEY_FINGERPRINT_ADD); - mFragment.onPreferenceTreeClick(preference); - - verify(mFragment).startActivityForResult(any(), eq(ADD_FINGERPRINT_REQUEST)); - verify(mFragmentTransaction, never()).add(any(), - eq(BiometricsSplitScreenDialog.class.getName())); - - } - - @Test - public void testAddFingerprint_inMultiWindow_showsDialog() { - // Start fragment - mFragment.onAttach(mContext); - mFragment.onCreate(null); - mFragment.onCreateView(LayoutInflater.from(mContext), mock(ViewGroup.class), Bundle.EMPTY); - mFragment.onResume(); - - doReturn(true).when(mActivity).isInMultiWindowMode(); - - // Click "Add Fingerprint" - final Preference preference = new Preference(mContext); - preference.setKey(KEY_FINGERPRINT_ADD); - mFragment.onPreferenceTreeClick(preference); - - verify(mFragment, times(0)).startActivityForResult(any(), eq(ADD_FINGERPRINT_REQUEST)); - verify(mFragmentTransaction).add(any(), eq(BiometricsSplitScreenDialog.class.getName())); } private void setSensor() { diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsAudioRoutingControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsAudioRoutingControllerTest.java deleted file mode 100644 index ea65856de61..00000000000 --- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsAudioRoutingControllerTest.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2022 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.android.settings.bluetooth.BluetoothDetailsAudioRoutingController.KEY_AUDIO_ROUTING; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.Mockito.when; - -import android.util.FeatureFlagUtils; - -import androidx.preference.Preference; -import androidx.preference.PreferenceCategory; - -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; - -/** Tests for {@link BluetoothDetailsAudioRoutingController}. */ -@RunWith(RobolectricTestRunner.class) -public class BluetoothDetailsAudioRoutingControllerTest extends - BluetoothDetailsControllerTestBase { - @Rule - public final MockitoRule mockito = MockitoJUnit.rule(); - - private static final String TEST_ADDRESS = "55:66:77:88:99:AA"; - - private BluetoothDetailsAudioRoutingController mController; - - @Override - public void setUp() { - super.setUp(); - - mController = new BluetoothDetailsAudioRoutingController(mContext, mFragment, mCachedDevice, - mLifecycle); - final PreferenceCategory preferenceCategory = new PreferenceCategory(mContext); - preferenceCategory.setKey(mController.getPreferenceKey()); - mScreen.addPreference(preferenceCategory); - } - - @Test - public void isAvailable_isHearingAidDevice_available() { - FeatureFlagUtils.setEnabled(mContext, - FeatureFlagUtils.SETTINGS_AUDIO_ROUTING, true); - when(mCachedDevice.isHearingAidDevice()).thenReturn(true); - - assertThat(mController.isAvailable()).isTrue(); - } - - @Test - public void isAvailable_isNotHearingAidDevice_notAvailable() { - FeatureFlagUtils.setEnabled(mContext, - FeatureFlagUtils.SETTINGS_AUDIO_ROUTING, true); - when(mCachedDevice.isHearingAidDevice()).thenReturn(false); - - assertThat(mController.isAvailable()).isFalse(); - } - - @Test - public void init_isHearingAidDevice_expectedAudioRoutingPreference() { - when(mCachedDevice.isHearingAidDevice()).thenReturn(true); - when(mCachedDevice.getAddress()).thenReturn(TEST_ADDRESS); - - mController.init(mScreen); - final Preference preference = mScreen.findPreference(KEY_AUDIO_ROUTING); - final String address = preference.getExtras().getString( - BluetoothDeviceDetailsFragment.KEY_DEVICE_ADDRESS); - final String fragment = preference.getFragment(); - - assertThat(address).isEqualTo(TEST_ADDRESS); - assertThat(fragment).isEqualTo(BluetoothDetailsAudioRoutingFragment.class.getName()); - - } -} diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsAudioRoutingFragmentTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsAudioRoutingFragmentTest.java deleted file mode 100644 index 9bd4f1b8d84..00000000000 --- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsAudioRoutingFragmentTest.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (C) 2023 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.when; - -import android.bluetooth.BluetoothDevice; -import android.content.Context; -import android.os.Bundle; - -import androidx.test.core.app.ApplicationProvider; - -import com.android.settings.testutils.shadow.ShadowBluetoothUtils; -import com.android.settingslib.bluetooth.CachedBluetoothDevice; -import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; -import com.android.settingslib.bluetooth.LocalBluetoothAdapter; -import com.android.settingslib.bluetooth.LocalBluetoothManager; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnit; -import org.mockito.junit.MockitoRule; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; - -/** Tests for {@link BluetoothDetailsAudioRoutingFragment}. */ -@RunWith(RobolectricTestRunner.class) -@Config(shadows = {ShadowBluetoothUtils.class}) -public class BluetoothDetailsAudioRoutingFragmentTest { - - @Rule - public MockitoRule mMockitoRule = MockitoJUnit.rule(); - - private static final String TEST_ADDRESS = "55:66:77:88:99:AA"; - - private final Context mContext = ApplicationProvider.getApplicationContext(); - - private BluetoothDetailsAudioRoutingFragment mFragment; - @Mock - private LocalBluetoothManager mLocalBluetoothManager; - @Mock - private CachedBluetoothDeviceManager mCachedDeviceManager; - @Mock - private LocalBluetoothAdapter mLocalBluetoothAdapter; - @Mock - private BluetoothDevice mBluetoothDevice; - @Mock - private CachedBluetoothDevice mCachedDevice; - - @Before - public void setUp() { - setupEnvironment(); - - when(mLocalBluetoothAdapter.getRemoteDevice(TEST_ADDRESS)).thenReturn(mBluetoothDevice); - when(mCachedDevice.getAddress()).thenReturn(TEST_ADDRESS); - when(mCachedDeviceManager.findDevice(mBluetoothDevice)).thenReturn(mCachedDevice); - - mFragment = new BluetoothDetailsAudioRoutingFragment(); - } - - @Test - public void onAttach_setArgumentsWithAddress_expectedCachedDeviceWithAddress() { - final Bundle args = new Bundle(); - args.putString(BluetoothDeviceDetailsFragment.KEY_DEVICE_ADDRESS, TEST_ADDRESS); - mFragment.setArguments(args); - - mFragment.onAttach(mContext); - - assertThat(mFragment.mCachedDevice.getAddress()).isEqualTo(TEST_ADDRESS); - } - - private void setupEnvironment() { - ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBluetoothManager; - when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(mCachedDeviceManager); - when(mLocalBluetoothManager.getBluetoothAdapter()).thenReturn(mLocalBluetoothAdapter); - } -} diff --git a/tests/robotests/src/com/android/settings/development/graphicsdriver/GraphicsDriverEnableAngleAsSystemDriverControllerTest.java b/tests/robotests/src/com/android/settings/development/graphicsdriver/GraphicsDriverEnableAngleAsSystemDriverControllerTest.java index 314f8c38091..de380c40c8e 100644 --- a/tests/robotests/src/com/android/settings/development/graphicsdriver/GraphicsDriverEnableAngleAsSystemDriverControllerTest.java +++ b/tests/robotests/src/com/android/settings/development/graphicsdriver/GraphicsDriverEnableAngleAsSystemDriverControllerTest.java @@ -81,9 +81,10 @@ public class GraphicsDriverEnableAngleAsSystemDriverControllerTest { // since GraphicsEnvironment is mocked in Robolectric test environment, // we will override the system property persist.graphics.egl as if it is changed by // mGraphicsEnvironment.toggleAngleAsSystemDriver(true). - // TODO: b/270994705 yuxinhu: - // add test coverage to test mGraphicsEnvironment.toggleAngleAsSystemDriver() - // works properly on Android devices / emulators. + + // for test that actually verifies mGraphicsEnvironment.toggleAngleAsSystemDriver(true) + // on a device/emulator, please refer to + // GraphicsDriverEnableAngleAsSystemDriverControllerJUnitTest ShadowSystemProperties.override(PROPERTY_PERSISTENT_GRAPHICS_EGL, ANGLE_DRIVER_SUFFIX); mController.onPreferenceChange(mPreference, true); final String systemEGLDriver = SystemProperties.get(PROPERTY_PERSISTENT_GRAPHICS_EGL); @@ -97,9 +98,10 @@ public class GraphicsDriverEnableAngleAsSystemDriverControllerTest { // since GraphicsEnvironment is mocked in Robolectric test environment, // we will override the system property persist.graphics.egl as if it is changed by // mGraphicsEnvironment.toggleAngleAsSystemDriver(false). - // TODO: b/270994705 yuxinhu: - // add test coverage to test mGraphicsEnvironment.toggleAngleAsSystemDriver() - // works properly on Android devices / emulators. + + // for test that actually verifies mGraphicsEnvironment.toggleAngleAsSystemDriver(true) + // on a device/emulator, please refer to + // GraphicsDriverEnableAngleAsSystemDriverControllerJUnitTest ShadowSystemProperties.override(PROPERTY_PERSISTENT_GRAPHICS_EGL, ""); mController.onPreferenceChange(mPreference, false); final String systemEGLDriver = SystemProperties.get(PROPERTY_PERSISTENT_GRAPHICS_EGL); @@ -124,20 +126,14 @@ public class GraphicsDriverEnableAngleAsSystemDriverControllerTest { @Test public void updateState_angleSupported_angleUsed_preferenceShouldBeChecked() { ShadowSystemProperties.override(PROPERTY_RO_GFX_ANGLE_SUPPORTED, "true"); - // TODO: b/270994705 yuxinhu: - // add test coverage to test mGraphicsEnvironment.toggleAngleAsSystemDriver() - // works properly on Android devices / emulators. ShadowSystemProperties.override(PROPERTY_PERSISTENT_GRAPHICS_EGL, ANGLE_DRIVER_SUFFIX); mController.updateState(mPreference); - verify(mPreference).setChecked(true); //false + verify(mPreference).setChecked(true); } @Test public void updateState_angleSupported_angleNotUsed_preferenceShouldNotBeChecked() { ShadowSystemProperties.override(PROPERTY_RO_GFX_ANGLE_SUPPORTED, "true"); - // TODO: b/270994705 yuxinhu: - // add test coverage to test mGraphicsEnvironment.toggleAngleAsSystemDriver(false) - // works properly on Android devices / emulators. ShadowSystemProperties.override(PROPERTY_PERSISTENT_GRAPHICS_EGL, ""); mController.updateState(mPreference); verify(mPreference).setChecked(false); diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryInfoTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryInfoTest.java index eb4b598823f..ded108cfc89 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/BatteryInfoTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryInfoTest.java @@ -43,7 +43,6 @@ import com.android.internal.os.BatteryStatsHistoryIterator; import com.android.settings.testutils.BatteryTestUtils; import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.widget.UsageView; -import com.android.settingslib.R; import com.android.settingslib.fuelgauge.Estimate; import org.junit.Before; @@ -163,26 +162,6 @@ public class BatteryInfoTest { assertThat(info2.suggestionLabel).contains(BATTERY_RUN_OUT_PREFIX); } - @Test - public void testGetBatteryInfo_basedOnUsageTrueLessThanSevenMinutes_usesCorrectString() { - Estimate estimate = new Estimate(Duration.ofMinutes(7).toMillis(), - true /* isBasedOnUsage */, - 1000 /* averageDischargeTime */); - BatteryInfo info = BatteryInfo.getBatteryInfo(mContext, mDisChargingBatteryBroadcast, - mBatteryUsageStats, estimate, SystemClock.elapsedRealtime() * 1000, - false /* shortString */); - BatteryInfo info2 = BatteryInfo.getBatteryInfo(mContext, mDisChargingBatteryBroadcast, - mBatteryUsageStats, estimate, SystemClock.elapsedRealtime() * 1000, - true /* shortString */); - - // These should be identical in either case - assertThat(info.remainingLabel.toString()).isEqualTo( - mContext.getString(R.string.power_remaining_duration_only_shutdown_imminent)); - assertThat(info2.remainingLabel.toString()).isEqualTo( - mContext.getString(R.string.power_remaining_duration_only_shutdown_imminent)); - assertThat(info2.suggestionLabel).contains(BATTERY_RUN_OUT_PREFIX); - } - @Test @Ignore public void getBatteryInfo_MoreThanOneDay_suggestionLabelIsCorrectString() { @@ -196,25 +175,6 @@ public class BatteryInfoTest { assertThat(info.suggestionLabel).doesNotContain(BATTERY_RUN_OUT_PREFIX); } - @Test - public void - testGetBatteryInfo_basedOnUsageTrueBetweenSevenAndFifteenMinutes_usesCorrectString() { - Estimate estimate = new Estimate(Duration.ofMinutes(10).toMillis(), - true /* isBasedOnUsage */, - 1000 /* averageDischargeTime */); - BatteryInfo info = BatteryInfo.getBatteryInfo(mContext, mDisChargingBatteryBroadcast, - mBatteryUsageStats, estimate, SystemClock.elapsedRealtime() * 1000, - false /* shortString */); - - // Check that strings are showing less than 15 minutes remaining regardless of exact time. - assertThat(info.chargeLabel.toString()).isEqualTo( - mContext.getString(R.string.power_remaining_less_than_duration, - FIFTEEN_MIN_FORMATTED, TEST_BATTERY_LEVEL_10)); - assertThat(info.remainingLabel.toString()).isEqualTo( - mContext.getString(R.string.power_remaining_less_than_duration_only, - FIFTEEN_MIN_FORMATTED)); - } - @Test public void testGetBatteryInfo_basedOnUsageFalse_usesDefaultString() { BatteryInfo info = BatteryInfo.getBatteryInfo(mContext, mDisChargingBatteryBroadcast, diff --git a/tests/robotests/src/com/android/settings/localepicker/LocaleListEditorTest.java b/tests/robotests/src/com/android/settings/localepicker/LocaleListEditorTest.java index 111ee5a7571..16d51beca64 100644 --- a/tests/robotests/src/com/android/settings/localepicker/LocaleListEditorTest.java +++ b/tests/robotests/src/com/android/settings/localepicker/LocaleListEditorTest.java @@ -18,19 +18,32 @@ package com.android.settings.localepicker; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.Activity; +import android.app.IActivityManager; import android.content.Context; +import android.content.Intent; +import android.content.res.Configuration; +import android.os.Bundle; +import android.os.LocaleList; +import android.view.MotionEvent; import android.view.View; import android.widget.TextView; import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.FragmentActivity; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentTransaction; import com.android.internal.app.LocaleStore; import com.android.settings.R; import com.android.settings.testutils.FakeFeatureFactory; +import com.android.settings.testutils.shadow.ShadowActivityManager; import com.android.settings.testutils.shadow.ShadowAlertDialogCompat; import org.junit.After; @@ -45,22 +58,42 @@ import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; import org.robolectric.util.ReflectionHelpers; +import java.util.ArrayList; +import java.util.List; import java.util.Locale; @RunWith(RobolectricTestRunner.class) -@Config(shadows = ShadowAlertDialogCompat.class) +@Config(shadows = {ShadowAlertDialogCompat.class, ShadowActivityManager.class}) public class LocaleListEditorTest { + private static final String ARG_DIALOG_TYPE = "arg_dialog_type"; + private static final String TAG_DIALOG_CONFIRM_SYSTEM_DEFAULT = "dialog_confirm_system_default"; + private static final String TAG_DIALOG_NOT_AVAILABLE = "dialog_not_available_locale"; + private static final int DIALOG_CONFIRM_SYSTEM_DEFAULT = 1; + private static final int REQUEST_CONFIRM_SYSTEM_DEFAULT = 1; + private LocaleListEditor mLocaleListEditor; private Context mContext; private FragmentActivity mActivity; + private List mLocaleList; + private Intent mIntent = new Intent(); @Mock private LocaleDragAndDropAdapter mAdapter; + @Mock + private LocaleStore.LocaleInfo mLocaleInfo; + @Mock + private FragmentManager mFragmentManager; + @Mock + private FragmentTransaction mFragmentTransaction; + @Mock + private View mView; + @Mock + private IActivityManager mActivityService; @Before - public void setUp() { + public void setUp() throws Exception { MockitoAnnotations.initMocks(this); mContext = RuntimeEnvironment.application; mLocaleListEditor = spy(new LocaleListEditor()); @@ -74,6 +107,8 @@ public class LocaleListEditorTest { ReflectionHelpers.setField(mLocaleListEditor, "mUserManager", RuntimeEnvironment.application.getSystemService(Context.USER_SERVICE)); ReflectionHelpers.setField(mLocaleListEditor, "mAdapter", mAdapter); + ReflectionHelpers.setField(mLocaleListEditor, "mFragmentManager", mFragmentManager); + when(mFragmentManager.beginTransaction()).thenReturn(mFragmentTransaction); FakeFeatureFactory.setupForTest(); } @@ -174,4 +209,65 @@ public class LocaleListEditorTest { assertThat(result.getLocale().getUnicodeLocaleType("fw")).isEqualTo("wed"); assertThat(result.getLocale().getUnicodeLocaleType("mu")).isEqualTo("celsius"); } + + @Test + public void onActivityResult_ResultCodeIsOk_showNotAvailableDialog() { + Bundle bundle = new Bundle(); + bundle.putInt(ARG_DIALOG_TYPE, DIALOG_CONFIRM_SYSTEM_DEFAULT); + mIntent.putExtras(bundle); + setUpLocaleConditions(); + mLocaleListEditor.onActivityResult(REQUEST_CONFIRM_SYSTEM_DEFAULT, Activity.RESULT_OK, + mIntent); + + verify(mFragmentTransaction).add(any(LocaleDialogFragment.class), + eq(TAG_DIALOG_NOT_AVAILABLE)); + } + + @Test + public void onActivityResult_ResultCodeIsCancel_notifyAdapterListChanged() { + Bundle bundle = new Bundle(); + bundle.putInt(ARG_DIALOG_TYPE, DIALOG_CONFIRM_SYSTEM_DEFAULT); + mIntent.putExtras(bundle); + setUpLocaleConditions(); + mLocaleListEditor.onActivityResult(REQUEST_CONFIRM_SYSTEM_DEFAULT, Activity.RESULT_CANCELED, + mIntent); + + verify(mAdapter).notifyListChanged(mLocaleInfo); + } + + @Test + public void onTouch_dragDifferentLocaleToTop_showConfirmDialog() throws Exception { + MotionEvent event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0.0f, 0.0f, 0); + setUpLocaleConditions(); + final Configuration config = new Configuration(); + config.setLocales((LocaleList.forLanguageTags("zh-TW,en-US"))); + when(mActivityService.getConfiguration()).thenReturn(config); + when(mAdapter.getFeedItemList()).thenReturn(mLocaleList); + mLocaleListEditor.onTouch(mView, event); + + verify(mFragmentTransaction).add(any(LocaleDialogFragment.class), + eq(TAG_DIALOG_CONFIRM_SYSTEM_DEFAULT)); + } + + @Test + public void onTouch_dragSameLocaleToTop_updateAdapter() throws Exception { + MotionEvent event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0.0f, 0.0f, 0); + setUpLocaleConditions(); + final Configuration config = new Configuration(); + config.setLocales((LocaleList.forLanguageTags("en-US,zh-TW"))); + when(mActivityService.getConfiguration()).thenReturn(config); + when(mAdapter.getFeedItemList()).thenReturn(mLocaleList); + mLocaleListEditor.onTouch(mView, event); + + verify(mAdapter).doTheUpdate(); + } + + private void setUpLocaleConditions() { + ShadowActivityManager.setService(mActivityService); + mLocaleList = new ArrayList<>(); + mLocaleList.add(mLocaleInfo); + when(mLocaleInfo.getFullNameNative()).thenReturn("English"); + when(mLocaleInfo.getLocale()).thenReturn(LocaleList.forLanguageTags("en-US").get(0)); + when(mAdapter.getFeedItemList()).thenReturn(mLocaleList); + } } diff --git a/tests/robotests/src/com/android/settings/network/MobileNetworkSummaryControllerTest.java b/tests/robotests/src/com/android/settings/network/MobileNetworkSummaryControllerTest.java index 5310ae09801..8d6d2d9bc4e 100644 --- a/tests/robotests/src/com/android/settings/network/MobileNetworkSummaryControllerTest.java +++ b/tests/robotests/src/com/android/settings/network/MobileNetworkSummaryControllerTest.java @@ -133,18 +133,12 @@ public class MobileNetworkSummaryControllerTest { assertThat(mController.isAvailable()).isFalse(); } - @Ignore @Test - public void getSummary_noSubscriptions_correctSummaryAndClickHandler() { + public void getSummary_noSubscriptions_returnSummaryCorrectly() { mController.displayPreference(mPreferenceScreen); mController.onResume(); - assertThat(mController.getSummary()).isEqualTo("Add a network"); - final ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); - doNothing().when(mContext).startActivity(intentCaptor.capture()); - mPreference.getOnPreferenceClickListener().onPreferenceClick(mPreference); - assertThat(intentCaptor.getValue().getAction()).isEqualTo( - EuiccManager.ACTION_PROVISION_EMBEDDED_SUBSCRIPTION); + assertThat(mController.getSummary()).isEqualTo("Add a network"); } @Test @@ -300,15 +294,13 @@ public class MobileNetworkSummaryControllerTest { assertThat(captor.getValue()).isFalse(); } - @Ignore @Test - public void onResume_noSubscriptionEsimDisabled_isDisabled() { + public void onAvailableSubInfoChanged_noSubscriptionEsimDisabled_isDisabled() { Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0); - SubscriptionUtil.setAvailableSubscriptionsForTesting(null); when(mEuiccManager.isEnabled()).thenReturn(false); mController.displayPreference(mPreferenceScreen); - mController.onResume(); + mController.onAvailableSubInfoChanged(null); assertThat(mPreference.isEnabled()).isFalse(); } diff --git a/tests/robotests/src/com/android/settings/password/ChooseLockGenericTest.java b/tests/robotests/src/com/android/settings/password/ChooseLockGenericTest.java index dbc60dce731..12a540d7c33 100644 --- a/tests/robotests/src/com/android/settings/password/ChooseLockGenericTest.java +++ b/tests/robotests/src/com/android/settings/password/ChooseLockGenericTest.java @@ -30,6 +30,8 @@ import static com.android.settings.password.ChooseLockGeneric.ChooseLockGenericF import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_CALLER_APP_NAME; import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_DEVICE_PASSWORD_REQUIREMENT_ONLY; import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_FOR_BIOMETRICS; +import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE; +import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT; import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_IS_CALLING_APP_ADMIN; import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_REQUESTED_MIN_COMPLEXITY; @@ -126,7 +128,9 @@ public class ChooseLockGenericTest { when(mFaceManager.isHardwareDetected()).thenReturn(true); when(mFingerprintManager.isHardwareDetected()).thenReturn(true); when(mFakeFeatureFactory.mFaceFeatureProvider.isSetupWizardSupported(any())).thenReturn( - false); + true); + ShadowUtils.setFingerprintManager(mFingerprintManager); + ShadowUtils.setFaceManager(mFaceManager); } @After @@ -540,35 +544,63 @@ public class ChooseLockGenericTest { @Test public void updatePreferenceText_supportBiometrics_showFaceAndFingerprint() { - ShadowLockPatternUtils.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_LOW); - final PasswordPolicy policy = new PasswordPolicy(); - policy.quality = PASSWORD_QUALITY_ALPHABETIC; - ShadowLockPatternUtils.setRequestedProfilePasswordMetrics(policy.getMinMetrics()); - + ShadowStorageManager.setIsFileEncrypted(false); final Intent intent = new Intent().putExtra(EXTRA_KEY_FOR_BIOMETRICS, true); initActivity(intent); - final Intent passwordIntent = mFragment.getLockPatternIntent(); - assertThat(passwordIntent.getIntExtra(ChooseLockPassword.EXTRA_KEY_MIN_COMPLEXITY, - PASSWORD_COMPLEXITY_NONE)).isEqualTo(PASSWORD_COMPLEXITY_LOW); final String supportFingerprint = capitalize(mActivity.getResources().getString( R.string.security_settings_fingerprint)); final String supportFace = capitalize(mActivity.getResources().getString( R.string.keywords_face_settings)); + String pinTitle = + (String) mFragment.findPreference(ScreenLockType.PIN.preferenceKey).getTitle(); + String patternTitle = + (String) mFragment.findPreference(ScreenLockType.PATTERN.preferenceKey).getTitle(); + String passwordTitle = + (String) mFragment.findPreference(ScreenLockType.PASSWORD.preferenceKey).getTitle(); - assertThat(mFragment.getBiometricsPreferenceTitle(ScreenLockType.PIN)).contains( - supportFingerprint); - assertThat(mFragment.getBiometricsPreferenceTitle(ScreenLockType.PIN)).contains( - supportFace); - assertThat(mFragment.getBiometricsPreferenceTitle(ScreenLockType.PATTERN)).contains( - supportFingerprint); - assertThat(mFragment.getBiometricsPreferenceTitle(ScreenLockType.PATTERN)).contains( - supportFace); - assertThat(mFragment.getBiometricsPreferenceTitle(ScreenLockType.PASSWORD)).contains( - supportFingerprint); - assertThat(mFragment.getBiometricsPreferenceTitle(ScreenLockType.PASSWORD)).contains( - supportFace); + assertThat(pinTitle).contains(supportFingerprint); + assertThat(pinTitle).contains(supportFace); + assertThat(patternTitle).contains(supportFingerprint); + assertThat(patternTitle).contains(supportFace); + assertThat(passwordTitle).contains(supportFingerprint); + assertThat(passwordTitle).contains(supportFace); + } + + @Test + public void updatePreferenceText_supportFingerprint_showFingerprint() { + ShadowStorageManager.setIsFileEncrypted(false); + final Intent intent = new Intent().putExtra(EXTRA_KEY_FOR_FINGERPRINT, true); + initActivity(intent); + mFragment.updatePreferencesOrFinish(false /* isRecreatingActivity */); + + assertThat(mFragment.findPreference(ScreenLockType.PIN.preferenceKey).getTitle()).isEqualTo( + mFragment.getString(R.string.fingerprint_unlock_set_unlock_pin)); + assertThat(mFragment.findPreference( + ScreenLockType.PATTERN.preferenceKey).getTitle()).isEqualTo( + mFragment.getString(R.string.fingerprint_unlock_set_unlock_pattern)); + assertThat(mFragment.findPreference( + ScreenLockType.PASSWORD.preferenceKey).getTitle()).isEqualTo( + mFragment.getString(R.string.fingerprint_unlock_set_unlock_password)); + } + + @Test + public void updatePreferenceText_supportFace_showFace() { + + ShadowStorageManager.setIsFileEncrypted(false); + final Intent intent = new Intent().putExtra(EXTRA_KEY_FOR_FACE, true); + initActivity(intent); + mFragment.updatePreferencesOrFinish(false /* isRecreatingActivity */); + + assertThat(mFragment.findPreference(ScreenLockType.PIN.preferenceKey).getTitle()).isEqualTo( + mFragment.getString(R.string.face_unlock_set_unlock_pin)); + assertThat(mFragment.findPreference( + ScreenLockType.PATTERN.preferenceKey).getTitle()).isEqualTo( + mFragment.getString(R.string.face_unlock_set_unlock_pattern)); + assertThat(mFragment.findPreference( + ScreenLockType.PASSWORD.preferenceKey).getTitle()).isEqualTo( + mFragment.getString(R.string.face_unlock_set_unlock_password)); } private void initActivity(@Nullable Intent intent) { diff --git a/tests/robotests/src/com/android/settings/slices/SlicePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/slices/SlicePreferenceControllerTest.java index 65eaddd50ab..e8bd27d97e6 100644 --- a/tests/robotests/src/com/android/settings/slices/SlicePreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/slices/SlicePreferenceControllerTest.java @@ -18,8 +18,10 @@ package com.android.settings.slices; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.content.Context; import android.net.Uri; @@ -43,11 +45,11 @@ public class SlicePreferenceControllerTest { private LiveData mLiveData; @Mock private SlicePreference mSlicePreference; + private Context mContext; private SlicePreferenceController mController; private Uri mUri; - @Before public void setUp() { MockitoAnnotations.initMocks(this); @@ -78,10 +80,30 @@ public class SlicePreferenceControllerTest { @Test public void onStop_unregisterObserver() { + when(mLiveData.hasActiveObservers()).thenReturn(true); + mController.onStart(); + mController.onStop(); verify(mLiveData).removeObserver(mController); } + @Test + public void onStop_noActiveObservers_notUnregisterObserver() { + when(mLiveData.hasActiveObservers()).thenReturn(false); + mController.onStart(); + + mController.onStop(); + verify(mLiveData, never()).removeObserver(mController); + } + + @Test + public void onStop_notRegisterObserver_notUnregisterObserver() { + when(mLiveData.hasActiveObservers()).thenReturn(true); + + mController.onStop(); + verify(mLiveData, never()).removeObserver(mController); + } + @Test public void onChanged_nullSlice_updateSlice() { mController.onChanged(null); diff --git a/tests/unit/src/com/android/settings/development/graphicsdriver/GraphicsDriverEnableAngleAsSystemDriverControllerJUnitTest.java b/tests/unit/src/com/android/settings/development/graphicsdriver/GraphicsDriverEnableAngleAsSystemDriverControllerJUnitTest.java new file mode 100644 index 00000000000..3f85535df53 --- /dev/null +++ b/tests/unit/src/com/android/settings/development/graphicsdriver/GraphicsDriverEnableAngleAsSystemDriverControllerJUnitTest.java @@ -0,0 +1,325 @@ +/* + * Copyright (C) 2023 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.development.graphicsdriver; + +import static com.android.settings.development.graphicsdriver.GraphicsDriverEnableAngleAsSystemDriverController.ANGLE_DRIVER_SUFFIX; +import static com.android.settings.development.graphicsdriver.GraphicsDriverEnableAngleAsSystemDriverController.Injector; +import static com.android.settings.development.graphicsdriver.GraphicsDriverEnableAngleAsSystemDriverController.PROPERTY_PERSISTENT_GRAPHICS_EGL; +import static com.android.settings.development.graphicsdriver.GraphicsDriverEnableAngleAsSystemDriverController.PROPERTY_RO_GFX_ANGLE_SUPPORTED; + +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.when; + +import android.content.Context; +import android.os.Looper; +import android.os.SystemProperties; + +import androidx.preference.PreferenceManager; +import androidx.preference.PreferenceScreen; +import androidx.preference.SwitchPreference; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.android.settings.development.DevelopmentSettingsDashboardFragment; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +@RunWith(AndroidJUnit4.class) +public class GraphicsDriverEnableAngleAsSystemDriverControllerJUnitTest { + private Context mContext; + private SwitchPreference mPreference; + + private GraphicsDriverEnableAngleAsSystemDriverController mController; + + @Mock + private DevelopmentSettingsDashboardFragment mFragment; + + @Mock + private GraphicsDriverSystemPropertiesWrapper mSystemPropertiesMock; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + if (Looper.myLooper() == null) { + Looper.prepare(); + } + + mContext = ApplicationProvider.getApplicationContext(); + + // Construct a GraphicsDriverEnableAngleAsSystemDriverController with two Overrides: + // 1) Override the mSystemProperties with mSystemPropertiesMock, + // so we can force the SystemProperties with values we need to run tests. + // 2) Override the showRebootDialog() to do nothing. + // We do not need to pop up the reboot dialog in the test. + mController = new GraphicsDriverEnableAngleAsSystemDriverController( + mContext, mFragment, new Injector(){ + @Override + public GraphicsDriverSystemPropertiesWrapper createSystemPropertiesWrapper() { + return mSystemPropertiesMock; + } + }) { + @Override + void showRebootDialog() { + // do nothing + } + }; + + final PreferenceManager preferenceManager = new PreferenceManager(mContext); + final PreferenceScreen screen = preferenceManager.createPreferenceScreen(mContext); + mPreference = new SwitchPreference(mContext); + mPreference.setKey(mController.getPreferenceKey()); + screen.addPreference(mPreference); + mController.displayPreference(screen); + } + + @Test + public void onPreferenceChange_switchOn_shouldEnableAngleAsSystemDriver() { + // Add a callback when SystemProperty changes. + // This allows the thread to wait until + // GpuService::toggleAngleAsSystemDriver() updates the persist.graphics.egl. + final CountDownLatch countDownLatch = new CountDownLatch(1); + Runnable countDown = new Runnable() { + @Override + public void run() { + countDownLatch.countDown(); + } + }; + SystemProperties.addChangeCallback(countDown); + + // Test onPreferenceChange(true) updates the persist.graphics.egl to "angle" + mController.onPreferenceChange(mPreference, true); + try { + countDownLatch.await(100, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + Assert.fail(e.getMessage()); + } + final String systemEGLDriver = SystemProperties.get(PROPERTY_PERSISTENT_GRAPHICS_EGL); + assertThat(systemEGLDriver).isEqualTo(ANGLE_DRIVER_SUFFIX); + + // Done with the test, remove the callback + SystemProperties.removeChangeCallback(countDown); + } + + @Test + public void onPreferenceChange_switchOff_shouldDisableAngleAsSystemDriver() { + // Add a callback when SystemProperty changes. + // This allows the thread to wait until + // GpuService::toggleAngleAsSystemDriver() updates the persist.graphics.egl. + final CountDownLatch countDownLatch = new CountDownLatch(1); + Runnable countDown = new Runnable() { + @Override + public void run() { + countDownLatch.countDown(); + } + }; + SystemProperties.addChangeCallback(countDown); + + // Test onPreferenceChange(false) updates the persist.graphics.egl to "" + mController.onPreferenceChange(mPreference, false); + try { + countDownLatch.await(100, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + Assert.fail(e.getMessage()); + } + final String systemEGLDriver = SystemProperties.get(PROPERTY_PERSISTENT_GRAPHICS_EGL); + assertThat(systemEGLDriver).isEqualTo(""); + + // Done with the test, remove the callback + SystemProperties.removeChangeCallback(countDown); + } + + @Test + public void updateState_angleNotSupported_PreferenceShouldDisabled() { + when(mSystemPropertiesMock.get(eq(PROPERTY_RO_GFX_ANGLE_SUPPORTED), any())).thenReturn(""); + mController.updateState(mPreference); + assertThat(mPreference.isEnabled()).isFalse(); + } + + @Test + public void updateState_angleNotSupported_PreferenceShouldNotBeChecked() { + when(mSystemPropertiesMock.get(eq(PROPERTY_RO_GFX_ANGLE_SUPPORTED), any())) + .thenReturn(""); + mController.updateState(mPreference); + assertThat(mPreference.isChecked()).isFalse(); + } + + @Test + public void updateState_angleSupported_PreferenceShouldEnabled() { + when(mSystemPropertiesMock.get(eq(PROPERTY_RO_GFX_ANGLE_SUPPORTED), any())) + .thenReturn("true"); + mController.updateState(mPreference); + assertThat(mPreference.isEnabled()).isTrue(); + } + + @Test + public void updateState_angleSupported_angleIsSystemGLESDriver_PreferenceShouldBeChecked() { + when(mSystemPropertiesMock.get(eq(PROPERTY_RO_GFX_ANGLE_SUPPORTED), any())) + .thenReturn("true"); + when(mSystemPropertiesMock.get(eq(PROPERTY_PERSISTENT_GRAPHICS_EGL), any())) + .thenReturn(ANGLE_DRIVER_SUFFIX); + mController.updateState(mPreference); + assertThat(mPreference.isChecked()).isTrue(); + } + + @Test + public void + updateState_angleSupported_angleIsNotSystemGLESDriver_PreferenceShouldNotBeChecked() { + when(mSystemPropertiesMock.get(eq(PROPERTY_RO_GFX_ANGLE_SUPPORTED), any())) + .thenReturn("true"); + when(mSystemPropertiesMock.get(eq(PROPERTY_PERSISTENT_GRAPHICS_EGL), any())) + .thenReturn(""); + mController.updateState(mPreference); + assertThat(mPreference.isChecked()).isFalse(); + } + + @Test + public void onDeveloperOptionSwitchEnabled_angleSupported_PreferenceShouldEnabled() { + when(mSystemPropertiesMock.get(eq(PROPERTY_RO_GFX_ANGLE_SUPPORTED), any())) + .thenReturn("true"); + mController.onDeveloperOptionsSwitchEnabled(); + assertThat(mPreference.isEnabled()).isTrue(); + } + + @Test + public void onDeveloperOptionSwitchEnabled_angleNotSupported_PrefenceShouldDisabled() { + when(mSystemPropertiesMock.get(eq(PROPERTY_RO_GFX_ANGLE_SUPPORTED), any())) + .thenReturn("false"); + mController.onDeveloperOptionsSwitchEnabled(); + assertThat(mPreference.isEnabled()).isFalse(); + } + + @Test + public void onDeveloperOptionSwitchDisabled_angleIsNotSystemGLESDriver() { + // Add a callback when SystemProperty changes. + // This allows the thread to wait until + // GpuService::toggleAngleAsSystemDriver() updates the persist.graphics.egl. + final CountDownLatch countDownLatch = new CountDownLatch(1); + Runnable countDown = new Runnable() { + @Override + public void run() { + countDownLatch.countDown(); + } + }; + SystemProperties.addChangeCallback(countDown); + + // Test that onDeveloperOptionSwitchDisabled, + // persist.graphics.egl updates to "" + mController.onDeveloperOptionsSwitchDisabled(); + try { + countDownLatch.await(100, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + Assert.fail(e.getMessage()); + } + final String systemEGLDriver = SystemProperties.get(PROPERTY_PERSISTENT_GRAPHICS_EGL); + assertThat(systemEGLDriver).isEqualTo(""); + + // Done with the test, remove the callback + SystemProperties.removeChangeCallback(countDown); + } + + @Test + public void onDeveloperOptionSwitchDisabled_PreferenceShouldNotBeChecked() { + mController.onDeveloperOptionsSwitchDisabled(); + assertThat(mPreference.isChecked()).isFalse(); + } + + @Test + public void onDeveloperOptionSwitchDisabled_PreferenceShouldDisabled() { + mController.onDeveloperOptionsSwitchDisabled(); + assertThat(mPreference.isEnabled()).isFalse(); + } + + @Test + public void onRebootCancelled_ToggleSwitchFromOnToOff() { + // Add a callback when SystemProperty changes. + // This allows the thread to wait until + // GpuService::toggleAngleAsSystemDriver() updates the persist.graphics.egl. + final CountDownLatch countDownLatch = new CountDownLatch(1); + Runnable countDown = new Runnable() { + @Override + public void run() { + countDownLatch.countDown(); + } + }; + SystemProperties.addChangeCallback(countDown); + + // Test that if the current persist.graphics.egl is "angle", + // when reboot is cancelled, persist.graphics.egl is changed back to "", + // and switch is set to unchecked. + when(mSystemPropertiesMock.get(eq(PROPERTY_PERSISTENT_GRAPHICS_EGL), any())) + .thenReturn(ANGLE_DRIVER_SUFFIX); + mController.onRebootCancelled(); + try { + countDownLatch.await(100, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + Assert.fail(e.getMessage()); + } + + final String systemEGLDriver = SystemProperties.get(PROPERTY_PERSISTENT_GRAPHICS_EGL); + assertThat(systemEGLDriver).isEqualTo(""); + assertThat(mPreference.isChecked()).isFalse(); + + // Done with the test, remove the callback. + SystemProperties.removeChangeCallback(countDown); + } + + @Test + public void onRebootCancelled_ToggleSwitchFromOffToOn() { + // Add a callback when SystemProperty changes. + // This allows the thread to wait until + // GpuService::toggleAngleAsSystemDriver() updates the persist.graphics.egl. + final CountDownLatch countDownLatch = new CountDownLatch(1); + Runnable countDown = new Runnable() { + @Override + public void run() { + countDownLatch.countDown(); + } + }; + SystemProperties.addChangeCallback(countDown); + + // Test that if the current persist.graphics.egl is "", + // when reboot is cancelled, persist.graphics.egl is changed back to "angle", + // and switch is set to checked. + when(mSystemPropertiesMock.get(eq(PROPERTY_PERSISTENT_GRAPHICS_EGL), any())) + .thenReturn(""); + mController.onRebootCancelled(); + try { + countDownLatch.await(100, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + Assert.fail(e.getMessage()); + } + + final String systemEGLDriver = SystemProperties.get(PROPERTY_PERSISTENT_GRAPHICS_EGL); + assertThat(systemEGLDriver).isEqualTo(ANGLE_DRIVER_SUFFIX); + assertThat(mPreference.isChecked()).isTrue(); + + // Done with the test, remove the callback. + SystemProperties.removeChangeCallback(countDown); + } + +} diff --git a/tests/unit/src/com/android/settings/localepicker/LocaleDialogFragmentTest.java b/tests/unit/src/com/android/settings/localepicker/LocaleDialogFragmentTest.java index b0998c023a6..824954da52d 100644 --- a/tests/unit/src/com/android/settings/localepicker/LocaleDialogFragmentTest.java +++ b/tests/unit/src/com/android/settings/localepicker/LocaleDialogFragmentTest.java @@ -17,8 +17,8 @@ package com.android.settings.localepicker; import static com.android.settings.localepicker.LocaleDialogFragment.ARG_DIALOG_TYPE; -import static com.android.settings.localepicker.LocaleDialogFragment.ARG_RESULT_RECEIVER; import static com.android.settings.localepicker.LocaleDialogFragment.ARG_TARGET_LOCALE; +import static com.android.settings.localepicker.LocaleDialogFragment.DIALOG_CONFIRM_SYSTEM_DEFAULT; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -27,12 +27,9 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; -import android.app.Activity; import android.app.settings.SettingsEnums; import android.content.Context; -import android.content.DialogInterface; import android.os.Bundle; -import android.os.ResultReceiver; import androidx.test.annotation.UiThreadTest; import androidx.test.core.app.ApplicationProvider; @@ -55,6 +52,7 @@ public class LocaleDialogFragmentTest { public final MockitoRule mockito = MockitoJUnit.rule(); private Context mContext; + private LocaleListEditor mLocaleListEditor; private LocaleDialogFragment mDialogFragment; private FakeFeatureFactory mFeatureFactory; @@ -62,30 +60,30 @@ public class LocaleDialogFragmentTest { public void setUp() throws Exception { mContext = ApplicationProvider.getApplicationContext(); mDialogFragment = new LocaleDialogFragment(); + mLocaleListEditor = spy(new LocaleListEditor()); mFeatureFactory = FakeFeatureFactory.setupForTest(); } - private void setArgument( - int type, ResultReceiver receiver) { + private void setArgument(int type) { LocaleStore.LocaleInfo localeInfo = LocaleStore.getLocaleInfo(Locale.ENGLISH); Bundle args = new Bundle(); args.putInt(ARG_DIALOG_TYPE, type); args.putSerializable(ARG_TARGET_LOCALE, localeInfo); - args.putParcelable(ARG_RESULT_RECEIVER, receiver); mDialogFragment.setArguments(args); } @Test public void getDialogContent_confirmSystemDefault_has2ButtonText() { - setArgument(LocaleDialogFragment.DIALOG_CONFIRM_SYSTEM_DEFAULT, null); + setArgument(DIALOG_CONFIRM_SYSTEM_DEFAULT); LocaleDialogFragment.LocaleDialogController controller = - new LocaleDialogFragment.LocaleDialogController(mContext, mDialogFragment); + mDialogFragment.getLocaleDialogController(mContext, mDialogFragment, + mLocaleListEditor); LocaleDialogFragment.LocaleDialogController.DialogContent dialogContent = controller.getDialogContent(); assertEquals(ResourcesUtils.getResourcesString( - mContext, "button_label_confirmation_of_system_locale_change"), + mContext, "button_label_confirmation_of_system_locale_change"), dialogContent.mPositiveButton); assertEquals(ResourcesUtils.getResourcesString(mContext, "cancel"), dialogContent.mNegativeButton); @@ -93,9 +91,10 @@ public class LocaleDialogFragmentTest { @Test public void getDialogContent_unavailableLocale_has1ButtonText() { - setArgument(LocaleDialogFragment.DIALOG_NOT_AVAILABLE_LOCALE, null); + setArgument(LocaleDialogFragment.DIALOG_NOT_AVAILABLE_LOCALE); LocaleDialogFragment.LocaleDialogController controller = - new LocaleDialogFragment.LocaleDialogController(mContext, mDialogFragment); + mDialogFragment.getLocaleDialogController(mContext, mDialogFragment, + mLocaleListEditor); LocaleDialogFragment.LocaleDialogController.DialogContent dialogContent = controller.getDialogContent(); @@ -105,38 +104,9 @@ public class LocaleDialogFragmentTest { assertTrue(dialogContent.mNegativeButton.isEmpty()); } - @Test - public void onClick_clickPositiveButton_sendOK() { - ResultReceiver resultReceiver = spy(new ResultReceiver(null)); - setArgument(LocaleDialogFragment.DIALOG_CONFIRM_SYSTEM_DEFAULT, resultReceiver); - LocaleDialogFragment.LocaleDialogController controller = - new LocaleDialogFragment.LocaleDialogController(mContext, mDialogFragment); - - controller.onClick(null, DialogInterface.BUTTON_POSITIVE); - - verify(resultReceiver).send(eq(Activity.RESULT_OK), any()); - verify(mFeatureFactory.metricsFeatureProvider).action( - mContext, SettingsEnums.ACTION_CHANGE_LANGUAGE, true); - } - - @Test - public void onClick_clickNegativeButton_sendCancel() { - ResultReceiver resultReceiver = spy(new ResultReceiver(null)); - setArgument(LocaleDialogFragment.DIALOG_CONFIRM_SYSTEM_DEFAULT, resultReceiver); - LocaleDialogFragment.LocaleDialogController controller = - new LocaleDialogFragment.LocaleDialogController(mContext, mDialogFragment); - - controller.onClick(null, DialogInterface.BUTTON_NEGATIVE); - - verify(resultReceiver).send(eq(Activity.RESULT_CANCELED), any()); - verify(mFeatureFactory.metricsFeatureProvider).action( - mContext, SettingsEnums.ACTION_CHANGE_LANGUAGE, false); - } - @Test public void getMetricsCategory_systemLocaleChange() { - setArgument(LocaleDialogFragment.DIALOG_CONFIRM_SYSTEM_DEFAULT, null); - + setArgument(DIALOG_CONFIRM_SYSTEM_DEFAULT); int result = mDialogFragment.getMetricsCategory(); assertEquals(SettingsEnums.DIALOG_SYSTEM_LOCALE_CHANGE, result); @@ -144,8 +114,7 @@ public class LocaleDialogFragmentTest { @Test public void getMetricsCategory_unavailableLocale() { - setArgument(LocaleDialogFragment.DIALOG_NOT_AVAILABLE_LOCALE, null); - + setArgument(LocaleDialogFragment.DIALOG_NOT_AVAILABLE_LOCALE); int result = mDialogFragment.getMetricsCategory(); assertEquals(SettingsEnums.DIALOG_SYSTEM_LOCALE_UNAVAILABLE, result);