1 /*
2  * Copyright (C) 2008 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.internal.policy;
18 
19 import android.app.KeyguardManager;
20 import android.app.SearchManager;
21 import android.compat.annotation.UnsupportedAppUsage;
22 import android.content.ActivityNotFoundException;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.res.Configuration;
26 import android.media.AudioManager;
27 import android.media.session.MediaSessionManager;
28 import android.os.UserHandle;
29 import android.provider.Settings;
30 import android.telephony.TelephonyManager;
31 import android.util.Log;
32 import android.view.FallbackEventHandler;
33 import android.view.HapticFeedbackConstants;
34 import android.view.KeyEvent;
35 import android.view.View;
36 
37 /**
38  * @hide
39  */
40 public class PhoneFallbackEventHandler implements FallbackEventHandler {
41     private static String TAG = "PhoneFallbackEventHandler";
42     private static final boolean DEBUG = false;
43 
44     @UnsupportedAppUsage
45     Context mContext;
46     @UnsupportedAppUsage
47     View mView;
48 
49     AudioManager mAudioManager;
50     KeyguardManager mKeyguardManager;
51     SearchManager mSearchManager;
52     TelephonyManager mTelephonyManager;
53     MediaSessionManager mMediaSessionManager;
54 
55     @UnsupportedAppUsage
PhoneFallbackEventHandler(Context context)56     public PhoneFallbackEventHandler(Context context) {
57         mContext = context;
58     }
59 
setView(View v)60     public void setView(View v) {
61         mView = v;
62     }
63 
preDispatchKeyEvent(KeyEvent event)64     public void preDispatchKeyEvent(KeyEvent event) {
65         getAudioManager().preDispatchKeyEvent(event, AudioManager.USE_DEFAULT_STREAM_TYPE);
66     }
67 
dispatchKeyEvent(KeyEvent event)68     public boolean dispatchKeyEvent(KeyEvent event) {
69 
70         final int action = event.getAction();
71         final int keyCode = event.getKeyCode();
72 
73         if (action == KeyEvent.ACTION_DOWN) {
74             return onKeyDown(keyCode, event);
75         } else {
76             return onKeyUp(keyCode, event);
77         }
78     }
79 
80     @UnsupportedAppUsage
onKeyDown(int keyCode, KeyEvent event)81     boolean onKeyDown(int keyCode, KeyEvent event) {
82         /* ****************************************************************************
83          * HOW TO DECIDE WHERE YOUR KEY HANDLING GOES.
84          * See the comment in PhoneWindow.onKeyDown
85          * ****************************************************************************/
86         final KeyEvent.DispatcherState dispatcher = mView.getKeyDispatcherState();
87 
88         switch (keyCode) {
89             case KeyEvent.KEYCODE_VOLUME_UP:
90             case KeyEvent.KEYCODE_VOLUME_DOWN:
91             case KeyEvent.KEYCODE_VOLUME_MUTE: {
92                 handleVolumeKeyEvent(event);
93                 return true;
94             }
95 
96 
97             case KeyEvent.KEYCODE_MEDIA_PLAY:
98             case KeyEvent.KEYCODE_MEDIA_PAUSE:
99             case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
100                 /* Suppress PLAY/PAUSE toggle when phone is ringing or in-call
101                  * to avoid music playback */
102                 if (getTelephonyManager().getCallState() != TelephonyManager.CALL_STATE_IDLE) {
103                     return true;  // suppress key event
104                 }
105             case KeyEvent.KEYCODE_MUTE:
106             case KeyEvent.KEYCODE_HEADSETHOOK:
107             case KeyEvent.KEYCODE_MEDIA_STOP:
108             case KeyEvent.KEYCODE_MEDIA_NEXT:
109             case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
110             case KeyEvent.KEYCODE_MEDIA_REWIND:
111             case KeyEvent.KEYCODE_MEDIA_RECORD:
112             case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
113             case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: {
114                 handleMediaKeyEvent(event);
115                 return true;
116             }
117 
118             case KeyEvent.KEYCODE_CALL: {
119                 if (isNotInstantAppAndKeyguardRestricted(dispatcher)) {
120                     break;
121                 }
122                 if (event.getRepeatCount() == 0) {
123                     dispatcher.startTracking(event, this);
124                 } else if (event.isLongPress() && dispatcher.isTracking(event)) {
125                     dispatcher.performedLongPress(event);
126                     if (isUserSetupComplete()) {
127                         mView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
128                         // launch the VoiceDialer
129                         Intent intent = new Intent(Intent.ACTION_VOICE_COMMAND);
130                         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
131                         try {
132                             sendCloseSystemWindows();
133                             mContext.startActivity(intent);
134                         } catch (ActivityNotFoundException e) {
135                             startCallActivity();
136                         }
137                     } else {
138                         Log.i(TAG, "Not starting call activity because user "
139                                 + "setup is in progress.");
140                     }
141                 }
142                 return true;
143             }
144 
145             case KeyEvent.KEYCODE_CAMERA: {
146                 if (isNotInstantAppAndKeyguardRestricted(dispatcher)) {
147                     break;
148                 }
149                 if (event.getRepeatCount() == 0) {
150                     dispatcher.startTracking(event, this);
151                 } else if (event.isLongPress() && dispatcher.isTracking(event)) {
152                     dispatcher.performedLongPress(event);
153                     if (isUserSetupComplete()) {
154                         mView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
155                         sendCloseSystemWindows();
156                         // Broadcast an intent that the Camera button was longpressed
157                         Intent intent = new Intent(Intent.ACTION_CAMERA_BUTTON, null);
158                         intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
159                         intent.putExtra(Intent.EXTRA_KEY_EVENT, event);
160                         mContext.sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT_OR_SELF,
161                                 null, null, null, 0, null, null);
162                     } else {
163                         Log.i(TAG, "Not dispatching CAMERA long press because user "
164                                 + "setup is in progress.");
165                     }
166                 }
167                 return true;
168             }
169 
170             case KeyEvent.KEYCODE_SEARCH: {
171                 if (isNotInstantAppAndKeyguardRestricted(dispatcher)) {
172                     break;
173                 }
174                 if (event.getRepeatCount() == 0) {
175                     dispatcher.startTracking(event, this);
176                 } else if (event.isLongPress() && dispatcher.isTracking(event)) {
177                     Configuration config = mContext.getResources().getConfiguration();
178                     if (config.keyboard == Configuration.KEYBOARD_NOKEYS
179                             || config.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES) {
180                         if (isUserSetupComplete()) {
181                             // launch the search activity
182                             Intent intent = new Intent(Intent.ACTION_SEARCH_LONG_PRESS);
183                             intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
184                             try {
185                                 mView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
186                                 sendCloseSystemWindows();
187                                 getSearchManager().stopSearch();
188                                 mContext.startActivity(intent);
189                                 // Only clear this if we successfully start the
190                                 // activity; otherwise we will allow the normal short
191                                 // press action to be performed.
192                                 dispatcher.performedLongPress(event);
193                                 return true;
194                             } catch (ActivityNotFoundException e) {
195                                 // Ignore
196                             }
197                         } else {
198                             Log.i(TAG, "Not dispatching SEARCH long press because user "
199                                     + "setup is in progress.");
200                         }
201                     }
202                 }
203                 break;
204             }
205         }
206         return false;
207     }
208 
isNotInstantAppAndKeyguardRestricted(KeyEvent.DispatcherState dispatcher)209     private boolean isNotInstantAppAndKeyguardRestricted(KeyEvent.DispatcherState dispatcher) {
210         return !mContext.getPackageManager().isInstantApp()
211                 && (getKeyguardManager().inKeyguardRestrictedInputMode() || dispatcher == null);
212     }
213 
214     @UnsupportedAppUsage
onKeyUp(int keyCode, KeyEvent event)215     boolean onKeyUp(int keyCode, KeyEvent event) {
216         if (DEBUG) {
217             Log.d(TAG, "up " + keyCode);
218         }
219         final KeyEvent.DispatcherState dispatcher = mView.getKeyDispatcherState();
220         if (dispatcher != null) {
221             dispatcher.handleUpEvent(event);
222         }
223 
224         switch (keyCode) {
225             case KeyEvent.KEYCODE_VOLUME_UP:
226             case KeyEvent.KEYCODE_VOLUME_DOWN:
227             case KeyEvent.KEYCODE_VOLUME_MUTE: {
228                 if (!event.isCanceled()) {
229                     handleVolumeKeyEvent(event);
230                 }
231                 return true;
232             }
233 
234             case KeyEvent.KEYCODE_HEADSETHOOK:
235             case KeyEvent.KEYCODE_MUTE:
236             case KeyEvent.KEYCODE_MEDIA_PLAY:
237             case KeyEvent.KEYCODE_MEDIA_PAUSE:
238             case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
239             case KeyEvent.KEYCODE_MEDIA_STOP:
240             case KeyEvent.KEYCODE_MEDIA_NEXT:
241             case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
242             case KeyEvent.KEYCODE_MEDIA_REWIND:
243             case KeyEvent.KEYCODE_MEDIA_RECORD:
244             case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
245             case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: {
246                 handleMediaKeyEvent(event);
247                 return true;
248             }
249 
250             case KeyEvent.KEYCODE_CAMERA: {
251                 if (isNotInstantAppAndKeyguardRestricted(dispatcher)) {
252                     break;
253                 }
254                 if (event.isTracking() && !event.isCanceled()) {
255                     // Add short press behavior here if desired
256                 }
257                 return true;
258             }
259 
260             case KeyEvent.KEYCODE_CALL: {
261                 if (isNotInstantAppAndKeyguardRestricted(dispatcher)) {
262                     break;
263                 }
264                 if (event.isTracking() && !event.isCanceled()) {
265                     if (isUserSetupComplete()) {
266                         startCallActivity();
267                     } else {
268                         Log.i(TAG, "Not starting call activity because user "
269                                 + "setup is in progress.");
270                     }
271                 }
272                 return true;
273             }
274         }
275         return false;
276     }
277 
278     @UnsupportedAppUsage
startCallActivity()279     void startCallActivity() {
280         sendCloseSystemWindows();
281         Intent intent = new Intent(Intent.ACTION_CALL_BUTTON);
282         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
283         try {
284             mContext.startActivity(intent);
285         } catch (ActivityNotFoundException e) {
286             Log.w(TAG, "No activity found for android.intent.action.CALL_BUTTON.");
287         }
288     }
289 
getSearchManager()290     SearchManager getSearchManager() {
291         if (mSearchManager == null) {
292             mSearchManager = (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE);
293         }
294         return mSearchManager;
295     }
296 
getTelephonyManager()297     TelephonyManager getTelephonyManager() {
298         if (mTelephonyManager == null) {
299             mTelephonyManager = (TelephonyManager)mContext.getSystemService(
300                     Context.TELEPHONY_SERVICE);
301         }
302         return mTelephonyManager;
303     }
304 
getKeyguardManager()305     KeyguardManager getKeyguardManager() {
306         if (mKeyguardManager == null) {
307             mKeyguardManager = (KeyguardManager)mContext.getSystemService(Context.KEYGUARD_SERVICE);
308         }
309         return mKeyguardManager;
310     }
311 
getAudioManager()312     AudioManager getAudioManager() {
313         if (mAudioManager == null) {
314             mAudioManager = (AudioManager)mContext.getSystemService(Context.AUDIO_SERVICE);
315         }
316         return mAudioManager;
317     }
318 
getMediaSessionManager()319     MediaSessionManager getMediaSessionManager() {
320         if (mMediaSessionManager == null) {
321             mMediaSessionManager =
322                     (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE);
323         }
324         return mMediaSessionManager;
325     }
326 
sendCloseSystemWindows()327     void sendCloseSystemWindows() {
328         PhoneWindow.sendCloseSystemWindows(mContext, null);
329     }
330 
handleVolumeKeyEvent(KeyEvent keyEvent)331     private void handleVolumeKeyEvent(KeyEvent keyEvent) {
332         getMediaSessionManager().dispatchVolumeKeyEventAsSystemService(keyEvent,
333                 AudioManager.USE_DEFAULT_STREAM_TYPE);
334     }
335 
handleMediaKeyEvent(KeyEvent keyEvent)336     private void handleMediaKeyEvent(KeyEvent keyEvent) {
337         getMediaSessionManager().dispatchMediaKeyEventAsSystemService(keyEvent);
338     }
339 
isUserSetupComplete()340     private boolean isUserSetupComplete() {
341         return Settings.Secure.getInt(mContext.getContentResolver(),
342                 Settings.Secure.USER_SETUP_COMPLETE, 0) != 0;
343     }
344 }
345 
346