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