From 46f8b18446c09f930f713ce3161d6437b483eb0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timi=20Rautam=C3=A4ki?= Date: Mon, 13 Dec 2021 16:10:40 +0000 Subject: [PATCH 1/8] Settings: only set phone number if it's not empty MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Only set the phone number in Rename mobile network dialog if it's not empty in order to preserve the ´Not available´ -text. Test: m, open ´SIMs´ in settings, click Pen-icon to edit SIM, verify either mobile number or ´Not available´ -text is shown. Change-Id: I3bc1f1f4eb26cda38e5cc7a12bd4db1bf922f076 --- .../network/telephony/RenameMobileNetworkDialogFragment.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/com/android/settings/network/telephony/RenameMobileNetworkDialogFragment.java b/src/com/android/settings/network/telephony/RenameMobileNetworkDialogFragment.java index 5f208949e52..ec48c82ce4a 100644 --- a/src/com/android/settings/network/telephony/RenameMobileNetworkDialogFragment.java +++ b/src/com/android/settings/network/telephony/RenameMobileNetworkDialogFragment.java @@ -184,7 +184,10 @@ public class RenameMobileNetworkDialogFragment extends InstrumentedDialogFragmen phoneTitle.setVisibility(info.isOpportunistic() ? View.GONE : View.VISIBLE); final TextView phoneNumber = view.findViewById(R.id.number_value); - phoneNumber.setText(DeviceInfoUtils.getBidiFormattedPhoneNumber(getContext(), info)); + final String pn = DeviceInfoUtils.getBidiFormattedPhoneNumber(getContext(), info); + if (!TextUtils.isEmpty(pn)) { + phoneNumber.setText(pn); + } } @Override From abae445ca8be83589c592a1e18921dcb41bff43f Mon Sep 17 00:00:00 2001 From: jasonwshsu Date: Wed, 16 Mar 2022 16:07:56 +0800 Subject: [PATCH 2/8] Add the string for new flow to hint user about triple-tap will delay when user select triple-tap shortcut on window-mode. Bug: 210593079 Test: manual test Change-Id: Id12f7f5be24842e8c5ea4b1e15d9b9bbef881f0b --- res/values/strings.xml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/res/values/strings.xml b/res/values/strings.xml index bd9cbbd2527..a9b367d21f7 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -5251,6 +5251,14 @@ Switch to accessibility button Use triple-tap + + This may slow down your keyboard + + When using triple-tap to magnify part of your screen, you may notice issues over the keyboard.\n\nTo avoid this, you can change your magnification shortcut from triple-tap to another option.\nChange setting + + Continue anyway + + Cancel Magnification settings From e7c89d616a30f1f2811dab9cac11c01b2ea59e30 Mon Sep 17 00:00:00 2001 From: SongFerngWang Date: Thu, 10 Mar 2022 23:43:20 +0800 Subject: [PATCH 3/8] [LE]Gray out the a2dp and hfp when LeAudio is enabled 1.Gray out the a2dp and hfp when le audio is enabled 2.When the user disables le audio, then the settings turn on the a2dp and hfp 3.When the user enables le audio, then the settings turn off the a2dp and hfp Bug: 218626162 Test: build pass. Change-Id: Ic728749112b0047cac291600b3279b9dedbf0b5a Merged-In: Ic728749112b0047cac291600b3279b9dedbf0b5a --- .../BluetoothDetailsProfilesController.java | 215 +++++++++++++++++- 1 file changed, 207 insertions(+), 8 deletions(-) diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java b/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java index aacf41fbbc9..b57ea928d99 100644 --- a/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java +++ b/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java @@ -16,10 +16,12 @@ package com.android.settings.bluetooth; +import android.bluetooth.BluetoothCsipSetCoordinator; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; import android.content.Context; import android.text.TextUtils; +import android.util.Log; import androidx.annotation.VisibleForTesting; import androidx.preference.Preference; @@ -31,6 +33,7 @@ import androidx.preference.SwitchPreference; import com.android.settings.R; import com.android.settingslib.bluetooth.A2dpProfile; import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.LeAudioProfile; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothProfile; import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; @@ -39,7 +42,10 @@ import com.android.settingslib.bluetooth.PanProfile; import com.android.settingslib.bluetooth.PbapServerProfile; import com.android.settingslib.core.lifecycle.Lifecycle; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** * This class adds switches for toggling the individual profiles that a Bluetooth device @@ -48,8 +54,11 @@ import java.util.List; public class BluetoothDetailsProfilesController extends BluetoothDetailsController implements Preference.OnPreferenceClickListener, LocalBluetoothProfileManager.ServiceListener { + private static final String TAG = "BtDetailsProfilesCtrl"; + private static final String KEY_PROFILES_GROUP = "bluetooth_profiles"; private static final String KEY_BOTTOM_PREFERENCE = "bottom_preference"; + private static final String HEADSET_CLIENT = "HEADSET_CLIENT"; private static final int ORDINAL = 99; @VisibleForTesting @@ -58,6 +67,9 @@ public class BluetoothDetailsProfilesController extends BluetoothDetailsControll private LocalBluetoothManager mManager; private LocalBluetoothProfileManager mProfileManager; private CachedBluetoothDevice mCachedDevice; + private List mAllOfCachedDevices; + private Map> mProfileDeviceMap = + new HashMap>(); @VisibleForTesting PreferenceCategory mProfilesContainer; @@ -68,6 +80,7 @@ public class BluetoothDetailsProfilesController extends BluetoothDetailsControll mManager = manager; mProfileManager = mManager.getProfileManager(); mCachedDevice = device; + mAllOfCachedDevices = getAllOfCachedBluetoothDevices(); lifecycle.addObserver(this); } @@ -100,11 +113,66 @@ public class BluetoothDetailsProfilesController extends BluetoothDetailsControll /** * Refreshes the state for an existing SwitchPreference for a profile. + * If the LeAudio profile is enabled on the LeAudio devices, then the SwitchPreferences of + * A2dp profile and Hfp profile are graied out. */ private void refreshProfilePreference(SwitchPreference profilePref, LocalBluetoothProfile profile) { BluetoothDevice device = mCachedDevice.getDevice(); - profilePref.setEnabled(!mCachedDevice.isBusy()); + boolean isLeAudioEnabled = false; + if (profile instanceof A2dpProfile || HEADSET_CLIENT.equals(profile.toString())) { + LocalBluetoothProfile leAudio = mProfileManager.getLeAudioProfile(); + if (leAudio != null) { + List leAudioDeviceList = mProfileDeviceMap.get( + leAudio.toString()); + if (leAudioDeviceList != null + && leAudioDeviceList.stream() + .anyMatch(item -> leAudio.isEnabled(item.getDevice()))) { + isLeAudioEnabled = true; + } + } + if (isLeAudioEnabled) { + // If the LeAudio profile is enabled on the LeAudio devices, then the + // SwitchPreferences of A2dp profile and Hfp profile are graied out. + profilePref.setEnabled(false); + } else { + List deviceList = mProfileDeviceMap.get( + profile.toString()); + boolean isBusy = deviceList != null + && deviceList.stream().anyMatch(item -> item.isBusy()); + profilePref.setEnabled(!isBusy); + } + } else if (profile instanceof LeAudioProfile) { + List leAudioDeviceList = mProfileDeviceMap.get( + profile.toString()); + boolean isLeAudioProfileEnable = + leAudioDeviceList != null && leAudioDeviceList.stream().anyMatch( + item -> profile.isEnabled(item.getDevice())); + boolean isBusy = leAudioDeviceList != null + && leAudioDeviceList.stream().anyMatch(item -> item.isBusy()); + if (isLeAudioProfileEnable && !isBusy) { + LocalBluetoothProfile a2dp = mProfileManager.getA2dpProfile(); + LocalBluetoothProfile hfp = mProfileManager.getHfpClientProfile(); + // If the LeAudio profile is enabled on the LeAudio devices, then the + // SwitchPreferences of A2dp profile and Hfp profile are graied out. + if (a2dp != null) { + SwitchPreference pref = mProfilesContainer.findPreference(a2dp.toString()); + if (pref != null) { + pref.setEnabled(false); + } + } + if (hfp != null) { + SwitchPreference pref = mProfilesContainer.findPreference(hfp.toString()); + if (pref != null) { + pref.setEnabled(false); + } + } + } + profilePref.setEnabled(!isBusy); + } else { + profilePref.setEnabled(!mCachedDevice.isBusy()); + } + if (profile instanceof MapProfile) { profilePref.setChecked(device.getMessageAccessPermission() == BluetoothDevice.ACCESS_ALLOWED); @@ -127,7 +195,7 @@ public class BluetoothDetailsProfilesController extends BluetoothDetailsControll highQualityPref.setVisible(true); highQualityPref.setTitle(a2dp.getHighQualityAudioOptionLabel(device)); highQualityPref.setChecked(a2dp.isHighQualityAudioEnabled(device)); - highQualityPref.setEnabled(!mCachedDevice.isBusy()); + highQualityPref.setEnabled(!mCachedDevice.isBusy() && !isLeAudioEnabled); } else { highQualityPref.setVisible(false); } @@ -148,6 +216,12 @@ public class BluetoothDetailsProfilesController extends BluetoothDetailsControll if (profile instanceof MapProfile) { bluetoothDevice.setMessageAccessPermission(BluetoothDevice.ACCESS_ALLOWED); } + + if (profile instanceof LeAudioProfile) { + enableLeAudioProfile(profile); + return; + } + profile.setEnabled(bluetoothDevice, true); } @@ -155,8 +229,14 @@ public class BluetoothDetailsProfilesController extends BluetoothDetailsControll * Helper method to disable a profile for a device */ private void disableProfile(LocalBluetoothProfile profile) { + if (profile instanceof LeAudioProfile) { + disableLeAudioProfile(profile); + return; + } + final BluetoothDevice bluetoothDevice = mCachedDevice.getDevice(); profile.setEnabled(bluetoothDevice, false); + if (profile instanceof MapProfile) { bluetoothDevice.setMessageAccessPermission(BluetoothDevice.ACCESS_REJECTED); } else if (profile instanceof PbapServerProfile) { @@ -190,14 +270,33 @@ public class BluetoothDetailsProfilesController extends BluetoothDetailsControll return true; } - /** * Helper to get the list of connectable and special profiles. */ private List getProfiles() { - List result = mCachedDevice.getConnectableProfiles(); - final BluetoothDevice device = mCachedDevice.getDevice(); + List result = new ArrayList(); + mProfileDeviceMap.clear(); + if (mAllOfCachedDevices == null || mAllOfCachedDevices.isEmpty()) { + return result; + } + for (CachedBluetoothDevice cachedItem : mAllOfCachedDevices) { + List tmpResult = cachedItem.getConnectableProfiles(); + for (LocalBluetoothProfile profile : tmpResult) { + if (mProfileDeviceMap.containsKey(profile.toString())) { + mProfileDeviceMap.get(profile.toString()).add(cachedItem); + Log.d(TAG, "getProfiles: " + profile.toString() + " add device " + + cachedItem.getDevice().getAnonymizedAddress()); + } else { + List tmpCachedDeviceList = + new ArrayList(); + tmpCachedDeviceList.add(cachedItem); + mProfileDeviceMap.put(profile.toString(), tmpCachedDeviceList); + result.add(profile); + } + } + } + final BluetoothDevice device = mCachedDevice.getDevice(); final int pbapPermission = device.getPhonebookAccessPermission(); // Only provide PBAP cabability if the client device has requested PBAP. if (pbapPermission != BluetoothDevice.ACCESS_UNKNOWN) { @@ -210,10 +309,93 @@ public class BluetoothDetailsProfilesController extends BluetoothDetailsControll if (mapPermission != BluetoothDevice.ACCESS_UNKNOWN) { result.add(mapProfile); } - + Log.d(TAG, "getProfiles:result:" + result); return result; } + private List getAllOfCachedBluetoothDevices() { + List cachedBluetoothDevices = new ArrayList<>(); + if (mCachedDevice == null) { + return cachedBluetoothDevices; + } + cachedBluetoothDevices.add(mCachedDevice); + if (mCachedDevice.getGroupId() != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) { + for (CachedBluetoothDevice member : mCachedDevice.getMemberDevice()) { + cachedBluetoothDevices.add(member); + } + } + return cachedBluetoothDevices; + } + + /** + * When user disable the Le Audio profile, the system needs to do two things. + * 1) Disable the Le Audio profile for each of the Le Audio devices. + * 2) Enable the A2dp profile and Hfp profile for the associated device. The system can't + * enable the A2dp profile and Hfp profile if the Le Audio profile is enabled. + * + * @param profile the LeAudio profile + */ + private void disableLeAudioProfile(LocalBluetoothProfile profile) { + if (profile == null || mProfileDeviceMap.get(profile.toString()) == null) { + Log.e(TAG, "There is no the LE profile or no device in mProfileDeviceMap. Do nothing."); + return; + } + for (CachedBluetoothDevice leAudioDevice : mProfileDeviceMap.get(profile.toString())) { + profile.setEnabled(leAudioDevice.getDevice(), false); + } + + LocalBluetoothProfile a2dp = mProfileManager.getA2dpProfile(); + LocalBluetoothProfile hfp = mProfileManager.getHfpClientProfile(); + if (a2dp != null && mProfileDeviceMap.get(a2dp.toString()) != null) { + for (CachedBluetoothDevice a2dpDevice : mProfileDeviceMap.get(a2dp.toString())) { + if (!a2dp.isEnabled(a2dpDevice.getDevice())) { + a2dp.setEnabled(a2dpDevice.getDevice(), true); + } + } + } + if (hfp != null && mProfileDeviceMap.get(hfp.toString()) != null) { + for (CachedBluetoothDevice hfpDevice : mProfileDeviceMap.get(hfp.toString())) { + if (!hfp.isEnabled(hfpDevice.getDevice())) { + hfp.setEnabled(hfpDevice.getDevice(), true); + } + } + } + } + + /** + * When user enable the Le Audio profile, the system needs to do two things. + * 1) Disable the A2dp profile and Hfp profile for the associated device. The system can't + * enable the Le Audio if the A2dp profile and Hfp profile are enabled. + * 2) Enable the Le Audio profile for each of the Le Audio devices. + * + * @param profile the LeAudio profile + */ + private void enableLeAudioProfile(LocalBluetoothProfile profile) { + if (profile == null || mProfileDeviceMap.get(profile.toString()) == null) { + Log.e(TAG, "There is no the LE profile or no device in mProfileDeviceMap. Do nothing."); + return; + } + LocalBluetoothProfile a2dp = mProfileManager.getA2dpProfile(); + LocalBluetoothProfile hfp = mProfileManager.getHfpClientProfile(); + if (a2dp != null && mProfileDeviceMap.get(a2dp.toString()) != null) { + for (CachedBluetoothDevice a2dpDevice : mProfileDeviceMap.get(a2dp.toString())) { + if (a2dp.isEnabled(a2dpDevice.getDevice())) { + a2dp.setEnabled(a2dpDevice.getDevice(), false); + } + } + } + if (hfp != null && mProfileDeviceMap.get(hfp.toString()) != null) { + for (CachedBluetoothDevice hfpDevice : mProfileDeviceMap.get(hfp.toString())) { + if (hfp.isEnabled(hfpDevice.getDevice())) { + hfp.setEnabled(hfpDevice.getDevice(), false); + } + } + } + for (CachedBluetoothDevice leAudioDevice : mProfileDeviceMap.get(profile.toString())) { + profile.setEnabled(leAudioDevice.getDevice(), true); + } + } + /** * This is a helper method to be called after adding a Preference for a profile. If that * profile happened to be A2dp and the device supports high quality audio, it will add a @@ -243,16 +425,33 @@ public class BluetoothDetailsProfilesController extends BluetoothDetailsControll @Override public void onPause() { - super.onPause(); + for (CachedBluetoothDevice item : mAllOfCachedDevices) { + item.unregisterCallback(this); + } mProfileManager.removeServiceListener(this); } @Override public void onResume() { - super.onResume(); + for (CachedBluetoothDevice item : mAllOfCachedDevices) { + item.registerCallback(this); + } mProfileManager.addServiceListener(this); } + @Override + public void onDeviceAttributesChanged() { + for (CachedBluetoothDevice item : mAllOfCachedDevices) { + item.unregisterCallback(this); + } + mAllOfCachedDevices = getAllOfCachedBluetoothDevices(); + for (CachedBluetoothDevice item : mAllOfCachedDevices) { + item.registerCallback(this); + } + + super.onDeviceAttributesChanged(); + } + @Override public void onServiceConnected() { refresh(); From b76eb99d4ad970c7587dd3172407598fc2303d30 Mon Sep 17 00:00:00 2001 From: SongFerngWang Date: Tue, 8 Mar 2022 14:34:20 +0800 Subject: [PATCH 4/8] Add the battery information into device details header Add the new LE audio header Add the battery information Update the condition for non-LE audio header Bug: 218626547 Test: build pass and manualy test Change-Id: Ib9f4c388c369dc374c43dd399111e02b5555159b Merged-In: Ib9f4c388c369dc374c43dd399111e02b5555159b --- res/layout/le_audio_bt_entity_header.xml | 137 ++++++++ res/values/dimens.xml | 7 + res/xml/bluetooth_device_details_fragment.xml | 8 + .../BluetoothDetailsHeaderController.java | 6 +- .../BluetoothDeviceDetailsFragment.java | 1 + ...AudioBluetoothDetailsHeaderController.java | 323 ++++++++++++++++++ 6 files changed, 481 insertions(+), 1 deletion(-) create mode 100644 res/layout/le_audio_bt_entity_header.xml create mode 100644 src/com/android/settings/bluetooth/LeAudioBluetoothDetailsHeaderController.java diff --git a/res/layout/le_audio_bt_entity_header.xml b/res/layout/le_audio_bt_entity_header.xml new file mode 100644 index 00000000000..6e2a1e85b07 --- /dev/null +++ b/res/layout/le_audio_bt_entity_header.xml @@ -0,0 +1,137 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 5fabd095fa7..f225150812c 100755 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -392,6 +392,13 @@ 27.5dp -4dp + + 5dp + 10dp + 6dp + 20dp + 1.5dp + 8dp 8dp diff --git a/res/xml/bluetooth_device_details_fragment.xml b/res/xml/bluetooth_device_details_fragment.xml index 9df195584d6..34599f7e2bf 100644 --- a/res/xml/bluetooth_device_details_fragment.xml +++ b/res/xml/bluetooth_device_details_fragment.xml @@ -34,6 +34,14 @@ settings:searchable="false" settings:controller="com.android.settings.bluetooth.AdvancedBluetoothDetailsHeaderController"/> + + diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsHeaderController.java b/src/com/android/settings/bluetooth/BluetoothDetailsHeaderController.java index 9f5e78e0396..9c7aa58cf7c 100644 --- a/src/com/android/settings/bluetooth/BluetoothDetailsHeaderController.java +++ b/src/com/android/settings/bluetooth/BluetoothDetailsHeaderController.java @@ -16,6 +16,7 @@ package com.android.settings.bluetooth; +import android.bluetooth.BluetoothProfile; import android.content.Context; import android.graphics.drawable.Drawable; import android.text.TextUtils; @@ -53,7 +54,10 @@ public class BluetoothDetailsHeaderController extends BluetoothDetailsController @Override public boolean isAvailable() { - return !Utils.isAdvancedDetailsHeader(mCachedDevice.getDevice()); + boolean hasLeAudio = mCachedDevice.getConnectableProfiles() + .stream() + .anyMatch(profile -> profile.getProfileId() == BluetoothProfile.LE_AUDIO); + return !Utils.isAdvancedDetailsHeader(mCachedDevice.getDevice()) && !hasLeAudio; } @Override diff --git a/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java b/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java index 4980ba313fb..425d1c44895 100644 --- a/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java +++ b/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java @@ -117,6 +117,7 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment return; } use(AdvancedBluetoothDetailsHeaderController.class).init(mCachedDevice); + use(LeAudioBluetoothDetailsHeaderController.class).init(mCachedDevice, mManager); final BluetoothFeatureProvider featureProvider = FeatureFactory.getFactory( context).getBluetoothFeatureProvider(context); diff --git a/src/com/android/settings/bluetooth/LeAudioBluetoothDetailsHeaderController.java b/src/com/android/settings/bluetooth/LeAudioBluetoothDetailsHeaderController.java new file mode 100644 index 00000000000..06cee852442 --- /dev/null +++ b/src/com/android/settings/bluetooth/LeAudioBluetoothDetailsHeaderController.java @@ -0,0 +1,323 @@ +/* + * 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 android.bluetooth.BluetoothCsipSetCoordinator; +import android.bluetooth.BluetoothLeAudio; +import android.bluetooth.BluetoothProfile; +import android.content.Context; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.graphics.drawable.Drawable; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; +import android.util.Pair; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.VisibleForTesting; +import androidx.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; +import com.android.settings.fuelgauge.BatteryMeterView; +import com.android.settingslib.bluetooth.BluetoothUtils; +import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.LeAudioProfile; +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.OnDestroy; +import com.android.settingslib.core.lifecycle.events.OnStart; +import com.android.settingslib.core.lifecycle.events.OnStop; +import com.android.settingslib.widget.LayoutPreference; + +import java.util.ArrayList; +import java.util.List; + +/** + * This class adds a header with device name and status (connected/disconnected, etc.). + */ +public class LeAudioBluetoothDetailsHeaderController extends BasePreferenceController implements + LifecycleObserver, OnStart, OnStop, OnDestroy, CachedBluetoothDevice.Callback { + private static final String TAG = "LeAudioBtHeaderCtrl"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + @VisibleForTesting + static final int LEFT_DEVICE_ID = + BluetoothLeAudio.AUDIO_LOCATION_FRONT_LEFT + | BluetoothLeAudio.AUDIO_LOCATION_BACK_LEFT + | BluetoothLeAudio.AUDIO_LOCATION_FRONT_LEFT_OF_CENTER + | BluetoothLeAudio.AUDIO_LOCATION_SIDE_LEFT + | BluetoothLeAudio.AUDIO_LOCATION_TOP_FRONT_LEFT + | BluetoothLeAudio.AUDIO_LOCATION_TOP_BACK_LEFT + | BluetoothLeAudio.AUDIO_LOCATION_TOP_SIDE_LEFT + | BluetoothLeAudio.AUDIO_LOCATION_BOTTOM_FRONT_LEFT + | BluetoothLeAudio.AUDIO_LOCATION_FRONT_LEFT_WIDE + | BluetoothLeAudio.AUDIO_LOCATION_LEFT_SURROUND; + + @VisibleForTesting + static final int RIGHT_DEVICE_ID = + BluetoothLeAudio.AUDIO_LOCATION_FRONT_RIGHT + | BluetoothLeAudio.AUDIO_LOCATION_BACK_RIGHT + | BluetoothLeAudio.AUDIO_LOCATION_FRONT_RIGHT_OF_CENTER + | BluetoothLeAudio.AUDIO_LOCATION_SIDE_RIGHT + | BluetoothLeAudio.AUDIO_LOCATION_TOP_FRONT_RIGHT + | BluetoothLeAudio.AUDIO_LOCATION_TOP_BACK_RIGHT + | BluetoothLeAudio.AUDIO_LOCATION_TOP_SIDE_RIGHT + | BluetoothLeAudio.AUDIO_LOCATION_BOTTOM_FRONT_RIGHT + | BluetoothLeAudio.AUDIO_LOCATION_FRONT_RIGHT_WIDE + | BluetoothLeAudio.AUDIO_LOCATION_RIGHT_SURROUND; + + @VisibleForTesting + static final int INVALID_RESOURCE_ID = -1; + + @VisibleForTesting + LayoutPreference mLayoutPreference; + private CachedBluetoothDevice mCachedDevice; + @VisibleForTesting + Handler mHandler = new Handler(Looper.getMainLooper()); + @VisibleForTesting + boolean mIsRegisterCallback = false; + + private LocalBluetoothProfileManager mProfileManager; + + public LeAudioBluetoothDetailsHeaderController(Context context, String prefKey) { + super(context, prefKey); + } + + @Override + public int getAvailabilityStatus() { + if (mCachedDevice == null || mProfileManager == null) { + return CONDITIONALLY_UNAVAILABLE; + } + boolean hasLeAudio = mCachedDevice.getConnectableProfiles() + .stream() + .anyMatch(profile -> profile.getProfileId() == BluetoothProfile.LE_AUDIO); + + return !Utils.isAdvancedDetailsHeader(mCachedDevice.getDevice()) && hasLeAudio + ? AVAILABLE : CONDITIONALLY_UNAVAILABLE; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mLayoutPreference = screen.findPreference(getPreferenceKey()); + mLayoutPreference.setVisible(isAvailable()); + } + + @Override + public void onStart() { + if (!isAvailable()) { + return; + } + mIsRegisterCallback = true; + mCachedDevice.registerCallback(this); + refresh(); + } + + @Override + public void onStop() { + if (!mIsRegisterCallback) { + return; + } + mCachedDevice.unregisterCallback(this); + mIsRegisterCallback = false; + } + + @Override + public void onDestroy() { + } + + public void init(CachedBluetoothDevice cachedBluetoothDevice, + LocalBluetoothManager bluetoothManager) { + mCachedDevice = cachedBluetoothDevice; + mProfileManager = bluetoothManager.getProfileManager(); + } + + @VisibleForTesting + void refresh() { + if (mLayoutPreference == null || mCachedDevice == null) { + return; + } + final ImageView imageView = mLayoutPreference.findViewById(R.id.entity_header_icon); + if (imageView != null) { + final Pair pair = + BluetoothUtils.getBtRainbowDrawableWithDescription(mContext, mCachedDevice); + imageView.setImageDrawable(pair.first); + imageView.setContentDescription(pair.second); + } + + final TextView title = mLayoutPreference.findViewById(R.id.entity_header_title); + if (title != null) { + title.setText(mCachedDevice.getName()); + } + final TextView summary = mLayoutPreference.findViewById(R.id.entity_header_summary); + if (summary != null) { + summary.setText(mCachedDevice.getConnectionSummary(true /* shortSummary */)); + } + + if (!mCachedDevice.isConnected() || mCachedDevice.isBusy()) { + hideAllOfBatteryLayouts(); + return; + } + + updateBatteryLayout(); + } + + @VisibleForTesting + Drawable createBtBatteryIcon(Context context, int level) { + final BatteryMeterView.BatteryMeterDrawable drawable = + new BatteryMeterView.BatteryMeterDrawable(context, + context.getColor(R.color.meter_background_color), + context.getResources().getDimensionPixelSize( + R.dimen.advanced_bluetooth_battery_meter_width), + context.getResources().getDimensionPixelSize( + R.dimen.advanced_bluetooth_battery_meter_height)); + drawable.setBatteryLevel(level); + drawable.setColorFilter(new PorterDuffColorFilter( + com.android.settings.Utils.getColorAttrDefaultColor(context, + android.R.attr.colorControlNormal), + PorterDuff.Mode.SRC)); + return drawable; + } + + private int getBatteryTitleResource(int deviceId) { + if (deviceId == LEFT_DEVICE_ID) { + return R.id.bt_battery_left_title; + } + if (deviceId == RIGHT_DEVICE_ID) { + return R.id.bt_battery_right_title; + } + Log.d(TAG, "No resource id. The deviceId is " + deviceId); + return INVALID_RESOURCE_ID; + } + + private int getBatterySummaryResource(int deviceId) { + if (deviceId == LEFT_DEVICE_ID) { + return R.id.bt_battery_left_summary; + } + if (deviceId == RIGHT_DEVICE_ID) { + return R.id.bt_battery_right_summary; + } + Log.d(TAG, "No resource id. The deviceId is " + deviceId); + return INVALID_RESOURCE_ID; + } + + private void hideAllOfBatteryLayouts() { + // hide the case + updateBatteryLayout(R.id.bt_battery_case_title, R.id.bt_battery_case_summary, + BluetoothUtils.META_INT_ERROR); + // hide the left + updateBatteryLayout(R.id.bt_battery_left_title, R.id.bt_battery_left_summary, + BluetoothUtils.META_INT_ERROR); + // hide the right + updateBatteryLayout(R.id.bt_battery_right_title, R.id.bt_battery_right_summary, + BluetoothUtils.META_INT_ERROR); + } + + private List getAllOfLeAudioDevices() { + if (mCachedDevice == null) { + return null; + } + List leAudioDevices = new ArrayList<>(); + leAudioDevices.add(mCachedDevice); + if (mCachedDevice.getGroupId() != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) { + for (CachedBluetoothDevice member : mCachedDevice.getMemberDevice()) { + leAudioDevices.add(member); + } + } + return leAudioDevices; + } + + private void updateBatteryLayout() { + // Init the battery layouts. + hideAllOfBatteryLayouts(); + final List leAudioDevices = getAllOfLeAudioDevices(); + LeAudioProfile leAudioProfile = mProfileManager.getLeAudioProfile(); + if (leAudioDevices == null || leAudioDevices.isEmpty()) { + Log.e(TAG, "There is no LeAudioProfile."); + return; + } + + if (!leAudioProfile.isEnabled(mCachedDevice.getDevice())) { + Log.d(TAG, "Show the legacy battery style if the LeAudio is not enabled."); + final TextView summary = mLayoutPreference.findViewById(R.id.entity_header_summary); + if (summary != null) { + summary.setText(mCachedDevice.getConnectionSummary()); + } + return; + } + + for (CachedBluetoothDevice cachedDevice : leAudioDevices) { + int deviceId = leAudioProfile.getAudioLocation(cachedDevice.getDevice()); + Log.d(TAG, "LeAudioDevices:" + cachedDevice.getDevice().getAnonymizedAddress() + + ", deviceId:" + deviceId); + + if (deviceId == BluetoothLeAudio.AUDIO_LOCATION_INVALID) { + Log.d(TAG, "The device does not support the AUDIO_LOCATION."); + return; + } + boolean isLeft = (deviceId & LEFT_DEVICE_ID) != 0; + boolean isRight = (deviceId & LEFT_DEVICE_ID) != 0; + boolean isLeftRight = isLeft && isRight; + // The LE device updates the BatteryLayout + if (isLeftRight) { + Log.d(TAG, "The device id is left+right. Do nothing."); + } else if (isLeft) { + updateBatteryLayout(getBatteryTitleResource(LEFT_DEVICE_ID), + getBatterySummaryResource(LEFT_DEVICE_ID), cachedDevice.getBatteryLevel()); + } else if (isRight) { + updateBatteryLayout(getBatteryTitleResource(RIGHT_DEVICE_ID), + getBatterySummaryResource(RIGHT_DEVICE_ID), cachedDevice.getBatteryLevel()); + } else { + Log.d(TAG, "The device id is other Audio Location. Do nothing."); + } + } + } + + private void updateBatteryLayout(int titleResId, int summaryResId, int batteryLevel) { + final TextView batteryTitleView = mLayoutPreference.findViewById(titleResId); + final TextView batterySummaryView = mLayoutPreference.findViewById(summaryResId); + if (batteryTitleView == null || batterySummaryView == null) { + Log.e(TAG, "updateBatteryLayout: No TextView"); + return; + } + if (batteryLevel != BluetoothUtils.META_INT_ERROR) { + batteryTitleView.setVisibility(View.VISIBLE); + batterySummaryView.setVisibility(View.VISIBLE); + batterySummaryView.setText( + com.android.settings.Utils.formatPercentage(batteryLevel)); + batterySummaryView.setCompoundDrawablesRelativeWithIntrinsicBounds( + createBtBatteryIcon(mContext, batteryLevel), /* top */ null, + /* end */ null, /* bottom */ null); + } else { + Log.d(TAG, "updateBatteryLayout: Hide it if it doesn't have battery information."); + batteryTitleView.setVisibility(View.GONE); + batterySummaryView.setVisibility(View.GONE); + } + } + + @Override + public void onDeviceAttributesChanged() { + if (mCachedDevice != null) { + refresh(); + } + } +} From 56e9b25b5f511bc6c139001513d9343fba255df8 Mon Sep 17 00:00:00 2001 From: Tsung-Mao Fang Date: Wed, 16 Mar 2022 18:03:40 +0800 Subject: [PATCH 5/8] Set higher priority for ADD_DEVICE_ADMIN intent Settings should set android:priority of the intent-filter > 1 so that regular apps can't provide an alternative. Test: Rebuilt apk Fix: 197960672 Change-Id: Ic12db4fa5e6a5f803930deba2a81254c375403df --- AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 214d32a3c17..9986e34d7c2 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1846,7 +1846,7 @@ android:exported="true" android:clearTaskOnLaunch="true" android:theme="@style/Theme.Settings.NoActionBar"> - + From 5f56a273c4fbe0cc2d93a45071d79e0c48216e3e Mon Sep 17 00:00:00 2001 From: Oli Lan Date: Wed, 16 Mar 2022 14:57:00 +0000 Subject: [PATCH 6/8] Reduce gap above user list in multiuser settings. This fixes a large gap above the user list, which occurred because a PreferenceCategory was used for this with a blank title. Bug: 224659273 Test: Install and inspect Change-Id: I82c4491cc6a5111c6517b2845b44632e0859e1b6 --- src/com/android/settings/users/UserSettings.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/com/android/settings/users/UserSettings.java b/src/com/android/settings/users/UserSettings.java index 8614c53d4a6..0f2eb66bcea 100644 --- a/src/com/android/settings/users/UserSettings.java +++ b/src/com/android/settings/users/UserSettings.java @@ -1102,6 +1102,7 @@ public class UserSettings extends SettingsPreferenceFragment mUserListCategory.setTitle(R.string.user_list_title); } else { mUserListCategory.setTitle(null); + mUserListCategory.setLayoutResource(R.layout.empty_view); } // Remove everything from mUserListCategory and add new users. From 4d1eb0f3014469cae1b342ee9e6f776bbb9a7584 Mon Sep 17 00:00:00 2001 From: Yuri Lin Date: Wed, 16 Mar 2022 15:02:27 -0400 Subject: [PATCH 7/8] Remove preference category in zen mode alarms page Bug: 223295009 Test: manual Change-Id: I7afd65519dd2db61d5d39a8e5df085ec71474775 --- res/xml/zen_mode_sound_vibration_settings.xml | 49 +++++++++---------- 1 file changed, 22 insertions(+), 27 deletions(-) diff --git a/res/xml/zen_mode_sound_vibration_settings.xml b/res/xml/zen_mode_sound_vibration_settings.xml index 74107168623..2db42ac498e 100644 --- a/res/xml/zen_mode_sound_vibration_settings.xml +++ b/res/xml/zen_mode_sound_vibration_settings.xml @@ -19,36 +19,31 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:title="@string/zen_category_exceptions" > - + + - - + + - - + + - - + + - - - - - - - + + From 4473f60448fc04c8012dc40c89d8a3cc0fc42ac6 Mon Sep 17 00:00:00 2001 From: Kate Montgomery Date: Wed, 16 Mar 2022 17:21:40 +0000 Subject: [PATCH 8/8] Fix settings page flicker in two ways: 1. On create use UiBlocker as recommended by settings team 2. On resume only update the preferences list if the system setting has changed. Bug: 220837804 Test: manual Change-Id: Ieebd2e7fd74ab05d4fb73aede6868553d7d84af1 --- .../settings/location/LocationSettings.java | 23 +++++++++++--- ...entLocationAccessPreferenceController.java | 31 ++++++++++++++++--- .../location/LocationSettingsTest.java | 4 +++ 3 files changed, 49 insertions(+), 9 deletions(-) diff --git a/src/com/android/settings/location/LocationSettings.java b/src/com/android/settings/location/LocationSettings.java index ab3ed20606b..ff8b4be390e 100644 --- a/src/com/android/settings/location/LocationSettings.java +++ b/src/com/android/settings/location/LocationSettings.java @@ -20,8 +20,12 @@ import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROF import android.app.settings.SettingsEnums; import android.content.Context; +import android.database.ContentObserver; import android.location.SettingInjectorService; import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.provider.Settings; import androidx.preference.Preference; import androidx.preference.PreferenceGroup; @@ -66,6 +70,7 @@ public class LocationSettings extends DashboardFragment implements private LocationSwitchBarController mSwitchBarController; private LocationEnabler mLocationEnabler; private RecentLocationAccessPreferenceController mController; + private ContentObserver mContentObserver; @Override public int getMetricsCategory() { @@ -82,6 +87,16 @@ public class LocationSettings extends DashboardFragment implements mSwitchBarController = new LocationSwitchBarController(activity, switchBar, getSettingsLifecycle()); mLocationEnabler = new LocationEnabler(getContext(), this, getSettingsLifecycle()); + mContentObserver = new ContentObserver(new Handler(Looper.getMainLooper())) { + @Override + public void onChange(boolean selfChange) { + mController.updateShowSystem(); + } + }; + getContentResolver().registerContentObserver( + Settings.Secure.getUriFor( + Settings.Secure.LOCATION_SHOW_SYSTEM_OPS), /* notifyForDescendants= */ + false, mContentObserver); } @Override @@ -97,11 +112,9 @@ public class LocationSettings extends DashboardFragment implements } @Override - public void onPause() { - super.onPause(); - if (mController != null) { - mController.clearPreferenceList(); - } + public void onDestroy() { + super.onDestroy(); + getContentResolver().unregisterContentObserver(mContentObserver); } @Override diff --git a/src/com/android/settings/location/RecentLocationAccessPreferenceController.java b/src/com/android/settings/location/RecentLocationAccessPreferenceController.java index ea3704cccc0..18221d0f409 100644 --- a/src/com/android/settings/location/RecentLocationAccessPreferenceController.java +++ b/src/com/android/settings/location/RecentLocationAccessPreferenceController.java @@ -28,6 +28,7 @@ import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceScreen; import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; import com.android.settings.dashboard.DashboardFragment; import com.android.settings.dashboard.profileselector.ProfileSelectFragment; import com.android.settingslib.applications.RecentAppOpsAccess; @@ -40,12 +41,15 @@ import java.util.List; /** * Preference controller that handles the display of apps that access locations. */ -public class RecentLocationAccessPreferenceController extends LocationBasePreferenceController { +public class RecentLocationAccessPreferenceController extends LocationBasePreferenceController + implements BasePreferenceController.UiBlocker { public static final int MAX_APPS = 3; @VisibleForTesting RecentAppOpsAccess mRecentLocationApps; private PreferenceCategory mCategoryRecentLocationRequests; private int mType = ProfileSelectFragment.ProfileType.ALL; + private boolean mShowSystem = false; + private boolean mSystemSettingChanged = false; private static class PackageEntryClickedListener implements Preference.OnPreferenceClickListener { @@ -80,23 +84,32 @@ public class RecentLocationAccessPreferenceController extends LocationBasePrefer RecentAppOpsAccess recentLocationApps) { super(context, key); mRecentLocationApps = recentLocationApps; + mShowSystem = Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.LOCATION_SHOW_SYSTEM_OPS, 0) == 1; } @Override public void displayPreference(PreferenceScreen screen) { super.displayPreference(screen); mCategoryRecentLocationRequests = screen.findPreference(getPreferenceKey()); + loadRecentAccesses(); } @Override public void updateState(Preference preference) { + // Only reload the recent accesses in updateState if the system setting has changed. + if (mSystemSettingChanged) { + loadRecentAccesses(); + mSystemSettingChanged = false; + } + } + + private void loadRecentAccesses() { mCategoryRecentLocationRequests.removeAll(); final Context prefContext = mCategoryRecentLocationRequests.getContext(); final List recentLocationAccesses = new ArrayList<>(); final UserManager userManager = UserManager.get(mContext); - final boolean showSystem = Settings.Secure.getInt(mContext.getContentResolver(), - Settings.Secure.LOCATION_SHOW_SYSTEM_OPS, 0) == 1; - for (RecentAppOpsAccess.Access access : mRecentLocationApps.getAppListSorted(showSystem)) { + for (RecentAppOpsAccess.Access access : mRecentLocationApps.getAppListSorted(mShowSystem)) { if (isRequestMatchesProfileType(userManager, access, mType)) { recentLocationAccesses.add(access); if (recentLocationAccesses.size() == MAX_APPS) { @@ -177,4 +190,14 @@ public class RecentLocationAccessPreferenceController extends LocationBasePrefer } return false; } + + /** + * Update the state of the showSystem setting flag and load the new results. + */ + void updateShowSystem() { + mSystemSettingChanged = true; + mShowSystem = !mShowSystem; + clearPreferenceList(); + loadRecentAccesses(); + } } diff --git a/tests/robotests/src/com/android/settings/location/LocationSettingsTest.java b/tests/robotests/src/com/android/settings/location/LocationSettingsTest.java index 3d624c0edd6..aae8cec8170 100644 --- a/tests/robotests/src/com/android/settings/location/LocationSettingsTest.java +++ b/tests/robotests/src/com/android/settings/location/LocationSettingsTest.java @@ -20,6 +20,7 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.content.ContentResolver; import android.content.Context; import android.os.Bundle; @@ -41,6 +42,8 @@ public class LocationSettingsTest { private SettingsActivity mActivity; @Mock private SettingsMainSwitchBar mSwitchBar; + @Mock + private ContentResolver mContentResolver; private Context mContext; private LocationSettings mLocationSettings; @@ -52,6 +55,7 @@ public class LocationSettingsTest { mLocationSettings = spy(new LocationSettings()); doReturn(mActivity).when(mLocationSettings).getActivity(); doReturn(mContext).when(mLocationSettings).getContext(); + doReturn(mContentResolver).when(mActivity).getContentResolver(); when(mActivity.getSwitchBar()).thenReturn(mSwitchBar); }