Loading res/values/strings.xml +1 −1 Original line number Diff line number Diff line Loading @@ -10399,7 +10399,7 @@ <!-- Body text of prompt dialog app can invoke to turn off optimization [CHAR LIMIT=NONE] --> <string name="high_power_prompt_body"> Allowing <xliff:g id="app_name" example="Settings">%1$s</xliff:g> to always run in the background may reduce battery life. \n\nYou can change this later from Settings > Apps.</string> \n\nYou can change this later from Settings -> Apps.</string> <!-- Summary of power usage for an app [CHAR LIMIT=NONE] --> <string name="battery_summary"><xliff:g id="percentage" example="2">%1$s</xliff:g> use since last full charge</string> <!-- Summary of power usage for an app within past 24 hr[CHAR LIMIT=NONE] --> src/com/android/settings/accessibility/RemoveAnimationsPreference.kt +9 −0 Original line number Diff line number Diff line Loading @@ -17,9 +17,12 @@ package com.android.settings.accessibility import android.annotation.DrawableRes import android.app.settings.SettingsEnums.ACTION_REMOVE_ANIMATION import android.content.Context import android.provider.Settings import com.android.settings.PreferenceActionMetricsProvider import com.android.settings.R import com.android.settings.contract.KEY_REMOVE_ANIMATION import com.android.settingslib.datastore.HandlerExecutor import com.android.settingslib.datastore.KeyValueStore import com.android.settingslib.datastore.KeyedObserver Loading @@ -37,6 +40,7 @@ class RemoveAnimationsPreference : R.string.accessibility_disable_animations, R.string.accessibility_disable_animations_summary, ), PreferenceActionMetricsProvider, PreferenceLifecycleProvider { private var mSettingsKeyedObserver: KeyedObserver<String>? = null Loading @@ -44,6 +48,11 @@ class RemoveAnimationsPreference : override val icon: Int @DrawableRes get() = R.drawable.ic_accessibility_animation override val preferenceActionMetrics: Int get() = ACTION_REMOVE_ANIMATION override fun tags(context: Context) = arrayOf(KEY_REMOVE_ANIMATION) override fun onStart(context: PreferenceLifecycleContext) { val observer = KeyedObserver<String> { _, _ -> context.notifyPreferenceChange(KEY) } mSettingsKeyedObserver = observer Loading src/com/android/settings/accessibility/VibrationMainSwitchPreference.kt +9 −0 Original line number Diff line number Diff line Loading @@ -15,13 +15,16 @@ */ package com.android.settings.accessibility import android.app.settings.SettingsEnums.ACTION_VIBRATION_HAPTICS import android.content.Context import android.os.VibrationAttributes import android.os.Vibrator import android.provider.Settings import android.widget.CompoundButton import android.widget.CompoundButton.OnCheckedChangeListener import com.android.settings.PreferenceActionMetricsProvider import com.android.settings.R import com.android.settings.contract.KEY_VIBRATION_HAPTICS import com.android.settingslib.datastore.KeyValueStore import com.android.settingslib.datastore.KeyedObservableDelegate import com.android.settingslib.datastore.SettingsStore Loading @@ -39,6 +42,7 @@ class VibrationMainSwitchPreference : key = Settings.System.VIBRATE_ON, title = R.string.accessibility_vibration_primary_switch_title, ), PreferenceActionMetricsProvider, PreferenceLifecycleProvider, OnCheckedChangeListener { override val keywords: Int Loading @@ -46,6 +50,11 @@ class VibrationMainSwitchPreference : lateinit var vibrator: Vibrator override val preferenceActionMetrics: Int get() = ACTION_VIBRATION_HAPTICS override fun tags(context: Context) = arrayOf(KEY_VIBRATION_HAPTICS) override fun storage(context: Context): KeyValueStore = VibrationMainSwitchToggleStorage(SettingsSystemStore.get(context)) Loading src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBase.java +81 −38 Original line number Diff line number Diff line Loading @@ -46,6 +46,7 @@ import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.bluetooth.BluetoothUtils; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.HearingAidStatsLogUtils; import com.android.settingslib.flags.Flags; import com.android.settingslib.utils.ThreadUtils; import com.google.common.collect.ImmutableList; Loading @@ -62,6 +63,7 @@ import java.util.concurrent.TimeUnit; public abstract class BluetoothDevicePairingDetailBase extends DeviceListPreferenceFragment { private static final long AUTO_DISMISS_TIME_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(15); private static final int AUTO_DISMISS_MESSAGE_ID = 1001; private static final int AUTO_FINISH_MESSAGE_ID = 1002; private static final ImmutableList<Integer> AUDIO_SHARING_PROFILES = ImmutableList.of( BluetoothProfile.LE_AUDIO, BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT, BluetoothProfile.VOLUME_CONTROL); Loading @@ -77,7 +79,7 @@ public abstract class BluetoothDevicePairingDetailBase extends DeviceListPrefere @Nullable ProgressDialogFragment mProgressDialog = null; @VisibleForTesting boolean mShouldTriggerAudioSharingShareThenPairFlow = false; boolean mShouldTriggerShareThenPairFlow = false; private CopyOnWriteArrayList<BluetoothDevice> mDevicesWithMetadataChangedListener = new CopyOnWriteArrayList<>(); Loading @@ -89,7 +91,8 @@ public abstract class BluetoothDevicePairingDetailBase extends DeviceListPrefere // onDeviceBondStateChanged(BOND_BONDED), BluetoothDevicePreference's summary has already // change from "Pairing..." to empty since it listens to metadata changes happens earlier. // // In share then pair flow, we have to wait on this page till the device is connected. // In pairing flow during audio sharing, we have to wait on this page till the device is // connected to check the device type and handle extra logic for audio sharing. // The BluetoothDevicePreference summary will be blank for seconds between "Pairing..." and // "Connecting..." To help users better understand the process, we listen to metadata change // as well and show a progress dialog with "Connecting to ...." once BluetoothDevice.getState() Loading @@ -100,10 +103,11 @@ public abstract class BluetoothDevicePairingDetailBase extends DeviceListPrefere public void onMetadataChanged(@NonNull BluetoothDevice device, int key, @Nullable byte[] value) { Log.d(getLogTag(), "onMetadataChanged device = " + device + ", key = " + key); if (mShouldTriggerAudioSharingShareThenPairFlow && mProgressDialog == null if ((mShouldTriggerShareThenPairFlow || shouldSetTempBondMetadata()) && mProgressDialog == null && device.getBondState() == BluetoothDevice.BOND_BONDED && mSelectedList.contains(device)) { triggerAudioSharingShareThenPairFlow(device); handleDeviceBondedInAudioSharing(device); // Once device is bonded, remove the listener removeOnMetadataChangedListener(device); } Loading Loading @@ -133,7 +137,7 @@ public abstract class BluetoothDevicePairingDetailBase extends DeviceListPrefere return; } updateBluetooth(); mShouldTriggerAudioSharingShareThenPairFlow = shouldTriggerAudioSharingShareThenPairFlow(); mShouldTriggerShareThenPairFlow = shouldTriggerShareThenPairFlow(); } @Override Loading Loading @@ -177,11 +181,12 @@ public abstract class BluetoothDevicePairingDetailBase extends DeviceListPrefere @Override public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) { boolean shouldSetTempBond = shouldSetTempBondMetadata(); if (bondState == BluetoothDevice.BOND_BONDED) { if (cachedDevice != null && mShouldTriggerAudioSharingShareThenPairFlow) { if (cachedDevice != null && (mShouldTriggerShareThenPairFlow || shouldSetTempBond)) { BluetoothDevice device = cachedDevice.getDevice(); if (device != null && mSelectedList.contains(device)) { triggerAudioSharingShareThenPairFlow(device); handleDeviceBondedInAudioSharing(device); removeOnMetadataChangedListener(device); return; } Loading @@ -190,7 +195,7 @@ public abstract class BluetoothDevicePairingDetailBase extends DeviceListPrefere finish(); return; } else if (bondState == BluetoothDevice.BOND_BONDING) { if (mShouldTriggerAudioSharingShareThenPairFlow && cachedDevice != null) { if ((mShouldTriggerShareThenPairFlow || shouldSetTempBond) && cachedDevice != null) { BluetoothDevice device = cachedDevice.getDevice(); if (device != null && mSelectedList.contains(device)) { addOnMetadataChangedListener(device); Loading @@ -203,7 +208,7 @@ public abstract class BluetoothDevicePairingDetailBase extends DeviceListPrefere pageId); HearingAidStatsLogUtils.setBondEntryForDevice(bondEntry, cachedDevice); } else if (bondState == BluetoothDevice.BOND_NONE) { if (mShouldTriggerAudioSharingShareThenPairFlow && cachedDevice != null) { if ((mShouldTriggerShareThenPairFlow || shouldSetTempBond) && cachedDevice != null) { BluetoothDevice device = cachedDevice.getDevice(); if (device != null && mSelectedList.contains(device)) { removeOnMetadataChangedListener(device); Loading Loading @@ -233,21 +238,29 @@ public abstract class BluetoothDevicePairingDetailBase extends DeviceListPrefere final BluetoothDevice device = cachedDevice.getDevice(); if (device != null && mSelectedList.contains(device)) { var unused = ThreadUtils.postOnBackgroundThread(() -> { if (BluetoothUtils.isAudioSharingUIAvailable(getContext())) { if (mShouldTriggerAudioSharingShareThenPairFlow if ((mShouldTriggerShareThenPairFlow || shouldSetTempBondMetadata()) && state == BluetoothAdapter.STATE_CONNECTED && device.equals(mJustBonded) && AUDIO_SHARING_PROFILES.contains(bluetoothProfile) && isReadyForAudioSharing(cachedDevice, bluetoothProfile)) { Log.d(getLogTag(), "onProfileConnectionStateChanged, ready for audio sharing"); Log.d(getLogTag(), "onProfileConnectionStateChanged, lea eligible"); dismissConnectingDialog(); BluetoothUtils.setTemporaryBondMetadata(device); if (mShouldTriggerShareThenPairFlow) { mHandler.removeMessages(AUTO_DISMISS_MESSAGE_ID); finishFragmentWithResultForAudioSharing(device); postOnMainThread(() -> finishFragmentWithResultForAudioSharing(device)); } else { mHandler.removeMessages(AUTO_FINISH_MESSAGE_ID); postOnMainThread(() -> finish()); } } } else { finish(); postOnMainThread(() -> finish()); } }); } else { onDeviceDeleted(cachedDevice); } Loading Loading @@ -314,7 +327,7 @@ public abstract class BluetoothDevicePairingDetailBase extends DeviceListPrefere } @VisibleForTesting boolean shouldTriggerAudioSharingShareThenPairFlow() { boolean shouldTriggerShareThenPairFlow() { if (BluetoothUtils.isAudioSharingUIAvailable(getContext())) { Activity activity = getActivity(); Intent intent = activity == null ? null : activity.getIntent(); Loading @@ -328,6 +341,16 @@ public abstract class BluetoothDevicePairingDetailBase extends DeviceListPrefere return false; } private boolean shouldSetTempBondMetadata() { return Flags.enableTemporaryBondDevicesUi() && BluetoothUtils.isAudioSharingUIAvailable(getContext()) && BluetoothUtils.isBroadcasting(mLocalManager) && mLocalManager != null && mLocalManager.getCachedDeviceManager() != null && mLocalManager.getProfileManager().getLeAudioBroadcastAssistantProfile() != null && !Utils.shouldBlockPairingInAudioSharing(mLocalManager); } private boolean isReadyForAudioSharing(@NonNull CachedBluetoothDevice cachedDevice, int justConnectedProfile) { for (int profile : AUDIO_SHARING_PROFILES) { Loading Loading @@ -382,11 +405,10 @@ public abstract class BluetoothDevicePairingDetailBase extends DeviceListPrefere }); } private void triggerAudioSharingShareThenPairFlow( @NonNull BluetoothDevice device) { private void handleDeviceBondedInAudioSharing(@Nullable BluetoothDevice device) { var unused = ThreadUtils.postOnBackgroundThread(() -> { if (mJustBonded != null) { Log.d(getLogTag(), "Skip triggerAudioSharingShareThenPairFlow, already done"); Log.d(getLogTag(), "Skip handleDeviceBondedInAudioSharing, already done"); return; } mJustBonded = device; Loading @@ -395,18 +417,39 @@ public abstract class BluetoothDevicePairingDetailBase extends DeviceListPrefere String deviceName = TextUtils.isEmpty(aliasName) ? device.getAddress() : aliasName; showConnectingDialog(deviceName); // Wait for AUTO_DISMISS_TIME_THRESHOLD_MS and check if the paired device supports audio // sharing. if (mShouldTriggerShareThenPairFlow) { // For share then pair flow, we have strong signal that users wish to pair new // device to join sharing. // So we wait for AUTO_DISMISS_TIME_THRESHOLD_MS, if we find that the bonded device // is lea in onProfileConnectionStateChanged, we finish the activity, set the device // as temp bond and auto add source; otherwise, show dialog to notify that the // device is incompatible for audio sharing. if (!mHandler.hasMessages(AUTO_DISMISS_MESSAGE_ID)) { mHandler.postDelayed(() -> postOnMainThread( () -> { Log.d(getLogTag(), "Show incompatible dialog when timeout"); Log.d(getLogTag(), "Show incompatible dialog when timeout"); dismissConnectingDialog(); AudioSharingIncompatibleDialogFragment.show(this, deviceName, AudioSharingIncompatibleDialogFragment.show(this, deviceName, () -> finish()); }), AUTO_DISMISS_MESSAGE_ID, AUTO_DISMISS_TIME_THRESHOLD_MS); } } else { // For other pairing request during audio sharing with sinks < 2, we wait for // AUTO_DISMISS_TIME_THRESHOLD_MS, if we find that the bonded device is lea in // onProfileConnectionStateChanged, we finish the activity and set the device as // temp bond; otherwise, we just finish the activity. if (!mHandler.hasMessages(AUTO_FINISH_MESSAGE_ID)) { mHandler.postDelayed(() -> postOnMainThread( () -> { Log.d(getLogTag(), "Finish activity when timeout"); finish(); }), AUTO_FINISH_MESSAGE_ID, AUTO_DISMISS_TIME_THRESHOLD_MS); } } }); } Loading src/com/android/settings/bluetooth/Utils.java +31 −0 Original line number Diff line number Diff line Loading @@ -33,6 +33,7 @@ import android.provider.Settings; import android.util.Log; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import androidx.appcompat.app.AlertDialog; Loading @@ -42,17 +43,21 @@ import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.bluetooth.BluetoothUtils; import com.android.settingslib.bluetooth.BluetoothUtils.ErrorListener; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothManager.BluetoothManagerCallback; import com.android.settingslib.utils.ThreadUtils; import com.google.common.base.Supplier; import com.google.common.collect.ImmutableList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; import java.util.stream.Collectors; /** * Utils is a helper class that contains constants for various Loading Loading @@ -293,4 +298,30 @@ public final class Utils { ThreadUtils.postOnMainThread(runnable); }); } /** * Check if need to block pairing during audio sharing * * @param localBtManager {@link LocalBluetoothManager} * @return if need to block pairing during audio sharing */ public static boolean shouldBlockPairingInAudioSharing( @NonNull LocalBluetoothManager localBtManager) { if (!BluetoothUtils.isBroadcasting(localBtManager)) return false; LocalBluetoothLeBroadcastAssistant assistant = localBtManager.getProfileManager().getLeAudioBroadcastAssistantProfile(); CachedBluetoothDeviceManager deviceManager = localBtManager.getCachedDeviceManager(); List<BluetoothDevice> connectedDevices = assistant == null ? ImmutableList.of() : assistant.getAllConnectedDevices(); // Block the pairing if there is ongoing audio sharing session and // a) there is already one temp bond sink connected // or b) there are already two sinks joining the audio sharing return assistant != null && deviceManager != null && (connectedDevices.stream().anyMatch(BluetoothUtils::isTemporaryBondDevice) || connectedDevices.stream().filter( d -> BluetoothUtils.hasActiveLocalBroadcastSourceForBtDevice(d, localBtManager)) .map(d -> BluetoothUtils.getGroupId(deviceManager.findDevice(d))).collect( Collectors.toSet()).size() >= 2); } } Loading
res/values/strings.xml +1 −1 Original line number Diff line number Diff line Loading @@ -10399,7 +10399,7 @@ <!-- Body text of prompt dialog app can invoke to turn off optimization [CHAR LIMIT=NONE] --> <string name="high_power_prompt_body"> Allowing <xliff:g id="app_name" example="Settings">%1$s</xliff:g> to always run in the background may reduce battery life. \n\nYou can change this later from Settings > Apps.</string> \n\nYou can change this later from Settings -> Apps.</string> <!-- Summary of power usage for an app [CHAR LIMIT=NONE] --> <string name="battery_summary"><xliff:g id="percentage" example="2">%1$s</xliff:g> use since last full charge</string> <!-- Summary of power usage for an app within past 24 hr[CHAR LIMIT=NONE] -->
src/com/android/settings/accessibility/RemoveAnimationsPreference.kt +9 −0 Original line number Diff line number Diff line Loading @@ -17,9 +17,12 @@ package com.android.settings.accessibility import android.annotation.DrawableRes import android.app.settings.SettingsEnums.ACTION_REMOVE_ANIMATION import android.content.Context import android.provider.Settings import com.android.settings.PreferenceActionMetricsProvider import com.android.settings.R import com.android.settings.contract.KEY_REMOVE_ANIMATION import com.android.settingslib.datastore.HandlerExecutor import com.android.settingslib.datastore.KeyValueStore import com.android.settingslib.datastore.KeyedObserver Loading @@ -37,6 +40,7 @@ class RemoveAnimationsPreference : R.string.accessibility_disable_animations, R.string.accessibility_disable_animations_summary, ), PreferenceActionMetricsProvider, PreferenceLifecycleProvider { private var mSettingsKeyedObserver: KeyedObserver<String>? = null Loading @@ -44,6 +48,11 @@ class RemoveAnimationsPreference : override val icon: Int @DrawableRes get() = R.drawable.ic_accessibility_animation override val preferenceActionMetrics: Int get() = ACTION_REMOVE_ANIMATION override fun tags(context: Context) = arrayOf(KEY_REMOVE_ANIMATION) override fun onStart(context: PreferenceLifecycleContext) { val observer = KeyedObserver<String> { _, _ -> context.notifyPreferenceChange(KEY) } mSettingsKeyedObserver = observer Loading
src/com/android/settings/accessibility/VibrationMainSwitchPreference.kt +9 −0 Original line number Diff line number Diff line Loading @@ -15,13 +15,16 @@ */ package com.android.settings.accessibility import android.app.settings.SettingsEnums.ACTION_VIBRATION_HAPTICS import android.content.Context import android.os.VibrationAttributes import android.os.Vibrator import android.provider.Settings import android.widget.CompoundButton import android.widget.CompoundButton.OnCheckedChangeListener import com.android.settings.PreferenceActionMetricsProvider import com.android.settings.R import com.android.settings.contract.KEY_VIBRATION_HAPTICS import com.android.settingslib.datastore.KeyValueStore import com.android.settingslib.datastore.KeyedObservableDelegate import com.android.settingslib.datastore.SettingsStore Loading @@ -39,6 +42,7 @@ class VibrationMainSwitchPreference : key = Settings.System.VIBRATE_ON, title = R.string.accessibility_vibration_primary_switch_title, ), PreferenceActionMetricsProvider, PreferenceLifecycleProvider, OnCheckedChangeListener { override val keywords: Int Loading @@ -46,6 +50,11 @@ class VibrationMainSwitchPreference : lateinit var vibrator: Vibrator override val preferenceActionMetrics: Int get() = ACTION_VIBRATION_HAPTICS override fun tags(context: Context) = arrayOf(KEY_VIBRATION_HAPTICS) override fun storage(context: Context): KeyValueStore = VibrationMainSwitchToggleStorage(SettingsSystemStore.get(context)) Loading
src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBase.java +81 −38 Original line number Diff line number Diff line Loading @@ -46,6 +46,7 @@ import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.bluetooth.BluetoothUtils; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.HearingAidStatsLogUtils; import com.android.settingslib.flags.Flags; import com.android.settingslib.utils.ThreadUtils; import com.google.common.collect.ImmutableList; Loading @@ -62,6 +63,7 @@ import java.util.concurrent.TimeUnit; public abstract class BluetoothDevicePairingDetailBase extends DeviceListPreferenceFragment { private static final long AUTO_DISMISS_TIME_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(15); private static final int AUTO_DISMISS_MESSAGE_ID = 1001; private static final int AUTO_FINISH_MESSAGE_ID = 1002; private static final ImmutableList<Integer> AUDIO_SHARING_PROFILES = ImmutableList.of( BluetoothProfile.LE_AUDIO, BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT, BluetoothProfile.VOLUME_CONTROL); Loading @@ -77,7 +79,7 @@ public abstract class BluetoothDevicePairingDetailBase extends DeviceListPrefere @Nullable ProgressDialogFragment mProgressDialog = null; @VisibleForTesting boolean mShouldTriggerAudioSharingShareThenPairFlow = false; boolean mShouldTriggerShareThenPairFlow = false; private CopyOnWriteArrayList<BluetoothDevice> mDevicesWithMetadataChangedListener = new CopyOnWriteArrayList<>(); Loading @@ -89,7 +91,8 @@ public abstract class BluetoothDevicePairingDetailBase extends DeviceListPrefere // onDeviceBondStateChanged(BOND_BONDED), BluetoothDevicePreference's summary has already // change from "Pairing..." to empty since it listens to metadata changes happens earlier. // // In share then pair flow, we have to wait on this page till the device is connected. // In pairing flow during audio sharing, we have to wait on this page till the device is // connected to check the device type and handle extra logic for audio sharing. // The BluetoothDevicePreference summary will be blank for seconds between "Pairing..." and // "Connecting..." To help users better understand the process, we listen to metadata change // as well and show a progress dialog with "Connecting to ...." once BluetoothDevice.getState() Loading @@ -100,10 +103,11 @@ public abstract class BluetoothDevicePairingDetailBase extends DeviceListPrefere public void onMetadataChanged(@NonNull BluetoothDevice device, int key, @Nullable byte[] value) { Log.d(getLogTag(), "onMetadataChanged device = " + device + ", key = " + key); if (mShouldTriggerAudioSharingShareThenPairFlow && mProgressDialog == null if ((mShouldTriggerShareThenPairFlow || shouldSetTempBondMetadata()) && mProgressDialog == null && device.getBondState() == BluetoothDevice.BOND_BONDED && mSelectedList.contains(device)) { triggerAudioSharingShareThenPairFlow(device); handleDeviceBondedInAudioSharing(device); // Once device is bonded, remove the listener removeOnMetadataChangedListener(device); } Loading Loading @@ -133,7 +137,7 @@ public abstract class BluetoothDevicePairingDetailBase extends DeviceListPrefere return; } updateBluetooth(); mShouldTriggerAudioSharingShareThenPairFlow = shouldTriggerAudioSharingShareThenPairFlow(); mShouldTriggerShareThenPairFlow = shouldTriggerShareThenPairFlow(); } @Override Loading Loading @@ -177,11 +181,12 @@ public abstract class BluetoothDevicePairingDetailBase extends DeviceListPrefere @Override public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) { boolean shouldSetTempBond = shouldSetTempBondMetadata(); if (bondState == BluetoothDevice.BOND_BONDED) { if (cachedDevice != null && mShouldTriggerAudioSharingShareThenPairFlow) { if (cachedDevice != null && (mShouldTriggerShareThenPairFlow || shouldSetTempBond)) { BluetoothDevice device = cachedDevice.getDevice(); if (device != null && mSelectedList.contains(device)) { triggerAudioSharingShareThenPairFlow(device); handleDeviceBondedInAudioSharing(device); removeOnMetadataChangedListener(device); return; } Loading @@ -190,7 +195,7 @@ public abstract class BluetoothDevicePairingDetailBase extends DeviceListPrefere finish(); return; } else if (bondState == BluetoothDevice.BOND_BONDING) { if (mShouldTriggerAudioSharingShareThenPairFlow && cachedDevice != null) { if ((mShouldTriggerShareThenPairFlow || shouldSetTempBond) && cachedDevice != null) { BluetoothDevice device = cachedDevice.getDevice(); if (device != null && mSelectedList.contains(device)) { addOnMetadataChangedListener(device); Loading @@ -203,7 +208,7 @@ public abstract class BluetoothDevicePairingDetailBase extends DeviceListPrefere pageId); HearingAidStatsLogUtils.setBondEntryForDevice(bondEntry, cachedDevice); } else if (bondState == BluetoothDevice.BOND_NONE) { if (mShouldTriggerAudioSharingShareThenPairFlow && cachedDevice != null) { if ((mShouldTriggerShareThenPairFlow || shouldSetTempBond) && cachedDevice != null) { BluetoothDevice device = cachedDevice.getDevice(); if (device != null && mSelectedList.contains(device)) { removeOnMetadataChangedListener(device); Loading Loading @@ -233,21 +238,29 @@ public abstract class BluetoothDevicePairingDetailBase extends DeviceListPrefere final BluetoothDevice device = cachedDevice.getDevice(); if (device != null && mSelectedList.contains(device)) { var unused = ThreadUtils.postOnBackgroundThread(() -> { if (BluetoothUtils.isAudioSharingUIAvailable(getContext())) { if (mShouldTriggerAudioSharingShareThenPairFlow if ((mShouldTriggerShareThenPairFlow || shouldSetTempBondMetadata()) && state == BluetoothAdapter.STATE_CONNECTED && device.equals(mJustBonded) && AUDIO_SHARING_PROFILES.contains(bluetoothProfile) && isReadyForAudioSharing(cachedDevice, bluetoothProfile)) { Log.d(getLogTag(), "onProfileConnectionStateChanged, ready for audio sharing"); Log.d(getLogTag(), "onProfileConnectionStateChanged, lea eligible"); dismissConnectingDialog(); BluetoothUtils.setTemporaryBondMetadata(device); if (mShouldTriggerShareThenPairFlow) { mHandler.removeMessages(AUTO_DISMISS_MESSAGE_ID); finishFragmentWithResultForAudioSharing(device); postOnMainThread(() -> finishFragmentWithResultForAudioSharing(device)); } else { mHandler.removeMessages(AUTO_FINISH_MESSAGE_ID); postOnMainThread(() -> finish()); } } } else { finish(); postOnMainThread(() -> finish()); } }); } else { onDeviceDeleted(cachedDevice); } Loading Loading @@ -314,7 +327,7 @@ public abstract class BluetoothDevicePairingDetailBase extends DeviceListPrefere } @VisibleForTesting boolean shouldTriggerAudioSharingShareThenPairFlow() { boolean shouldTriggerShareThenPairFlow() { if (BluetoothUtils.isAudioSharingUIAvailable(getContext())) { Activity activity = getActivity(); Intent intent = activity == null ? null : activity.getIntent(); Loading @@ -328,6 +341,16 @@ public abstract class BluetoothDevicePairingDetailBase extends DeviceListPrefere return false; } private boolean shouldSetTempBondMetadata() { return Flags.enableTemporaryBondDevicesUi() && BluetoothUtils.isAudioSharingUIAvailable(getContext()) && BluetoothUtils.isBroadcasting(mLocalManager) && mLocalManager != null && mLocalManager.getCachedDeviceManager() != null && mLocalManager.getProfileManager().getLeAudioBroadcastAssistantProfile() != null && !Utils.shouldBlockPairingInAudioSharing(mLocalManager); } private boolean isReadyForAudioSharing(@NonNull CachedBluetoothDevice cachedDevice, int justConnectedProfile) { for (int profile : AUDIO_SHARING_PROFILES) { Loading Loading @@ -382,11 +405,10 @@ public abstract class BluetoothDevicePairingDetailBase extends DeviceListPrefere }); } private void triggerAudioSharingShareThenPairFlow( @NonNull BluetoothDevice device) { private void handleDeviceBondedInAudioSharing(@Nullable BluetoothDevice device) { var unused = ThreadUtils.postOnBackgroundThread(() -> { if (mJustBonded != null) { Log.d(getLogTag(), "Skip triggerAudioSharingShareThenPairFlow, already done"); Log.d(getLogTag(), "Skip handleDeviceBondedInAudioSharing, already done"); return; } mJustBonded = device; Loading @@ -395,18 +417,39 @@ public abstract class BluetoothDevicePairingDetailBase extends DeviceListPrefere String deviceName = TextUtils.isEmpty(aliasName) ? device.getAddress() : aliasName; showConnectingDialog(deviceName); // Wait for AUTO_DISMISS_TIME_THRESHOLD_MS and check if the paired device supports audio // sharing. if (mShouldTriggerShareThenPairFlow) { // For share then pair flow, we have strong signal that users wish to pair new // device to join sharing. // So we wait for AUTO_DISMISS_TIME_THRESHOLD_MS, if we find that the bonded device // is lea in onProfileConnectionStateChanged, we finish the activity, set the device // as temp bond and auto add source; otherwise, show dialog to notify that the // device is incompatible for audio sharing. if (!mHandler.hasMessages(AUTO_DISMISS_MESSAGE_ID)) { mHandler.postDelayed(() -> postOnMainThread( () -> { Log.d(getLogTag(), "Show incompatible dialog when timeout"); Log.d(getLogTag(), "Show incompatible dialog when timeout"); dismissConnectingDialog(); AudioSharingIncompatibleDialogFragment.show(this, deviceName, AudioSharingIncompatibleDialogFragment.show(this, deviceName, () -> finish()); }), AUTO_DISMISS_MESSAGE_ID, AUTO_DISMISS_TIME_THRESHOLD_MS); } } else { // For other pairing request during audio sharing with sinks < 2, we wait for // AUTO_DISMISS_TIME_THRESHOLD_MS, if we find that the bonded device is lea in // onProfileConnectionStateChanged, we finish the activity and set the device as // temp bond; otherwise, we just finish the activity. if (!mHandler.hasMessages(AUTO_FINISH_MESSAGE_ID)) { mHandler.postDelayed(() -> postOnMainThread( () -> { Log.d(getLogTag(), "Finish activity when timeout"); finish(); }), AUTO_FINISH_MESSAGE_ID, AUTO_DISMISS_TIME_THRESHOLD_MS); } } }); } Loading
src/com/android/settings/bluetooth/Utils.java +31 −0 Original line number Diff line number Diff line Loading @@ -33,6 +33,7 @@ import android.provider.Settings; import android.util.Log; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import androidx.appcompat.app.AlertDialog; Loading @@ -42,17 +43,21 @@ import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.bluetooth.BluetoothUtils; import com.android.settingslib.bluetooth.BluetoothUtils.ErrorListener; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothManager.BluetoothManagerCallback; import com.android.settingslib.utils.ThreadUtils; import com.google.common.base.Supplier; import com.google.common.collect.ImmutableList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; import java.util.stream.Collectors; /** * Utils is a helper class that contains constants for various Loading Loading @@ -293,4 +298,30 @@ public final class Utils { ThreadUtils.postOnMainThread(runnable); }); } /** * Check if need to block pairing during audio sharing * * @param localBtManager {@link LocalBluetoothManager} * @return if need to block pairing during audio sharing */ public static boolean shouldBlockPairingInAudioSharing( @NonNull LocalBluetoothManager localBtManager) { if (!BluetoothUtils.isBroadcasting(localBtManager)) return false; LocalBluetoothLeBroadcastAssistant assistant = localBtManager.getProfileManager().getLeAudioBroadcastAssistantProfile(); CachedBluetoothDeviceManager deviceManager = localBtManager.getCachedDeviceManager(); List<BluetoothDevice> connectedDevices = assistant == null ? ImmutableList.of() : assistant.getAllConnectedDevices(); // Block the pairing if there is ongoing audio sharing session and // a) there is already one temp bond sink connected // or b) there are already two sinks joining the audio sharing return assistant != null && deviceManager != null && (connectedDevices.stream().anyMatch(BluetoothUtils::isTemporaryBondDevice) || connectedDevices.stream().filter( d -> BluetoothUtils.hasActiveLocalBroadcastSourceForBtDevice(d, localBtManager)) .map(d -> BluetoothUtils.getGroupId(deviceManager.findDevice(d))).collect( Collectors.toSet()).size() >= 2); } }