Snap for 10130956 from 2e5bc64514 to udc-qpr1-release

Change-Id: I5ab8f9dedbfd4e777935f7c02a592363c688d203
This commit is contained in:
Android Build Coastguard Worker
2023-05-13 05:32:54 +00:00
61 changed files with 1817 additions and 1128 deletions

View File

@@ -20,7 +20,7 @@
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?androidprv:attr/materialColorPrimaryContainer">
android:tint="?android:attr/colorPrimary">
<path
android:fillColor="@android:color/white"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10c5.52,0 10,-4.48 10,-10S17.52,2 12,2zM10.59,16.6l-4.24,-4.24l1.41,-1.41l2.83,2.83l5.66,-5.66l1.41,1.41L10.59,16.6z"/>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<ImageView
xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@drawable/ic_chevron_right_24dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="8dp" />

View File

@@ -129,7 +129,9 @@
<!-- Title for all hearing devices related controls section. [CHAR LIMIT=60] -->
<string name="bluetooth_device_controls_general">For all available hearing devices</string>
<!-- Connected devices settings. Title of the preference to show the entrance of the hearing device controls related page. [CHAR LIMIT=65] -->
<string name="bluetooth_device_controls_title">Shortcuts &amp; hearing aid compatibility</string>
<string name="bluetooth_device_controls_title">Hearing device settings</string>
<!-- Connected devices settings. Title of the preference to show the entrance of the hearing device controls related page. [CHAR LIMIT=65] -->
<string name="bluetooth_device_controls_summary">Audio output, shortcut, hearing aid compatibility</string>
<!-- Title for this device specific controls section. [CHAR LIMIT=30] -->
<string name="bluetooth_device_controls_specific">For this device</string>
<!-- Connected devices settings. Title of the preference to show the entrance of the audio output page. It can change different types of audio are played on phone or other bluetooth devices. [CHAR LIMIT=35] -->
@@ -5877,12 +5879,8 @@
<string name="add_account_label">Add account</string>
<!-- Label for the state of the work profile [CHAR LIMIT=80] -->
<string name="managed_profile_not_available_label">Work profile isn\u2019t available yet</string>
<!-- This string is the title of a setting. If a user taps the setting, they can turn their work profile on or off. The work profile is a section of their phone that's managed by their employer. "Work" is an adjective. -->
<string name="work_mode_label">Work profile</string>
<!-- This string is located under a setting and describes what the setting does. It's letting a user know whether their work profile is on or off, and they can use the setting to turn it on or off. The work profile is a section of their phone that's managed by their employer. "Work" is an adjective.-->
<string name="work_mode_on_summary">Managed by your organization</string>
<!-- This string is located under a setting and describes what the setting does. It's letting a user know whether their work profile is on or off, and they can use the setting to turn it on or off. The work profile is a section of their phone that's managed by their employer. "Work" is an adjective.-->
<string name="work_mode_off_summary">Apps and notifications are off</string>
<!-- This string is the title of a setting. If a user taps the setting, they can turn their work apps on or off. The work apps are a group of apps that are managed by the the user's employer. While this setting is off, the user cannot interact with those apps or get notifications from them. "Work" is an adjective. -->
<string name="work_mode_label">Work apps</string>
<!-- Button label to remove the work profile [CHAR LIMIT=35] -->
<string name="remove_managed_profile_label">Remove work profile</string>
<!-- Data synchronization settings screen, title of setting that controls whether background data should be used [CHAR LIMIT=30] -->
@@ -7175,7 +7173,7 @@
<string name="docking_sounds_title">Docking sounds</string>
<!-- Sound: Other sounds: Title for the option enabling touch sounds. [CHAR LIMIT=30] -->
<string name="touch_sounds_title">Touch sounds</string>
<string name="touch_sounds_title">Tap &amp; click sounds</string>
<!-- Sound: Other sounds: Title for the option enabling the vibrate icon. [CHAR LIMIT=50] -->
<string name="vibrate_icon_title">Always show icon when in vibrate mode</string>
@@ -9783,6 +9781,8 @@
<string name="cross_profile_calendar_title">Cross-profile calendar</string>
<!-- [CHAR LIMIT=NONE] Setting description. If the user turns on this setting, they can see their work events on their personal calendar. -->
<string name="cross_profile_calendar_summary">Show work events on your personal calendar</string>
<!-- [CHAR_LIMIT_NONE] Footer description. Explains to the user what will happen when work apps are turned off. -->
<string name="managed_profile_settings_footer">When work apps are off, theyre paused and cant be accessed or send you notifications</string>
<!-- Used as title on the automatic storage manager settings. [CHAR LIMIT=60] -->
<string name="automatic_storage_manager_settings">Manage storage</string>

View File

@@ -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" />
<ListPreference
android:entries="@array/bluetooth_audio_routing_titles"
@@ -40,7 +40,7 @@
android:key="audio_routing_call"
android:persistent="false"
android:title="@string/bluetooth_call_title"
settings:controller="com.android.settings.bluetooth.HearingDeviceCallRoutingPreferenceController" />
settings:controller="com.android.settings.accessibility.HearingDeviceCallRoutingPreferenceController" />
<ListPreference
android:entries="@array/bluetooth_audio_routing_titles"
@@ -49,7 +49,7 @@
android:key="audio_routing_media"
android:persistent="false"
android:title="@string/bluetooth_media_title"
settings:controller="com.android.settings.bluetooth.HearingDeviceMediaRoutingPreferenceController" />
settings:controller="com.android.settings.accessibility.HearingDeviceMediaRoutingPreferenceController" />
<ListPreference
android:entries="@array/bluetooth_audio_routing_titles"
@@ -58,7 +58,7 @@
android:key="audio_routing_system_sounds"
android:persistent="false"
android:title="@string/bluetooth_system_sounds_title"
settings:controller="com.android.settings.bluetooth.HearingDeviceSystemSoundsRoutingPreferenceController" />
settings:controller="com.android.settings.accessibility.HearingDeviceSystemSoundsRoutingPreferenceController" />
<com.android.settings.accessibility.AccessibilityFooterPreference
android:key="hearing_device_footer"

View File

@@ -45,8 +45,17 @@
<PreferenceCategory
android:key="hearing_options_category"
android:title="@string/accessibility_screen_option">
<Preference
android:key="audio_routing"
android:title="@string/bluetooth_audio_routing_title"
android:summary="@string/bluetooth_audio_routing_summary"
android:fragment="com.android.settings.accessibility.AccessibilityAudioRoutingFragment"
settings:controller="com.android.settings.accessibility.HearingAidAudioRoutingPreferenceController"/>
<SwitchPreference
android:key="hearing_aid_compatibility"
android:order="30"
android:title="@string/accessibility_hac_mode_title"
android:summary="@string/accessibility_hac_mode_summary"
settings:searchable="true"

View File

@@ -69,12 +69,7 @@
android:key="device_companion_apps"/>
<PreferenceCategory
android:key="device_controls_general"
android:title="@string/bluetooth_device_controls_general"/>
<PreferenceCategory
android:key="device_controls_specific"
android:title="@string/bluetooth_device_controls_specific"/>
android:key="device_controls_general" />
<PreferenceCategory
android:key="spatial_audio_group"/>

View File

@@ -19,10 +19,9 @@
android:key="managed_profile_settings_screen"
android:title="@string/managed_profile_settings_title">
<SwitchPreference
<com.android.settingslib.widget.MainSwitchPreference
android:key="work_mode"
android:title="@string/work_mode_label"
android:summary="@string/summary_placeholder"
settings:controller="com.android.settings.accounts.WorkModePreferenceController"/>
<com.android.settingslib.RestrictedSwitchPreference
@@ -38,4 +37,9 @@
android:title="@string/cross_profile_calendar_title"
settings:controller="com.android.settings.accounts.CrossProfileCalendarPreferenceController"/>
<com.android.settingslib.widget.FooterPreference
android:title="@string/managed_profile_settings_footer"
android:key="managed_profile_footer"
settings:searchable="false"/>
</PreferenceScreen>

View File

@@ -38,9 +38,10 @@
settings:controller="com.android.settings.network.BluetoothWiFiResetPreferenceController" />
<!-- Reset app preferences -->
<Preference
<com.android.settingslib.RestrictedPreference
android:key="reset_app_prefs"
android:title="@string/reset_app_preferences" />
android:title="@string/reset_app_preferences"
settings:userRestriction="no_control_apps" />
<!-- Erase Euicc data -->
<Preference

View File

@@ -51,8 +51,9 @@ public class SettingsApplication extends Application {
// Set Spa environment.
setSpaEnvironment();
if (FeatureFlagUtils.isEnabled(this, FeatureFlagUtils.SETTINGS_SUPPORT_LARGE_SCREEN)
&& ActivityEmbeddingUtils.isSettingsSplitEnabled(this)) {
if (ActivityEmbeddingUtils.isSettingsSplitEnabled(this)
&& FeatureFlagUtils.isEnabled(this,
FeatureFlagUtils.SETTINGS_SUPPORT_LARGE_SCREEN)) {
if (WizardManagerHelper.isUserSetupComplete(this)) {
new ActivityEmbeddingRulesController(this).initRules();
} else {

View File

@@ -0,0 +1,52 @@
/*
* 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 android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH;
import android.app.settings.SettingsEnums;
import com.android.settings.R;
import com.android.settings.dashboard.RestrictedDashboardFragment;
import com.android.settings.search.BaseSearchIndexProvider;
/** Settings fragment containing bluetooth audio routing. */
public class AccessibilityAudioRoutingFragment extends RestrictedDashboardFragment {
private static final String TAG = "AccessibilityAudioRoutingFragment";
public AccessibilityAudioRoutingFragment() {
super(DISALLOW_CONFIG_BLUETOOTH);
}
@Override
public int getMetricsCategory() {
return SettingsEnums.HEARING_AID_AUDIO_ROUTING;
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.accessibility_audio_routing_fragment;
}
@Override
protected String getLogTag() {
return TAG;
}
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider(R.xml.accessibility_audio_routing_fragment);
}

View File

@@ -17,7 +17,6 @@
package com.android.settings.accessibility;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHapClient;
import android.bluetooth.BluetoothHearingAid;
import android.bluetooth.BluetoothLeAudio;
@@ -41,20 +40,14 @@ import com.android.settings.core.BasePreferenceController;
import com.android.settings.core.SubSettingLauncher;
import com.android.settingslib.bluetooth.BluetoothCallback;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.HapClientProfile;
import com.android.settingslib.bluetooth.HearingAidInfo;
import com.android.settingslib.bluetooth.HearingAidProfile;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnStart;
import com.android.settingslib.core.lifecycle.events.OnStop;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Controller that shows and updates the bluetooth device name
@@ -73,10 +66,8 @@ public class AccessibilityHearingAidPreferenceController extends BasePreferenceC
};
private final LocalBluetoothManager mLocalBluetoothManager;
private final BluetoothAdapter mBluetoothAdapter;
private final LocalBluetoothProfileManager mProfileManager;
private final CachedBluetoothDeviceManager mCachedDeviceManager;
private final HearingAidHelper mHelper;
private FragmentManager mFragmentManager;
public AccessibilityHearingAidPreferenceController(Context context, String preferenceKey) {
@@ -84,8 +75,7 @@ public class AccessibilityHearingAidPreferenceController extends BasePreferenceC
mLocalBluetoothManager = com.android.settings.bluetooth.Utils.getLocalBluetoothManager(
context);
mProfileManager = mLocalBluetoothManager.getProfileManager();
mCachedDeviceManager = mLocalBluetoothManager.getCachedDeviceManager();
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
mHelper = new HearingAidHelper(context);
}
@Override
@@ -96,7 +86,7 @@ public class AccessibilityHearingAidPreferenceController extends BasePreferenceC
@Override
public int getAvailabilityStatus() {
return isHearingAidSupported() ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
return mHelper.isHearingAidSupported() ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
}
@Override
@@ -111,7 +101,7 @@ public class AccessibilityHearingAidPreferenceController extends BasePreferenceC
// Can't get connected hearing aids when hearing aids related profiles are not ready. The
// profiles will be ready after the services are connected. Needs to add listener and
// updates the information when all hearing aids related services are connected.
if (isAnyHearingAidRelatedProfilesNotReady()) {
if (!mHelper.isAllHearingAidRelatedProfilesReady()) {
mProfileManager.addServiceListener(this);
}
}
@@ -126,7 +116,7 @@ public class AccessibilityHearingAidPreferenceController extends BasePreferenceC
@Override
public boolean handlePreferenceTreeClick(Preference preference) {
if (TextUtils.equals(preference.getKey(), getPreferenceKey())) {
final CachedBluetoothDevice device = getConnectedHearingAidDevice();
final CachedBluetoothDevice device = mHelper.getConnectedHearingAidDevice();
if (FeatureFlagUtils.isEnabled(mContext,
FeatureFlagUtils.SETTINGS_ACCESSIBILITY_HEARING_AID_PAGE)) {
launchHearingAidPage();
@@ -144,10 +134,7 @@ public class AccessibilityHearingAidPreferenceController extends BasePreferenceC
@Override
public CharSequence getSummary() {
if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
return mContext.getText(R.string.accessibility_hearingaid_not_connected_summary);
}
final CachedBluetoothDevice device = getConnectedHearingAidDevice();
final CachedBluetoothDevice device = mHelper.getConnectedHearingAidDevice();
if (device == null) {
return mContext.getText(R.string.accessibility_hearingaid_not_connected_summary);
}
@@ -203,7 +190,7 @@ public class AccessibilityHearingAidPreferenceController extends BasePreferenceC
@Override
public void onServiceConnected() {
if (!isAnyHearingAidRelatedProfilesNotReady()) {
if (mHelper.isAllHearingAidRelatedProfilesReady()) {
updateState(mHearingAidPreference);
mProfileManager.removeServiceListener(this);
}
@@ -218,53 +205,8 @@ public class AccessibilityHearingAidPreferenceController extends BasePreferenceC
mFragmentManager = fragmentManager;
}
@VisibleForTesting
CachedBluetoothDevice getConnectedHearingAidDevice() {
final List<BluetoothDevice> deviceList = getConnectedHearingAidDeviceList();
return deviceList.isEmpty() ? null : mCachedDeviceManager.findDevice(deviceList.get(0));
}
private int getConnectedHearingAidDeviceNum() {
return getConnectedHearingAidDeviceList().size();
}
private List<BluetoothDevice> getConnectedHearingAidDeviceList() {
if (!isHearingAidSupported()) {
return new ArrayList<>();
}
final List<BluetoothDevice> 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<Integer> 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)

View File

@@ -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;

View File

@@ -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<CharSequence> 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= */

View File

@@ -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;
}
}

View File

@@ -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<BluetoothDevice> getConnectedHearingAidDeviceList() {
if (!isHearingAidSupported()) {
return new ArrayList<>();
}
final List<BluetoothDevice> 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<BluetoothDevice> 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<Integer> 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;
}
}

View File

@@ -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<AudioProductStrategy> supportedStrategies = mHelper.getSupportedStrategies(
audioAttributes);
final List<AudioProductStrategy> 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<String> 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.
*

View File

@@ -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(),

View File

@@ -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(),

View File

@@ -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(),

View File

@@ -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(),

View File

@@ -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;
}

View File

@@ -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;
}
}
@Override
public int getSliceHighlightMenuRes() {
return R.string.menu_key_accounts;
}
}

View File

@@ -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());
}
}
};
}

View File

@@ -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;
}
}
@Override
public int getSliceHighlightMenuRes() {
return R.string.menu_key_accounts;
}
@VisibleForTesting
void setPreference(MainSwitchPreference preference) {
mSwitchPreference = preference;
}
}

View File

@@ -97,16 +97,24 @@ public class ActivityEmbeddingUtils {
* </ul>
*/
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);
}
}

View File

@@ -51,7 +51,7 @@ public final class CombinedProviderInfo {
@Nullable List<CredentialProviderInfo> cpis,
@Nullable AutofillServiceInfo asi,
boolean isDefaultAutofillProvider,
boolean IsPrimaryCredmanProvider) {
boolean isPrimaryCredmanProvider) {
if (cpis == null) {
mCredentialProviderInfos = new ArrayList<>();
} else {
@@ -59,11 +59,11 @@ public final class CombinedProviderInfo {
}
mAutofillServiceInfo = asi;
mIsDefaultAutofillProvider = isDefaultAutofillProvider;
mIsPrimaryCredmanProvider = IsPrimaryCredmanProvider;
mIsPrimaryCredmanProvider = isPrimaryCredmanProvider;
}
/** Returns the credential provider info. */
@Nullable
@NonNull
public List<CredentialProviderInfo> getCredentialProviderInfos() {
return mCredentialProviderInfos;
}
@@ -85,11 +85,13 @@ public final class CombinedProviderInfo {
/** Returns the app icon. */
@Nullable
public Drawable getAppIcon(@NonNull Context context, int userId) {
IconDrawableFactory factory = IconDrawableFactory.newInstance(context);
final IconDrawableFactory factory = IconDrawableFactory.newInstance(context);
final ServiceInfo brandingService = getBrandingService();
final ApplicationInfo appInfo = getApplicationInfo();
Drawable icon = null;
ServiceInfo brandingService = getBrandingService();
if (brandingService != null) {
icon = factory.getBadgedIcon(brandingService, getApplicationInfo(), userId);
if (brandingService != null && appInfo != null) {
icon = factory.getBadgedIcon(brandingService, appInfo, userId);
}
// If the branding service gave us a icon then use that.
@@ -98,7 +100,10 @@ public final class CombinedProviderInfo {
}
// Otherwise fallback to the app icon and then the package name.
return factory.getBadgedIcon(getApplicationInfo(), userId);
if (appInfo != null) {
return factory.getBadgedIcon(appInfo, userId);
}
return null;
}
/** Returns the app name. */
@@ -116,11 +121,14 @@ public final class CombinedProviderInfo {
}
// Otherwise fallback to the app label and then the package name.
name = getApplicationInfo().loadLabel(context.getPackageManager());
if (TextUtils.isEmpty(name)) {
name = getApplicationInfo().packageName;
final ApplicationInfo appInfo = getApplicationInfo();
if (appInfo != null) {
name = appInfo.loadLabel(context.getPackageManager());
if (TextUtils.isEmpty(name)) {
return appInfo.packageName;
}
}
return name;
return "";
}
/** Gets the service to use for branding (name, icons). */
@@ -245,8 +253,10 @@ public final class CombinedProviderInfo {
// Now go through and build the joint datasets.
List<CombinedProviderInfo> cmpi = new ArrayList<>();
for (String packageName : packageNames) {
List<AutofillServiceInfo> asi = autofillServices.get(packageName);
List<CredentialProviderInfo> cpi = credmanServices.get(packageName);
List<AutofillServiceInfo> asi =
autofillServices.getOrDefault(packageName, new ArrayList<>());
List<CredentialProviderInfo> cpi =
credmanServices.getOrDefault(packageName, new ArrayList<>());
// If there are multiple autofill services then pick the first one.
AutofillServiceInfo selectedAsi = null;

View File

@@ -35,6 +35,7 @@ import android.view.autofill.AutofillManager;
import com.android.settings.applications.defaultapps.DefaultAppPreferenceController;
import com.android.settingslib.applications.DefaultAppInfo;
import java.util.ArrayList;
import java.util.List;
public class DefaultCombinedPreferenceController extends DefaultAppPreferenceController {
@@ -110,15 +111,19 @@ public class DefaultCombinedPreferenceController extends DefaultAppPreferenceCon
private List<CombinedProviderInfo> getAllProviders(int userId) {
final List<AutofillServiceInfo> autofillProviders =
AutofillServiceInfo.getAvailableServices(mContext, userId);
final List<CredentialProviderInfo> credManProviders =
mCredentialManager.getCredentialProviderServices(
userId, CredentialManager.PROVIDER_FILTER_USER_PROVIDERS_ONLY);
final String selectedAutofillProvider =
Settings.Secure.getStringForUser(
mContext.getContentResolver(),
DefaultCombinedPicker.AUTOFILL_SETTING,
userId);
final List<CredentialProviderInfo> credManProviders = new ArrayList<>();
if (mCredentialManager != null) {
credManProviders.addAll(
mCredentialManager.getCredentialProviderServices(
userId, CredentialManager.PROVIDER_FILTER_USER_PROVIDERS_ONLY));
}
return CombinedProviderInfo.buildMergedList(
autofillProviders, credManProviders, selectedAutofillProvider);
}

View File

@@ -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()) {

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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<BluetoothDevice> 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<CachedBluetoothDevice> 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;
}

View File

@@ -33,6 +33,7 @@ import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SWIT
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITLE;
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITLE_URI;
import android.app.PendingIntent;
import android.app.settings.SettingsEnums;
import android.content.ComponentName;
import android.content.Context;
@@ -75,6 +76,8 @@ import com.android.settingslib.drawer.TileUtils;
import com.android.settingslib.utils.ThreadUtils;
import com.android.settingslib.widget.AdaptiveIcon;
import com.google.common.collect.Iterables;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -152,7 +155,14 @@ public class DashboardFeatureProviderImpl implements DashboardFeatureProvider {
}
bindIcon(pref, tile, forceRoundedIcon);
if (tile instanceof ActivityTile) {
if (tile.hasPendingIntent()) {
// Pending intent cannot be launched within the settings app panel, and will thus always
// be executed directly.
pref.setOnPreferenceClickListener(preference -> {
launchPendingIntentOrSelectProfile(activity, tile, fragment.getMetricsCategory());
return true;
});
} else if (tile instanceof ActivityTile) {
final int sourceMetricsCategory = fragment.getMetricsCategory();
final Bundle metadata = tile.getMetaData();
String clsName = null;
@@ -441,6 +451,33 @@ public class DashboardFeatureProviderImpl implements DashboardFeatureProvider {
preference.setIcon(iconDrawable);
}
private void launchPendingIntentOrSelectProfile(FragmentActivity activity, Tile tile,
int sourceMetricCategory) {
ProfileSelectDialog.updatePendingIntentsIfNeeded(mContext, tile);
if (tile.pendingIntentMap.isEmpty()) {
Log.w(TAG, "Cannot resolve pendingIntent, skipping. " + tile.getIntent());
return;
}
mMetricsFeatureProvider.logSettingsTileClick(tile.getKey(mContext), sourceMetricCategory);
// Launch the pending intent directly if there's only one available.
if (tile.pendingIntentMap.size() == 1) {
PendingIntent pendingIntent = Iterables.getOnlyElement(tile.pendingIntentMap.values());
try {
pendingIntent.send();
} catch (PendingIntent.CanceledException e) {
Log.w(TAG, "Failed executing pendingIntent. " + pendingIntent.getIntent(), e);
}
return;
}
ProfileSelectDialog.show(activity.getSupportFragmentManager(), tile,
sourceMetricCategory, /* onShowListener= */ null,
/* onDismissListener= */ null, /* onCancelListener= */ null);
}
private void launchIntentOrSelectProfile(FragmentActivity activity, Tile tile, Intent intent,
int sourceMetricCategory, TopLevelHighlightMixin highlightMixin,
boolean isDuplicateClick) {

View File

@@ -31,6 +31,7 @@ import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.LifecycleObserver;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceScreen;
@@ -47,7 +48,6 @@ import com.android.settingslib.PrimarySwitchPreference;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.drawer.DashboardCategory;
import com.android.settingslib.drawer.ProviderTile;
import com.android.settingslib.drawer.Tile;
import com.android.settingslib.search.Indexable;
@@ -55,6 +55,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -504,6 +505,10 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment
// Install dashboard tiles and collect pending observers.
final boolean forceRoundedIcons = shouldForceRoundedIcon();
final List<DynamicDataObserver> pendingObservers = new ArrayList<>();
// Move group tiles to the beginning of the list to ensure they are created before the
// other tiles.
tiles.sort(Comparator.comparingInt(tile -> tile.getType() == Tile.Type.GROUP ? 0 : 1));
for (Tile tile : tiles) {
final String key = mDashboardFeatureProvider.getDashboardKeyForTile(tile);
if (TextUtils.isEmpty(key)) {
@@ -526,7 +531,14 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment
observers = mDashboardFeatureProvider.bindPreferenceToTileAndGetObservers(
getActivity(), this, forceRoundedIcons, pref, tile, key,
mPlaceholderPreferenceController.getOrder());
screen.addPreference(pref);
if (tile.hasGroupKey() && mDashboardTilePrefKeys.containsKey(tile.getGroupKey())) {
final Preference group = screen.findPreference(tile.getGroupKey());
if (group instanceof PreferenceCategory) {
((PreferenceCategory) group).addPreference(pref);
}
} else {
screen.addPreference(pref);
}
registerDynamicDataObservers(observers);
mDashboardTilePrefKeys.put(key, observers);
}
@@ -569,11 +581,28 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment
}
protected Preference createPreference(Tile tile) {
return tile instanceof ProviderTile
? new SwitchPreference(getPrefContext())
: tile.hasSwitch()
? new PrimarySwitchPreference(getPrefContext())
: new Preference(getPrefContext());
switch (tile.getType()) {
case EXTERNAL_ACTION:
Preference externalActionPreference = new Preference(getPrefContext());
externalActionPreference
.setWidgetLayoutResource(R.layout.preference_external_action_icon);
return externalActionPreference;
case SWITCH:
return new SwitchPreference(getPrefContext());
case SWITCH_WITH_ACTION:
return new PrimarySwitchPreference(getPrefContext());
case GROUP:
mMetricsFeatureProvider.action(
mMetricsFeatureProvider.getAttribution(getActivity()),
SettingsEnums.ACTION_SETTINGS_GROUP_TILE_ADDED_TO_SCREEN,
getMetricsCategory(),
tile.getKey(getContext()),
/* value= */ 0);
return new PreferenceCategory((getPrefContext()));
case ACTION:
default:
return new Preference(getPrefContext());
}
}
@VisibleForTesting

View File

@@ -17,6 +17,7 @@
package com.android.settings.dashboard.profileselector;
import android.app.Dialog;
import android.app.PendingIntent;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
@@ -127,13 +128,25 @@ public class ProfileSelectDialog extends DialogFragment implements UserAdapter.O
@Override
public void onClick(int position) {
final UserHandle user = mSelectedTile.userHandle.get(position);
// Show menu on top level items.
final Intent intent = new Intent(mSelectedTile.getIntent());
FeatureFactory.getFactory(getContext()).getMetricsFeatureProvider()
.logStartedIntentWithProfile(intent, mSourceMetricCategory,
position == 1 /* isWorkProfile */);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
getActivity().startActivityAsUser(intent, user);
if (!mSelectedTile.hasPendingIntent()) {
final Intent intent = new Intent(mSelectedTile.getIntent());
FeatureFactory.getFactory(getContext()).getMetricsFeatureProvider()
.logStartedIntentWithProfile(intent, mSourceMetricCategory,
position == 1 /* isWorkProfile */);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
getActivity().startActivityAsUser(intent, user);
} else {
PendingIntent pendingIntent = mSelectedTile.pendingIntentMap.get(user);
FeatureFactory.getFactory(getContext()).getMetricsFeatureProvider()
.logSettingsTileClickWithProfile(mSelectedTile.getKey(getContext()),
mSourceMetricCategory,
position == 1 /* isWorkProfile */);
try {
pendingIntent.send();
} catch (PendingIntent.CanceledException e) {
Log.w(TAG, "Failed executing pendingIntent. " + pendingIntent.getIntent(), e);
}
}
dismiss();
}
@@ -178,4 +191,36 @@ public class ProfileSelectDialog extends DialogFragment implements UserAdapter.O
}
}
}
/**
* Checks the userHandle and pendingIntentMap in the provided tile, and remove the invalid
* entries if any.
*/
public static void updatePendingIntentsIfNeeded(Context context, Tile tile) {
if (tile.userHandle == null || tile.userHandle.size() <= 1
|| tile.pendingIntentMap.size() <= 1) {
return;
}
for (UserHandle userHandle : List.copyOf(tile.userHandle)) {
if (!tile.pendingIntentMap.containsKey(userHandle)) {
if (DEBUG) {
Log.d(TAG, "Delete the user without pending intent: "
+ userHandle.getIdentifier());
}
tile.userHandle.remove(userHandle);
}
}
final UserManager userManager = UserManager.get(context);
for (UserHandle userHandle : List.copyOf(tile.pendingIntentMap.keySet())) {
UserInfo userInfo = userManager.getUserInfo(userHandle.getIdentifier());
if (userInfo == null || userInfo.isCloneProfile()) {
if (DEBUG) {
Log.d(TAG, "Delete the user: " + userHandle.getIdentifier());
}
tile.userHandle.remove(userHandle);
tile.pendingIntentMap.remove(userHandle);
}
}
}
}

View File

@@ -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 {

View File

@@ -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<HomepageLoadedListener> 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;
}

View File

@@ -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(

View File

@@ -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<LocaleStore.LocaleInfo> mFeedItemList;
private List<LocaleStore.LocaleInfo> 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<LocaleStore.LocaleInfo> feedItemList) {
LocaleDragAndDropAdapter(LocaleListEditor parent, List<LocaleStore.LocaleInfo> 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<LocaleStore.LocaleInfo> 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<String> 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<String> 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());
}
}
}

View File

@@ -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<LocaleStore.LocaleInfo> 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() {

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -44,6 +44,7 @@ public class SlicePreferenceController extends BasePreferenceController implemen
LiveData<Slice> 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);
}
}
}

View File

@@ -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(

View File

@@ -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);
}
}

View File

@@ -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());
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;

View File

@@ -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);
}
}
@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();
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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());
}
}

View File

@@ -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);
}
}

View File

@@ -43,6 +43,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
@@ -57,6 +58,7 @@ import android.os.UserHandle;
import android.os.UserManager;
import android.util.Pair;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.preference.Preference;
import androidx.preference.SwitchPreference;
@@ -199,6 +201,27 @@ public class DashboardFeatureProviderImplTest {
assertThat(observers.get(0).getUri().toString()).isEqualTo(SWITCH_URI);
}
@Test
public void bindPreference_providerTileWithPendingIntent_shouldBindIntent() {
final Preference preference = new SwitchPreference(RuntimeEnvironment.application);
Bundle metaData = new Bundle();
metaData.putInt(META_DATA_PREFERENCE_TITLE, R.string.settings_label);
metaData.putInt(META_DATA_PREFERENCE_SUMMARY, R.string.about_settings_summary);
metaData.putInt(META_DATA_KEY_ORDER, 10);
metaData.putString(META_DATA_PREFERENCE_KEYHINT, KEY);
final Tile tile = new ProviderTile(mProviderInfo, CategoryKey.CATEGORY_HOMEPAGE, metaData);
PendingIntent pendingIntent =
PendingIntent.getActivity(RuntimeEnvironment.application, 0, new Intent("test"), 0);
tile.pendingIntentMap.put(UserHandle.CURRENT, pendingIntent);
mImpl.bindPreferenceToTileAndGetObservers(mActivity, mFragment, mForceRoundedIcon,
preference, tile, "123", Preference.DEFAULT_ORDER);
assertThat(preference.getFragment()).isNull();
assertThat(preference.getOnPreferenceClickListener()).isNotNull();
assertThat(preference.getOrder()).isEqualTo(tile.getOrder());
}
@Test
public void bindPreference_noFragmentMetadata_shouldBindIntent() {
final Preference preference = new Preference(RuntimeEnvironment.application);
@@ -630,6 +653,55 @@ public class DashboardFeatureProviderImplTest {
assertThat(launchIntent).isNull();
}
@Test
public void clickPreference_providerTileWithPendingIntent_singleUser_executesPendingIntent() {
final Preference preference = new SwitchPreference(RuntimeEnvironment.application);
Bundle metaData = new Bundle();
metaData.putInt(META_DATA_PREFERENCE_TITLE, R.string.settings_label);
metaData.putInt(META_DATA_PREFERENCE_SUMMARY, R.string.about_settings_summary);
metaData.putInt(META_DATA_KEY_ORDER, 10);
metaData.putString(META_DATA_PREFERENCE_KEYHINT, KEY);
final Tile tile = new ProviderTile(mProviderInfo, CategoryKey.CATEGORY_HOMEPAGE, metaData);
PendingIntent pendingIntent =
PendingIntent.getActivity(RuntimeEnvironment.application, 0, new Intent("test"), 0);
tile.pendingIntentMap.put(UserHandle.CURRENT, pendingIntent);
mImpl.bindPreferenceToTileAndGetObservers(mActivity, mFragment, mForceRoundedIcon,
preference, tile, "123", Preference.DEFAULT_ORDER);
preference.performClick();
Intent nextStartedActivity =
Shadows.shadowOf(RuntimeEnvironment.application).peekNextStartedActivity();
assertThat(nextStartedActivity).isNotNull();
assertThat(nextStartedActivity.getAction()).isEqualTo("test");
}
@Test
public void clickPreference_providerTileWithPendingIntent_multiUser_showsProfileDialog() {
final Preference preference = new SwitchPreference(RuntimeEnvironment.application);
Bundle metaData = new Bundle();
metaData.putInt(META_DATA_PREFERENCE_TITLE, R.string.settings_label);
metaData.putInt(META_DATA_PREFERENCE_SUMMARY, R.string.about_settings_summary);
metaData.putInt(META_DATA_KEY_ORDER, 10);
metaData.putString(META_DATA_PREFERENCE_KEYHINT, KEY);
final Tile tile = new ProviderTile(mProviderInfo, CategoryKey.CATEGORY_HOMEPAGE, metaData);
PendingIntent pendingIntent =
PendingIntent.getActivity(RuntimeEnvironment.application, 0, new Intent("test"), 0);
tile.pendingIntentMap.put(UserHandle.CURRENT, pendingIntent);
tile.pendingIntentMap.put(new UserHandle(10), pendingIntent);
mImpl.bindPreferenceToTileAndGetObservers(mActivity, mFragment, mForceRoundedIcon,
preference, tile, "123", Preference.DEFAULT_ORDER);
preference.performClick();
Fragment dialogFragment =
mActivity.getSupportFragmentManager().findFragmentByTag("select_profile");
assertThat(dialogFragment).isNotNull();
Intent nextStartedActivity =
Shadows.shadowOf(RuntimeEnvironment.application).peekNextStartedActivity();
assertThat(nextStartedActivity).isNull();
}
@Test
public void openTileIntent_profileSelectionDialog_shouldShow() {
ShadowUserManager.getShadow().addUser(10, "Someone", 0);

View File

@@ -16,7 +16,9 @@
package com.android.settings.dashboard;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.DASHBOARD_CONTAINER;
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_GROUP_KEY;
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_KEYHINT;
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_PENDING_INTENT;
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SWITCH_URI;
import static com.google.common.truth.Truth.assertThat;
@@ -30,6 +32,7 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.PendingIntent;
import android.app.settings.SettingsEnums;
import android.content.ContentResolver;
import android.content.Context;
@@ -38,15 +41,18 @@ import android.content.pm.ActivityInfo;
import android.content.pm.ProviderInfo;
import android.net.Uri;
import android.os.Bundle;
import android.os.UserHandle;
import android.preference.PreferenceManager.OnActivityResultListener;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceScreen;
import androidx.preference.SwitchPreference;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settings.R;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.slices.BlockingSlicePrefController;
import com.android.settings.testutils.FakeFeatureFactory;
@@ -57,6 +63,7 @@ import com.android.settingslib.core.instrumentation.VisibilityLoggerMixin;
import com.android.settingslib.drawer.ActivityTile;
import com.android.settingslib.drawer.DashboardCategory;
import com.android.settingslib.drawer.ProviderTile;
import com.android.settingslib.drawer.Tile;
import org.junit.Before;
import org.junit.Ignore;
@@ -177,6 +184,43 @@ public class DashboardFragmentTest {
verify(mTestFragment.mScreen, times(2)).addPreference(nullable(Preference.class));
}
@Test
public void displayTilesAsPreference_withGroup_shouldAddTilesIntoGroup() {
final ProviderInfo providerInfo = new ProviderInfo();
providerInfo.packageName = "pkg";
providerInfo.name = "provider";
providerInfo.authority = "authority";
final Bundle groupTileMetaData = new Bundle();
groupTileMetaData.putString(META_DATA_PREFERENCE_KEYHINT, "injected_tile_group_key");
ProviderTile groupTile = new ProviderTile(providerInfo, mDashboardCategory.key,
groupTileMetaData);
mDashboardCategory.addTile(groupTile);
final Bundle subTileMetaData = new Bundle();
subTileMetaData.putString(META_DATA_PREFERENCE_KEYHINT, "injected_tile_key3");
subTileMetaData.putString(META_DATA_PREFERENCE_GROUP_KEY, "injected_tile_group_key");
subTileMetaData.putParcelable(
META_DATA_PREFERENCE_PENDING_INTENT,
PendingIntent.getActivity(mContext, 0, new Intent(), 0));
ProviderTile subTile = new ProviderTile(providerInfo, mDashboardCategory.key,
subTileMetaData);
mDashboardCategory.addTile(subTile);
PreferenceCategory groupPreference = mock(PreferenceCategory.class);
when(mFakeFeatureFactory.dashboardFeatureProvider
.getTilesForCategory(nullable(String.class)))
.thenReturn(mDashboardCategory);
when(mFakeFeatureFactory.dashboardFeatureProvider
.getDashboardKeyForTile(any(Tile.class)))
.then(invocation -> ((Tile) invocation.getArgument(0)).getKey(mContext));
when(mTestFragment.mScreen.findPreference("injected_tile_group_key"))
.thenReturn(groupPreference);
mTestFragment.onCreatePreferences(new Bundle(), "rootKey");
verify(mTestFragment.mScreen, times(3)).addPreference(nullable(Preference.class));
verify(groupPreference).addPreference(nullable(Preference.class));
}
@Test
public void displayTilesAsPreference_shouldNotAddTilesWithoutIntent() {
mTestFragment.onCreatePreferences(new Bundle(), "rootKey");
@@ -351,6 +395,16 @@ public class DashboardFragmentTest {
assertThat(pref).isInstanceOf(SwitchPreference.class);
}
@Test
public void createPreference_isActivityTile_returnPreference() {
final Preference pref = mTestFragment.createPreference(mActivityTile);
assertThat(pref).isInstanceOf(Preference.class);
assertThat(pref).isNotInstanceOf(PrimarySwitchPreference.class);
assertThat(pref).isNotInstanceOf(SwitchPreference.class);
assertThat(pref.getWidgetLayoutResource()).isEqualTo(0);
}
@Test
public void createPreference_isActivityTileAndHasSwitch_returnPrimarySwitchPreference() {
mActivityTile.getMetaData().putString(META_DATA_PREFERENCE_SWITCH_URI, "uri");
@@ -360,6 +414,64 @@ public class DashboardFragmentTest {
assertThat(pref).isInstanceOf(PrimarySwitchPreference.class);
}
@Test
public void createPreference_isProviderTileWithPendingIntent_returnPreferenceWithIcon() {
final ProviderInfo providerInfo = new ProviderInfo();
providerInfo.packageName = "pkg";
providerInfo.name = "provider";
providerInfo.authority = "authority";
final Bundle metaData = new Bundle();
metaData.putString(META_DATA_PREFERENCE_KEYHINT, "injected_tile_key2");
ProviderTile providerTile = new ProviderTile(providerInfo, mDashboardCategory.key,
metaData);
providerTile.pendingIntentMap.put(
UserHandle.CURRENT, PendingIntent.getActivity(mContext, 0, new Intent(), 0));
final Preference pref = mTestFragment.createPreference(providerTile);
assertThat(pref).isInstanceOf(Preference.class);
assertThat(pref).isNotInstanceOf(PrimarySwitchPreference.class);
assertThat(pref).isNotInstanceOf(SwitchPreference.class);
assertThat(pref.getWidgetLayoutResource())
.isEqualTo(R.layout.preference_external_action_icon);
}
@Test
public void createPreference_isProviderTileWithPendingIntentAndSwitch_returnPrimarySwitch() {
mProviderTile.pendingIntentMap.put(
UserHandle.CURRENT, PendingIntent.getActivity(mContext, 0, new Intent(), 0));
final Preference pref = mTestFragment.createPreference(mProviderTile);
assertThat(pref).isInstanceOf(PrimarySwitchPreference.class);
}
@Test
public void createPreference_isGroupTile_returnPreferenceCategory_logTileAdded() {
final ProviderInfo providerInfo = new ProviderInfo();
providerInfo.packageName = "pkg";
providerInfo.name = "provider";
providerInfo.authority = "authority";
final Bundle metaData = new Bundle();
metaData.putString(META_DATA_PREFERENCE_KEYHINT, "injected_tile_key2");
ProviderTile providerTile =
new ProviderTile(providerInfo, mDashboardCategory.key, metaData);
MetricsFeatureProvider metricsFeatureProvider =
mFakeFeatureFactory.getMetricsFeatureProvider();
when(metricsFeatureProvider.getAttribution(any())).thenReturn(123);
final Preference pref = mTestFragment.createPreference(providerTile);
assertThat(pref).isInstanceOf(PreferenceCategory.class);
verify(metricsFeatureProvider)
.action(
123,
SettingsEnums.ACTION_SETTINGS_GROUP_TILE_ADDED_TO_SCREEN,
mTestFragment.getMetricsCategory(),
"injected_tile_key2",
0);
}
@Test
public void onActivityResult_test() {
final int requestCode = 10;

View File

@@ -24,7 +24,9 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.Dialog;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.UserInfo;
import android.os.UserHandle;
@@ -118,6 +120,28 @@ public class ProfileSelectDialogTest {
verify(mUserManager, times(1)).getUserInfo(CLONE_USER.getIdentifier());
}
@Test
public void updatePendingIntentsIfNeeded_removesUsersWithNoPendingIntentsAndCloneProfile() {
final UserInfo userInfo = new UserInfo(CLONE_USER.getIdentifier(), "clone_user", null,
UserInfo.FLAG_PROFILE, UserManager.USER_TYPE_PROFILE_CLONE);
when(mUserManager.getUserInfo(CLONE_USER.getIdentifier())).thenReturn(userInfo);
final Tile tile = new ActivityTile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE);
tile.userHandle.add(CLONE_USER);
tile.userHandle.add(NORMAL_USER);
tile.userHandle.add(new UserHandle(10));
PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
tile.pendingIntentMap.put(CLONE_USER, pendingIntent);
tile.pendingIntentMap.put(NORMAL_USER, pendingIntent);
ProfileSelectDialog.updatePendingIntentsIfNeeded(mContext, tile);
assertThat(tile.userHandle).hasSize(1);
assertThat(tile.userHandle).containsExactly(NORMAL_USER);
assertThat(tile.pendingIntentMap).hasSize(1);
assertThat(tile.pendingIntentMap).containsKey(NORMAL_USER);
verify(mUserManager, times(1)).getUserInfo(CLONE_USER.getIdentifier());
}
@Test
public void createDialog_showsCorrectTitle() {
mContext.setTheme(R.style.Theme_AppCompat);

View File

@@ -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,

View File

@@ -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);
}
}

View File

@@ -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<Slice> 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);

View File

@@ -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);