diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index af6df32a02b04..fbe7175b757f3 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -591,4 +591,12 @@ true + + + false + + + false diff --git a/packages/SystemUI/src/com/android/systemui/media/systemsounds/HomeSoundEffectController.java b/packages/SystemUI/src/com/android/systemui/media/systemsounds/HomeSoundEffectController.java index dd3d02a86672a..31e49390b9b8a 100644 --- a/packages/SystemUI/src/com/android/systemui/media/systemsounds/HomeSoundEffectController.java +++ b/packages/SystemUI/src/com/android/systemui/media/systemsounds/HomeSoundEffectController.java @@ -16,38 +16,64 @@ package com.android.systemui.media.systemsounds; +import android.Manifest; import android.app.ActivityManager; import android.app.WindowConfiguration; import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; import android.media.AudioManager; +import android.util.Slog; +import com.android.systemui.R; import com.android.systemui.SystemUI; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.TaskStackChangeListener; import com.android.systemui.shared.system.TaskStackChangeListeners; import javax.inject.Inject; /** - * If a sound effect is defined for {@link android.media.AudioManager#FX_HOME}, a sound is played - * when the home task moves to front and the last task that moved to front was not the home task. + * If a sound effect is defined for {@link android.media.AudioManager#FX_HOME}, a + * {@link TaskStackChangeListener} is registered to play a home sound effect when conditions + * documented at {@link #handleTaskStackChanged} apply. */ @SysUISingleton public class HomeSoundEffectController extends SystemUI { + private static final String TAG = "HomeSoundEffectController"; private final AudioManager mAudioManager; private final TaskStackChangeListeners mTaskStackChangeListeners; + private final ActivityManagerWrapper mActivityManagerWrapper; + private final PackageManager mPm; + private final boolean mPlayHomeSoundAfterAssistant; + private final boolean mPlayHomeSoundAfterDream; // Initialize true because home sound should not be played when the system boots. private boolean mIsLastTaskHome = true; + // mLastHomePackageName could go out of sync in rare circumstances if launcher changes, + // but it's cheaper than the alternative and potential impact is low + private String mLastHomePackageName; + private @WindowConfiguration.ActivityType int mLastActivityType; + private boolean mLastActivityHasNoHomeSound = false; + private int mLastTaskId; @Inject public HomeSoundEffectController( Context context, AudioManager audioManager, - TaskStackChangeListeners taskStackChangeListeners) { + TaskStackChangeListeners taskStackChangeListeners, + ActivityManagerWrapper activityManagerWrapper, + PackageManager packageManager) { super(context); mAudioManager = audioManager; mTaskStackChangeListeners = taskStackChangeListeners; + mActivityManagerWrapper = activityManagerWrapper; + mPm = packageManager; + mPlayHomeSoundAfterAssistant = context.getResources().getBoolean( + R.bool.config_playHomeSoundAfterAssistant); + mPlayHomeSoundAfterDream = context.getResources().getBoolean( + R.bool.config_playHomeSoundAfterDream); } @Override @@ -56,27 +82,94 @@ public class HomeSoundEffectController extends SystemUI { mTaskStackChangeListeners.registerTaskStackListener( new TaskStackChangeListener() { @Override - public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) { - handleHomeTaskMovedToFront(taskInfo); + public void onTaskStackChanged() { + ActivityManager.RunningTaskInfo currentTask = + mActivityManagerWrapper.getRunningTask(); + if (currentTask == null || currentTask.topActivityInfo == null) { + return; + } + handleTaskStackChanged(currentTask); } }); } } - private boolean isHomeTask(ActivityManager.RunningTaskInfo taskInfo) { - return taskInfo.getActivityType() == WindowConfiguration.ACTIVITY_TYPE_HOME; + private boolean hasFlagNoSound(ActivityInfo activityInfo) { + if ((activityInfo.privateFlags & ActivityInfo.PRIVATE_FLAG_HOME_TRANSITION_SOUND) == 0) { + // Only allow flag if app has permission + if (mPm.checkPermission(Manifest.permission.DISABLE_SYSTEM_SOUND_EFFECTS, + activityInfo.packageName) == PackageManager.PERMISSION_GRANTED) { + return true; + } else { + Slog.w(TAG, + "Activity has flag playHomeTransition set to false but doesn't hold " + + "required permission " + + Manifest.permission.DISABLE_SYSTEM_SOUND_EFFECTS); + return false; + } + } + return false; } /** - * To enable a home sound, check if the home app moves to front. + * The home sound is played if all of the following conditions are met: + * */ - private void handleHomeTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) { - boolean isCurrentTaskHome = isHomeTask(taskInfo); - // If the last task is home we don't want to play the home sound. This avoids playing - // the home sound after FallbackHome transitions to Home - if (!mIsLastTaskHome && isCurrentTaskHome) { + private boolean shouldPlayHomeSoundForCurrentTransition( + ActivityManager.RunningTaskInfo currentTask) { + boolean isHomeActivity = + currentTask.topActivityType == WindowConfiguration.ACTIVITY_TYPE_HOME; + if (currentTask.taskId == mLastTaskId) { + return false; + } + if (mIsLastTaskHome || !isHomeActivity) { + return false; + } + if (mLastActivityHasNoHomeSound) { + return false; + } + if (mLastActivityType == WindowConfiguration.ACTIVITY_TYPE_ASSISTANT + && !mPlayHomeSoundAfterAssistant) { + return false; + } + if (mLastActivityType == WindowConfiguration.ACTIVITY_TYPE_DREAM + && !mPlayHomeSoundAfterDream) { + return false; + } + return true; + } + + private void updateLastTaskInfo(ActivityManager.RunningTaskInfo currentTask) { + mLastTaskId = currentTask.taskId; + mLastActivityType = currentTask.topActivityType; + mLastActivityHasNoHomeSound = hasFlagNoSound(currentTask.topActivityInfo); + boolean isHomeActivity = + currentTask.topActivityType == WindowConfiguration.ACTIVITY_TYPE_HOME; + boolean isHomePackage = currentTask.topActivityInfo.packageName.equals( + mLastHomePackageName); + mIsLastTaskHome = isHomeActivity || isHomePackage; + if (isHomeActivity && !isHomePackage) { + mLastHomePackageName = currentTask.topActivityInfo.packageName; + } + } + + private void handleTaskStackChanged(ActivityManager.RunningTaskInfo frontTask) { + if (shouldPlayHomeSoundForCurrentTransition(frontTask)) { mAudioManager.playSoundEffect(AudioManager.FX_HOME); } - mIsLastTaskHome = isCurrentTaskHome; + updateLastTaskInfo(frontTask); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/systemsounds/HomeSoundEffectControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/systemsounds/HomeSoundEffectControllerTest.java index 3a77f7eec7f91..33a30e0bcc277 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/systemsounds/HomeSoundEffectControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/systemsounds/HomeSoundEffectControllerTest.java @@ -21,16 +21,20 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import android.Manifest; import android.app.ActivityManager; import android.app.WindowConfiguration; -import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; import android.media.AudioManager; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.systemui.SysuiTestCase; +import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.TaskStackChangeListener; import com.android.systemui.shared.system.TaskStackChangeListeners; @@ -41,30 +45,50 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; + @SmallTest @RunWith(AndroidJUnit4.class) public class HomeSoundEffectControllerTest extends SysuiTestCase { - private @Mock Context mContext; + private static final String HOME_PACKAGE_NAME = "com.android.apps.home"; + private static final String NON_HOME_PACKAGE_NAME = "com.android.apps.not.home"; + private static final int HOME_TASK_ID = 0; + private static final int NON_HOME_TASK_ID = 1; + private @Mock AudioManager mAudioManager; private @Mock TaskStackChangeListeners mTaskStackChangeListeners; - private @Mock ActivityManager.RunningTaskInfo mStandardActivityTaskInfo; - private @Mock ActivityManager.RunningTaskInfo mHomeActivityTaskInfo; + private @Mock ActivityManagerWrapper mActivityManagerWrapper; + private @Mock PackageManager mPackageManager; + private ActivityManager.RunningTaskInfo mTaskAStandardActivity; + private ActivityManager.RunningTaskInfo mTaskAExceptionActivity; + private ActivityManager.RunningTaskInfo mHomeTaskHomeActivity; + private ActivityManager.RunningTaskInfo mHomeTaskStandardActivity; + private ActivityManager.RunningTaskInfo mEmptyTask; private HomeSoundEffectController mController; private TaskStackChangeListener mTaskStackChangeListener; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - - doReturn(WindowConfiguration.ACTIVITY_TYPE_STANDARD).when( - mStandardActivityTaskInfo).getActivityType(); - doReturn(WindowConfiguration.ACTIVITY_TYPE_HOME).when( - mHomeActivityTaskInfo).getActivityType(); - + mTaskAStandardActivity = createRunningTaskInfo(NON_HOME_PACKAGE_NAME, + WindowConfiguration.ACTIVITY_TYPE_STANDARD, NON_HOME_TASK_ID, + true /* playHomeTransitionSound */); + mTaskAExceptionActivity = createRunningTaskInfo(NON_HOME_PACKAGE_NAME, + WindowConfiguration.ACTIVITY_TYPE_STANDARD, NON_HOME_TASK_ID, + false /* playHomeTransitionSound */); + mHomeTaskHomeActivity = createRunningTaskInfo(HOME_PACKAGE_NAME, + WindowConfiguration.ACTIVITY_TYPE_HOME, HOME_TASK_ID, + true /* playHomeTransitionSound */); + mHomeTaskStandardActivity = createRunningTaskInfo(HOME_PACKAGE_NAME, + WindowConfiguration.ACTIVITY_TYPE_STANDARD, HOME_TASK_ID, + true /* playHomeTransitionSound */); + mEmptyTask = new ActivityManager.RunningTaskInfo(); + mContext.setMockPackageManager(mPackageManager); + when(mPackageManager.checkPermission(Manifest.permission.DISABLE_SYSTEM_SOUND_EFFECTS, + NON_HOME_PACKAGE_NAME)).thenReturn(PackageManager.PERMISSION_GRANTED); mController = new HomeSoundEffectController(mContext, mAudioManager, - mTaskStackChangeListeners); + mTaskStackChangeListeners, mActivityManagerWrapper, mPackageManager); } @Test @@ -73,43 +97,60 @@ public class HomeSoundEffectControllerTest extends SysuiTestCase { startController(true /* isHomeSoundEffectEnabled */); // And the home task moves to the front, - mTaskStackChangeListener.onTaskMovedToFront(mHomeActivityTaskInfo); + when(mActivityManagerWrapper.getRunningTask()).thenReturn(mHomeTaskHomeActivity); + mTaskStackChangeListener.onTaskStackChanged(); // Then no home sound effect should be played. verify(mAudioManager, never()).playSoundEffect(AudioManager.FX_HOME); } + /** + * Task A (playHomeTransitionSound = true) -> HOME + * Expectation: Home sound is played + */ @Test public void testHomeSoundEffectPlayedWhenEnabled() { // When HomeSoundEffectController is started and the home sound effect is enabled, startController(true /* isHomeSoundEffectEnabled */); + when(mActivityManagerWrapper.getRunningTask()).thenReturn( + mTaskAStandardActivity, + mHomeTaskHomeActivity); // And first a task different from the home task moves to front, - mTaskStackChangeListener.onTaskMovedToFront(mStandardActivityTaskInfo); + mTaskStackChangeListener.onTaskStackChanged(); // And the home task moves to the front, - mTaskStackChangeListener.onTaskMovedToFront(mHomeActivityTaskInfo); + mTaskStackChangeListener.onTaskStackChanged(); // Then the home sound effect should be played. verify(mAudioManager).playSoundEffect(AudioManager.FX_HOME); } + /** + * Task A (playHomeTransitionSound = true) -> HOME -> HOME + * Expectation: Home sound is played once after HOME moves to front the first time + */ @Test public void testHomeSoundEffectNotPlayedTwiceInRow() { // When HomeSoundEffectController is started and the home sound effect is enabled, startController(true /* isHomeSoundEffectEnabled */); + when(mActivityManagerWrapper.getRunningTask()).thenReturn( + mTaskAStandardActivity, + mHomeTaskHomeActivity, + mHomeTaskHomeActivity); + // And first a task different from the home task moves to front, - mTaskStackChangeListener.onTaskMovedToFront(mStandardActivityTaskInfo); + mTaskStackChangeListener.onTaskStackChanged(); // And the home task moves to the front, - mTaskStackChangeListener.onTaskMovedToFront(mHomeActivityTaskInfo); + mTaskStackChangeListener.onTaskStackChanged(); // Then the home sound effect should be played. verify(mAudioManager).playSoundEffect(AudioManager.FX_HOME); // If the home task moves to front a second time in a row, - mTaskStackChangeListener.onTaskMovedToFront(mHomeActivityTaskInfo); + mTaskStackChangeListener.onTaskStackChanged(); // Then no home sound effect should be played. verify(mAudioManager, times(1)).playSoundEffect(AudioManager.FX_HOME); @@ -121,7 +162,59 @@ public class HomeSoundEffectControllerTest extends SysuiTestCase { startController(true /* isHomeSoundEffectEnabled */); // And a standard, non-home task, moves to the front, - mTaskStackChangeListener.onTaskMovedToFront(mStandardActivityTaskInfo); + when(mActivityManagerWrapper.getRunningTask()).thenReturn(mTaskAStandardActivity); + mTaskStackChangeListener.onTaskStackChanged(); + + // Then no home sound effect should be played. + verify(mAudioManager, never()).playSoundEffect(AudioManager.FX_HOME); + } + + /** + * Task A (playHomeTransitionSound = true) -> HOME -> HOME (activity type standard) + * Expectation: Home sound is played once after HOME moves to front + */ + @Test + public void testHomeSoundEffectNotPlayedWhenOtherHomeActivityMovesToFront() { + // When HomeSoundEffectController is started and the home sound effect is enabled, + startController(true /* isHomeSoundEffectEnabled */); + when(mActivityManagerWrapper.getRunningTask()).thenReturn( + mTaskAStandardActivity, + mHomeTaskHomeActivity, + mHomeTaskStandardActivity); + + // And first a task different from the home task moves to front, + mTaskStackChangeListener.onTaskStackChanged(); + + // And the home task moves to the front, + mTaskStackChangeListener.onTaskStackChanged(); + + // Then the home sound effect should be played. + verify(mAudioManager).playSoundEffect(AudioManager.FX_HOME); + + // If the home task moves to front a second time in a row, + mTaskStackChangeListener.onTaskStackChanged(); + + // Then no home sound effect should be played. + verify(mAudioManager, times(1)).playSoundEffect(AudioManager.FX_HOME); + } + + /** + * Task A (playHomeTransitionSound = true) -> HOME (activity type standard) + * Expectation: Home sound is not played + */ + @Test + public void testHomeSoundEffectNotPlayedWhenOtherHomeActivityMovesToFrontOfOtherApp() { + // When HomeSoundEffectController is started and the home sound effect is enabled, + startController(true /* isHomeSoundEffectEnabled */); + + // And first a task different from the home task moves to front, + when(mActivityManagerWrapper.getRunningTask()).thenReturn( + mTaskAStandardActivity, + mHomeTaskStandardActivity); + mTaskStackChangeListener.onTaskStackChanged(); + + // And an activity from the home package but not the home root activity moves to front + mTaskStackChangeListener.onTaskStackChanged(); // Then no home sound effect should be played. verify(mAudioManager, never()).playSoundEffect(AudioManager.FX_HOME); @@ -137,6 +230,171 @@ public class HomeSoundEffectControllerTest extends SysuiTestCase { any(TaskStackChangeListener.class)); } + /** + * Task A (playHomeTransitionSound = false) -> HOME + * Expectation: Home sound is not played + */ + @Test + public void testHomeSoundEffectNotPlayedWhenHomeActivityMovesToFrontAfterException() { + // When HomeSoundEffectController is started and the home sound effect is enabled, + startController(true /* isHomeSoundEffectEnabled */); + + // And first a task different from the home task moves to front, which has + // {@link ActivityInfo#PRIVATE_FLAG_HOME_TRANSITION_SOUND} set to false + when(mActivityManagerWrapper.getRunningTask()).thenReturn( + mTaskAExceptionActivity, + mHomeTaskHomeActivity); + mTaskStackChangeListener.onTaskStackChanged(); + + // And the home task moves to the front, + mTaskStackChangeListener.onTaskStackChanged(); + + // Then no home sound effect should be played because the last package is an exception. + verify(mAudioManager, never()).playSoundEffect(AudioManager.FX_HOME); + } + + /** + * HOME -> Task A (playHomeTransitionSound = true) -> Task A (playHomeTransitionSound = false) + * -> HOME + * Expectation: Home sound is not played + */ + @Test + public void testHomeSoundEffectNotPlayedWhenHomeActivityMovesToFrontAfterException2() { + // When HomeSoundEffectController is started and the home sound effect is enabled, + startController(true /* isHomeSoundEffectEnabled */); + + when(mActivityManagerWrapper.getRunningTask()).thenReturn( + mTaskAStandardActivity, + mTaskAExceptionActivity, + mHomeTaskHomeActivity); + + // And first a task different from the home task moves to front + mTaskStackChangeListener.onTaskStackChanged(); + + // Then a different activity from the same task moves to front, which has + // {@link ActivityInfo#PRIVATE_FLAG_HOME_TRANSITION_SOUND} set to false + mTaskStackChangeListener.onTaskStackChanged(); + + // And the home task moves to the front, + mTaskStackChangeListener.onTaskStackChanged(); + + // Then no home sound effect should be played. + verify(mAudioManager, never()).playSoundEffect(AudioManager.FX_HOME); + } + + /** + * HOME -> Task A (playHomeTransitionSound = false) -> Task A (playHomeTransitionSound = true) + * -> HOME + * Expectation: Home sound is played + */ + @Test + public void testHomeSoundEffectPlayedWhenHomeActivityMovesToFrontAfterException() { + // When HomeSoundEffectController is started and the home sound effect is enabled, + startController(true /* isHomeSoundEffectEnabled */); + when(mActivityManagerWrapper.getRunningTask()).thenReturn( + mTaskAExceptionActivity, + mTaskAStandardActivity, + mHomeTaskHomeActivity); + + // And first a task different from the home task moves to front, + // the topActivity of this task has {@link ActivityInfo#PRIVATE_FLAG_HOME_TRANSITION_SOUND} + // set to false + mTaskStackChangeListener.onTaskStackChanged(); + + // Then a different activity from the same task moves to front, which has + // {@link ActivityInfo#PRIVATE_FLAG_HOME_TRANSITION_SOUND} set to true + mTaskStackChangeListener.onTaskStackChanged(); + + // And the home task moves to the front, + mTaskStackChangeListener.onTaskStackChanged(); + + // Then the home sound effect should be played. + verify(mAudioManager).playSoundEffect(AudioManager.FX_HOME); + } + + /** + * HOME -> Task A (playHomeTransitionSound = false) -> Task A (empty task, no top activity) + * -> HOME + * Expectation: Home sound is not played + */ + @Test + public void testHomeSoundEffectNotPlayedWhenEmptyTaskMovesToFrontAfterException() { + // When HomeSoundEffectController is started and the home sound effect is enabled, + startController(true /* isHomeSoundEffectEnabled */); + when(mActivityManagerWrapper.getRunningTask()).thenReturn( + mTaskAExceptionActivity, + mEmptyTask, + mHomeTaskHomeActivity); + + // And first a task different from the home task moves to front, whose topActivity has + // {@link ActivityInfo#PRIVATE_FLAG_HOME_TRANSITION_SOUND} set to false + mTaskStackChangeListener.onTaskStackChanged(); + + // Then a task with no topActivity moves to front, + mTaskStackChangeListener.onTaskStackChanged(); + + // And the home task moves to the front, + mTaskStackChangeListener.onTaskStackChanged(); + + // Then no home sound effect should be played. + verify(mAudioManager, never()).playSoundEffect(AudioManager.FX_HOME); + } + + /** + * HOME -> Task A (playHomeTransitionSound = true) -> Task A (empty task, no top activity) + * -> HOME + * Expectation: Home sound is played + */ + @Test + public void testHomeSoundEffectPlayedWhenEmptyTaskMovesToFrontAfterStandardActivity() { + // When HomeSoundEffectController is started and the home sound effect is enabled, + startController(true /* isHomeSoundEffectEnabled */); + when(mActivityManagerWrapper.getRunningTask()).thenReturn( + mTaskAStandardActivity, + mEmptyTask, + mHomeTaskHomeActivity); + + // And first a task different from the home task moves to front, whose topActivity has + // {@link ActivityInfo#PRIVATE_FLAG_HOME_TRANSITION_SOUND} set to false + mTaskStackChangeListener.onTaskStackChanged(); + + // Then a task with no topActivity moves to front, + mTaskStackChangeListener.onTaskStackChanged(); + + // And the home task moves to the front, + mTaskStackChangeListener.onTaskStackChanged(); + + // Then the home sound effect should be played. + verify(mAudioManager).playSoundEffect(AudioManager.FX_HOME); + } + + /** + * HOME -> Task A (playHomeTransitionSound = false, no permission) -> HOME + * Expectation: Home sound is played + */ + @Test + public void testHomeSoundEffectPlayedWhenFlagSetButPermissionNotGranted() { + // When HomeSoundEffectController is started and the home sound effect is enabled, + startController(true /* isHomeSoundEffectEnabled */); + when(mActivityManagerWrapper.getRunningTask()).thenReturn( + mTaskAStandardActivity, + mHomeTaskHomeActivity); + when(mPackageManager.checkPermission(Manifest.permission.DISABLE_SYSTEM_SOUND_EFFECTS, + NON_HOME_PACKAGE_NAME)).thenReturn(PackageManager.PERMISSION_DENIED); + + // And first a task different from the home task moves to front, whose topActivity has + // {@link ActivityInfo#PRIVATE_FLAG_HOME_TRANSITION_SOUND} set to true, + // but the app doesn't have the right permission granted + mTaskStackChangeListener.onTaskStackChanged(); + + + // And the home task moves to the front, + mTaskStackChangeListener.onTaskStackChanged(); + + // Then the home sound effect should be played. + verify(mAudioManager).playSoundEffect(AudioManager.FX_HOME); + } + /** * Sets {@link AudioManager#isHomeSoundEffectEnabled()} and starts HomeSoundEffectController. * If the home sound effect is enabled, the registered TaskStackChangeListener is extracted. @@ -155,4 +413,20 @@ public class HomeSoundEffectControllerTest extends SysuiTestCase { mTaskStackChangeListener = listenerCaptor.getValue(); } } + + private ActivityManager.RunningTaskInfo createRunningTaskInfo(String packageName, + int activityType, int taskId, boolean playHomeTransitionSound) { + ActivityManager.RunningTaskInfo res = new ActivityManager.RunningTaskInfo(); + res.topActivityInfo = new ActivityInfo(); + res.topActivityInfo.packageName = packageName; + res.topActivityType = activityType; + res.taskId = taskId; + if (!playHomeTransitionSound) { + // set the flag to 0 + res.topActivityInfo.privateFlags &= ~ActivityInfo.PRIVATE_FLAG_HOME_TRANSITION_SOUND; + } else { + res.topActivityInfo.privateFlags |= ActivityInfo.PRIVATE_FLAG_HOME_TRANSITION_SOUND; + } + return res; + } }