Loading packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java +183 −128 Original line number Diff line number Diff line Loading @@ -3,10 +3,13 @@ package com.android.settingslib.bluetooth; import static com.android.settingslib.widget.AdaptiveOutlineDrawable.ICON_TYPE_ADVANCED; import android.annotation.SuppressLint; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothCsipSetCoordinator; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothLeBroadcastReceiveState; import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothStatusCodes; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; Loading @@ -30,6 +33,7 @@ import androidx.annotation.WorkerThread; import androidx.core.graphics.drawable.IconCompat; import com.android.settingslib.R; import com.android.settingslib.flags.Flags; import com.android.settingslib.widget.AdaptiveIcon; import com.android.settingslib.widget.AdaptiveOutlineDrawable; Loading @@ -52,8 +56,8 @@ public class BluetoothUtils { public static final String BT_ADVANCED_HEADER_ENABLED = "bt_advanced_header_enabled"; private static final int METADATA_FAST_PAIR_CUSTOMIZED_FIELDS = 25; private static final String KEY_HEARABLE_CONTROL_SLICE = "HEARABLE_CONTROL_SLICE_WITH_WIDTH"; private static final Set<String> EXCLUSIVE_MANAGERS = ImmutableSet.of( "com.google.android.gms.dck"); private static final Set<String> EXCLUSIVE_MANAGERS = ImmutableSet.of("com.google.android.gms.dck"); private static ErrorListener sErrorListener; Loading Loading @@ -89,23 +93,23 @@ public class BluetoothUtils { /** * @param context to access resources from * @param cachedDevice to get class from * @return pair containing the drawable and the description of the Bluetooth class * of the device. * @return pair containing the drawable and the description of the Bluetooth class of the * device. */ public static Pair<Drawable, String> getBtClassDrawableWithDescription(Context context, CachedBluetoothDevice cachedDevice) { public static Pair<Drawable, String> getBtClassDrawableWithDescription( Context context, CachedBluetoothDevice cachedDevice) { BluetoothClass btClass = cachedDevice.getBtClass(); if (btClass != null) { switch (btClass.getMajorDeviceClass()) { case BluetoothClass.Device.Major.COMPUTER: return new Pair<>(getBluetoothDrawable(context, com.android.internal.R.drawable.ic_bt_laptop), return new Pair<>( getBluetoothDrawable( context, com.android.internal.R.drawable.ic_bt_laptop), context.getString(R.string.bluetooth_talkback_computer)); case BluetoothClass.Device.Major.PHONE: return new Pair<>( getBluetoothDrawable(context, com.android.internal.R.drawable.ic_phone), getBluetoothDrawable(context, com.android.internal.R.drawable.ic_phone), context.getString(R.string.bluetooth_talkback_phone)); case BluetoothClass.Device.Major.PERIPHERAL: Loading @@ -115,8 +119,8 @@ public class BluetoothUtils { case BluetoothClass.Device.Major.IMAGING: return new Pair<>( getBluetoothDrawable(context, com.android.internal.R.drawable.ic_settings_print), getBluetoothDrawable( context, com.android.internal.R.drawable.ic_settings_print), context.getString(R.string.bluetooth_talkback_imaging)); default: Loading @@ -125,8 +129,9 @@ public class BluetoothUtils { } if (cachedDevice.isHearingAidDevice()) { return new Pair<>(getBluetoothDrawable(context, com.android.internal.R.drawable.ic_bt_hearing_aid), return new Pair<>( getBluetoothDrawable( context, com.android.internal.R.drawable.ic_bt_hearing_aid), context.getString(R.string.bluetooth_talkback_hearing_aids)); } Loading @@ -138,7 +143,8 @@ public class BluetoothUtils { // The device should show hearing aid icon if it contains any hearing aid related // profiles if (profile instanceof HearingAidProfile || profile instanceof HapClientProfile) { return new Pair<>(getBluetoothDrawable(context, profileResId), return new Pair<>( getBluetoothDrawable(context, profileResId), context.getString(R.string.bluetooth_talkback_hearing_aids)); } if (resId == 0) { Loading @@ -153,42 +159,40 @@ public class BluetoothUtils { if (btClass != null) { if (doesClassMatch(btClass, BluetoothClass.PROFILE_HEADSET)) { return new Pair<>( getBluetoothDrawable(context, com.android.internal.R.drawable.ic_bt_headset_hfp), getBluetoothDrawable( context, com.android.internal.R.drawable.ic_bt_headset_hfp), context.getString(R.string.bluetooth_talkback_headset)); } if (doesClassMatch(btClass, BluetoothClass.PROFILE_A2DP)) { return new Pair<>( getBluetoothDrawable(context, com.android.internal.R.drawable.ic_bt_headphones_a2dp), getBluetoothDrawable( context, com.android.internal.R.drawable.ic_bt_headphones_a2dp), context.getString(R.string.bluetooth_talkback_headphone)); } } return new Pair<>( getBluetoothDrawable(context, com.android.internal.R.drawable.ic_settings_bluetooth).mutate(), getBluetoothDrawable(context, com.android.internal.R.drawable.ic_settings_bluetooth) .mutate(), context.getString(R.string.bluetooth_talkback_bluetooth)); } /** * Get bluetooth drawable by {@code resId} */ /** Get bluetooth drawable by {@code resId} */ public static Drawable getBluetoothDrawable(Context context, @DrawableRes int resId) { return context.getDrawable(resId); } /** * Get colorful bluetooth icon with description */ public static Pair<Drawable, String> getBtRainbowDrawableWithDescription(Context context, CachedBluetoothDevice cachedDevice) { /** Get colorful bluetooth icon with description */ public static Pair<Drawable, String> getBtRainbowDrawableWithDescription( Context context, CachedBluetoothDevice cachedDevice) { final Resources resources = context.getResources(); final Pair<Drawable, String> pair = BluetoothUtils.getBtDrawableWithDescription(context, cachedDevice); final Pair<Drawable, String> pair = BluetoothUtils.getBtDrawableWithDescription(context, cachedDevice); if (pair.first instanceof BitmapDrawable) { return new Pair<>(new AdaptiveOutlineDrawable( resources, ((BitmapDrawable) pair.first).getBitmap()), pair.second); return new Pair<>( new AdaptiveOutlineDrawable( resources, ((BitmapDrawable) pair.first).getBitmap()), pair.second); } int hashCode; Loading @@ -198,15 +202,12 @@ public class BluetoothUtils { hashCode = cachedDevice.getAddress().hashCode(); } return new Pair<>(buildBtRainbowDrawable(context, pair.first, hashCode), pair.second); return new Pair<>(buildBtRainbowDrawable(context, pair.first, hashCode), pair.second); } /** * Build Bluetooth device icon with rainbow */ private static Drawable buildBtRainbowDrawable(Context context, Drawable drawable, int hashCode) { /** Build Bluetooth device icon with rainbow */ private static Drawable buildBtRainbowDrawable( Context context, Drawable drawable, int hashCode) { final Resources resources = context.getResources(); // Deal with normal headset Loading @@ -222,38 +223,37 @@ public class BluetoothUtils { return adaptiveIcon; } /** * Get bluetooth icon with description */ public static Pair<Drawable, String> getBtDrawableWithDescription(Context context, CachedBluetoothDevice cachedDevice) { final Pair<Drawable, String> pair = BluetoothUtils.getBtClassDrawableWithDescription( context, cachedDevice); /** Get bluetooth icon with description */ public static Pair<Drawable, String> getBtDrawableWithDescription( Context context, CachedBluetoothDevice cachedDevice) { final Pair<Drawable, String> pair = BluetoothUtils.getBtClassDrawableWithDescription(context, cachedDevice); final BluetoothDevice bluetoothDevice = cachedDevice.getDevice(); final int iconSize = context.getResources().getDimensionPixelSize( R.dimen.bt_nearby_icon_size); final int iconSize = context.getResources().getDimensionPixelSize(R.dimen.bt_nearby_icon_size); final Resources resources = context.getResources(); // Deal with advanced device icon if (isAdvancedDetailsHeader(bluetoothDevice)) { final Uri iconUri = getUriMetaData(bluetoothDevice, BluetoothDevice.METADATA_MAIN_ICON); final Uri iconUri = getUriMetaData(bluetoothDevice, BluetoothDevice.METADATA_MAIN_ICON); if (iconUri != null) { try { context.getContentResolver().takePersistableUriPermission(iconUri, Intent.FLAG_GRANT_READ_URI_PERMISSION); context.getContentResolver() .takePersistableUriPermission( iconUri, Intent.FLAG_GRANT_READ_URI_PERMISSION); } catch (SecurityException e) { Log.e(TAG, "Failed to take persistable permission for: " + iconUri, e); } try { final Bitmap bitmap = MediaStore.Images.Media.getBitmap( final Bitmap bitmap = MediaStore.Images.Media.getBitmap( context.getContentResolver(), iconUri); if (bitmap != null) { final Bitmap resizedBitmap = Bitmap.createScaledBitmap(bitmap, iconSize, iconSize, false); final Bitmap resizedBitmap = Bitmap.createScaledBitmap(bitmap, iconSize, iconSize, false); bitmap.recycle(); return new Pair<>(new BitmapDrawable(resources, resizedBitmap), pair.second); return new Pair<>( new BitmapDrawable(resources, resizedBitmap), pair.second); } } catch (IOException e) { Log.e(TAG, "Failed to get drawable for: " + iconUri, e); Loading @@ -280,8 +280,8 @@ public class BluetoothUtils { return true; } // The metadata is for Android S String deviceType = getStringMetaData(bluetoothDevice, BluetoothDevice.METADATA_DEVICE_TYPE); String deviceType = getStringMetaData(bluetoothDevice, BluetoothDevice.METADATA_DEVICE_TYPE); if (TextUtils.equals(deviceType, BluetoothDevice.DEVICE_TYPE_UNTETHERED_HEADSET) || TextUtils.equals(deviceType, BluetoothDevice.DEVICE_TYPE_WATCH) || TextUtils.equals(deviceType, BluetoothDevice.DEVICE_TYPE_DEFAULT) Loading @@ -306,8 +306,8 @@ public class BluetoothUtils { return true; } // The metadata is for Android S String deviceType = getStringMetaData(bluetoothDevice, BluetoothDevice.METADATA_DEVICE_TYPE); String deviceType = getStringMetaData(bluetoothDevice, BluetoothDevice.METADATA_DEVICE_TYPE); if (TextUtils.equals(deviceType, BluetoothDevice.DEVICE_TYPE_UNTETHERED_HEADSET)) { Log.d(TAG, "isAdvancedUntetheredDevice: is untethered device "); return true; Loading @@ -321,15 +321,15 @@ public class BluetoothUtils { * @param device Must be one of the public constants in {@link BluetoothClass.Device} * @return true if device class matches, false otherwise. */ public static boolean isDeviceClassMatched(@NonNull BluetoothDevice bluetoothDevice, int device) { public static boolean isDeviceClassMatched( @NonNull BluetoothDevice bluetoothDevice, int device) { final BluetoothClass bluetoothClass = bluetoothDevice.getBluetoothClass(); return bluetoothClass != null && bluetoothClass.getDeviceClass() == device; } private static boolean isAdvancedHeaderEnabled() { if (!DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SETTINGS_UI, BT_ADVANCED_HEADER_ENABLED, true)) { if (!DeviceConfig.getBoolean( DeviceConfig.NAMESPACE_SETTINGS_UI, BT_ADVANCED_HEADER_ENABLED, true)) { Log.d(TAG, "isAdvancedDetailsHeader: advancedEnabled is false"); return false; } Loading @@ -345,9 +345,7 @@ public class BluetoothUtils { return false; } /** * Create an Icon pointing to a drawable. */ /** Create an Icon pointing to a drawable. */ public static IconCompat createIconWithDrawable(Drawable drawable) { Bitmap bitmap; if (drawable instanceof BitmapDrawable) { Loading @@ -355,19 +353,15 @@ public class BluetoothUtils { } else { final int width = drawable.getIntrinsicWidth(); final int height = drawable.getIntrinsicHeight(); bitmap = createBitmap(drawable, width > 0 ? width : 1, height > 0 ? height : 1); bitmap = createBitmap(drawable, width > 0 ? width : 1, height > 0 ? height : 1); } return IconCompat.createWithBitmap(bitmap); } /** * Build device icon with advanced outline */ /** Build device icon with advanced outline */ public static Drawable buildAdvancedDrawable(Context context, Drawable drawable) { final int iconSize = context.getResources().getDimensionPixelSize( R.dimen.advanced_icon_size); final int iconSize = context.getResources().getDimensionPixelSize(R.dimen.advanced_icon_size); final Resources resources = context.getResources(); Bitmap bitmap = null; Loading @@ -376,14 +370,12 @@ public class BluetoothUtils { } else { final int width = drawable.getIntrinsicWidth(); final int height = drawable.getIntrinsicHeight(); bitmap = createBitmap(drawable, width > 0 ? width : 1, height > 0 ? height : 1); bitmap = createBitmap(drawable, width > 0 ? width : 1, height > 0 ? height : 1); } if (bitmap != null) { final Bitmap resizedBitmap = Bitmap.createScaledBitmap(bitmap, iconSize, iconSize, false); final Bitmap resizedBitmap = Bitmap.createScaledBitmap(bitmap, iconSize, iconSize, false); bitmap.recycle(); return new AdaptiveOutlineDrawable(resources, resizedBitmap, ICON_TYPE_ADVANCED); } Loading @@ -391,9 +383,7 @@ public class BluetoothUtils { return drawable; } /** * Creates a drawable with specified width and height. */ /** Creates a drawable with specified width and height. */ public static Bitmap createBitmap(Drawable drawable, int width, int height) { final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); final Canvas canvas = new Canvas(bitmap); Loading Loading @@ -487,11 +477,8 @@ public class BluetoothUtils { } /** * Check if the Bluetooth device is an AvailableMediaBluetoothDevice, which means: * 1) currently connected * 2) is Hearing Aid or LE Audio * OR * 3) connected profile matches currentAudioProfile * Check if the Bluetooth device is an AvailableMediaBluetoothDevice, which means: 1) currently * connected 2) is Hearing Aid or LE Audio OR 3) connected profile matches currentAudioProfile * * @param cachedDevice the CachedBluetoothDevice * @param audioManager audio manager to get the current audio profile Loading Loading @@ -519,8 +506,11 @@ public class BluetoothUtils { // It would show in Available Devices group. if (cachedDevice.isConnectedAshaHearingAidDevice() || cachedDevice.isConnectedLeAudioDevice()) { Log.d(TAG, "isFilterMatched() device : " + cachedDevice.getName() + ", the profile is connected."); Log.d( TAG, "isFilterMatched() device : " + cachedDevice.getName() + ", the profile is connected."); return true; } // According to the current audio profile type, Loading @@ -541,11 +531,79 @@ public class BluetoothUtils { return isFilterMatched; } /** Returns if the le audio sharing is enabled. */ public static boolean isAudioSharingEnabled() { BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); return Flags.enableLeAudioSharing() && adapter.isLeAudioBroadcastSourceSupported() == BluetoothStatusCodes.FEATURE_SUPPORTED && adapter.isLeAudioBroadcastAssistantSupported() == BluetoothStatusCodes.FEATURE_SUPPORTED; } /** Returns if the broadcast is on-going. */ @WorkerThread public static boolean isBroadcasting(@Nullable LocalBluetoothManager manager) { if (manager == null) return false; LocalBluetoothLeBroadcast broadcast = manager.getProfileManager().getLeAudioBroadcastProfile(); return broadcast != null && broadcast.isEnabled(null); } /** * Checks if the Bluetooth device is an available hearing device, which means: * 1) currently connected * 2) is Hearing Aid * 3) connected profile match hearing aid related profiles (e.g. ASHA, HAP) * Check if {@link CachedBluetoothDevice} has connected to a broadcast source. * * @param cachedDevice The cached bluetooth device to check. * @param localBtManager The BT manager to provide BT functions. * @return Whether the device has connected to a broadcast source. */ @WorkerThread public static boolean hasConnectedBroadcastSource( CachedBluetoothDevice cachedDevice, LocalBluetoothManager localBtManager) { if (localBtManager == null) { Log.d(TAG, "Skip check hasConnectedBroadcastSource due to bt manager is null"); return false; } LocalBluetoothLeBroadcastAssistant assistant = localBtManager.getProfileManager().getLeAudioBroadcastAssistantProfile(); if (assistant == null) { Log.d(TAG, "Skip check hasConnectedBroadcastSource due to assistant profile is null"); return false; } List<BluetoothLeBroadcastReceiveState> sourceList = assistant.getAllSources(cachedDevice.getDevice()); if (!sourceList.isEmpty() && sourceList.stream().anyMatch(BluetoothUtils::isConnected)) { Log.d( TAG, "Lead device has connected broadcast source, device = " + cachedDevice.getDevice().getAnonymizedAddress()); return true; } // Return true if member device is in broadcast. for (CachedBluetoothDevice device : cachedDevice.getMemberDevice()) { List<BluetoothLeBroadcastReceiveState> list = assistant.getAllSources(device.getDevice()); if (!list.isEmpty() && list.stream().anyMatch(BluetoothUtils::isConnected)) { Log.d( TAG, "Member device has connected broadcast source, device = " + device.getDevice().getAnonymizedAddress()); return true; } } return false; } /** Checks the connectivity status based on the provided broadcast receive state. */ @WorkerThread public static boolean isConnected(BluetoothLeBroadcastReceiveState state) { return state.getBisSyncState().stream().anyMatch(bitmap -> bitmap != 0); } /** * Checks if the Bluetooth device is an available hearing device, which means: 1) currently * connected 2) is Hearing Aid 3) connected profile match hearing aid related profiles (e.g. * ASHA, HAP) * * @param cachedDevice the CachedBluetoothDevice * @return if the device is Available hearing device Loading @@ -553,19 +611,20 @@ public class BluetoothUtils { @WorkerThread public static boolean isAvailableHearingDevice(CachedBluetoothDevice cachedDevice) { if (isDeviceConnected(cachedDevice) && cachedDevice.isConnectedHearingAidDevice()) { Log.d(TAG, "isFilterMatched() device : " + cachedDevice.getName() + ", the profile is connected."); Log.d( TAG, "isFilterMatched() device : " + cachedDevice.getName() + ", the profile is connected."); return true; } return false; } /** * Check if the Bluetooth device is a ConnectedBluetoothDevice, which means: * 1) currently connected * 2) is not Hearing Aid or LE Audio * AND * 3) connected profile does not match currentAudioProfile * Check if the Bluetooth device is a ConnectedBluetoothDevice, which means: 1) currently * connected 2) is not Hearing Aid or LE Audio AND 3) connected profile does not match * currentAudioProfile * * @param cachedDevice the CachedBluetoothDevice * @param audioManager audio manager to get the current audio profile Loading Loading @@ -675,29 +734,28 @@ public class BluetoothUtils { } /** * Returns the BluetoothDevice's exclusive manager * ({@link BluetoothDevice.METADATA_EXCLUSIVE_MANAGER} in metadata) if it exists and is in the * given set, otherwise null. * Returns the BluetoothDevice's exclusive manager ({@link * BluetoothDevice.METADATA_EXCLUSIVE_MANAGER} in metadata) if it exists and is in the given * set, otherwise null. */ @Nullable private static String getAllowedExclusiveManager(BluetoothDevice bluetoothDevice) { byte[] exclusiveManagerNameBytes = bluetoothDevice.getMetadata( BluetoothDevice.METADATA_EXCLUSIVE_MANAGER); byte[] exclusiveManagerNameBytes = bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER); if (exclusiveManagerNameBytes == null) { Log.d(TAG, "Bluetooth device " + bluetoothDevice.getName() Log.d( TAG, "Bluetooth device " + bluetoothDevice.getName() + " doesn't have exclusive manager"); return null; } String exclusiveManagerName = new String(exclusiveManagerNameBytes); return getExclusiveManagers().contains(exclusiveManagerName) ? exclusiveManagerName : null; return getExclusiveManagers().contains(exclusiveManagerName) ? exclusiveManagerName : null; } /** * Checks if given package is installed */ private static boolean isPackageInstalled(Context context, String packageName) { /** Checks if given package is installed */ private static boolean isPackageInstalled(Context context, String packageName) { PackageManager packageManager = context.getPackageManager(); try { packageManager.getPackageInfo(packageName, 0); Loading @@ -709,13 +767,12 @@ public class BluetoothUtils { } /** * A BluetoothDevice is exclusively managed if * 1) it has field {@link BluetoothDevice.METADATA_EXCLUSIVE_MANAGER} in metadata. * 2) the exclusive manager app name is in the allowlist. * 3) the exclusive manager app is installed. * A BluetoothDevice is exclusively managed if 1) it has field {@link * BluetoothDevice.METADATA_EXCLUSIVE_MANAGER} in metadata. 2) the exclusive manager app name is * in the allowlist. 3) the exclusive manager app is installed. */ public static boolean isExclusivelyManagedBluetoothDevice(@NonNull Context context, @NonNull BluetoothDevice bluetoothDevice) { public static boolean isExclusivelyManagedBluetoothDevice( @NonNull Context context, @NonNull BluetoothDevice bluetoothDevice) { String exclusiveManagerName = getAllowedExclusiveManager(bluetoothDevice); if (exclusiveManagerName == null) { return false; Loading @@ -728,9 +785,7 @@ public class BluetoothUtils { } } /** * Return the allowlist for exclusive manager names. */ /** Return the allowlist for exclusive manager names. */ @NonNull public static Set<String> getExclusiveManagers() { return EXCLUSIVE_MANAGERS; Loading packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java +42 −0 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java +183 −128 Original line number Diff line number Diff line Loading @@ -3,10 +3,13 @@ package com.android.settingslib.bluetooth; import static com.android.settingslib.widget.AdaptiveOutlineDrawable.ICON_TYPE_ADVANCED; import android.annotation.SuppressLint; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothCsipSetCoordinator; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothLeBroadcastReceiveState; import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothStatusCodes; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; Loading @@ -30,6 +33,7 @@ import androidx.annotation.WorkerThread; import androidx.core.graphics.drawable.IconCompat; import com.android.settingslib.R; import com.android.settingslib.flags.Flags; import com.android.settingslib.widget.AdaptiveIcon; import com.android.settingslib.widget.AdaptiveOutlineDrawable; Loading @@ -52,8 +56,8 @@ public class BluetoothUtils { public static final String BT_ADVANCED_HEADER_ENABLED = "bt_advanced_header_enabled"; private static final int METADATA_FAST_PAIR_CUSTOMIZED_FIELDS = 25; private static final String KEY_HEARABLE_CONTROL_SLICE = "HEARABLE_CONTROL_SLICE_WITH_WIDTH"; private static final Set<String> EXCLUSIVE_MANAGERS = ImmutableSet.of( "com.google.android.gms.dck"); private static final Set<String> EXCLUSIVE_MANAGERS = ImmutableSet.of("com.google.android.gms.dck"); private static ErrorListener sErrorListener; Loading Loading @@ -89,23 +93,23 @@ public class BluetoothUtils { /** * @param context to access resources from * @param cachedDevice to get class from * @return pair containing the drawable and the description of the Bluetooth class * of the device. * @return pair containing the drawable and the description of the Bluetooth class of the * device. */ public static Pair<Drawable, String> getBtClassDrawableWithDescription(Context context, CachedBluetoothDevice cachedDevice) { public static Pair<Drawable, String> getBtClassDrawableWithDescription( Context context, CachedBluetoothDevice cachedDevice) { BluetoothClass btClass = cachedDevice.getBtClass(); if (btClass != null) { switch (btClass.getMajorDeviceClass()) { case BluetoothClass.Device.Major.COMPUTER: return new Pair<>(getBluetoothDrawable(context, com.android.internal.R.drawable.ic_bt_laptop), return new Pair<>( getBluetoothDrawable( context, com.android.internal.R.drawable.ic_bt_laptop), context.getString(R.string.bluetooth_talkback_computer)); case BluetoothClass.Device.Major.PHONE: return new Pair<>( getBluetoothDrawable(context, com.android.internal.R.drawable.ic_phone), getBluetoothDrawable(context, com.android.internal.R.drawable.ic_phone), context.getString(R.string.bluetooth_talkback_phone)); case BluetoothClass.Device.Major.PERIPHERAL: Loading @@ -115,8 +119,8 @@ public class BluetoothUtils { case BluetoothClass.Device.Major.IMAGING: return new Pair<>( getBluetoothDrawable(context, com.android.internal.R.drawable.ic_settings_print), getBluetoothDrawable( context, com.android.internal.R.drawable.ic_settings_print), context.getString(R.string.bluetooth_talkback_imaging)); default: Loading @@ -125,8 +129,9 @@ public class BluetoothUtils { } if (cachedDevice.isHearingAidDevice()) { return new Pair<>(getBluetoothDrawable(context, com.android.internal.R.drawable.ic_bt_hearing_aid), return new Pair<>( getBluetoothDrawable( context, com.android.internal.R.drawable.ic_bt_hearing_aid), context.getString(R.string.bluetooth_talkback_hearing_aids)); } Loading @@ -138,7 +143,8 @@ public class BluetoothUtils { // The device should show hearing aid icon if it contains any hearing aid related // profiles if (profile instanceof HearingAidProfile || profile instanceof HapClientProfile) { return new Pair<>(getBluetoothDrawable(context, profileResId), return new Pair<>( getBluetoothDrawable(context, profileResId), context.getString(R.string.bluetooth_talkback_hearing_aids)); } if (resId == 0) { Loading @@ -153,42 +159,40 @@ public class BluetoothUtils { if (btClass != null) { if (doesClassMatch(btClass, BluetoothClass.PROFILE_HEADSET)) { return new Pair<>( getBluetoothDrawable(context, com.android.internal.R.drawable.ic_bt_headset_hfp), getBluetoothDrawable( context, com.android.internal.R.drawable.ic_bt_headset_hfp), context.getString(R.string.bluetooth_talkback_headset)); } if (doesClassMatch(btClass, BluetoothClass.PROFILE_A2DP)) { return new Pair<>( getBluetoothDrawable(context, com.android.internal.R.drawable.ic_bt_headphones_a2dp), getBluetoothDrawable( context, com.android.internal.R.drawable.ic_bt_headphones_a2dp), context.getString(R.string.bluetooth_talkback_headphone)); } } return new Pair<>( getBluetoothDrawable(context, com.android.internal.R.drawable.ic_settings_bluetooth).mutate(), getBluetoothDrawable(context, com.android.internal.R.drawable.ic_settings_bluetooth) .mutate(), context.getString(R.string.bluetooth_talkback_bluetooth)); } /** * Get bluetooth drawable by {@code resId} */ /** Get bluetooth drawable by {@code resId} */ public static Drawable getBluetoothDrawable(Context context, @DrawableRes int resId) { return context.getDrawable(resId); } /** * Get colorful bluetooth icon with description */ public static Pair<Drawable, String> getBtRainbowDrawableWithDescription(Context context, CachedBluetoothDevice cachedDevice) { /** Get colorful bluetooth icon with description */ public static Pair<Drawable, String> getBtRainbowDrawableWithDescription( Context context, CachedBluetoothDevice cachedDevice) { final Resources resources = context.getResources(); final Pair<Drawable, String> pair = BluetoothUtils.getBtDrawableWithDescription(context, cachedDevice); final Pair<Drawable, String> pair = BluetoothUtils.getBtDrawableWithDescription(context, cachedDevice); if (pair.first instanceof BitmapDrawable) { return new Pair<>(new AdaptiveOutlineDrawable( resources, ((BitmapDrawable) pair.first).getBitmap()), pair.second); return new Pair<>( new AdaptiveOutlineDrawable( resources, ((BitmapDrawable) pair.first).getBitmap()), pair.second); } int hashCode; Loading @@ -198,15 +202,12 @@ public class BluetoothUtils { hashCode = cachedDevice.getAddress().hashCode(); } return new Pair<>(buildBtRainbowDrawable(context, pair.first, hashCode), pair.second); return new Pair<>(buildBtRainbowDrawable(context, pair.first, hashCode), pair.second); } /** * Build Bluetooth device icon with rainbow */ private static Drawable buildBtRainbowDrawable(Context context, Drawable drawable, int hashCode) { /** Build Bluetooth device icon with rainbow */ private static Drawable buildBtRainbowDrawable( Context context, Drawable drawable, int hashCode) { final Resources resources = context.getResources(); // Deal with normal headset Loading @@ -222,38 +223,37 @@ public class BluetoothUtils { return adaptiveIcon; } /** * Get bluetooth icon with description */ public static Pair<Drawable, String> getBtDrawableWithDescription(Context context, CachedBluetoothDevice cachedDevice) { final Pair<Drawable, String> pair = BluetoothUtils.getBtClassDrawableWithDescription( context, cachedDevice); /** Get bluetooth icon with description */ public static Pair<Drawable, String> getBtDrawableWithDescription( Context context, CachedBluetoothDevice cachedDevice) { final Pair<Drawable, String> pair = BluetoothUtils.getBtClassDrawableWithDescription(context, cachedDevice); final BluetoothDevice bluetoothDevice = cachedDevice.getDevice(); final int iconSize = context.getResources().getDimensionPixelSize( R.dimen.bt_nearby_icon_size); final int iconSize = context.getResources().getDimensionPixelSize(R.dimen.bt_nearby_icon_size); final Resources resources = context.getResources(); // Deal with advanced device icon if (isAdvancedDetailsHeader(bluetoothDevice)) { final Uri iconUri = getUriMetaData(bluetoothDevice, BluetoothDevice.METADATA_MAIN_ICON); final Uri iconUri = getUriMetaData(bluetoothDevice, BluetoothDevice.METADATA_MAIN_ICON); if (iconUri != null) { try { context.getContentResolver().takePersistableUriPermission(iconUri, Intent.FLAG_GRANT_READ_URI_PERMISSION); context.getContentResolver() .takePersistableUriPermission( iconUri, Intent.FLAG_GRANT_READ_URI_PERMISSION); } catch (SecurityException e) { Log.e(TAG, "Failed to take persistable permission for: " + iconUri, e); } try { final Bitmap bitmap = MediaStore.Images.Media.getBitmap( final Bitmap bitmap = MediaStore.Images.Media.getBitmap( context.getContentResolver(), iconUri); if (bitmap != null) { final Bitmap resizedBitmap = Bitmap.createScaledBitmap(bitmap, iconSize, iconSize, false); final Bitmap resizedBitmap = Bitmap.createScaledBitmap(bitmap, iconSize, iconSize, false); bitmap.recycle(); return new Pair<>(new BitmapDrawable(resources, resizedBitmap), pair.second); return new Pair<>( new BitmapDrawable(resources, resizedBitmap), pair.second); } } catch (IOException e) { Log.e(TAG, "Failed to get drawable for: " + iconUri, e); Loading @@ -280,8 +280,8 @@ public class BluetoothUtils { return true; } // The metadata is for Android S String deviceType = getStringMetaData(bluetoothDevice, BluetoothDevice.METADATA_DEVICE_TYPE); String deviceType = getStringMetaData(bluetoothDevice, BluetoothDevice.METADATA_DEVICE_TYPE); if (TextUtils.equals(deviceType, BluetoothDevice.DEVICE_TYPE_UNTETHERED_HEADSET) || TextUtils.equals(deviceType, BluetoothDevice.DEVICE_TYPE_WATCH) || TextUtils.equals(deviceType, BluetoothDevice.DEVICE_TYPE_DEFAULT) Loading @@ -306,8 +306,8 @@ public class BluetoothUtils { return true; } // The metadata is for Android S String deviceType = getStringMetaData(bluetoothDevice, BluetoothDevice.METADATA_DEVICE_TYPE); String deviceType = getStringMetaData(bluetoothDevice, BluetoothDevice.METADATA_DEVICE_TYPE); if (TextUtils.equals(deviceType, BluetoothDevice.DEVICE_TYPE_UNTETHERED_HEADSET)) { Log.d(TAG, "isAdvancedUntetheredDevice: is untethered device "); return true; Loading @@ -321,15 +321,15 @@ public class BluetoothUtils { * @param device Must be one of the public constants in {@link BluetoothClass.Device} * @return true if device class matches, false otherwise. */ public static boolean isDeviceClassMatched(@NonNull BluetoothDevice bluetoothDevice, int device) { public static boolean isDeviceClassMatched( @NonNull BluetoothDevice bluetoothDevice, int device) { final BluetoothClass bluetoothClass = bluetoothDevice.getBluetoothClass(); return bluetoothClass != null && bluetoothClass.getDeviceClass() == device; } private static boolean isAdvancedHeaderEnabled() { if (!DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SETTINGS_UI, BT_ADVANCED_HEADER_ENABLED, true)) { if (!DeviceConfig.getBoolean( DeviceConfig.NAMESPACE_SETTINGS_UI, BT_ADVANCED_HEADER_ENABLED, true)) { Log.d(TAG, "isAdvancedDetailsHeader: advancedEnabled is false"); return false; } Loading @@ -345,9 +345,7 @@ public class BluetoothUtils { return false; } /** * Create an Icon pointing to a drawable. */ /** Create an Icon pointing to a drawable. */ public static IconCompat createIconWithDrawable(Drawable drawable) { Bitmap bitmap; if (drawable instanceof BitmapDrawable) { Loading @@ -355,19 +353,15 @@ public class BluetoothUtils { } else { final int width = drawable.getIntrinsicWidth(); final int height = drawable.getIntrinsicHeight(); bitmap = createBitmap(drawable, width > 0 ? width : 1, height > 0 ? height : 1); bitmap = createBitmap(drawable, width > 0 ? width : 1, height > 0 ? height : 1); } return IconCompat.createWithBitmap(bitmap); } /** * Build device icon with advanced outline */ /** Build device icon with advanced outline */ public static Drawable buildAdvancedDrawable(Context context, Drawable drawable) { final int iconSize = context.getResources().getDimensionPixelSize( R.dimen.advanced_icon_size); final int iconSize = context.getResources().getDimensionPixelSize(R.dimen.advanced_icon_size); final Resources resources = context.getResources(); Bitmap bitmap = null; Loading @@ -376,14 +370,12 @@ public class BluetoothUtils { } else { final int width = drawable.getIntrinsicWidth(); final int height = drawable.getIntrinsicHeight(); bitmap = createBitmap(drawable, width > 0 ? width : 1, height > 0 ? height : 1); bitmap = createBitmap(drawable, width > 0 ? width : 1, height > 0 ? height : 1); } if (bitmap != null) { final Bitmap resizedBitmap = Bitmap.createScaledBitmap(bitmap, iconSize, iconSize, false); final Bitmap resizedBitmap = Bitmap.createScaledBitmap(bitmap, iconSize, iconSize, false); bitmap.recycle(); return new AdaptiveOutlineDrawable(resources, resizedBitmap, ICON_TYPE_ADVANCED); } Loading @@ -391,9 +383,7 @@ public class BluetoothUtils { return drawable; } /** * Creates a drawable with specified width and height. */ /** Creates a drawable with specified width and height. */ public static Bitmap createBitmap(Drawable drawable, int width, int height) { final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); final Canvas canvas = new Canvas(bitmap); Loading Loading @@ -487,11 +477,8 @@ public class BluetoothUtils { } /** * Check if the Bluetooth device is an AvailableMediaBluetoothDevice, which means: * 1) currently connected * 2) is Hearing Aid or LE Audio * OR * 3) connected profile matches currentAudioProfile * Check if the Bluetooth device is an AvailableMediaBluetoothDevice, which means: 1) currently * connected 2) is Hearing Aid or LE Audio OR 3) connected profile matches currentAudioProfile * * @param cachedDevice the CachedBluetoothDevice * @param audioManager audio manager to get the current audio profile Loading Loading @@ -519,8 +506,11 @@ public class BluetoothUtils { // It would show in Available Devices group. if (cachedDevice.isConnectedAshaHearingAidDevice() || cachedDevice.isConnectedLeAudioDevice()) { Log.d(TAG, "isFilterMatched() device : " + cachedDevice.getName() + ", the profile is connected."); Log.d( TAG, "isFilterMatched() device : " + cachedDevice.getName() + ", the profile is connected."); return true; } // According to the current audio profile type, Loading @@ -541,11 +531,79 @@ public class BluetoothUtils { return isFilterMatched; } /** Returns if the le audio sharing is enabled. */ public static boolean isAudioSharingEnabled() { BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); return Flags.enableLeAudioSharing() && adapter.isLeAudioBroadcastSourceSupported() == BluetoothStatusCodes.FEATURE_SUPPORTED && adapter.isLeAudioBroadcastAssistantSupported() == BluetoothStatusCodes.FEATURE_SUPPORTED; } /** Returns if the broadcast is on-going. */ @WorkerThread public static boolean isBroadcasting(@Nullable LocalBluetoothManager manager) { if (manager == null) return false; LocalBluetoothLeBroadcast broadcast = manager.getProfileManager().getLeAudioBroadcastProfile(); return broadcast != null && broadcast.isEnabled(null); } /** * Checks if the Bluetooth device is an available hearing device, which means: * 1) currently connected * 2) is Hearing Aid * 3) connected profile match hearing aid related profiles (e.g. ASHA, HAP) * Check if {@link CachedBluetoothDevice} has connected to a broadcast source. * * @param cachedDevice The cached bluetooth device to check. * @param localBtManager The BT manager to provide BT functions. * @return Whether the device has connected to a broadcast source. */ @WorkerThread public static boolean hasConnectedBroadcastSource( CachedBluetoothDevice cachedDevice, LocalBluetoothManager localBtManager) { if (localBtManager == null) { Log.d(TAG, "Skip check hasConnectedBroadcastSource due to bt manager is null"); return false; } LocalBluetoothLeBroadcastAssistant assistant = localBtManager.getProfileManager().getLeAudioBroadcastAssistantProfile(); if (assistant == null) { Log.d(TAG, "Skip check hasConnectedBroadcastSource due to assistant profile is null"); return false; } List<BluetoothLeBroadcastReceiveState> sourceList = assistant.getAllSources(cachedDevice.getDevice()); if (!sourceList.isEmpty() && sourceList.stream().anyMatch(BluetoothUtils::isConnected)) { Log.d( TAG, "Lead device has connected broadcast source, device = " + cachedDevice.getDevice().getAnonymizedAddress()); return true; } // Return true if member device is in broadcast. for (CachedBluetoothDevice device : cachedDevice.getMemberDevice()) { List<BluetoothLeBroadcastReceiveState> list = assistant.getAllSources(device.getDevice()); if (!list.isEmpty() && list.stream().anyMatch(BluetoothUtils::isConnected)) { Log.d( TAG, "Member device has connected broadcast source, device = " + device.getDevice().getAnonymizedAddress()); return true; } } return false; } /** Checks the connectivity status based on the provided broadcast receive state. */ @WorkerThread public static boolean isConnected(BluetoothLeBroadcastReceiveState state) { return state.getBisSyncState().stream().anyMatch(bitmap -> bitmap != 0); } /** * Checks if the Bluetooth device is an available hearing device, which means: 1) currently * connected 2) is Hearing Aid 3) connected profile match hearing aid related profiles (e.g. * ASHA, HAP) * * @param cachedDevice the CachedBluetoothDevice * @return if the device is Available hearing device Loading @@ -553,19 +611,20 @@ public class BluetoothUtils { @WorkerThread public static boolean isAvailableHearingDevice(CachedBluetoothDevice cachedDevice) { if (isDeviceConnected(cachedDevice) && cachedDevice.isConnectedHearingAidDevice()) { Log.d(TAG, "isFilterMatched() device : " + cachedDevice.getName() + ", the profile is connected."); Log.d( TAG, "isFilterMatched() device : " + cachedDevice.getName() + ", the profile is connected."); return true; } return false; } /** * Check if the Bluetooth device is a ConnectedBluetoothDevice, which means: * 1) currently connected * 2) is not Hearing Aid or LE Audio * AND * 3) connected profile does not match currentAudioProfile * Check if the Bluetooth device is a ConnectedBluetoothDevice, which means: 1) currently * connected 2) is not Hearing Aid or LE Audio AND 3) connected profile does not match * currentAudioProfile * * @param cachedDevice the CachedBluetoothDevice * @param audioManager audio manager to get the current audio profile Loading Loading @@ -675,29 +734,28 @@ public class BluetoothUtils { } /** * Returns the BluetoothDevice's exclusive manager * ({@link BluetoothDevice.METADATA_EXCLUSIVE_MANAGER} in metadata) if it exists and is in the * given set, otherwise null. * Returns the BluetoothDevice's exclusive manager ({@link * BluetoothDevice.METADATA_EXCLUSIVE_MANAGER} in metadata) if it exists and is in the given * set, otherwise null. */ @Nullable private static String getAllowedExclusiveManager(BluetoothDevice bluetoothDevice) { byte[] exclusiveManagerNameBytes = bluetoothDevice.getMetadata( BluetoothDevice.METADATA_EXCLUSIVE_MANAGER); byte[] exclusiveManagerNameBytes = bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER); if (exclusiveManagerNameBytes == null) { Log.d(TAG, "Bluetooth device " + bluetoothDevice.getName() Log.d( TAG, "Bluetooth device " + bluetoothDevice.getName() + " doesn't have exclusive manager"); return null; } String exclusiveManagerName = new String(exclusiveManagerNameBytes); return getExclusiveManagers().contains(exclusiveManagerName) ? exclusiveManagerName : null; return getExclusiveManagers().contains(exclusiveManagerName) ? exclusiveManagerName : null; } /** * Checks if given package is installed */ private static boolean isPackageInstalled(Context context, String packageName) { /** Checks if given package is installed */ private static boolean isPackageInstalled(Context context, String packageName) { PackageManager packageManager = context.getPackageManager(); try { packageManager.getPackageInfo(packageName, 0); Loading @@ -709,13 +767,12 @@ public class BluetoothUtils { } /** * A BluetoothDevice is exclusively managed if * 1) it has field {@link BluetoothDevice.METADATA_EXCLUSIVE_MANAGER} in metadata. * 2) the exclusive manager app name is in the allowlist. * 3) the exclusive manager app is installed. * A BluetoothDevice is exclusively managed if 1) it has field {@link * BluetoothDevice.METADATA_EXCLUSIVE_MANAGER} in metadata. 2) the exclusive manager app name is * in the allowlist. 3) the exclusive manager app is installed. */ public static boolean isExclusivelyManagedBluetoothDevice(@NonNull Context context, @NonNull BluetoothDevice bluetoothDevice) { public static boolean isExclusivelyManagedBluetoothDevice( @NonNull Context context, @NonNull BluetoothDevice bluetoothDevice) { String exclusiveManagerName = getAllowedExclusiveManager(bluetoothDevice); if (exclusiveManagerName == null) { return false; Loading @@ -728,9 +785,7 @@ public class BluetoothUtils { } } /** * Return the allowlist for exclusive manager names. */ /** Return the allowlist for exclusive manager names. */ @NonNull public static Set<String> getExclusiveManagers() { return EXCLUSIVE_MANAGERS; Loading
packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java +42 −0 File changed.Preview size limit exceeded, changes collapsed. Show changes