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 
17 package com.android.keyguard;
18 
19 import android.app.Activity;
20 import android.app.ActivityManager;
21 import android.content.Context;
22 import android.content.res.Resources;
23 import android.graphics.Canvas;
24 import android.media.AudioManager;
25 import android.os.SystemClock;
26 import android.service.trust.TrustAgentService;
27 import android.telephony.TelephonyManager;
28 import android.util.AttributeSet;
29 import android.util.Log;
30 import android.view.KeyEvent;
31 import android.view.accessibility.AccessibilityEvent;
32 import android.widget.FrameLayout;
33 
34 import com.android.internal.widget.LockPatternUtils;
35 import com.android.keyguard.KeyguardSecurityContainer.SecurityCallback;
36 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
37 
38 import 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  */
49 public class KeyguardHostView extends FrameLayout implements SecurityCallback {
50 
51     public interface OnDismissAction {
52         /**
53          * @return true if the dismiss should be deferred
54          */
onDismiss()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 
KeyguardHostView(Context context)108     public KeyguardHostView(Context context) {
109         this(context, null);
110     }
111 
KeyguardHostView(Context context, AttributeSet attrs)112     public KeyguardHostView(Context context, AttributeSet attrs) {
113         super(context, attrs);
114         KeyguardUpdateMonitor.getInstance(context).registerCallback(mUpdateCallback);
115     }
116 
117     @Override
dispatchDraw(Canvas canvas)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      */
setOnDismissAction(OnDismissAction action, Runnable cancelAction)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 
cancelDismissAction()139     public void cancelDismissAction() {
140         setOnDismissAction(null, null);
141     }
142 
143     @Override
onFinishInflate()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      */
showPrimarySecurityScreen()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      */
showPromptReason(int reason)170     public void showPromptReason(int reason) {
171         mSecurityContainer.showPromptReason(reason);
172     }
173 
showMessage(String message, int color)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      */
dismiss()183     public boolean dismiss() {
184         return dismiss(false);
185     }
186 
handleBackKey()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
dispatchPopulateAccessibilityEvent(AccessibilityEvent event)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 
getSecurityContainer()205     protected KeyguardSecurityContainer getSecurityContainer() {
206         return mSecurityContainer;
207     }
208 
209     @Override
dismiss(boolean authenticated)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
finish(boolean strongAuth)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
reset()241     public void reset() {
242         mViewMediatorCallback.resetKeyguard();
243     }
244 
245     @Override
onSecurityModeChanged(SecurityMode securityMode, boolean needsInput)246     public void onSecurityModeChanged(SecurityMode securityMode, boolean needsInput) {
247         if (mViewMediatorCallback != null) {
248             mViewMediatorCallback.setNeedsInput(needsInput);
249         }
250     }
251 
userActivity()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      */
onPause()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      */
onResume()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      */
startAppearAnimation()281     public void startAppearAnimation() {
282         mSecurityContainer.startAppearAnimation();
283     }
284 
startDisappearAnimation(Runnable finishRunnable)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      */
cleanUp()294     public void cleanUp() {
295         getSecurityContainer().onPause();
296     }
297 
298     @Override
dispatchKeyEvent(KeyEvent event)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      */
interceptMediaKey(KeyEvent event)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 
handleMediaKeyEvent(KeyEvent keyEvent)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
dispatchSystemUiVisibilityChanged(int visibility)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";
shouldEnableMenuKey()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 
setViewMediatorCallback(ViewMediatorCallback viewMediatorCallback)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 
setLockPatternUtils(LockPatternUtils utils)430     public void setLockPatternUtils(LockPatternUtils utils) {
431         mLockPatternUtils = utils;
432         mSecurityContainer.setLockPatternUtils(utils);
433     }
434 
getSecurityMode()435     public SecurityMode getSecurityMode() {
436         return mSecurityContainer.getSecurityMode();
437     }
438 
getCurrentSecurityMode()439     public SecurityMode getCurrentSecurityMode() {
440         return mSecurityContainer.getCurrentSecurityMode();
441     }
442 }
443