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 */);
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                (KeyguardSecurityContainer) 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     *
181     *  @return True if the keyguard is done.
182     */
183    public boolean dismiss() {
184        return dismiss(false);
185    }
186
187    public boolean handleBackKey() {
188        if (mSecurityContainer.getCurrentSecuritySelection() != SecurityMode.None) {
189            mSecurityContainer.dismiss(false);
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) {
211        return mSecurityContainer.showNextSecurityScreenOrFinish(authenticated);
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     */
221    @Override
222    public void finish(boolean strongAuth) {
223        // If there's a pending runnable because the user interacted with a widget
224        // and we're leaving keyguard, then run it.
225        boolean deferKeyguardDone = false;
226        if (mDismissAction != null) {
227            deferKeyguardDone = mDismissAction.onDismiss();
228            mDismissAction = null;
229            mCancelAction = null;
230        }
231        if (mViewMediatorCallback != null) {
232            if (deferKeyguardDone) {
233                mViewMediatorCallback.keyguardDonePending(strongAuth);
234            } else {
235                mViewMediatorCallback.keyguardDone(strongAuth);
236            }
237        }
238    }
239
240    @Override
241    public void reset() {
242        mViewMediatorCallback.resetKeyguard();
243    }
244
245    @Override
246    public void onSecurityModeChanged(SecurityMode securityMode, boolean needsInput) {
247        if (mViewMediatorCallback != null) {
248            mViewMediatorCallback.setNeedsInput(needsInput);
249        }
250    }
251
252    public void userActivity() {
253        if (mViewMediatorCallback != null) {
254            mViewMediatorCallback.userActivity();
255        }
256    }
257
258    /**
259     * Called when the Keyguard is not actively shown anymore on the screen.
260     */
261    public void onPause() {
262        if (DEBUG) Log.d(TAG, String.format("screen off, instance %s at %s",
263                Integer.toHexString(hashCode()), SystemClock.uptimeMillis()));
264        mSecurityContainer.showPrimarySecurityScreen(true);
265        mSecurityContainer.onPause();
266        clearFocus();
267    }
268
269    /**
270     * Called when the Keyguard is actively shown on the screen.
271     */
272    public void onResume() {
273        if (DEBUG) Log.d(TAG, "screen on, instance " + Integer.toHexString(hashCode()));
274        mSecurityContainer.onResume(KeyguardSecurityView.SCREEN_ON);
275        requestFocus();
276    }
277
278    /**
279     * Starts the animation when the Keyguard gets shown.
280     */
281    public void startAppearAnimation() {
282        mSecurityContainer.startAppearAnimation();
283    }
284
285    public void startDisappearAnimation(Runnable finishRunnable) {
286        if (!mSecurityContainer.startDisappearAnimation(finishRunnable) && finishRunnable != null) {
287            finishRunnable.run();
288        }
289    }
290
291    /**
292     * Called before this view is being removed.
293     */
294    public void cleanUp() {
295        getSecurityContainer().onPause();
296    }
297
298    @Override
299    public boolean dispatchKeyEvent(KeyEvent event) {
300        if (interceptMediaKey(event)) {
301            return true;
302        }
303        return super.dispatchKeyEvent(event);
304    }
305
306    /**
307     * Allows the media keys to work when the keyguard is showing.
308     * The media keys should be of no interest to the actual keyguard view(s),
309     * so intercepting them here should not be of any harm.
310     * @param event The key event
311     * @return whether the event was consumed as a media key.
312     */
313    public boolean interceptMediaKey(KeyEvent event) {
314        final int keyCode = event.getKeyCode();
315        if (event.getAction() == KeyEvent.ACTION_DOWN) {
316            switch (keyCode) {
317                case KeyEvent.KEYCODE_MEDIA_PLAY:
318                case KeyEvent.KEYCODE_MEDIA_PAUSE:
319                case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
320                    /* Suppress PLAY/PAUSE toggle when phone is ringing or
321                     * in-call to avoid music playback */
322                    if (mTelephonyManager == null) {
323                        mTelephonyManager = (TelephonyManager) getContext().getSystemService(
324                                Context.TELEPHONY_SERVICE);
325                    }
326                    if (mTelephonyManager != null &&
327                            mTelephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE) {
328                        return true;  // suppress key event
329                    }
330                case KeyEvent.KEYCODE_MUTE:
331                case KeyEvent.KEYCODE_HEADSETHOOK:
332                case KeyEvent.KEYCODE_MEDIA_STOP:
333                case KeyEvent.KEYCODE_MEDIA_NEXT:
334                case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
335                case KeyEvent.KEYCODE_MEDIA_REWIND:
336                case KeyEvent.KEYCODE_MEDIA_RECORD:
337                case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
338                case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: {
339                    handleMediaKeyEvent(event);
340                    return true;
341                }
342
343                case KeyEvent.KEYCODE_VOLUME_UP:
344                case KeyEvent.KEYCODE_VOLUME_DOWN:
345                case KeyEvent.KEYCODE_VOLUME_MUTE: {
346                    if (KEYGUARD_MANAGES_VOLUME) {
347                        synchronized (this) {
348                            if (mAudioManager == null) {
349                                mAudioManager = (AudioManager) getContext().getSystemService(
350                                        Context.AUDIO_SERVICE);
351                            }
352                        }
353                        // Volume buttons should only function for music (local or remote).
354                        // TODO: Actually handle MUTE.
355                        mAudioManager.adjustSuggestedStreamVolume(
356                                keyCode == KeyEvent.KEYCODE_VOLUME_UP
357                                        ? AudioManager.ADJUST_RAISE
358                                        : AudioManager.ADJUST_LOWER /* direction */,
359                                AudioManager.STREAM_MUSIC /* stream */, 0 /* flags */);
360                        // Don't execute default volume behavior
361                        return true;
362                    } else {
363                        return false;
364                    }
365                }
366            }
367        } else if (event.getAction() == KeyEvent.ACTION_UP) {
368            switch (keyCode) {
369                case KeyEvent.KEYCODE_MUTE:
370                case KeyEvent.KEYCODE_HEADSETHOOK:
371                case KeyEvent.KEYCODE_MEDIA_PLAY:
372                case KeyEvent.KEYCODE_MEDIA_PAUSE:
373                case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
374                case KeyEvent.KEYCODE_MEDIA_STOP:
375                case KeyEvent.KEYCODE_MEDIA_NEXT:
376                case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
377                case KeyEvent.KEYCODE_MEDIA_REWIND:
378                case KeyEvent.KEYCODE_MEDIA_RECORD:
379                case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
380                case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: {
381                    handleMediaKeyEvent(event);
382                    return true;
383                }
384            }
385        }
386        return false;
387    }
388
389    private void handleMediaKeyEvent(KeyEvent keyEvent) {
390        synchronized (this) {
391            if (mAudioManager == null) {
392                mAudioManager = (AudioManager) getContext().getSystemService(
393                        Context.AUDIO_SERVICE);
394            }
395        }
396        mAudioManager.dispatchMediaKeyEvent(keyEvent);
397    }
398
399    @Override
400    public void dispatchSystemUiVisibilityChanged(int visibility) {
401        super.dispatchSystemUiVisibilityChanged(visibility);
402
403        if (!(mContext instanceof Activity)) {
404            setSystemUiVisibility(STATUS_BAR_DISABLE_BACK);
405        }
406    }
407
408    /**
409     * In general, we enable unlocking the insecure keyguard with the menu key. However, there are
410     * some cases where we wish to disable it, notably when the menu button placement or technology
411     * is prone to false positives.
412     *
413     * @return true if the menu key should be enabled
414     */
415    private static final String ENABLE_MENU_KEY_FILE = "/data/local/enable_menu_key";
416    public boolean shouldEnableMenuKey() {
417        final Resources res = getResources();
418        final boolean configDisabled = res.getBoolean(R.bool.config_disableMenuKeyInLockScreen);
419        final boolean isTestHarness = ActivityManager.isRunningInTestHarness();
420        final boolean fileOverride = (new File(ENABLE_MENU_KEY_FILE)).exists();
421        return !configDisabled || isTestHarness || fileOverride;
422    }
423
424    public void setViewMediatorCallback(ViewMediatorCallback viewMediatorCallback) {
425        mViewMediatorCallback = viewMediatorCallback;
426        // Update ViewMediator with the current input method requirements
427        mViewMediatorCallback.setNeedsInput(mSecurityContainer.needsInput());
428    }
429
430    public void setLockPatternUtils(LockPatternUtils utils) {
431        mLockPatternUtils = utils;
432        mSecurityContainer.setLockPatternUtils(utils);
433    }
434
435    public SecurityMode getSecurityMode() {
436        return mSecurityContainer.getSecurityMode();
437    }
438
439    public SecurityMode getCurrentSecurityMode() {
440        return mSecurityContainer.getCurrentSecurityMode();
441    }
442}
443