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

Commit 959076ef authored by Adam Bookatz's avatar Adam Bookatz
Browse files

Don't auto-stop bg user if it's playing audio

Under certain conditions, we may stop a background user due to
inactivity. However, that user may be "active" even if it isn't visible;
in particular, it could be playing audio (due to an alarm). In such a
case, the user isn't inactive, so we shouldn't automatically stop it.

Bug: 378575468
Bug: 330351042
Flag: android.multiuser.schedule_stop_of_background_user
Test: atest UserControllerTest
Test: had bg user sound an alarm and confirmed it wasn't stopped
Change-Id: Ifbb359afc2508c288c3a8ead67e553f45d56f71b
parent 77af797d
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -15,6 +15,8 @@
 */
package android.media;

import android.annotation.SpecialUsers.CannotBeSpecialUser;
import android.annotation.UserIdInt;
import android.util.IntArray;

import com.android.server.LocalServices;
@@ -73,6 +75,9 @@ public abstract class AudioManagerInternal {
     */
    public abstract void setInputMethodServiceUid(int uid);

    /** Returns whether the given user is currently playing audio. */
    public abstract boolean isUserPlayingAudio(@CannotBeSpecialUser @UserIdInt int userId);

    public interface RingerModeDelegate {
        /** Called when external ringer mode is evaluated, returns the new internal ringer mode */
        int onSetRingerModeExternal(int ringerModeOld, int ringerModeNew, String caller,
+13 −0
Original line number Diff line number Diff line
@@ -87,6 +87,7 @@ import android.content.pm.PackageManager;
import android.content.pm.PackagePartitions;
import android.content.pm.UserInfo;
import android.content.pm.UserProperties;
import android.media.AudioManagerInternal;
import android.os.BatteryStats;
import android.os.Binder;
import android.os.Bundle;
@@ -2637,11 +2638,19 @@ class UserController implements Handler.Callback {

        if (avoidStoppingUserDueToUpcomingAlarm(userId)) {
            // We want this user running soon for alarm-purposes, so don't stop it now. Reschedule.
            Slogf.d(TAG, "User %d will fire an alarm soon, so reschedule bg stopping", userId);
            scheduleStopOfBackgroundUser(userId);
            return;
        }
        if (mInjector.getAudioManagerInternal().isUserPlayingAudio(userId)) {
            // User is audible (even if invisibly, e.g. via an alarm), so don't stop it. Reschedule.
            Slogf.d(TAG, "User %d is playing audio, so reschedule bg stopping", userId);
            scheduleStopOfBackgroundUser(userId);
            return;
        }
        synchronized (mLock) {
            if (getCurrentOrTargetUserIdLU() == userId) {
                // User is (somehow) already in the foreground, or we're currently switching to it.
                return;
            }
            if (mPendingTargetUserIds.contains(userIdInteger)) {
@@ -4107,6 +4116,10 @@ class UserController implements Handler.Callback {
            return mPowerManagerInternal;
        }

        AudioManagerInternal getAudioManagerInternal() {
            return LocalServices.getService(AudioManagerInternal.class);
        }

        AlarmManagerInternal getAlarmManagerInternal() {
            return LocalServices.getService(AlarmManagerInternal.class);
        }
+13 −0
Original line number Diff line number Diff line
@@ -92,6 +92,7 @@ import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SpecialUsers.CannotBeSpecialUser;
import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
@@ -13705,6 +13706,18 @@ public class AudioService extends IAudioService.Stub
                }
            }
        }
        @Override
        public boolean isUserPlayingAudio(@CannotBeSpecialUser @UserIdInt int userId) {
            final List<AudioPlaybackConfiguration> configs = getActivePlaybackConfigurations();
            for (AudioPlaybackConfiguration cfg : configs) {
                if (UserHandle.getUserId(cfg.getClientUid()) == userId &&
                        cfg.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
                    return true;
                }
            }
            return false;
        }
    }
    private void onUpdateAccessibilityServiceUids() {
+38 −0
Original line number Diff line number Diff line
@@ -85,6 +85,7 @@ import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.content.pm.UserInfo.UserInfoFlag;
import android.media.AudioManagerInternal;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
@@ -828,6 +829,36 @@ public class UserControllerTest {
                .getNextAlarmTriggerTimeForUser(eq(SYSTEM_USER_ID));
    }

    /** Test scheduling stopping of background users - reschedule if user is sounding audio. */
    @Test
    public void testScheduleStopOfBackgroundUser_rescheduleIfAudio() throws Exception {
        mSetFlagsRule.enableFlags(
                android.multiuser.Flags.FLAG_SCHEDULE_STOP_OF_BACKGROUND_USER,
                android.multiuser.Flags.FLAG_SCHEDULE_STOP_OF_BACKGROUND_USER_BY_DEFAULT);
        assumeFalse(UserManager.isVisibleBackgroundUsersEnabled());

        mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
                /* maxRunningUsers= */ 10, /* delayUserDataLocking= */ false,
                /* backgroundUserScheduledStopTimeSecs= */ 2);

        setUpAndStartUserInBackground(TEST_USER_ID);
        setUpAndStartUserInBackground(TEST_USER_ID1);
        assertEquals(newHashSet(SYSTEM_USER_ID, TEST_USER_ID, TEST_USER_ID1),
                new HashSet<>(mUserController.getRunningUsersLU()));

        when(mInjector.mAudioManagerInternal
                .isUserPlayingAudio(eq(TEST_USER_ID))).thenReturn(true);
        when(mInjector.mAudioManagerInternal
                .isUserPlayingAudio(eq(TEST_USER_ID1))).thenReturn(false);

        assertAndProcessScheduledStopBackgroundUser(true, TEST_USER_ID);
        assertAndProcessScheduledStopBackgroundUser(true, TEST_USER_ID1);

        // TEST_USER_ID1 should be stopped. But TEST_USER_ID shouldn't, since it was playing audio.
        assertEquals(newHashSet(SYSTEM_USER_ID, TEST_USER_ID),
                new HashSet<>(mUserController.getRunningUsersLU()));
    }

    /**
     * Process queued SCHEDULED_STOP_BACKGROUND_USER_MSG message, if expected.
     * @param userId the user we are checking to see whether it is scheduled.
@@ -1849,6 +1880,7 @@ public class UserControllerTest {
        private final WindowManagerService mWindowManagerMock;
        private final PowerManagerInternal mPowerManagerInternal;
        private final AlarmManagerInternal mAlarmManagerInternal;
        private final AudioManagerInternal mAudioManagerInternal;
        private final KeyguardManager mKeyguardManagerMock;
        private final LockPatternUtils mLockPatternUtilsMock;

@@ -1871,6 +1903,7 @@ public class UserControllerTest {
            mStorageManagerMock = mock(IStorageManager.class);
            mPowerManagerInternal = mock(PowerManagerInternal.class);
            mAlarmManagerInternal = mock(AlarmManagerInternal.class);
            mAudioManagerInternal = mock(AudioManagerInternal.class);
            mKeyguardManagerMock = mock(KeyguardManager.class);
            when(mKeyguardManagerMock.isDeviceSecure(anyInt())).thenReturn(true);
            mLockPatternUtilsMock = mock(LockPatternUtils.class);
@@ -1940,6 +1973,11 @@ public class UserControllerTest {
            return mAlarmManagerInternal;
        }

        @Override
        AudioManagerInternal getAudioManagerInternal() {
            return mAudioManagerInternal;
        }

        @Override
        KeyguardManager getKeyguardManager() {
            return mKeyguardManagerMock;