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

Commit e88c1e66 authored by Tetiana Meronyk's avatar Tetiana Meronyk
Browse files

Add handling of simultaneous alarms/timers

Initial implementation handled only one alarm at a time, if a new alarm came in, a notification for it would not be shown until the initial alarm is muted/dismissed.

Now we can keep track of all uids that have audio focus for ALARM usage type and therefore handle each of them independently.

Bug: 367615180
Test: atest BackgroundUserSoundNotifier
Flag: android.multiuser.multiple_alarm_notifications_support
Change-Id: I7a988e6c8f426b596ebc93f1433174424e62b50f
parent 482ef73a
Loading
Loading
Loading
Loading
+55 −14
Original line number Diff line number Diff line
@@ -34,16 +34,19 @@ import android.media.AudioFocusInfo;
import android.media.AudioManager;
import android.media.AudioPlaybackConfiguration;
import android.media.audiopolicy.AudioPolicy;
import android.multiuser.Flags;
import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.ArraySet;
import android.util.Log;

import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;

import java.util.List;
import java.util.Set;

public class BackgroundUserSoundNotifier {

@@ -65,6 +68,10 @@ public class BackgroundUserSoundNotifier {
     */
    @VisibleForTesting
    int mNotificationClientUid = -1;
    /**
     * UIDs of audio focus infos with active notifications.
     */
    Set<Integer> mNotificationClientUids = new ArraySet<>();
    @VisibleForTesting
    AudioPolicy mFocusControlAudioPolicy;
    @VisibleForTesting
@@ -149,27 +156,43 @@ public class BackgroundUserSoundNotifier {
            @SuppressLint("MissingPermission")
            @Override
            public void onReceive(Context context, Intent intent) {
                if (Flags.multipleAlarmNotificationsSupport()) {
                    if (!intent.hasExtra(EXTRA_NOTIFICATION_CLIENT_UID)) {
                        return;
                    }
                } else {
                    if (mNotificationClientUid == -1) {
                        return;
                    }
                dismissNotification(mNotificationClientUid);
                }

                int clientUid;
                if (Flags.multipleAlarmNotificationsSupport()) {
                    clientUid = intent.getIntExtra(EXTRA_NOTIFICATION_CLIENT_UID, -1);
                } else {
                    clientUid = mNotificationClientUid;
                }
                dismissNotification(clientUid);

                if (DEBUG) {
                    final int actionIndex = intent.getAction().lastIndexOf(".") + 1;
                    final String action = intent.getAction().substring(actionIndex);
                    Log.d(LOG_TAG, "Action requested: " + action + ", by userId "
                            + ActivityManager.getCurrentUser() + " for alarm on user "
                            + UserHandle.getUserHandleForUid(mNotificationClientUid));
                            + UserHandle.getUserHandleForUid(clientUid));
                }

                if (ACTION_MUTE_SOUND.equals(intent.getAction())) {
                    muteAlarmSounds(mNotificationClientUid);
                    muteAlarmSounds(clientUid);
                } else if (ACTION_SWITCH_USER.equals(intent.getAction())) {
                    activityManager.switchUser(UserHandle.getUserId(mNotificationClientUid));
                    activityManager.switchUser(UserHandle.getUserId(clientUid));
                }

                if (Flags.multipleAlarmNotificationsSupport()) {
                    mNotificationClientUids.remove(clientUid);
                } else {
                    mNotificationClientUid = -1;
                }
            }
        };

        IntentFilter filter = new IntentFilter();
@@ -215,10 +238,11 @@ public class BackgroundUserSoundNotifier {
        final int userId = UserHandle.getUserId(afi.getClientUid());
        final int usage = afi.getAttributes().getUsage();
        UserInfo userInfo = mUserManager.getUserInfo(userId);

        // Only show notification if the sound is coming from background user and the notification
        // is not already shown.
        // for this UID is not already shown.
        if (userInfo != null && userId != foregroundContext.getUserId()
                && mNotificationClientUid == -1) {
                && !isNotificationShown(afi.getClientUid())) {
            //TODO: b/349138482 - Add handling of cases when usage == USAGE_NOTIFICATION_RINGTONE
            if (usage == USAGE_ALARM) {
                if (DEBUG) {
@@ -226,8 +250,11 @@ public class BackgroundUserSoundNotifier {
                            + ", displaying notification for current user "
                            + foregroundContext.getUserId());
                }

                if (Flags.multipleAlarmNotificationsSupport()) {
                    mNotificationClientUids.add(afi.getClientUid());
                } else {
                    mNotificationClientUid = afi.getClientUid();
                }

                mNotificationManager.notifyAsUser(LOG_TAG, afi.getClientUid(),
                        createNotification(userInfo.name, foregroundContext, afi.getClientUid()),
@@ -243,17 +270,23 @@ public class BackgroundUserSoundNotifier {
     */
    @VisibleForTesting
    void dismissNotificationIfNecessary(int notificationClientUid) {

        if (getAudioFocusInfoForNotification(notificationClientUid) == null
                && mNotificationClientUid >= 0) {
                && isNotificationShown(notificationClientUid)) {
            if (DEBUG) {
                Log.d(LOG_TAG, "Alarm ringing on background user "
                        + UserHandle.getUserHandleForUid(notificationClientUid).getIdentifier()
                        + " left focus stack, dismissing notification");
            }
            dismissNotification(notificationClientUid);

            if (Flags.multipleAlarmNotificationsSupport()) {
                mNotificationClientUids.remove(notificationClientUid);
            } else {
                mNotificationClientUid = -1;
            }
        }
    }

    /**
     * Dismisses notification for all users in case user switch occurred after notification was
@@ -331,4 +364,12 @@ public class BackgroundUserSoundNotifier {

        return notificationBuilder.build();
    }

    private boolean isNotificationShown(int notificationClientUid) {
        if (Flags.multipleAlarmNotificationsSupport()) {
            return mNotificationClientUids.contains(notificationClientUid);
        } else {
            return mNotificationClientUid != -1;
        }
    }
}
+80 −3
Original line number Diff line number Diff line
@@ -41,14 +41,19 @@ import android.media.AudioManager;
import android.media.AudioPlaybackConfiguration;
import android.media.PlayerProxy;
import android.media.audiopolicy.AudioPolicy;
import android.multiuser.Flags;
import android.os.Build;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.util.ArraySet;

import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -60,7 +65,6 @@ import java.util.List;
import java.util.Stack;

@RunWith(JUnit4.class)

public class BackgroundUserSoundNotifierTest {
    private final Context mRealContext = androidx.test.InstrumentationRegistry.getInstrumentation()
            .getTargetContext();
@@ -72,6 +76,10 @@ public class BackgroundUserSoundNotifierTest {

    @Mock
    private NotificationManager mNotificationManager;

    @Rule
    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
@@ -142,8 +150,11 @@ public class BackgroundUserSoundNotifierTest {
        final int fgUserId = mSpiedContext.getUserId();
        int bgUserId = fgUserId + 1;
        int bgUserUid = bgUserId * 100000;
        if (Flags.multipleAlarmNotificationsSupport()) {
            mBackgroundUserSoundNotifier.mNotificationClientUids.add(bgUserUid);
        } else {
            mBackgroundUserSoundNotifier.mNotificationClientUid = bgUserUid;

        }
        AudioManager mockAudioManager = mock(AudioManager.class);
        when(mSpiedContext.getSystemService(AudioManager.class)).thenReturn(mockAudioManager);

@@ -243,6 +254,72 @@ public class BackgroundUserSoundNotifierTest {
                notification.actions[0].title);
    }

    @RequiresFlagsEnabled({Flags.FLAG_MULTIPLE_ALARM_NOTIFICATIONS_SUPPORT})
    @Test
    public void testMultipleAlarmsSameUid_OneNotificationCreated() throws RemoteException {
        assumeTrue(UserManager.supportsMultipleUsers());
        UserInfo user = createUser("User", UserManager.USER_TYPE_FULL_SECONDARY, 0);
        final int fgUserId = mSpiedContext.getUserId();
        final int bgUserUid = user.id * 100000;
        doReturn(UserHandle.of(fgUserId)).when(mSpiedContext).getUser();

        AudioAttributes aa = new AudioAttributes.Builder().setUsage(USAGE_ALARM).build();
        AudioFocusInfo afi1 = new AudioFocusInfo(aa, bgUserUid, "",
                /* packageName= */ "com.android.car.audio", AudioManager.AUDIOFOCUS_GAIN,
                AudioManager.AUDIOFOCUS_NONE, /* flags= */ 0, Build.VERSION.SDK_INT);

        mBackgroundUserSoundNotifier.notifyForegroundUserAboutSoundIfNecessary(afi1);
        verify(mNotificationManager)
                .notifyAsUser(eq(BackgroundUserSoundNotifier.class.getSimpleName()),
                        eq(afi1.getClientUid()), any(Notification.class),
                        eq(UserHandle.of(fgUserId)));

        AudioFocusInfo afi2 = new AudioFocusInfo(aa, bgUserUid, "",
                /* packageName= */ "com.android.car.audio", AudioManager.AUDIOFOCUS_GAIN,
                AudioManager.AUDIOFOCUS_NONE, /* flags= */ 0, Build.VERSION.SDK_INT);
        clearInvocations(mNotificationManager);
        mBackgroundUserSoundNotifier.notifyForegroundUserAboutSoundIfNecessary(afi2);
        verify(mNotificationManager, never())
                .notifyAsUser(eq(BackgroundUserSoundNotifier.class.getSimpleName()),
                        eq(afi2.getClientUid()), any(Notification.class),
                        eq(UserHandle.of(fgUserId)));
    }

    @RequiresFlagsEnabled({Flags.FLAG_MULTIPLE_ALARM_NOTIFICATIONS_SUPPORT})
    @Test
    public void testMultipleAlarmsDifferentUsers_multipleNotificationsCreated()
            throws RemoteException {
        assumeTrue(UserManager.supportsMultipleUsers());
        UserInfo user1 = createUser("User1", UserManager.USER_TYPE_FULL_SECONDARY, 0);
        UserInfo user2 = createUser("User2", UserManager.USER_TYPE_FULL_SECONDARY, 0);
        final int fgUserId = mSpiedContext.getUserId();
        final int bgUserUid1 = user1.id * 100000;
        final int bgUserUid2 = user2.id * 100000;
        doReturn(UserHandle.of(fgUserId)).when(mSpiedContext).getUser();

        AudioAttributes aa = new AudioAttributes.Builder().setUsage(USAGE_ALARM).build();
        AudioFocusInfo afi1 = new AudioFocusInfo(aa, bgUserUid1, "",
                /* packageName= */ "com.android.car.audio", AudioManager.AUDIOFOCUS_GAIN,
                AudioManager.AUDIOFOCUS_NONE, /* flags= */ 0, Build.VERSION.SDK_INT);

        mBackgroundUserSoundNotifier.notifyForegroundUserAboutSoundIfNecessary(afi1);
        verify(mNotificationManager)
                .notifyAsUser(eq(BackgroundUserSoundNotifier.class.getSimpleName()),
                        eq(afi1.getClientUid()), any(Notification.class),
                        eq(UserHandle.of(fgUserId)));

        AudioFocusInfo afi2 = new AudioFocusInfo(aa, bgUserUid2, "",
                /* packageName= */ "com.android.car.audio", AudioManager.AUDIOFOCUS_GAIN,
                AudioManager.AUDIOFOCUS_NONE, /* flags= */ 0, Build.VERSION.SDK_INT);
        clearInvocations(mNotificationManager);
        mBackgroundUserSoundNotifier.notifyForegroundUserAboutSoundIfNecessary(afi2);
        verify(mNotificationManager)
                .notifyAsUser(eq(BackgroundUserSoundNotifier.class.getSimpleName()),
                        eq(afi2.getClientUid()), any(Notification.class),
                        eq(UserHandle.of(fgUserId)));
    }


    private UserInfo createUser(String name, String userType, int flags) {
        UserInfo user = mUserManager.createUser(name, userType, flags);
        if (user != null) {