Loading res/values/strings.xml +2 −0 Original line number Diff line number Diff line Loading @@ -14057,6 +14057,8 @@ <string name="audio_sharing_block_pairing_dialog_content">To pair a new device, turn off Audio Sharing first.</string> <!-- Text for audio sharing turn off button [CHAR LIMIT=none]--> <string name="audio_sharing_turn_off_button_label">Turn off</string> <!-- Title for audio sharing notification to share audio with headset [CHAR LIMIT=none]--> <string name="share_audio_notification_title">Share audio with <xliff:g example="My buds" id="device_name">%1$s</xliff:g></string> <!-- Title for audio streams preference category [CHAR LIMIT=none]--> <string name="audio_streams_category_title">Connect to a LE audio stream</string> src/com/android/settings/connecteddevice/audiosharing/AudioSharingReceiver.java +146 −17 Original line number Diff line number Diff line Loading @@ -16,17 +16,25 @@ package com.android.settings.connecteddevice.audiosharing; import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast.EXTRA_BLUETOOTH_DEVICE; import android.Manifest; import android.app.ActivityManager; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.settings.SettingsEnums; import android.bluetooth.BluetoothDevice; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.os.Bundle; import android.text.TextUtils; import android.util.Log; import androidx.annotation.NonNull; import androidx.core.app.NotificationCompat; import com.android.settings.R; Loading @@ -43,12 +51,19 @@ public class AudioSharingReceiver extends BroadcastReceiver { "com.android.settings.BLUETOOTH_AUDIO_SHARING_SETTINGS"; private static final String ACTION_LE_AUDIO_SHARING_STOP = "com.android.settings.action.BLUETOOTH_LE_AUDIO_SHARING_STOP"; private static final String ACTION_LE_AUDIO_SHARING_ADD_SOURCE = "com.android.settings.action.BLUETOOTH_LE_AUDIO_SHARING_ADD_SOURCE"; private static final String ACTION_LE_AUDIO_SHARING_CANCEL_NOTIF = "com.android.settings.action.BLUETOOTH_LE_AUDIO_SHARING_CANCEL_NOTIF"; private static final String EXTRA_NOTIF_ID = "NOTIF_ID"; private static final String CHANNEL_ID = "bluetooth_notification_channel"; private static final int NOTIFICATION_ID = private static final int AUDIO_SHARING_NOTIFICATION_ID = com.android.settingslib.R.drawable.ic_bt_le_audio_sharing; private static final int ADD_SOURCE_NOTIFICATION_ID = R.string.share_audio_notification_title; private static final int NOTIF_AUTO_DISMISS_MILLIS = 300000; //5mins @Override public void onReceive(Context context, Intent intent) { public void onReceive(@NonNull Context context, @NonNull Intent intent) { String action = intent.getAction(); if (action == null) { Log.w(TAG, "Received unexpected intent with null action."); Loading @@ -74,10 +89,12 @@ public class AudioSharingReceiver extends BroadcastReceiver { // isLeAudioBroadcastSourceSupported() and BluetoothAdapter# // isLeAudioBroadcastAssistantSupported() always return FEATURE_SUPPORTED // or FEATURE_NOT_SUPPORTED when BT and BLE off cancelSharingNotification(context); cancelSharingNotification(context, AUDIO_SHARING_NOTIFICATION_ID); metricsFeatureProvider.action( context, SettingsEnums.ACTION_CANCEL_AUDIO_SHARING_NOTIFICATION, LocalBluetoothLeBroadcast.ACTION_LE_AUDIO_SHARING_STATE_CHANGE); cancelSharingNotification(context, ADD_SOURCE_NOTIFICATION_ID); // TODO: add metric } else { Log.w( TAG, Loading @@ -99,27 +116,40 @@ public class AudioSharingReceiver extends BroadcastReceiver { // isLeAudioBroadcastSourceSupported() and BluetoothAdapter# // isLeAudioBroadcastAssistantSupported() always return FEATURE_SUPPORTED // or FEATURE_NOT_SUPPORTED when BT and BLE off cancelSharingNotification(context); cancelSharingNotification(context, AUDIO_SHARING_NOTIFICATION_ID); metricsFeatureProvider.action( context, SettingsEnums.ACTION_CANCEL_AUDIO_SHARING_NOTIFICATION, ACTION_LE_AUDIO_SHARING_STOP); cancelSharingNotification(context, ADD_SOURCE_NOTIFICATION_ID); break; case LocalBluetoothLeBroadcast.ACTION_LE_AUDIO_SHARING_DEVICE_CONNECTED: if (!BluetoothUtils.isAudioSharingUIAvailable(context)) { Log.d(TAG, "Skip ACTION_LE_AUDIO_SHARING_DEVICE_CONNECTED, feature disabled."); return; } BluetoothDevice device = intent.getParcelableExtra(EXTRA_BLUETOOTH_DEVICE, BluetoothDevice.class); if (device == null) { Log.d(TAG, "Skip ACTION_LE_AUDIO_SHARING_DEVICE_CONNECTED, null device"); return; } if (isAppInForeground(context)) { // TODO: show dialog Log.d(TAG, "App in foreground, show share audio dialog"); } else { Log.d(TAG, "App not in foreground, show share audio notification"); showAddSourceNotification(context, device); } break; default: Log.w(TAG, "Received unexpected intent " + intent.getAction()); } } private void showSharingNotification(Context context) { private void showSharingNotification(@NonNull Context context) { NotificationManager nm = context.getSystemService(NotificationManager.class); if (nm.getNotificationChannel(CHANNEL_ID) == null) { Log.d(TAG, "Create bluetooth notification channel"); NotificationChannel notificationChannel = new NotificationChannel( CHANNEL_ID, context.getString(com.android.settings.R.string.bluetooth), NotificationManager.IMPORTANCE_HIGH); nm.createNotificationChannel(notificationChannel); } if (nm == null) return; createNotificationChannelIfNeeded(nm, context); Intent stopIntent = new Intent(ACTION_LE_AUDIO_SHARING_STOP).setPackage(context.getPackageName()); PendingIntent stopPendingIntent = Loading Loading @@ -174,11 +204,110 @@ public class AudioSharingReceiver extends BroadcastReceiver { .addAction(stopAction) .addAction(settingsAction) .addExtras(extras); nm.notify(NOTIFICATION_ID, builder.build()); nm.notify(AUDIO_SHARING_NOTIFICATION_ID, builder.build()); } private void showAddSourceNotification(@NonNull Context context, @NonNull BluetoothDevice device) { NotificationManager nm = context.getSystemService(NotificationManager.class); if (nm == null) return; createNotificationChannelIfNeeded(nm, context); Intent addSourceIntent = new Intent(ACTION_LE_AUDIO_SHARING_ADD_SOURCE).setPackage(context.getPackageName()) .putExtra(EXTRA_BLUETOOTH_DEVICE, device); PendingIntent addSourcePendingIntent = PendingIntent.getBroadcast( context, R.string.audio_sharing_share_button_label, addSourceIntent, PendingIntent.FLAG_IMMUTABLE); NotificationCompat.Action addSourceAction = new NotificationCompat.Action.Builder( 0, context.getString(R.string.audio_sharing_share_button_label), addSourcePendingIntent) .build(); Intent cancelIntent = new Intent(ACTION_LE_AUDIO_SHARING_CANCEL_NOTIF).setPackage( context.getPackageName()) .putExtra(EXTRA_NOTIF_ID, ADD_SOURCE_NOTIFICATION_ID); PendingIntent cancelPendingIntent = PendingIntent.getBroadcast( context, R.string.cancel, cancelIntent, PendingIntent.FLAG_IMMUTABLE); NotificationCompat.Action cancelAction = new NotificationCompat.Action.Builder( 0, context.getString(R.string.cancel), cancelPendingIntent) .build(); final Bundle extras = new Bundle(); extras.putString( Notification.EXTRA_SUBSTITUTE_APP_NAME, context.getString(R.string.audio_sharing_title)); String deviceName = device.getAlias(); if (TextUtils.isEmpty(deviceName)) { deviceName = device.getAddress(); } NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID) .setSmallIcon(com.android.settingslib.R.drawable.ic_bt_le_audio_sharing) .setLocalOnly(true) .setContentTitle(context.getString(R.string.share_audio_notification_title, deviceName)) .setContentText( context.getString(R.string.audio_sharing_notification_content)) .setOngoing(true) .setSilent(true) .setColor( context.getColor( com.android.internal.R.color .system_notification_accent_color)) .addAction(addSourceAction) .addAction(cancelAction) .setTimeoutAfter(NOTIF_AUTO_DISMISS_MILLIS) .addExtras(extras); nm.notify(ADD_SOURCE_NOTIFICATION_ID, builder.build()); } private void cancelSharingNotification(Context context) { private void cancelSharingNotification(@NonNull Context context, int notifId) { NotificationManager nm = context.getSystemService(NotificationManager.class); nm.cancel(NOTIFICATION_ID); if (nm != null) { nm.cancel(notifId); } } private void createNotificationChannelIfNeeded(@NonNull NotificationManager nm, @NonNull Context context) { if (nm.getNotificationChannel(CHANNEL_ID) == null) { Log.d(TAG, "Create bluetooth notification channel"); NotificationChannel notificationChannel = new NotificationChannel( CHANNEL_ID, context.getString(com.android.settings.R.string.bluetooth), NotificationManager.IMPORTANCE_HIGH); nm.createNotificationChannel(notificationChannel); } } private boolean isAppInForeground(@NonNull Context context) { try { ActivityManager activityManager = context.getSystemService(ActivityManager.class); String packageName = context.getPackageName(); if (context.getPackageManager().checkPermission(Manifest.permission.PACKAGE_USAGE_STATS, packageName) != PackageManager.PERMISSION_GRANTED) { Log.d(TAG, "check isAppInForeground, returns false due to no permission"); return false; } if (packageName != null && activityManager.getPackageImportance(packageName) == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) { Log.d(TAG, "check isAppInForeground, returns true"); return true; } } catch (RuntimeException e) { Log.d(TAG, "check isAppInForeground, error = " + e.getMessage()); } return false; } } tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingReceiverTest.java +88 −14 Original line number Diff line number Diff line Loading @@ -16,28 +16,38 @@ package com.android.settings.connecteddevice.audiosharing; import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast.ACTION_LE_AUDIO_SHARING_DEVICE_CONNECTED; import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast.ACTION_LE_AUDIO_SHARING_STATE_CHANGE; import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast.BROADCAST_STATE_OFF; import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast.BROADCAST_STATE_ON; import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast.EXTRA_BLUETOOTH_DEVICE; import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast.EXTRA_LE_AUDIO_SHARING_STATE; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; import android.Manifest; import android.app.ActivityManager; import android.app.Notification; import android.app.NotificationManager; import android.app.settings.SettingsEnums; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothStatusCodes; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; import com.android.settings.bluetooth.Utils; Loading Loading @@ -72,6 +82,7 @@ import java.util.stream.Collectors; public class AudioSharingReceiverTest { private static final String ACTION_LE_AUDIO_SHARING_STOP = "com.android.settings.action.BLUETOOTH_LE_AUDIO_SHARING_STOP"; private static final String TEST_DEVICE_NAME = "test"; @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); Loading @@ -88,7 +99,7 @@ public class AudioSharingReceiverTest { @Before public void setUp() { mContext = RuntimeEnvironment.getApplication(); mContext = spy(RuntimeEnvironment.getApplication()); mShadowApplication = Shadow.extract(mContext); mShadowApplication.setSystemService(Context.NOTIFICATION_SERVICE, mNm); mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter()); Loading Loading @@ -130,9 +141,8 @@ public class AudioSharingReceiverTest { } @Test @DisableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) public void broadcastReceiver_receiveAudioSharingStateOn_flagOff_doNothing() { mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); Intent intent = new Intent(ACTION_LE_AUDIO_SHARING_STATE_CHANGE); intent.setPackage(mContext.getPackageName()); intent.putExtra(EXTRA_LE_AUDIO_SHARING_STATE, BROADCAST_STATE_ON); Loading @@ -144,8 +154,8 @@ public class AudioSharingReceiverTest { } @Test @EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) public void broadcastReceiver_receiveAudioSharingStateOn_broadcastDisabled_doNothing() { mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported( BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED); Loading @@ -160,9 +170,8 @@ public class AudioSharingReceiverTest { } @Test @EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) public void broadcastReceiver_receiveAudioSharingStateChangeIntentNoState_doNothing() { mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); Intent intent = new Intent(ACTION_LE_AUDIO_SHARING_STATE_CHANGE); intent.setPackage(mContext.getPackageName()); AudioSharingReceiver audioSharingReceiver = getAudioSharingReceiver(intent); Loading @@ -173,9 +182,8 @@ public class AudioSharingReceiverTest { } @Test @EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) public void broadcastReceiver_receiveAudioSharingStateOn_broadcastEnabled_showNotification() { mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); Intent intent = new Intent(ACTION_LE_AUDIO_SHARING_STATE_CHANGE); intent.setPackage(mContext.getPackageName()); intent.putExtra(EXTRA_LE_AUDIO_SHARING_STATE, BROADCAST_STATE_ON); Loading @@ -188,9 +196,9 @@ public class AudioSharingReceiverTest { } @Test @EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) public void broadcastReceiver_receiveAudioSharingStateOff_broadcastDisabled_cancelNotification() { mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported( BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED); Loading @@ -207,10 +215,9 @@ public class AudioSharingReceiverTest { } @Test @EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) public void broadcastReceiver_receiveAudioSharingStateOff_broadcastEnabled_cancelNotification() { mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); Intent intent = new Intent(ACTION_LE_AUDIO_SHARING_STATE_CHANGE); intent.setPackage(mContext.getPackageName()); intent.putExtra(EXTRA_LE_AUDIO_SHARING_STATE, BROADCAST_STATE_OFF); Loading @@ -224,8 +231,8 @@ public class AudioSharingReceiverTest { } @Test @EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) public void broadcastReceiver_receiveAudioSharingStop_broadcastDisabled_cancelNotification() { mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported( BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED); Loading @@ -242,8 +249,8 @@ public class AudioSharingReceiverTest { } @Test @EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) public void broadcastReceiver_receiveAudioSharingStop_notInBroadcast_cancelNotification() { mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); when(mBroadcast.isEnabled(null)).thenReturn(false); int broadcastId = 1; when(mBroadcast.getLatestBroadcastId()).thenReturn(broadcastId); Loading @@ -261,8 +268,8 @@ public class AudioSharingReceiverTest { } @Test @EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) public void broadcastReceiver_receiveAudioSharingStop_inBroadcast_stopBroadcast() { mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); when(mBroadcast.isEnabled(null)).thenReturn(true); int broadcastId = 1; when(mBroadcast.getLatestBroadcastId()).thenReturn(broadcastId); Loading @@ -281,6 +288,61 @@ public class AudioSharingReceiverTest { ACTION_LE_AUDIO_SHARING_STOP); } @Test @EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) public void broadcastReceiver_receiveAudioSharingDeviceConnected_broadcastDisabled_doNothing() { mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported( BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED); BluetoothDevice device = mock(BluetoothDevice.class); when(device.getAlias()).thenReturn(TEST_DEVICE_NAME); setAppInForeground(false); Intent intent = new Intent(ACTION_LE_AUDIO_SHARING_DEVICE_CONNECTED); intent.setPackage(mContext.getPackageName()); intent.putExtra(EXTRA_BLUETOOTH_DEVICE, device); AudioSharingReceiver audioSharingReceiver = getAudioSharingReceiver(intent); audioSharingReceiver.onReceive(mContext, intent); verify(mNm, never()).notify( eq(com.android.settings.R.string.share_audio_notification_title), any(Notification.class)); } @Test @EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) public void broadcastReceiver_receiveAudioSharingDeviceConnected_showDialog() { BluetoothDevice device = mock(BluetoothDevice.class); when(device.getAlias()).thenReturn(TEST_DEVICE_NAME); setAppInForeground(true); Intent intent = new Intent(ACTION_LE_AUDIO_SHARING_DEVICE_CONNECTED); intent.setPackage(mContext.getPackageName()); intent.putExtra(EXTRA_BLUETOOTH_DEVICE, device); AudioSharingReceiver audioSharingReceiver = getAudioSharingReceiver(intent); audioSharingReceiver.onReceive(mContext, intent); verify(mNm, never()).notify( eq(com.android.settings.R.string.share_audio_notification_title), any(Notification.class)); // TODO: verify show dialog once impl complete } @Test @EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) public void broadcastReceiver_receiveAudioSharingDeviceConnected_showNotification() { BluetoothDevice device = mock(BluetoothDevice.class); when(device.getAlias()).thenReturn(TEST_DEVICE_NAME); setAppInForeground(false); Intent intent = new Intent(ACTION_LE_AUDIO_SHARING_DEVICE_CONNECTED); intent.setPackage(mContext.getPackageName()); intent.putExtra(EXTRA_BLUETOOTH_DEVICE, device); AudioSharingReceiver audioSharingReceiver = getAudioSharingReceiver(intent); audioSharingReceiver.onReceive(mContext, intent); // TODO: verify no dialog once impl complete verify(mNm).notify(eq(com.android.settings.R.string.share_audio_notification_title), any(Notification.class)); } private AudioSharingReceiver getAudioSharingReceiver(Intent intent) { assertThat(mShadowApplication.hasReceiverForIntent(intent)).isTrue(); List<BroadcastReceiver> receiversForIntent = Loading @@ -290,4 +352,16 @@ public class AudioSharingReceiverTest { assertThat(broadcastReceiver).isInstanceOf(AudioSharingReceiver.class); return (AudioSharingReceiver) broadcastReceiver; } private void setAppInForeground(boolean foreground) { ActivityManager activityManager = mock(ActivityManager.class); when(mContext.getSystemService(ActivityManager.class)).thenReturn(activityManager); when(activityManager.getPackageImportance(mContext.getPackageName())).thenReturn( foreground ? ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND : ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE); PackageManager packageManager = mock(PackageManager.class); when(mContext.getPackageManager()).thenReturn(packageManager); when(packageManager.checkPermission(Manifest.permission.PACKAGE_USAGE_STATS, mContext.getPackageName())).thenReturn(PackageManager.PERMISSION_GRANTED); } } Loading
res/values/strings.xml +2 −0 Original line number Diff line number Diff line Loading @@ -14057,6 +14057,8 @@ <string name="audio_sharing_block_pairing_dialog_content">To pair a new device, turn off Audio Sharing first.</string> <!-- Text for audio sharing turn off button [CHAR LIMIT=none]--> <string name="audio_sharing_turn_off_button_label">Turn off</string> <!-- Title for audio sharing notification to share audio with headset [CHAR LIMIT=none]--> <string name="share_audio_notification_title">Share audio with <xliff:g example="My buds" id="device_name">%1$s</xliff:g></string> <!-- Title for audio streams preference category [CHAR LIMIT=none]--> <string name="audio_streams_category_title">Connect to a LE audio stream</string>
src/com/android/settings/connecteddevice/audiosharing/AudioSharingReceiver.java +146 −17 Original line number Diff line number Diff line Loading @@ -16,17 +16,25 @@ package com.android.settings.connecteddevice.audiosharing; import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast.EXTRA_BLUETOOTH_DEVICE; import android.Manifest; import android.app.ActivityManager; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.settings.SettingsEnums; import android.bluetooth.BluetoothDevice; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.os.Bundle; import android.text.TextUtils; import android.util.Log; import androidx.annotation.NonNull; import androidx.core.app.NotificationCompat; import com.android.settings.R; Loading @@ -43,12 +51,19 @@ public class AudioSharingReceiver extends BroadcastReceiver { "com.android.settings.BLUETOOTH_AUDIO_SHARING_SETTINGS"; private static final String ACTION_LE_AUDIO_SHARING_STOP = "com.android.settings.action.BLUETOOTH_LE_AUDIO_SHARING_STOP"; private static final String ACTION_LE_AUDIO_SHARING_ADD_SOURCE = "com.android.settings.action.BLUETOOTH_LE_AUDIO_SHARING_ADD_SOURCE"; private static final String ACTION_LE_AUDIO_SHARING_CANCEL_NOTIF = "com.android.settings.action.BLUETOOTH_LE_AUDIO_SHARING_CANCEL_NOTIF"; private static final String EXTRA_NOTIF_ID = "NOTIF_ID"; private static final String CHANNEL_ID = "bluetooth_notification_channel"; private static final int NOTIFICATION_ID = private static final int AUDIO_SHARING_NOTIFICATION_ID = com.android.settingslib.R.drawable.ic_bt_le_audio_sharing; private static final int ADD_SOURCE_NOTIFICATION_ID = R.string.share_audio_notification_title; private static final int NOTIF_AUTO_DISMISS_MILLIS = 300000; //5mins @Override public void onReceive(Context context, Intent intent) { public void onReceive(@NonNull Context context, @NonNull Intent intent) { String action = intent.getAction(); if (action == null) { Log.w(TAG, "Received unexpected intent with null action."); Loading @@ -74,10 +89,12 @@ public class AudioSharingReceiver extends BroadcastReceiver { // isLeAudioBroadcastSourceSupported() and BluetoothAdapter# // isLeAudioBroadcastAssistantSupported() always return FEATURE_SUPPORTED // or FEATURE_NOT_SUPPORTED when BT and BLE off cancelSharingNotification(context); cancelSharingNotification(context, AUDIO_SHARING_NOTIFICATION_ID); metricsFeatureProvider.action( context, SettingsEnums.ACTION_CANCEL_AUDIO_SHARING_NOTIFICATION, LocalBluetoothLeBroadcast.ACTION_LE_AUDIO_SHARING_STATE_CHANGE); cancelSharingNotification(context, ADD_SOURCE_NOTIFICATION_ID); // TODO: add metric } else { Log.w( TAG, Loading @@ -99,27 +116,40 @@ public class AudioSharingReceiver extends BroadcastReceiver { // isLeAudioBroadcastSourceSupported() and BluetoothAdapter# // isLeAudioBroadcastAssistantSupported() always return FEATURE_SUPPORTED // or FEATURE_NOT_SUPPORTED when BT and BLE off cancelSharingNotification(context); cancelSharingNotification(context, AUDIO_SHARING_NOTIFICATION_ID); metricsFeatureProvider.action( context, SettingsEnums.ACTION_CANCEL_AUDIO_SHARING_NOTIFICATION, ACTION_LE_AUDIO_SHARING_STOP); cancelSharingNotification(context, ADD_SOURCE_NOTIFICATION_ID); break; case LocalBluetoothLeBroadcast.ACTION_LE_AUDIO_SHARING_DEVICE_CONNECTED: if (!BluetoothUtils.isAudioSharingUIAvailable(context)) { Log.d(TAG, "Skip ACTION_LE_AUDIO_SHARING_DEVICE_CONNECTED, feature disabled."); return; } BluetoothDevice device = intent.getParcelableExtra(EXTRA_BLUETOOTH_DEVICE, BluetoothDevice.class); if (device == null) { Log.d(TAG, "Skip ACTION_LE_AUDIO_SHARING_DEVICE_CONNECTED, null device"); return; } if (isAppInForeground(context)) { // TODO: show dialog Log.d(TAG, "App in foreground, show share audio dialog"); } else { Log.d(TAG, "App not in foreground, show share audio notification"); showAddSourceNotification(context, device); } break; default: Log.w(TAG, "Received unexpected intent " + intent.getAction()); } } private void showSharingNotification(Context context) { private void showSharingNotification(@NonNull Context context) { NotificationManager nm = context.getSystemService(NotificationManager.class); if (nm.getNotificationChannel(CHANNEL_ID) == null) { Log.d(TAG, "Create bluetooth notification channel"); NotificationChannel notificationChannel = new NotificationChannel( CHANNEL_ID, context.getString(com.android.settings.R.string.bluetooth), NotificationManager.IMPORTANCE_HIGH); nm.createNotificationChannel(notificationChannel); } if (nm == null) return; createNotificationChannelIfNeeded(nm, context); Intent stopIntent = new Intent(ACTION_LE_AUDIO_SHARING_STOP).setPackage(context.getPackageName()); PendingIntent stopPendingIntent = Loading Loading @@ -174,11 +204,110 @@ public class AudioSharingReceiver extends BroadcastReceiver { .addAction(stopAction) .addAction(settingsAction) .addExtras(extras); nm.notify(NOTIFICATION_ID, builder.build()); nm.notify(AUDIO_SHARING_NOTIFICATION_ID, builder.build()); } private void showAddSourceNotification(@NonNull Context context, @NonNull BluetoothDevice device) { NotificationManager nm = context.getSystemService(NotificationManager.class); if (nm == null) return; createNotificationChannelIfNeeded(nm, context); Intent addSourceIntent = new Intent(ACTION_LE_AUDIO_SHARING_ADD_SOURCE).setPackage(context.getPackageName()) .putExtra(EXTRA_BLUETOOTH_DEVICE, device); PendingIntent addSourcePendingIntent = PendingIntent.getBroadcast( context, R.string.audio_sharing_share_button_label, addSourceIntent, PendingIntent.FLAG_IMMUTABLE); NotificationCompat.Action addSourceAction = new NotificationCompat.Action.Builder( 0, context.getString(R.string.audio_sharing_share_button_label), addSourcePendingIntent) .build(); Intent cancelIntent = new Intent(ACTION_LE_AUDIO_SHARING_CANCEL_NOTIF).setPackage( context.getPackageName()) .putExtra(EXTRA_NOTIF_ID, ADD_SOURCE_NOTIFICATION_ID); PendingIntent cancelPendingIntent = PendingIntent.getBroadcast( context, R.string.cancel, cancelIntent, PendingIntent.FLAG_IMMUTABLE); NotificationCompat.Action cancelAction = new NotificationCompat.Action.Builder( 0, context.getString(R.string.cancel), cancelPendingIntent) .build(); final Bundle extras = new Bundle(); extras.putString( Notification.EXTRA_SUBSTITUTE_APP_NAME, context.getString(R.string.audio_sharing_title)); String deviceName = device.getAlias(); if (TextUtils.isEmpty(deviceName)) { deviceName = device.getAddress(); } NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID) .setSmallIcon(com.android.settingslib.R.drawable.ic_bt_le_audio_sharing) .setLocalOnly(true) .setContentTitle(context.getString(R.string.share_audio_notification_title, deviceName)) .setContentText( context.getString(R.string.audio_sharing_notification_content)) .setOngoing(true) .setSilent(true) .setColor( context.getColor( com.android.internal.R.color .system_notification_accent_color)) .addAction(addSourceAction) .addAction(cancelAction) .setTimeoutAfter(NOTIF_AUTO_DISMISS_MILLIS) .addExtras(extras); nm.notify(ADD_SOURCE_NOTIFICATION_ID, builder.build()); } private void cancelSharingNotification(Context context) { private void cancelSharingNotification(@NonNull Context context, int notifId) { NotificationManager nm = context.getSystemService(NotificationManager.class); nm.cancel(NOTIFICATION_ID); if (nm != null) { nm.cancel(notifId); } } private void createNotificationChannelIfNeeded(@NonNull NotificationManager nm, @NonNull Context context) { if (nm.getNotificationChannel(CHANNEL_ID) == null) { Log.d(TAG, "Create bluetooth notification channel"); NotificationChannel notificationChannel = new NotificationChannel( CHANNEL_ID, context.getString(com.android.settings.R.string.bluetooth), NotificationManager.IMPORTANCE_HIGH); nm.createNotificationChannel(notificationChannel); } } private boolean isAppInForeground(@NonNull Context context) { try { ActivityManager activityManager = context.getSystemService(ActivityManager.class); String packageName = context.getPackageName(); if (context.getPackageManager().checkPermission(Manifest.permission.PACKAGE_USAGE_STATS, packageName) != PackageManager.PERMISSION_GRANTED) { Log.d(TAG, "check isAppInForeground, returns false due to no permission"); return false; } if (packageName != null && activityManager.getPackageImportance(packageName) == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) { Log.d(TAG, "check isAppInForeground, returns true"); return true; } } catch (RuntimeException e) { Log.d(TAG, "check isAppInForeground, error = " + e.getMessage()); } return false; } }
tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingReceiverTest.java +88 −14 Original line number Diff line number Diff line Loading @@ -16,28 +16,38 @@ package com.android.settings.connecteddevice.audiosharing; import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast.ACTION_LE_AUDIO_SHARING_DEVICE_CONNECTED; import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast.ACTION_LE_AUDIO_SHARING_STATE_CHANGE; import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast.BROADCAST_STATE_OFF; import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast.BROADCAST_STATE_ON; import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast.EXTRA_BLUETOOTH_DEVICE; import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast.EXTRA_LE_AUDIO_SHARING_STATE; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; import android.Manifest; import android.app.ActivityManager; import android.app.Notification; import android.app.NotificationManager; import android.app.settings.SettingsEnums; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothStatusCodes; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; import com.android.settings.bluetooth.Utils; Loading Loading @@ -72,6 +82,7 @@ import java.util.stream.Collectors; public class AudioSharingReceiverTest { private static final String ACTION_LE_AUDIO_SHARING_STOP = "com.android.settings.action.BLUETOOTH_LE_AUDIO_SHARING_STOP"; private static final String TEST_DEVICE_NAME = "test"; @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); Loading @@ -88,7 +99,7 @@ public class AudioSharingReceiverTest { @Before public void setUp() { mContext = RuntimeEnvironment.getApplication(); mContext = spy(RuntimeEnvironment.getApplication()); mShadowApplication = Shadow.extract(mContext); mShadowApplication.setSystemService(Context.NOTIFICATION_SERVICE, mNm); mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter()); Loading Loading @@ -130,9 +141,8 @@ public class AudioSharingReceiverTest { } @Test @DisableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) public void broadcastReceiver_receiveAudioSharingStateOn_flagOff_doNothing() { mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); Intent intent = new Intent(ACTION_LE_AUDIO_SHARING_STATE_CHANGE); intent.setPackage(mContext.getPackageName()); intent.putExtra(EXTRA_LE_AUDIO_SHARING_STATE, BROADCAST_STATE_ON); Loading @@ -144,8 +154,8 @@ public class AudioSharingReceiverTest { } @Test @EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) public void broadcastReceiver_receiveAudioSharingStateOn_broadcastDisabled_doNothing() { mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported( BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED); Loading @@ -160,9 +170,8 @@ public class AudioSharingReceiverTest { } @Test @EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) public void broadcastReceiver_receiveAudioSharingStateChangeIntentNoState_doNothing() { mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); Intent intent = new Intent(ACTION_LE_AUDIO_SHARING_STATE_CHANGE); intent.setPackage(mContext.getPackageName()); AudioSharingReceiver audioSharingReceiver = getAudioSharingReceiver(intent); Loading @@ -173,9 +182,8 @@ public class AudioSharingReceiverTest { } @Test @EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) public void broadcastReceiver_receiveAudioSharingStateOn_broadcastEnabled_showNotification() { mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); Intent intent = new Intent(ACTION_LE_AUDIO_SHARING_STATE_CHANGE); intent.setPackage(mContext.getPackageName()); intent.putExtra(EXTRA_LE_AUDIO_SHARING_STATE, BROADCAST_STATE_ON); Loading @@ -188,9 +196,9 @@ public class AudioSharingReceiverTest { } @Test @EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) public void broadcastReceiver_receiveAudioSharingStateOff_broadcastDisabled_cancelNotification() { mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported( BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED); Loading @@ -207,10 +215,9 @@ public class AudioSharingReceiverTest { } @Test @EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) public void broadcastReceiver_receiveAudioSharingStateOff_broadcastEnabled_cancelNotification() { mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); Intent intent = new Intent(ACTION_LE_AUDIO_SHARING_STATE_CHANGE); intent.setPackage(mContext.getPackageName()); intent.putExtra(EXTRA_LE_AUDIO_SHARING_STATE, BROADCAST_STATE_OFF); Loading @@ -224,8 +231,8 @@ public class AudioSharingReceiverTest { } @Test @EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) public void broadcastReceiver_receiveAudioSharingStop_broadcastDisabled_cancelNotification() { mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported( BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED); Loading @@ -242,8 +249,8 @@ public class AudioSharingReceiverTest { } @Test @EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) public void broadcastReceiver_receiveAudioSharingStop_notInBroadcast_cancelNotification() { mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); when(mBroadcast.isEnabled(null)).thenReturn(false); int broadcastId = 1; when(mBroadcast.getLatestBroadcastId()).thenReturn(broadcastId); Loading @@ -261,8 +268,8 @@ public class AudioSharingReceiverTest { } @Test @EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) public void broadcastReceiver_receiveAudioSharingStop_inBroadcast_stopBroadcast() { mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); when(mBroadcast.isEnabled(null)).thenReturn(true); int broadcastId = 1; when(mBroadcast.getLatestBroadcastId()).thenReturn(broadcastId); Loading @@ -281,6 +288,61 @@ public class AudioSharingReceiverTest { ACTION_LE_AUDIO_SHARING_STOP); } @Test @EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) public void broadcastReceiver_receiveAudioSharingDeviceConnected_broadcastDisabled_doNothing() { mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported( BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED); BluetoothDevice device = mock(BluetoothDevice.class); when(device.getAlias()).thenReturn(TEST_DEVICE_NAME); setAppInForeground(false); Intent intent = new Intent(ACTION_LE_AUDIO_SHARING_DEVICE_CONNECTED); intent.setPackage(mContext.getPackageName()); intent.putExtra(EXTRA_BLUETOOTH_DEVICE, device); AudioSharingReceiver audioSharingReceiver = getAudioSharingReceiver(intent); audioSharingReceiver.onReceive(mContext, intent); verify(mNm, never()).notify( eq(com.android.settings.R.string.share_audio_notification_title), any(Notification.class)); } @Test @EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) public void broadcastReceiver_receiveAudioSharingDeviceConnected_showDialog() { BluetoothDevice device = mock(BluetoothDevice.class); when(device.getAlias()).thenReturn(TEST_DEVICE_NAME); setAppInForeground(true); Intent intent = new Intent(ACTION_LE_AUDIO_SHARING_DEVICE_CONNECTED); intent.setPackage(mContext.getPackageName()); intent.putExtra(EXTRA_BLUETOOTH_DEVICE, device); AudioSharingReceiver audioSharingReceiver = getAudioSharingReceiver(intent); audioSharingReceiver.onReceive(mContext, intent); verify(mNm, never()).notify( eq(com.android.settings.R.string.share_audio_notification_title), any(Notification.class)); // TODO: verify show dialog once impl complete } @Test @EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) public void broadcastReceiver_receiveAudioSharingDeviceConnected_showNotification() { BluetoothDevice device = mock(BluetoothDevice.class); when(device.getAlias()).thenReturn(TEST_DEVICE_NAME); setAppInForeground(false); Intent intent = new Intent(ACTION_LE_AUDIO_SHARING_DEVICE_CONNECTED); intent.setPackage(mContext.getPackageName()); intent.putExtra(EXTRA_BLUETOOTH_DEVICE, device); AudioSharingReceiver audioSharingReceiver = getAudioSharingReceiver(intent); audioSharingReceiver.onReceive(mContext, intent); // TODO: verify no dialog once impl complete verify(mNm).notify(eq(com.android.settings.R.string.share_audio_notification_title), any(Notification.class)); } private AudioSharingReceiver getAudioSharingReceiver(Intent intent) { assertThat(mShadowApplication.hasReceiverForIntent(intent)).isTrue(); List<BroadcastReceiver> receiversForIntent = Loading @@ -290,4 +352,16 @@ public class AudioSharingReceiverTest { assertThat(broadcastReceiver).isInstanceOf(AudioSharingReceiver.class); return (AudioSharingReceiver) broadcastReceiver; } private void setAppInForeground(boolean foreground) { ActivityManager activityManager = mock(ActivityManager.class); when(mContext.getSystemService(ActivityManager.class)).thenReturn(activityManager); when(activityManager.getPackageImportance(mContext.getPackageName())).thenReturn( foreground ? ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND : ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE); PackageManager packageManager = mock(PackageManager.class); when(mContext.getPackageManager()).thenReturn(packageManager); when(packageManager.checkPermission(Manifest.permission.PACKAGE_USAGE_STATS, mContext.getPackageName())).thenReturn(PackageManager.PERMISSION_GRANTED); } }