Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f5098877 authored by Yiyi Shen's avatar Yiyi Shen Committed by Android (Google) Code Review
Browse files

Merge "[Audiosharing] Show add source notif when app not in foreground" into main

parents baf07b18 6dc3e593
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -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>
+146 −17
Original line number Diff line number Diff line
@@ -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;
@@ -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.");
@@ -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,
@@ -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 =
@@ -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;
    }
}
+88 −14
Original line number Diff line number Diff line
@@ -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;
@@ -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();
@@ -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());
@@ -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);
@@ -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);

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

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

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