/* * 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.content.Context; import android.content.res.ColorStateList; import android.content.res.Resources; import android.graphics.Canvas; import android.media.AudioManager; import android.os.SystemClock; import android.service.trust.TrustAgentService; import android.telephony.TelephonyManager; import android.util.AttributeSet; import android.util.Log; import android.view.KeyEvent; import android.widget.FrameLayout; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardSecurityContainer.SecurityCallback; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; import com.android.settingslib.Utils; import com.android.systemui.plugins.ActivityStarter.OnDismissAction; 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 class KeyguardHostView extends FrameLayout implements SecurityCallback { private AudioManager mAudioManager; private TelephonyManager mTelephonyManager = null; protected ViewMediatorCallback mViewMediatorCallback; protected LockPatternUtils mLockPatternUtils; private OnDismissAction mDismissAction; private Runnable mCancelAction; private final KeyguardUpdateMonitorCallback mUpdateCallback = new KeyguardUpdateMonitorCallback() { @Override public void onUserSwitchComplete(int userId) { getSecurityContainer().showPrimarySecurityScreen(false /* turning off */); } @Override public void onTrustGrantedWithFlags(int flags, int userId) { if (userId != KeyguardUpdateMonitor.getCurrentUser()) return; if (!isAttachedToWindow()) return; boolean bouncerVisible = isVisibleToUser(); boolean initiatedByUser = (flags & TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER) != 0; boolean dismissKeyguard = (flags & TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD) != 0; if (initiatedByUser || dismissKeyguard) { if (mViewMediatorCallback.isScreenOn() && (bouncerVisible || dismissKeyguard)) { if (!bouncerVisible) { // The trust agent dismissed the keyguard without the user proving // that they are present (by swiping up to show the bouncer). That's fine if // the user proved presence via some other way to the trust agent. Log.i(TAG, "TrustAgent dismissed Keyguard."); } dismiss(false /* authenticated */, userId); } else { mViewMediatorCallback.playTrustedSound(); } } } }; // 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 = false; public static final boolean DEBUG = KeyguardConstants.DEBUG; private static final String TAG = "KeyguardViewBase"; private KeyguardSecurityContainer mSecurityContainer; public KeyguardHostView(Context context) { this(context, null); } public KeyguardHostView(Context context, AttributeSet attrs) { super(context, attrs); KeyguardUpdateMonitor.getInstance(context).registerCallback(mUpdateCallback); } @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, Runnable cancelAction) { if (mCancelAction != null) { mCancelAction.run(); mCancelAction = null; } mDismissAction = action; mCancelAction = cancelAction; } public boolean hasDismissActions() { return mDismissAction != null || mCancelAction != null; } public void cancelDismissAction() { setOnDismissAction(null, null); } @Override protected void onFinishInflate() { mSecurityContainer = findViewById(R.id.keyguard_security_container); mLockPatternUtils = new LockPatternUtils(mContext); mSecurityContainer.setLockPatternUtils(mLockPatternUtils); mSecurityContainer.setSecurityCallback(this); mSecurityContainer.showPrimarySecurityScreen(false); } /** * Called when the view needs to be shown. */ public void showPrimarySecurityScreen() { if (DEBUG) Log.d(TAG, "show()"); mSecurityContainer.showPrimarySecurityScreen(false); } public KeyguardSecurityView getCurrentSecurityView() { return mSecurityContainer != null ? mSecurityContainer.getCurrentSecurityView() : null; } /** * Show a string explaining why the security view needs to be solved. * * @param reason a flag indicating which string should be shown, see * {@link KeyguardSecurityView#PROMPT_REASON_NONE}, * {@link KeyguardSecurityView#PROMPT_REASON_RESTART} and * {@link KeyguardSecurityView#PROMPT_REASON_TIMEOUT}. */ public void showPromptReason(int reason) { mSecurityContainer.showPromptReason(reason); } public void showMessage(CharSequence message, ColorStateList colorState) { mSecurityContainer.showMessage(message, colorState); } public void showErrorMessage(CharSequence message) { showMessage(message, Utils.getColorError(mContext)); } /** * Dismisses the keyguard by going to the next screen or making it gone. * @param targetUserId a user that needs to be the foreground user at the dismissal completion. * @return True if the keyguard is done. */ public boolean dismiss(int targetUserId) { return dismiss(false, targetUserId); } public boolean handleBackKey() { if (mSecurityContainer.getCurrentSecuritySelection() != SecurityMode.None) { mSecurityContainer.dismiss(false, KeyguardUpdateMonitor.getCurrentUser()); return true; } return false; } protected KeyguardSecurityContainer getSecurityContainer() { return mSecurityContainer; } @Override public boolean dismiss(boolean authenticated, int targetUserId) { return mSecurityContainer.showNextSecurityScreenOrFinish(authenticated, targetUserId); } /** * Authentication has happened and it's time to dismiss keyguard. This function * should clean up and inform KeyguardViewMediator. * * @param strongAuth whether the user has authenticated with strong authentication like * pattern, password or PIN but not by trust agents or fingerprint * @param targetUserId a user that needs to be the foreground user at the dismissal completion. */ @Override public void finish(boolean strongAuth, int targetUserId) { // 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; mCancelAction = null; } if (mViewMediatorCallback != null) { if (deferKeyguardDone) { mViewMediatorCallback.keyguardDonePending(strongAuth, targetUserId); } else { mViewMediatorCallback.keyguardDone(strongAuth, targetUserId); } } } @Override public void reset() { mViewMediatorCallback.resetKeyguard(); } @Override public void onCancelClicked() { mViewMediatorCallback.onCancelClicked(); } public void resetSecurityContainer() { mSecurityContainer.reset(); } @Override public void onSecurityModeChanged(SecurityMode securityMode, boolean needsInput) { if (mViewMediatorCallback != null) { mViewMediatorCallback.setNeedsInput(needsInput); } } public CharSequence getAccessibilityTitleForCurrentMode() { return mSecurityContainer.getTitle(); } public void userActivity() { if (mViewMediatorCallback != null) { mViewMediatorCallback.userActivity(); } } /** * Called when the Keyguard is not actively shown anymore on the screen. */ public void onPause() { if (DEBUG) Log.d(TAG, String.format("screen off, instance %s at %s", Integer.toHexString(hashCode()), SystemClock.uptimeMillis())); mSecurityContainer.showPrimarySecurityScreen(true); mSecurityContainer.onPause(); clearFocus(); } /** * Called when the Keyguard is actively shown on the screen. */ public void onResume() { if (DEBUG) Log.d(TAG, "screen on, instance " + Integer.toHexString(hashCode())); mSecurityContainer.onResume(KeyguardSecurityView.SCREEN_ON); requestFocus(); } /** * Starts the animation when the Keyguard gets shown. */ public void startAppearAnimation() { mSecurityContainer.startAppearAnimation(); } public void startDisappearAnimation(Runnable finishRunnable) { if (!mSecurityContainer.startDisappearAnimation(finishRunnable) && finishRunnable != null) { finishRunnable.run(); } } /** * Called before this view is being removed. */ public void cleanUp() { getSecurityContainer().onPause(); } @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. */ public 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.adjustSuggestedStreamVolume( keyCode == KeyEvent.KEYCODE_VOLUME_UP ? AudioManager.ADJUST_RAISE : AudioManager.ADJUST_LOWER /* direction */, AudioManager.STREAM_MUSIC /* stream */, 0 /* flags */); // 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) { synchronized (this) { if (mAudioManager == null) { mAudioManager = (AudioManager) getContext().getSystemService( Context.AUDIO_SERVICE); } } mAudioManager.dispatchMediaKeyEvent(keyEvent); } @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"; public 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 void setViewMediatorCallback(ViewMediatorCallback viewMediatorCallback) { mViewMediatorCallback = viewMediatorCallback; // Update ViewMediator with the current input method requirements mViewMediatorCallback.setNeedsInput(mSecurityContainer.needsInput()); } public void setLockPatternUtils(LockPatternUtils utils) { mLockPatternUtils = utils; mSecurityContainer.setLockPatternUtils(utils); } public SecurityMode getSecurityMode() { return mSecurityContainer.getSecurityMode(); } public SecurityMode getCurrentSecurityMode() { return mSecurityContainer.getCurrentSecurityMode(); } }