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.content.Context;
22import android.content.res.Resources;
23import android.graphics.Canvas;
24import android.media.AudioManager;
25import android.os.SystemClock;
26import android.service.trust.TrustAgentService;
27import android.telephony.TelephonyManager;
28import android.util.AttributeSet;
29import android.util.Log;
30import android.view.KeyEvent;
31import android.view.accessibility.AccessibilityEvent;
32import android.widget.FrameLayout;
33
34import com.android.internal.widget.LockPatternUtils;
35import com.android.keyguard.KeyguardSecurityContainer.SecurityCallback;
36import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
37
38import java.io.File;
39
40/**
41 * Base class for keyguard view.  {@link #reset} is where you should
42 * reset the state of your view.  Use the {@link KeyguardViewCallback} via
43 * {@link #getCallback()} to send information back (such as poking the wake lock,
44 * or finishing the keyguard).
45 *
46 * Handles intercepting of media keys that still work when the keyguard is
47 * showing.
48 */
49public class KeyguardHostView extends FrameLayout implements SecurityCallback {
50
51    public interface OnDismissAction {
52        /**
53         * @return true if the dismiss should be deferred
54         */
55        boolean onDismiss();
56    }
57
58    private AudioManager mAudioManager;
59    private TelephonyManager mTelephonyManager = null;
60    protected ViewMediatorCallback mViewMediatorCallback;
61    protected LockPatternUtils mLockPatternUtils;
62    private OnDismissAction mDismissAction;
63    private Runnable mCancelAction;
64
65    private final KeyguardUpdateMonitorCallback mUpdateCallback =
66            new KeyguardUpdateMonitorCallback() {
67
68        @Override
69        public void onUserSwitchComplete(int userId) {
70            getSecurityContainer().showPrimarySecurityScreen(false /* turning off */);
71        }
72
73        @Override
74        public void onTrustGrantedWithFlags(int flags, int userId) {
75            if (userId != KeyguardUpdateMonitor.getCurrentUser()) return;
76            if (!isAttachedToWindow()) return;
77            boolean bouncerVisible = isVisibleToUser();
78            boolean initiatedByUser =
79                    (flags & TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER) != 0;
80            boolean dismissKeyguard =
81                    (flags & TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD) != 0;
82
83            if (initiatedByUser || dismissKeyguard) {
84                if (mViewMediatorCallback.isScreenOn() && (bouncerVisible || dismissKeyguard)) {
85                    if (!bouncerVisible) {
86                        // The trust agent dismissed the keyguard without the user proving
87                        // that they are present (by swiping up to show the bouncer). That's fine if
88                        // the user proved presence via some other way to the trust agent.
89                        Log.i(TAG, "TrustAgent dismissed Keyguard.");
90                    }
91                    dismiss(false /* authenticated */, userId);
92                } else {
93                    mViewMediatorCallback.playTrustedSound();
94                }
95            }
96        }
97    };
98
99    // Whether the volume keys should be handled by keyguard. If true, then
100    // they will be handled here for specific media types such as music, otherwise
101    // the audio service will bring up the volume dialog.
102    private static final boolean KEYGUARD_MANAGES_VOLUME = false;
103    public static final boolean DEBUG = KeyguardConstants.DEBUG;
104    private static final String TAG = "KeyguardViewBase";
105
106    private KeyguardSecurityContainer mSecurityContainer;
107
108    public KeyguardHostView(Context context) {
109        this(context, null);
110    }
111
112    public KeyguardHostView(Context context, AttributeSet attrs) {
113        super(context, attrs);
114        KeyguardUpdateMonitor.getInstance(context).registerCallback(mUpdateCallback);
115    }
116
117    @Override
118    protected void dispatchDraw(Canvas canvas) {
119        super.dispatchDraw(canvas);
120        if (mViewMediatorCallback != null) {
121            mViewMediatorCallback.keyguardDoneDrawing();
122        }
123    }
124
125    /**
126     * Sets an action to run when keyguard finishes.
127     *
128     * @param action
129     */
130    public void setOnDismissAction(OnDismissAction action, Runnable cancelAction) {
131        if (mCancelAction != null) {
132            mCancelAction.run();
133            mCancelAction = null;
134        }
135        mDismissAction = action;
136        mCancelAction = cancelAction;
137    }
138
139    public void cancelDismissAction() {
140        setOnDismissAction(null, null);
141    }
142
143    @Override
144    protected void onFinishInflate() {
145        mSecurityContainer =
146                findViewById(R.id.keyguard_security_container);
147        mLockPatternUtils = new LockPatternUtils(mContext);
148        mSecurityContainer.setLockPatternUtils(mLockPatternUtils);
149        mSecurityContainer.setSecurityCallback(this);
150        mSecurityContainer.showPrimarySecurityScreen(false);
151        // mSecurityContainer.updateSecurityViews(false /* not bouncing */);
152    }
153
154    /**
155     * Called when the view needs to be shown.
156     */
157    public void showPrimarySecurityScreen() {
158        if (DEBUG) Log.d(TAG, "show()");
159        mSecurityContainer.showPrimarySecurityScreen(false);
160    }
161
162    /**
163     * Show a string explaining why the security view needs to be solved.
164     *
165     * @param reason a flag indicating which string should be shown, see
166     *               {@link KeyguardSecurityView#PROMPT_REASON_NONE},
167     *               {@link KeyguardSecurityView#PROMPT_REASON_RESTART} and
168     *               {@link KeyguardSecurityView#PROMPT_REASON_TIMEOUT}.
169     */
170    public void showPromptReason(int reason) {
171        mSecurityContainer.showPromptReason(reason);
172    }
173
174    public void showMessage(String message, int color) {
175        mSecurityContainer.showMessage(message, color);
176    }
177
178    /**
179     * Dismisses the keyguard by going to the next screen or making it gone.
180     * @param targetUserId a user that needs to be the foreground user at the dismissal completion.
181     * @return True if the keyguard is done.
182     */
183    public boolean dismiss(int targetUserId) {
184        return dismiss(false, targetUserId);
185    }
186
187    public boolean handleBackKey() {
188        if (mSecurityContainer.getCurrentSecuritySelection() != SecurityMode.None) {
189            mSecurityContainer.dismiss(false, KeyguardUpdateMonitor.getCurrentUser());
190            return true;
191        }
192        return false;
193    }
194
195    @Override
196    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
197        if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
198            event.getText().add(mSecurityContainer.getCurrentSecurityModeContentDescription());
199            return true;
200        } else {
201            return super.dispatchPopulateAccessibilityEvent(event);
202        }
203    }
204
205    protected KeyguardSecurityContainer getSecurityContainer() {
206        return mSecurityContainer;
207    }
208
209    @Override
210    public boolean dismiss(boolean authenticated, int targetUserId) {
211        return mSecurityContainer.showNextSecurityScreenOrFinish(authenticated, targetUserId);
212    }
213
214    /**
215     * Authentication has happened and it's time to dismiss keyguard. This function
216     * should clean up and inform KeyguardViewMediator.
217     *
218     * @param strongAuth whether the user has authenticated with strong authentication like
219     *                   pattern, password or PIN but not by trust agents or fingerprint
220     * @param targetUserId a user that needs to be the foreground user at the dismissal completion.
221     */
222    @Override
223    public void finish(boolean strongAuth, int targetUserId) {
224        // If there's a pending runnable because the user interacted with a widget
225        // and we're leaving keyguard, then run it.
226        boolean deferKeyguardDone = false;
227        if (mDismissAction != null) {
228            deferKeyguardDone = mDismissAction.onDismiss();
229            mDismissAction = null;
230            mCancelAction = null;
231        }
232        if (mViewMediatorCallback != null) {
233            if (deferKeyguardDone) {
234                mViewMediatorCallback.keyguardDonePending(strongAuth, targetUserId);
235            } else {
236                mViewMediatorCallback.keyguardDone(strongAuth, targetUserId);
237            }
238        }
239    }
240
241    @Override
242    public void reset() {
243        mViewMediatorCallback.resetKeyguard();
244    }
245
246    @Override
247    public void onSecurityModeChanged(SecurityMode securityMode, boolean needsInput) {
248        if (mViewMediatorCallback != null) {
249            mViewMediatorCallback.setNeedsInput(needsInput);
250        }
251    }
252
253    public void userActivity() {
254        if (mViewMediatorCallback != null) {
255            mViewMediatorCallback.userActivity();
256        }
257    }
258
259    /**
260     * Called when the Keyguard is not actively shown anymore on the screen.
261     */
262    public void onPause() {
263        if (DEBUG) Log.d(TAG, String.format("screen off, instance %s at %s",
264                Integer.toHexString(hashCode()), SystemClock.uptimeMillis()));
265        mSecurityContainer.showPrimarySecurityScreen(true);
266        mSecurityContainer.onPause();
267        clearFocus();
268    }
269
270    /**
271     * Called when the Keyguard is actively shown on the screen.
272     */
273    public void onResume() {
274        if (DEBUG) Log.d(TAG, "screen on, instance " + Integer.toHexString(hashCode()));
275        mSecurityContainer.onResume(KeyguardSecurityView.SCREEN_ON);
276        requestFocus();
277    }
278
279    /**
280     * Starts the animation when the Keyguard gets shown.
281     */
282    public void startAppearAnimation() {
283        mSecurityContainer.startAppearAnimation();
284    }
285
286    public void startDisappearAnimation(Runnable finishRunnable) {
287        if (!mSecurityContainer.startDisappearAnimation(finishRunnable) && finishRunnable != null) {
288            finishRunnable.run();
289        }
290    }
291
292    /**
293     * Called before this view is being removed.
294     */
295    public void cleanUp() {
296        getSecurityContainer().onPause();
297    }
298
299    @Override
300    public boolean dispatchKeyEvent(KeyEvent event) {
301        if (interceptMediaKey(event)) {
302            return true;
303        }
304        return super.dispatchKeyEvent(event);
305    }
306
307    /**
308     * Allows the media keys to work when the keyguard is showing.
309     * The media keys should be of no interest to the actual keyguard view(s),
310     * so intercepting them here should not be of any harm.
311     * @param event The key event
312     * @return whether the event was consumed as a media key.
313     */
314    public boolean interceptMediaKey(KeyEvent event) {
315        final int keyCode = event.getKeyCode();
316        if (event.getAction() == KeyEvent.ACTION_DOWN) {
317            switch (keyCode) {
318                case KeyEvent.KEYCODE_MEDIA_PLAY:
319                case KeyEvent.KEYCODE_MEDIA_PAUSE:
320                case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
321                    /* Suppress PLAY/PAUSE toggle when phone is ringing or
322                     * in-call to avoid music playback */
323                    if (mTelephonyManager == null) {
324                        mTelephonyManager = (TelephonyManager) getContext().getSystemService(
325                                Context.TELEPHONY_SERVICE);
326                    }
327                    if (mTelephonyManager != null &&
328                            mTelephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE) {
329                        return true;  // suppress key event
330                    }
331                case KeyEvent.KEYCODE_MUTE:
332                case KeyEvent.KEYCODE_HEADSETHOOK:
333                case KeyEvent.KEYCODE_MEDIA_STOP:
334                case KeyEvent.KEYCODE_MEDIA_NEXT:
335                case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
336                case KeyEvent.KEYCODE_MEDIA_REWIND:
337                case KeyEvent.KEYCODE_MEDIA_RECORD:
338                case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
339                case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: {
340                    handleMediaKeyEvent(event);
341                    return true;
342                }
343
344                case KeyEvent.KEYCODE_VOLUME_UP:
345                case KeyEvent.KEYCODE_VOLUME_DOWN:
346                case KeyEvent.KEYCODE_VOLUME_MUTE: {
347                    if (KEYGUARD_MANAGES_VOLUME) {
348                        synchronized (this) {
349                            if (mAudioManager == null) {
350                                mAudioManager = (AudioManager) getContext().getSystemService(
351                                        Context.AUDIO_SERVICE);
352                            }
353                        }
354                        // Volume buttons should only function for music (local or remote).
355                        // TODO: Actually handle MUTE.
356                        mAudioManager.adjustSuggestedStreamVolume(
357                                keyCode == KeyEvent.KEYCODE_VOLUME_UP
358                                        ? AudioManager.ADJUST_RAISE
359                                        : AudioManager.ADJUST_LOWER /* direction */,
360                                AudioManager.STREAM_MUSIC /* stream */, 0 /* flags */);
361                        // Don't execute default volume behavior
362                        return true;
363                    } else {
364                        return false;
365                    }
366                }
367            }
368        } else if (event.getAction() == KeyEvent.ACTION_UP) {
369            switch (keyCode) {
370                case KeyEvent.KEYCODE_MUTE:
371                case KeyEvent.KEYCODE_HEADSETHOOK:
372                case KeyEvent.KEYCODE_MEDIA_PLAY:
373                case KeyEvent.KEYCODE_MEDIA_PAUSE:
374                case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
375                case KeyEvent.KEYCODE_MEDIA_STOP:
376                case KeyEvent.KEYCODE_MEDIA_NEXT:
377                case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
378                case KeyEvent.KEYCODE_MEDIA_REWIND:
379                case KeyEvent.KEYCODE_MEDIA_RECORD:
380                case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
381                case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: {
382                    handleMediaKeyEvent(event);
383                    return true;
384                }
385            }
386        }
387        return false;
388    }
389
390    private void handleMediaKeyEvent(KeyEvent keyEvent) {
391        synchronized (this) {
392            if (mAudioManager == null) {
393                mAudioManager = (AudioManager) getContext().getSystemService(
394                        Context.AUDIO_SERVICE);
395            }
396        }
397        mAudioManager.dispatchMediaKeyEvent(keyEvent);
398    }
399
400    @Override
401    public void dispatchSystemUiVisibilityChanged(int visibility) {
402        super.dispatchSystemUiVisibilityChanged(visibility);
403
404        if (!(mContext instanceof Activity)) {
405            setSystemUiVisibility(STATUS_BAR_DISABLE_BACK);
406        }
407    }
408
409    /**
410     * In general, we enable unlocking the insecure keyguard with the menu key. However, there are
411     * some cases where we wish to disable it, notably when the menu button placement or technology
412     * is prone to false positives.
413     *
414     * @return true if the menu key should be enabled
415     */
416    private static final String ENABLE_MENU_KEY_FILE = "/data/local/enable_menu_key";
417    public boolean shouldEnableMenuKey() {
418        final Resources res = getResources();
419        final boolean configDisabled = res.getBoolean(R.bool.config_disableMenuKeyInLockScreen);
420        final boolean isTestHarness = ActivityManager.isRunningInTestHarness();
421        final boolean fileOverride = (new File(ENABLE_MENU_KEY_FILE)).exists();
422        return !configDisabled || isTestHarness || fileOverride;
423    }
424
425    public void setViewMediatorCallback(ViewMediatorCallback viewMediatorCallback) {
426        mViewMediatorCallback = viewMediatorCallback;
427        // Update ViewMediator with the current input method requirements
428        mViewMediatorCallback.setNeedsInput(mSecurityContainer.needsInput());
429    }
430
431    public void setLockPatternUtils(LockPatternUtils utils) {
432        mLockPatternUtils = utils;
433        mSecurityContainer.setLockPatternUtils(utils);
434    }
435
436    public SecurityMode getSecurityMode() {
437        return mSecurityContainer.getSecurityMode();
438    }
439
440    public SecurityMode getCurrentSecurityMode() {
441        return mSecurityContainer.getCurrentSecurityMode();
442    }
443}
444