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