1/*
2 * Copyright (C) 2007 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.keyguard;
18
19import android.app.Activity;
20import android.app.ActivityManager;
21import android.app.ActivityOptions;
22import android.app.SearchManager;
23import android.content.Context;
24import android.content.Intent;
25import android.content.res.Resources;
26import android.graphics.Canvas;
27import android.media.AudioManager;
28import android.media.IAudioService;
29import android.os.Bundle;
30import android.os.RemoteException;
31import android.os.ServiceManager;
32import android.os.SystemClock;
33import android.os.UserHandle;
34import android.telephony.TelephonyManager;
35import android.util.AttributeSet;
36import android.util.Log;
37import android.util.Slog;
38import android.view.KeyEvent;
39import android.view.MotionEvent;
40import android.view.View;
41import android.widget.FrameLayout;
42
43import com.android.internal.widget.LockPatternUtils;
44import com.android.keyguard.KeyguardHostView.OnDismissAction;
45import com.android.keyguard.KeyguardSecurityContainer.SecurityCallback;
46import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
47
48import java.io.File;
49
50/**
51 * Base class for keyguard view.  {@link #reset} is where you should
52 * reset the state of your view.  Use the {@link KeyguardViewCallback} via
53 * {@link #getCallback()} to send information back (such as poking the wake lock,
54 * or finishing the keyguard).
55 *
56 * Handles intercepting of media keys that still work when the keyguard is
57 * showing.
58 */
59public abstract class KeyguardViewBase extends FrameLayout implements SecurityCallback {
60
61    private AudioManager mAudioManager;
62    private TelephonyManager mTelephonyManager = null;
63    protected ViewMediatorCallback mViewMediatorCallback;
64    protected LockPatternUtils mLockPatternUtils;
65    private OnDismissAction mDismissAction;
66
67    // Whether the volume keys should be handled by keyguard. If true, then
68    // they will be handled here for specific media types such as music, otherwise
69    // the audio service will bring up the volume dialog.
70    private static final boolean KEYGUARD_MANAGES_VOLUME = false;
71    public static final boolean DEBUG = KeyguardConstants.DEBUG;
72    private static final String TAG = "KeyguardViewBase";
73
74    private KeyguardSecurityContainer mSecurityContainer;
75
76    public KeyguardViewBase(Context context) {
77        this(context, null);
78    }
79
80    public KeyguardViewBase(Context context, AttributeSet attrs) {
81        super(context, attrs);
82    }
83
84    @Override
85    protected void dispatchDraw(Canvas canvas) {
86        super.dispatchDraw(canvas);
87        if (mViewMediatorCallback != null) {
88            mViewMediatorCallback.keyguardDoneDrawing();
89        }
90    }
91
92    /**
93     * Sets an action to run when keyguard finishes.
94     *
95     * @param action
96     */
97    public void setOnDismissAction(OnDismissAction action) {
98        mDismissAction = action;
99    }
100
101    @Override
102    protected void onFinishInflate() {
103        mSecurityContainer =
104                (KeyguardSecurityContainer) findViewById(R.id.keyguard_security_container);
105        mLockPatternUtils = new LockPatternUtils(mContext);
106        mSecurityContainer.setLockPatternUtils(mLockPatternUtils);
107        mSecurityContainer.setSecurityCallback(this);
108        mSecurityContainer.showPrimarySecurityScreen(false);
109        // mSecurityContainer.updateSecurityViews(false /* not bouncing */);
110    }
111
112    /**
113     * Called when the view needs to be shown.
114     */
115    public void show() {
116        if (DEBUG) Log.d(TAG, "show()");
117        mSecurityContainer.showPrimarySecurityScreen(false);
118    }
119
120    /**
121     *  Dismisses the keyguard by going to the next screen or making it gone.
122     *
123     *  @return True if the keyguard is done.
124     */
125    public boolean dismiss() {
126        return dismiss(false);
127    }
128
129    protected void showBouncer(boolean show) {
130        CharSequence what = getContext().getResources().getText(
131                show ? R.string.keyguard_accessibility_show_bouncer
132                        : R.string.keyguard_accessibility_hide_bouncer);
133        announceForAccessibility(what);
134        announceCurrentSecurityMethod();
135    }
136
137    public boolean handleBackKey() {
138        if (mSecurityContainer.getCurrentSecuritySelection() == SecurityMode.Account) {
139            // go back to primary screen
140            mSecurityContainer.showPrimarySecurityScreen(false /*turningOff*/);
141            return true;
142        }
143        if (mSecurityContainer.getCurrentSecuritySelection() != SecurityMode.None) {
144            mSecurityContainer.dismiss(false);
145            return true;
146        }
147        return false;
148    }
149
150    protected void announceCurrentSecurityMethod() {
151        mSecurityContainer.announceCurrentSecurityMethod();
152    }
153
154    protected KeyguardSecurityContainer getSecurityContainer() {
155        return mSecurityContainer;
156    }
157
158    @Override
159    public boolean dismiss(boolean authenticated) {
160        return mSecurityContainer.showNextSecurityScreenOrFinish(authenticated);
161    }
162
163    /**
164     * Authentication has happened and it's time to dismiss keyguard. This function
165     * should clean up and inform KeyguardViewMediator.
166     */
167    @Override
168    public void finish() {
169        // If the alternate unlock was suppressed, it can now be safely
170        // enabled because the user has left keyguard.
171        KeyguardUpdateMonitor.getInstance(mContext).setAlternateUnlockEnabled(true);
172
173        // If there's a pending runnable because the user interacted with a widget
174        // and we're leaving keyguard, then run it.
175        boolean deferKeyguardDone = false;
176        if (mDismissAction != null) {
177            deferKeyguardDone = mDismissAction.onDismiss();
178            mDismissAction = null;
179        }
180        if (mViewMediatorCallback != null) {
181            if (deferKeyguardDone) {
182                mViewMediatorCallback.keyguardDonePending();
183            } else {
184                mViewMediatorCallback.keyguardDone(true);
185            }
186        }
187    }
188
189    @Override
190    public void onSecurityModeChanged(SecurityMode securityMode, boolean needsInput) {
191        if (mViewMediatorCallback != null) {
192            mViewMediatorCallback.setNeedsInput(needsInput);
193        }
194    }
195
196    public void userActivity() {
197        if (mViewMediatorCallback != null) {
198            mViewMediatorCallback.userActivity();
199        }
200    }
201
202    protected void onUserActivityTimeoutChanged() {
203        if (mViewMediatorCallback != null) {
204            mViewMediatorCallback.onUserActivityTimeoutChanged();
205        }
206    }
207
208    /**
209     * Called when the Keyguard is not actively shown anymore on the screen.
210     */
211    public void onPause() {
212        if (DEBUG) Log.d(TAG, String.format("screen off, instance %s at %s",
213                Integer.toHexString(hashCode()), SystemClock.uptimeMillis()));
214        // Once the screen turns off, we no longer consider this to be first boot and we want the
215        // biometric unlock to start next time keyguard is shown.
216        KeyguardUpdateMonitor.getInstance(mContext).setAlternateUnlockEnabled(true);
217        mSecurityContainer.showPrimarySecurityScreen(true);
218        mSecurityContainer.onPause();
219        clearFocus();
220    }
221
222    /**
223     * Called when the Keyguard is actively shown on the screen.
224     */
225    public void onResume() {
226        if (DEBUG) Log.d(TAG, "screen on, instance " + Integer.toHexString(hashCode()));
227        mSecurityContainer.showPrimarySecurityScreen(false);
228        mSecurityContainer.onResume(KeyguardSecurityView.SCREEN_ON);
229        requestFocus();
230    }
231
232    /**
233     * Starts the animation when the Keyguard gets shown.
234     */
235    public void startAppearAnimation() {
236        mSecurityContainer.startAppearAnimation();
237    }
238
239    public void startDisappearAnimation(Runnable finishRunnable) {
240        if (!mSecurityContainer.startDisappearAnimation(finishRunnable) && finishRunnable != null) {
241            finishRunnable.run();
242        }
243    }
244
245    /**
246     * Verify that the user can get past the keyguard securely.  This is called,
247     * for example, when the phone disables the keyguard but then wants to launch
248     * something else that requires secure access.
249     *
250     * The result will be propogated back via {@link KeyguardViewCallback#keyguardDone(boolean)}
251     */
252    public void verifyUnlock() {
253        SecurityMode securityMode = mSecurityContainer.getSecurityMode();
254        if (securityMode == KeyguardSecurityModel.SecurityMode.None) {
255            if (mViewMediatorCallback != null) {
256                mViewMediatorCallback.keyguardDone(true);
257            }
258        } else if (securityMode != KeyguardSecurityModel.SecurityMode.Pattern
259                && securityMode != KeyguardSecurityModel.SecurityMode.PIN
260                && securityMode != KeyguardSecurityModel.SecurityMode.Password) {
261            // can only verify unlock when in pattern/password mode
262            if (mViewMediatorCallback != null) {
263                mViewMediatorCallback.keyguardDone(false);
264            }
265        } else {
266            // otherwise, go to the unlock screen, see if they can verify it
267            mSecurityContainer.verifyUnlock();
268        }
269    }
270
271    /**
272     * Called before this view is being removed.
273     */
274    abstract public void cleanUp();
275
276    /**
277     * Gets the desired user activity timeout in milliseconds, or -1 if the
278     * default should be used.
279     */
280    abstract public long getUserActivityTimeout();
281
282    @Override
283    public boolean dispatchKeyEvent(KeyEvent event) {
284        if (interceptMediaKey(event)) {
285            return true;
286        }
287        return super.dispatchKeyEvent(event);
288    }
289
290    /**
291     * Allows the media keys to work when the keyguard is showing.
292     * The media keys should be of no interest to the actual keyguard view(s),
293     * so intercepting them here should not be of any harm.
294     * @param event The key event
295     * @return whether the event was consumed as a media key.
296     */
297    public boolean interceptMediaKey(KeyEvent event) {
298        final int keyCode = event.getKeyCode();
299        if (event.getAction() == KeyEvent.ACTION_DOWN) {
300            switch (keyCode) {
301                case KeyEvent.KEYCODE_MEDIA_PLAY:
302                case KeyEvent.KEYCODE_MEDIA_PAUSE:
303                case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
304                    /* Suppress PLAY/PAUSE toggle when phone is ringing or
305                     * in-call to avoid music playback */
306                    if (mTelephonyManager == null) {
307                        mTelephonyManager = (TelephonyManager) getContext().getSystemService(
308                                Context.TELEPHONY_SERVICE);
309                    }
310                    if (mTelephonyManager != null &&
311                            mTelephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE) {
312                        return true;  // suppress key event
313                    }
314                case KeyEvent.KEYCODE_MUTE:
315                case KeyEvent.KEYCODE_HEADSETHOOK:
316                case KeyEvent.KEYCODE_MEDIA_STOP:
317                case KeyEvent.KEYCODE_MEDIA_NEXT:
318                case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
319                case KeyEvent.KEYCODE_MEDIA_REWIND:
320                case KeyEvent.KEYCODE_MEDIA_RECORD:
321                case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
322                case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: {
323                    handleMediaKeyEvent(event);
324                    return true;
325                }
326
327                case KeyEvent.KEYCODE_VOLUME_UP:
328                case KeyEvent.KEYCODE_VOLUME_DOWN:
329                case KeyEvent.KEYCODE_VOLUME_MUTE: {
330                    if (KEYGUARD_MANAGES_VOLUME) {
331                        synchronized (this) {
332                            if (mAudioManager == null) {
333                                mAudioManager = (AudioManager) getContext().getSystemService(
334                                        Context.AUDIO_SERVICE);
335                            }
336                        }
337                        // Volume buttons should only function for music (local or remote).
338                        // TODO: Actually handle MUTE.
339                        mAudioManager.adjustSuggestedStreamVolume(
340                                keyCode == KeyEvent.KEYCODE_VOLUME_UP
341                                        ? AudioManager.ADJUST_RAISE
342                                        : AudioManager.ADJUST_LOWER /* direction */,
343                                AudioManager.STREAM_MUSIC /* stream */, 0 /* flags */);
344                        // Don't execute default volume behavior
345                        return true;
346                    } else {
347                        return false;
348                    }
349                }
350            }
351        } else if (event.getAction() == KeyEvent.ACTION_UP) {
352            switch (keyCode) {
353                case KeyEvent.KEYCODE_MUTE:
354                case KeyEvent.KEYCODE_HEADSETHOOK:
355                case KeyEvent.KEYCODE_MEDIA_PLAY:
356                case KeyEvent.KEYCODE_MEDIA_PAUSE:
357                case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
358                case KeyEvent.KEYCODE_MEDIA_STOP:
359                case KeyEvent.KEYCODE_MEDIA_NEXT:
360                case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
361                case KeyEvent.KEYCODE_MEDIA_REWIND:
362                case KeyEvent.KEYCODE_MEDIA_RECORD:
363                case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
364                case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: {
365                    handleMediaKeyEvent(event);
366                    return true;
367                }
368            }
369        }
370        return false;
371    }
372
373    private void handleMediaKeyEvent(KeyEvent keyEvent) {
374        synchronized (this) {
375            if (mAudioManager == null) {
376                mAudioManager = (AudioManager) getContext().getSystemService(
377                        Context.AUDIO_SERVICE);
378            }
379        }
380        mAudioManager.dispatchMediaKeyEvent(keyEvent);
381    }
382
383    @Override
384    public void dispatchSystemUiVisibilityChanged(int visibility) {
385        super.dispatchSystemUiVisibilityChanged(visibility);
386
387        if (!(mContext instanceof Activity)) {
388            setSystemUiVisibility(STATUS_BAR_DISABLE_BACK);
389        }
390    }
391
392    /**
393     * In general, we enable unlocking the insecure keyguard with the menu key. However, there are
394     * some cases where we wish to disable it, notably when the menu button placement or technology
395     * is prone to false positives.
396     *
397     * @return true if the menu key should be enabled
398     */
399    private static final String ENABLE_MENU_KEY_FILE = "/data/local/enable_menu_key";
400    private boolean shouldEnableMenuKey() {
401        final Resources res = getResources();
402        final boolean configDisabled = res.getBoolean(R.bool.config_disableMenuKeyInLockScreen);
403        final boolean isTestHarness = ActivityManager.isRunningInTestHarness();
404        final boolean fileOverride = (new File(ENABLE_MENU_KEY_FILE)).exists();
405        return !configDisabled || isTestHarness || fileOverride;
406    }
407
408    public boolean handleMenuKey() {
409        // The following enables the MENU key to work for testing automation
410        if (shouldEnableMenuKey()) {
411            dismiss();
412            return true;
413        }
414        return false;
415    }
416
417    public void setViewMediatorCallback(ViewMediatorCallback viewMediatorCallback) {
418        mViewMediatorCallback = viewMediatorCallback;
419        // Update ViewMediator with the current input method requirements
420        mViewMediatorCallback.setNeedsInput(mSecurityContainer.needsInput());
421    }
422
423    protected KeyguardActivityLauncher getActivityLauncher() {
424        return mActivityLauncher;
425    }
426
427    private final KeyguardActivityLauncher mActivityLauncher = new KeyguardActivityLauncher() {
428        @Override
429        Context getContext() {
430            return mContext;
431        }
432
433        @Override
434        void setOnDismissAction(OnDismissAction action) {
435            KeyguardViewBase.this.setOnDismissAction(action);
436        }
437
438        @Override
439        LockPatternUtils getLockPatternUtils() {
440            return mLockPatternUtils;
441        }
442
443        @Override
444        void requestDismissKeyguard() {
445            KeyguardViewBase.this.dismiss(false);
446        }
447    };
448
449    public void showAssistant() {
450        final Intent intent = ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE))
451          .getAssistIntent(mContext, true, UserHandle.USER_CURRENT);
452
453        if (intent == null) return;
454
455        final ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext,
456                R.anim.keyguard_action_assist_enter, R.anim.keyguard_action_assist_exit,
457                getHandler(), null);
458
459        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
460        mActivityLauncher.launchActivityWithAnimation(intent, false, opts.toBundle(), null, null);
461    }
462
463    public void launchCamera() {
464        mActivityLauncher.launchCamera(getHandler(), null);
465    }
466
467    public void setLockPatternUtils(LockPatternUtils utils) {
468        mLockPatternUtils = utils;
469        mSecurityContainer.setLockPatternUtils(utils);
470    }
471
472    public SecurityMode getSecurityMode() {
473        return mSecurityContainer.getSecurityMode();
474    }
475
476    protected abstract void onUserSwitching(boolean switching);
477
478    protected abstract void onCreateOptions(Bundle options);
479
480    protected abstract void onExternalMotionEvent(MotionEvent event);
481
482}
483