| /* |
| * Copyright (C) 2007 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.keyguard; |
| |
| import android.app.Activity; |
| import android.app.ActivityManager; |
| import android.app.ActivityOptions; |
| import android.app.SearchManager; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.res.Resources; |
| import android.graphics.Canvas; |
| import android.media.AudioManager; |
| import android.media.IAudioService; |
| import android.os.RemoteException; |
| import android.os.ServiceManager; |
| import android.os.SystemClock; |
| import android.os.UserHandle; |
| import android.telephony.TelephonyManager; |
| import android.util.AttributeSet; |
| import android.util.Log; |
| import android.util.Slog; |
| import android.view.KeyEvent; |
| import android.view.View; |
| import android.widget.FrameLayout; |
| |
| import com.android.internal.widget.LockPatternUtils; |
| import com.android.keyguard.KeyguardHostView.OnDismissAction; |
| import com.android.keyguard.KeyguardSecurityContainer.SecurityCallback; |
| import com.android.keyguard.KeyguardSecurityModel.SecurityMode; |
| |
| import java.io.File; |
| |
| /** |
| * Base class for keyguard view. {@link #reset} is where you should |
| * reset the state of your view. Use the {@link KeyguardViewCallback} via |
| * {@link #getCallback()} to send information back (such as poking the wake lock, |
| * or finishing the keyguard). |
| * |
| * Handles intercepting of media keys that still work when the keyguard is |
| * showing. |
| */ |
| public abstract class KeyguardViewBase extends FrameLayout { |
| |
| private AudioManager mAudioManager; |
| private TelephonyManager mTelephonyManager = null; |
| protected KeyguardViewMediator.ViewMediatorCallback mViewMediatorCallback; |
| protected LockPatternUtils mLockPatternUtils; |
| private OnDismissAction mDismissAction; |
| |
| // Whether the volume keys should be handled by keyguard. If true, then |
| // they will be handled here for specific media types such as music, otherwise |
| // the audio service will bring up the volume dialog. |
| private static final boolean KEYGUARD_MANAGES_VOLUME = true; |
| private static final boolean DEBUG = false; |
| private static final String TAG = "KeyguardViewBase"; |
| |
| private KeyguardSecurityContainer mSecurityContainer; |
| |
| public KeyguardViewBase(Context context) { |
| this(context, null); |
| } |
| |
| public KeyguardViewBase(Context context, AttributeSet attrs) { |
| super(context, attrs); |
| } |
| |
| @Override |
| protected void dispatchDraw(Canvas canvas) { |
| super.dispatchDraw(canvas); |
| if (mViewMediatorCallback != null) { |
| mViewMediatorCallback.keyguardDoneDrawing(); |
| } |
| } |
| |
| /** |
| * Sets an action to run when keyguard finishes. |
| * |
| * @param action |
| */ |
| public void setOnDismissAction(OnDismissAction action) { |
| mDismissAction = action; |
| } |
| |
| @Override |
| protected void onFinishInflate() { |
| mSecurityContainer = |
| (KeyguardSecurityContainer) findViewById(R.id.keyguard_security_container); |
| mSecurityContainer.setLockPatternUtils(mLockPatternUtils); |
| mSecurityContainer.setSecurityCallback(new SecurityCallback() { |
| @Override |
| public void userActivity(long timeout) { |
| KeyguardViewBase.this.userActivity(timeout); |
| } |
| |
| @Override |
| public void dismiss(boolean authenticated) { |
| KeyguardViewBase.this.dismiss(authenticated); |
| } |
| |
| @Override |
| public void finish() { |
| KeyguardViewBase.this.finish(); |
| } |
| }); |
| mSecurityContainer.showPrimarySecurityScreen(false); |
| // mSecurityContainer.updateSecurityViews(false /* not bouncing */); |
| setBackButtonEnabled(false); |
| } |
| |
| /** |
| * Called when the view needs to be shown. |
| */ |
| public void show() { |
| if (DEBUG) Log.d(TAG, "show()"); |
| mSecurityContainer.showPrimarySecurityScreen(false); |
| } |
| |
| /** |
| * Dismisses the keyguard by going to the next screen or making it gone. |
| */ |
| public void dismiss() { |
| dismiss(false); |
| } |
| |
| protected boolean dismiss(boolean authenticated) { |
| boolean finished = getSecurityContainer().showNextSecurityScreenOrFinish(authenticated); |
| if (!finished) { |
| updateAfterSecuritySelection(); |
| } |
| return finished; |
| } |
| |
| private void setBackButtonEnabled(boolean enabled) { |
| setSystemUiVisibility(enabled ? |
| getSystemUiVisibility() & ~View.STATUS_BAR_DISABLE_BACK : |
| getSystemUiVisibility() | View.STATUS_BAR_DISABLE_BACK); |
| } |
| |
| protected void showBouncer(boolean show) { |
| CharSequence what = getContext().getResources().getText( |
| show ? R.string.keyguard_accessibility_show_bouncer |
| : R.string.keyguard_accessibility_hide_bouncer); |
| announceForAccessibility(what); |
| announceCurrentSecurityMethod(); |
| } |
| |
| public boolean handleBackKey() { |
| if (mSecurityContainer.getCurrentSecuritySelection() == SecurityMode.Account) { |
| // go back to primary screen and re-disable back |
| setBackButtonEnabled(false); |
| mSecurityContainer.showPrimarySecurityScreen(false /*turningOff*/); |
| return true; |
| } |
| if (mSecurityContainer.getCurrentSecuritySelection() != SecurityMode.None) { |
| mSecurityContainer.dismiss(false); |
| return true; |
| } |
| return false; |
| } |
| |
| protected void announceCurrentSecurityMethod() { |
| mSecurityContainer.announceCurrentSecurityMethod(); |
| } |
| |
| private void updateAfterSecuritySelection() { |
| // Enable or disable the back button based on security mode |
| if (getSecurityContainer().getSecurityMode() == SecurityMode.Account |
| && !mLockPatternUtils.isPermanentlyLocked()) { |
| // we're showing account as a backup, provide a way to get back to primary |
| setBackButtonEnabled(true); |
| } |
| |
| if (mViewMediatorCallback != null) { |
| mViewMediatorCallback.setNeedsInput(getSecurityContainer().needsInput()); |
| } |
| } |
| |
| protected KeyguardSecurityContainer getSecurityContainer() { |
| return mSecurityContainer; |
| } |
| |
| /** |
| * Authentication has happened and it's time to dismiss keyguard. This function |
| * should clean up and inform KeyguardViewMediator. |
| */ |
| public void finish() { |
| // If the alternate unlock was suppressed, it can now be safely |
| // enabled because the user has left keyguard. |
| KeyguardUpdateMonitor.getInstance(mContext).setAlternateUnlockEnabled(true); |
| |
| // If there's a pending runnable because the user interacted with a widget |
| // and we're leaving keyguard, then run it. |
| boolean deferKeyguardDone = false; |
| if (mDismissAction != null) { |
| deferKeyguardDone = mDismissAction.onDismiss(); |
| mDismissAction = null; |
| } |
| if (mViewMediatorCallback != null) { |
| if (deferKeyguardDone) { |
| mViewMediatorCallback.keyguardDonePending(); |
| } else { |
| mViewMediatorCallback.keyguardDone(true); |
| } |
| } |
| } |
| |
| /** |
| * Extend display timeout |
| * @param timeout duration to delay timeout, in ms. |
| */ |
| protected void userActivity(long timeout) { |
| if (mViewMediatorCallback != null) { |
| mViewMediatorCallback.userActivity(timeout); |
| } |
| } |
| |
| protected void userActivity() { |
| if (mViewMediatorCallback != null) { |
| mViewMediatorCallback.userActivity(); |
| } |
| } |
| |
| protected void onUserActivityTimeoutChanged() { |
| if (mViewMediatorCallback != null) { |
| mViewMediatorCallback.onUserActivityTimeoutChanged(); |
| } |
| } |
| |
| /** |
| * Called when the screen turned off. |
| */ |
| protected void onScreenTurnedOff() { |
| if (DEBUG) Log.d(TAG, String.format("screen off, instance %s at %s", |
| Integer.toHexString(hashCode()), SystemClock.uptimeMillis())); |
| // Once the screen turns off, we no longer consider this to be first boot and we want the |
| // biometric unlock to start next time keyguard is shown. |
| KeyguardUpdateMonitor.getInstance(mContext).setAlternateUnlockEnabled(true); |
| mSecurityContainer.showPrimarySecurityScreen(true); |
| mSecurityContainer.onPause(); |
| clearFocus(); |
| } |
| |
| /** |
| * Called when the screen turned on. |
| */ |
| protected void onScreenTurnedOn() { |
| if (DEBUG) Log.d(TAG, "screen on, instance " + Integer.toHexString(hashCode())); |
| mSecurityContainer.showPrimarySecurityScreen(false); |
| mSecurityContainer.onResume(KeyguardSecurityView.SCREEN_ON); |
| |
| // This is a an attempt to fix bug 7137389 where the device comes back on but the entire |
| // layout is blank but forcing a layout causes it to reappear (e.g. with with |
| // hierarchyviewer). |
| requestLayout(); |
| requestFocus(); |
| } |
| |
| /** |
| * Verify that the user can get past the keyguard securely. This is called, |
| * for example, when the phone disables the keyguard but then wants to launch |
| * something else that requires secure access. |
| * |
| * The result will be propogated back via {@link KeyguardViewCallback#keyguardDone(boolean)} |
| */ |
| public void verifyUnlock() { |
| SecurityMode securityMode = getSecurityContainer().getSecurityMode(); |
| if (securityMode == KeyguardSecurityModel.SecurityMode.None) { |
| if (mViewMediatorCallback != null) { |
| mViewMediatorCallback.keyguardDone(true); |
| } |
| } else if (securityMode != KeyguardSecurityModel.SecurityMode.Pattern |
| && securityMode != KeyguardSecurityModel.SecurityMode.PIN |
| && securityMode != KeyguardSecurityModel.SecurityMode.Password) { |
| // can only verify unlock when in pattern/password mode |
| if (mViewMediatorCallback != null) { |
| mViewMediatorCallback.keyguardDone(false); |
| } |
| } else { |
| // otherwise, go to the unlock screen, see if they can verify it |
| mSecurityContainer.verifyUnlock(); |
| } |
| } |
| |
| /** |
| * Called before this view is being removed. |
| */ |
| abstract public void cleanUp(); |
| |
| /** |
| * Gets the desired user activity timeout in milliseconds, or -1 if the |
| * default should be used. |
| */ |
| abstract public long getUserActivityTimeout(); |
| |
| @Override |
| public boolean dispatchKeyEvent(KeyEvent event) { |
| if (interceptMediaKey(event)) { |
| return true; |
| } |
| return super.dispatchKeyEvent(event); |
| } |
| |
| /** |
| * Allows the media keys to work when the keyguard is showing. |
| * The media keys should be of no interest to the actual keyguard view(s), |
| * so intercepting them here should not be of any harm. |
| * @param event The key event |
| * @return whether the event was consumed as a media key. |
| */ |
| private boolean interceptMediaKey(KeyEvent event) { |
| final int keyCode = event.getKeyCode(); |
| if (event.getAction() == KeyEvent.ACTION_DOWN) { |
| switch (keyCode) { |
| case KeyEvent.KEYCODE_MEDIA_PLAY: |
| case KeyEvent.KEYCODE_MEDIA_PAUSE: |
| case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: |
| /* Suppress PLAY/PAUSE toggle when phone is ringing or |
| * in-call to avoid music playback */ |
| if (mTelephonyManager == null) { |
| mTelephonyManager = (TelephonyManager) getContext().getSystemService( |
| Context.TELEPHONY_SERVICE); |
| } |
| if (mTelephonyManager != null && |
| mTelephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE) { |
| return true; // suppress key event |
| } |
| case KeyEvent.KEYCODE_MUTE: |
| case KeyEvent.KEYCODE_HEADSETHOOK: |
| case KeyEvent.KEYCODE_MEDIA_STOP: |
| case KeyEvent.KEYCODE_MEDIA_NEXT: |
| case KeyEvent.KEYCODE_MEDIA_PREVIOUS: |
| case KeyEvent.KEYCODE_MEDIA_REWIND: |
| case KeyEvent.KEYCODE_MEDIA_RECORD: |
| case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: |
| case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: { |
| handleMediaKeyEvent(event); |
| return true; |
| } |
| |
| case KeyEvent.KEYCODE_VOLUME_UP: |
| case KeyEvent.KEYCODE_VOLUME_DOWN: |
| case KeyEvent.KEYCODE_VOLUME_MUTE: { |
| if (KEYGUARD_MANAGES_VOLUME) { |
| synchronized (this) { |
| if (mAudioManager == null) { |
| mAudioManager = (AudioManager) getContext().getSystemService( |
| Context.AUDIO_SERVICE); |
| } |
| } |
| // Volume buttons should only function for music (local or remote). |
| // TODO: Actually handle MUTE. |
| mAudioManager.adjustLocalOrRemoteStreamVolume( |
| AudioManager.STREAM_MUSIC, |
| keyCode == KeyEvent.KEYCODE_VOLUME_UP |
| ? AudioManager.ADJUST_RAISE |
| : AudioManager.ADJUST_LOWER); |
| // Don't execute default volume behavior |
| return true; |
| } else { |
| return false; |
| } |
| } |
| } |
| } else if (event.getAction() == KeyEvent.ACTION_UP) { |
| switch (keyCode) { |
| case KeyEvent.KEYCODE_MUTE: |
| case KeyEvent.KEYCODE_HEADSETHOOK: |
| case KeyEvent.KEYCODE_MEDIA_PLAY: |
| case KeyEvent.KEYCODE_MEDIA_PAUSE: |
| case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: |
| case KeyEvent.KEYCODE_MEDIA_STOP: |
| case KeyEvent.KEYCODE_MEDIA_NEXT: |
| case KeyEvent.KEYCODE_MEDIA_PREVIOUS: |
| case KeyEvent.KEYCODE_MEDIA_REWIND: |
| case KeyEvent.KEYCODE_MEDIA_RECORD: |
| case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: |
| case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: { |
| handleMediaKeyEvent(event); |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| private void handleMediaKeyEvent(KeyEvent keyEvent) { |
| IAudioService audioService = IAudioService.Stub.asInterface( |
| ServiceManager.checkService(Context.AUDIO_SERVICE)); |
| if (audioService != null) { |
| try { |
| audioService.dispatchMediaKeyEvent(keyEvent); |
| } catch (RemoteException e) { |
| Log.e("KeyguardViewBase", "dispatchMediaKeyEvent threw exception " + e); |
| } |
| } else { |
| Slog.w("KeyguardViewBase", "Unable to find IAudioService for media key event"); |
| } |
| } |
| |
| @Override |
| public void dispatchSystemUiVisibilityChanged(int visibility) { |
| super.dispatchSystemUiVisibilityChanged(visibility); |
| |
| if (!(mContext instanceof Activity)) { |
| setSystemUiVisibility(STATUS_BAR_DISABLE_BACK); |
| } |
| } |
| |
| /** |
| * In general, we enable unlocking the insecure keyguard with the menu key. However, there are |
| * some cases where we wish to disable it, notably when the menu button placement or technology |
| * is prone to false positives. |
| * |
| * @return true if the menu key should be enabled |
| */ |
| private static final String ENABLE_MENU_KEY_FILE = "/data/local/enable_menu_key"; |
| private boolean shouldEnableMenuKey() { |
| final Resources res = getResources(); |
| final boolean configDisabled = res.getBoolean(R.bool.config_disableMenuKeyInLockScreen); |
| final boolean isTestHarness = ActivityManager.isRunningInTestHarness(); |
| final boolean fileOverride = (new File(ENABLE_MENU_KEY_FILE)).exists(); |
| return !configDisabled || isTestHarness || fileOverride; |
| } |
| |
| public boolean handleMenuKey() { |
| // The following enables the MENU key to work for testing automation |
| if (shouldEnableMenuKey()) { |
| dismiss(); |
| return true; |
| } |
| return false; |
| } |
| |
| public void setViewMediatorCallback( |
| KeyguardViewMediator.ViewMediatorCallback viewMediatorCallback) { |
| mViewMediatorCallback = viewMediatorCallback; |
| } |
| |
| protected KeyguardActivityLauncher getActivityLauncher() { |
| return mActivityLauncher; |
| } |
| |
| private final KeyguardActivityLauncher mActivityLauncher = new KeyguardActivityLauncher() { |
| @Override |
| Context getContext() { |
| return mContext; |
| } |
| |
| @Override |
| void setOnDismissAction(OnDismissAction action) { |
| KeyguardViewBase.this.setOnDismissAction(action); |
| } |
| |
| @Override |
| LockPatternUtils getLockPatternUtils() { |
| return mLockPatternUtils; |
| } |
| |
| @Override |
| void requestDismissKeyguard() { |
| KeyguardViewBase.this.dismiss(false); |
| } |
| }; |
| |
| public void showAssistant() { |
| final Intent intent = ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE)) |
| .getAssistIntent(mContext, true, UserHandle.USER_CURRENT); |
| |
| if (intent == null) return; |
| |
| final ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext, |
| R.anim.keyguard_action_assist_enter, R.anim.keyguard_action_assist_exit, |
| getHandler(), null); |
| |
| intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| mActivityLauncher.launchActivityWithAnimation(intent, false, opts.toBundle(), null, null); |
| } |
| |
| public void launchCamera() { |
| mActivityLauncher.launchCamera(getHandler(), null); |
| } |
| |
| } |