1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.media.systemsounds; 18 19 import android.Manifest; 20 import android.app.ActivityManager; 21 import android.app.WindowConfiguration; 22 import android.content.Context; 23 import android.content.pm.ActivityInfo; 24 import android.content.pm.PackageManager; 25 import android.media.AudioManager; 26 import android.util.Slog; 27 28 import com.android.systemui.CoreStartable; 29 import com.android.systemui.R; 30 import com.android.systemui.dagger.SysUISingleton; 31 import com.android.systemui.shared.system.ActivityManagerWrapper; 32 import com.android.systemui.shared.system.TaskStackChangeListener; 33 import com.android.systemui.shared.system.TaskStackChangeListeners; 34 35 import javax.inject.Inject; 36 37 /** 38 * If a sound effect is defined for {@link android.media.AudioManager#FX_HOME}, a 39 * {@link TaskStackChangeListener} is registered to play a home sound effect when conditions 40 * documented at {@link #handleTaskStackChanged} apply. 41 */ 42 @SysUISingleton 43 public class HomeSoundEffectController implements CoreStartable { 44 45 private static final String TAG = "HomeSoundEffectController"; 46 private final AudioManager mAudioManager; 47 private final TaskStackChangeListeners mTaskStackChangeListeners; 48 private final ActivityManagerWrapper mActivityManagerWrapper; 49 private final PackageManager mPm; 50 private final boolean mPlayHomeSoundAfterAssistant; 51 private final boolean mPlayHomeSoundAfterDream; 52 // Initialize true because home sound should not be played when the system boots. 53 private boolean mIsLastTaskHome = true; 54 // mLastHomePackageName could go out of sync in rare circumstances if launcher changes, 55 // but it's cheaper than the alternative and potential impact is low 56 private String mLastHomePackageName; 57 private @WindowConfiguration.ActivityType int mLastActivityType; 58 private boolean mLastActivityHasNoHomeSound = false; 59 private int mLastTaskId; 60 61 @Inject HomeSoundEffectController( Context context, AudioManager audioManager, TaskStackChangeListeners taskStackChangeListeners, ActivityManagerWrapper activityManagerWrapper, PackageManager packageManager)62 public HomeSoundEffectController( 63 Context context, 64 AudioManager audioManager, 65 TaskStackChangeListeners taskStackChangeListeners, 66 ActivityManagerWrapper activityManagerWrapper, 67 PackageManager packageManager) { 68 mAudioManager = audioManager; 69 mTaskStackChangeListeners = taskStackChangeListeners; 70 mActivityManagerWrapper = activityManagerWrapper; 71 mPm = packageManager; 72 mPlayHomeSoundAfterAssistant = context.getResources().getBoolean( 73 R.bool.config_playHomeSoundAfterAssistant); 74 mPlayHomeSoundAfterDream = context.getResources().getBoolean( 75 R.bool.config_playHomeSoundAfterDream); 76 } 77 78 @Override start()79 public void start() { 80 if (mAudioManager.isHomeSoundEffectEnabled()) { 81 mTaskStackChangeListeners.registerTaskStackListener( 82 new TaskStackChangeListener() { 83 @Override 84 public void onTaskStackChanged() { 85 ActivityManager.RunningTaskInfo currentTask = 86 mActivityManagerWrapper.getRunningTask(); 87 if (currentTask == null || currentTask.topActivityInfo == null) { 88 return; 89 } 90 handleTaskStackChanged(currentTask); 91 } 92 }); 93 } 94 } 95 hasFlagNoSound(ActivityInfo activityInfo)96 private boolean hasFlagNoSound(ActivityInfo activityInfo) { 97 if ((activityInfo.privateFlags & ActivityInfo.PRIVATE_FLAG_HOME_TRANSITION_SOUND) == 0) { 98 // Only allow flag if app has permission 99 if (mPm.checkPermission(Manifest.permission.DISABLE_SYSTEM_SOUND_EFFECTS, 100 activityInfo.packageName) == PackageManager.PERMISSION_GRANTED) { 101 return true; 102 } else { 103 Slog.w(TAG, 104 "Activity has flag playHomeTransition set to false but doesn't hold " 105 + "required permission " 106 + Manifest.permission.DISABLE_SYSTEM_SOUND_EFFECTS); 107 return false; 108 } 109 } 110 return false; 111 } 112 113 /** 114 * The home sound is played if all of the following conditions are met: 115 * <ul> 116 * <li>The last task which moved to front was not home. This avoids playing the sound 117 * e.g. after FallbackHome transitions to home, another activity of the home app like a 118 * notification panel moved to front, or in case the home app crashed.</li> 119 * <li>The current activity which moved to front is home</li> 120 * <li>The topActivity of the last task has {@link android.R.attr#playHomeTransitionSound} set 121 * to <code>true</code>.</li> 122 * <li>The topActivity of the last task is not of type 123 * {@link WindowConfiguration#ACTIVITY_TYPE_ASSISTANT} if config_playHomeSoundAfterAssistant is 124 * set to <code>false</code> (default).</li> 125 * <li>The topActivity of the last task is not of type 126 * {@link WindowConfiguration#ACTIVITY_TYPE_DREAM} if config_playHomeSoundAfterDream is 127 * set to <code>false</code> (default).</li> 128 * </ul> 129 */ shouldPlayHomeSoundForCurrentTransition( ActivityManager.RunningTaskInfo currentTask)130 private boolean shouldPlayHomeSoundForCurrentTransition( 131 ActivityManager.RunningTaskInfo currentTask) { 132 boolean isHomeActivity = 133 currentTask.topActivityType == WindowConfiguration.ACTIVITY_TYPE_HOME; 134 if (currentTask.taskId == mLastTaskId) { 135 return false; 136 } 137 if (mIsLastTaskHome || !isHomeActivity) { 138 return false; 139 } 140 if (mLastActivityHasNoHomeSound) { 141 return false; 142 } 143 if (mLastActivityType == WindowConfiguration.ACTIVITY_TYPE_ASSISTANT 144 && !mPlayHomeSoundAfterAssistant) { 145 return false; 146 } 147 if (mLastActivityType == WindowConfiguration.ACTIVITY_TYPE_DREAM 148 && !mPlayHomeSoundAfterDream) { 149 return false; 150 } 151 return true; 152 } 153 updateLastTaskInfo(ActivityManager.RunningTaskInfo currentTask)154 private void updateLastTaskInfo(ActivityManager.RunningTaskInfo currentTask) { 155 mLastTaskId = currentTask.taskId; 156 mLastActivityType = currentTask.topActivityType; 157 mLastActivityHasNoHomeSound = hasFlagNoSound(currentTask.topActivityInfo); 158 boolean isHomeActivity = 159 currentTask.topActivityType == WindowConfiguration.ACTIVITY_TYPE_HOME; 160 boolean isHomePackage = currentTask.topActivityInfo.packageName.equals( 161 mLastHomePackageName); 162 mIsLastTaskHome = isHomeActivity || isHomePackage; 163 if (isHomeActivity && !isHomePackage) { 164 mLastHomePackageName = currentTask.topActivityInfo.packageName; 165 } 166 } 167 handleTaskStackChanged(ActivityManager.RunningTaskInfo frontTask)168 private void handleTaskStackChanged(ActivityManager.RunningTaskInfo frontTask) { 169 if (shouldPlayHomeSoundForCurrentTransition(frontTask)) { 170 mAudioManager.playSoundEffect(AudioManager.FX_HOME); 171 } 172 updateLastTaskInfo(frontTask); 173 } 174 } 175