| /* |
| * Copyright (C) 2012 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.internal.policy.impl.keyguard; |
| |
| import android.app.Activity; |
| import android.app.ActivityManager; |
| import android.app.ActivityOptions; |
| import android.app.AlertDialog; |
| import android.app.admin.DevicePolicyManager; |
| import android.appwidget.AppWidgetHost; |
| import android.appwidget.AppWidgetHostView; |
| import android.appwidget.AppWidgetManager; |
| import android.appwidget.AppWidgetProviderInfo; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentSender; |
| import android.content.pm.UserInfo; |
| import android.content.res.Resources; |
| import android.graphics.Canvas; |
| import android.graphics.Rect; |
| import android.os.Looper; |
| import android.os.Parcel; |
| import android.os.Parcelable; |
| import android.os.UserManager; |
| import android.util.AttributeSet; |
| import android.util.Log; |
| import android.util.Slog; |
| import android.view.KeyEvent; |
| import android.view.LayoutInflater; |
| import android.view.MotionEvent; |
| import android.view.View; |
| import android.view.WindowManager; |
| import android.view.animation.AnimationUtils; |
| import android.widget.RemoteViews.OnClickHandler; |
| import android.widget.TextView; |
| import android.widget.ViewFlipper; |
| |
| import com.android.internal.R; |
| import com.android.internal.policy.impl.keyguard.KeyguardSecurityModel.SecurityMode; |
| import com.android.internal.widget.LockPatternUtils; |
| |
| import java.io.File; |
| import java.util.List; |
| |
| public class KeyguardHostView extends KeyguardViewBase { |
| private static final String TAG = "KeyguardViewHost"; |
| |
| // Use this to debug all of keyguard |
| public static boolean DEBUG; |
| |
| // also referenced in SecuritySettings.java |
| static final int APPWIDGET_HOST_ID = 0x4B455947; |
| |
| // transport control states |
| private static final int TRANSPORT_GONE = 0; |
| private static final int TRANSPORT_INVISIBLE = 1; |
| private static final int TRANSPORT_VISIBLE = 2; |
| |
| private AppWidgetHost mAppWidgetHost; |
| private KeyguardWidgetRegion mAppWidgetRegion; |
| private KeyguardWidgetPager mAppWidgetContainer; |
| private ViewFlipper mSecurityViewContainer; |
| private KeyguardSelectorView mKeyguardSelectorView; |
| private KeyguardTransportControlView mTransportControl; |
| private boolean mEnableMenuKey; |
| private boolean mIsVerifyUnlockOnly; |
| private boolean mEnableFallback; // TODO: This should get the value from KeyguardPatternView |
| private SecurityMode mCurrentSecuritySelection = SecurityMode.Invalid; |
| |
| protected Runnable mLaunchRunnable; |
| |
| protected int mFailedAttempts; |
| private LockPatternUtils mLockPatternUtils; |
| |
| private KeyguardSecurityModel mSecurityModel; |
| |
| private Rect mTempRect = new Rect(); |
| private int mTransportState = TRANSPORT_GONE; |
| |
| /*package*/ interface TransportCallback { |
| void onListenerDetached(); |
| void onListenerAttached(); |
| void onPlayStateChanged(); |
| } |
| |
| /*package*/ interface UserSwitcherCallback { |
| void hideSecurityView(int duration); |
| void showSecurityView(); |
| void showUnlockHint(); |
| } |
| |
| public KeyguardHostView(Context context) { |
| this(context, null); |
| } |
| |
| public KeyguardHostView(Context context, AttributeSet attrs) { |
| super(context, attrs); |
| mLockPatternUtils = new LockPatternUtils(context); |
| mAppWidgetHost = new AppWidgetHost( |
| context, APPWIDGET_HOST_ID, mOnClickHandler, Looper.myLooper()); |
| mSecurityModel = new KeyguardSecurityModel(context); |
| |
| // The following enables the MENU key to work for testing automation |
| mEnableMenuKey = shouldEnableMenuKey(); |
| setFocusable(true); |
| setFocusableInTouchMode(true); |
| } |
| |
| @Override |
| public boolean onTouchEvent(MotionEvent ev) { |
| boolean result = super.onTouchEvent(ev); |
| mTempRect.set(0, 0, 0, 0); |
| offsetRectIntoDescendantCoords(mSecurityViewContainer, mTempRect); |
| ev.offsetLocation(mTempRect.left, mTempRect.top); |
| result = mSecurityViewContainer.dispatchTouchEvent(ev) || result; |
| ev.offsetLocation(-mTempRect.left, -mTempRect.top); |
| return result; |
| } |
| |
| @Override |
| protected void dispatchDraw(Canvas canvas) { |
| super.dispatchDraw(canvas); |
| if (mViewMediatorCallback != null) { |
| mViewMediatorCallback.keyguardDoneDrawing(); |
| } |
| } |
| |
| private int getWidgetPosition(int id) { |
| final int children = mAppWidgetContainer.getChildCount(); |
| for (int i = 0; i < children; i++) { |
| if (mAppWidgetContainer.getChildAt(i).getId() == id) { |
| return i; |
| } |
| } |
| return -1; |
| } |
| |
| @Override |
| protected void onFinishInflate() { |
| mAppWidgetRegion = (KeyguardWidgetRegion) findViewById(R.id.kg_widget_region); |
| mAppWidgetRegion.setVisibility(VISIBLE); |
| mAppWidgetRegion.setCallbacks(mWidgetCallbacks); |
| |
| mAppWidgetContainer = (KeyguardWidgetPager) findViewById(R.id.app_widget_container); |
| mSecurityViewContainer = (ViewFlipper) findViewById(R.id.view_flipper); |
| mKeyguardSelectorView = (KeyguardSelectorView) findViewById(R.id.keyguard_selector_view); |
| |
| addDefaultWidgets(); |
| updateSecurityViews(); |
| setSystemUiVisibility(getSystemUiVisibility() | View.STATUS_BAR_DISABLE_BACK); |
| |
| if (KeyguardUpdateMonitor.getInstance(mContext).getIsFirstBoot()) { |
| showPrimarySecurityScreen(false); |
| } |
| } |
| |
| private void updateSecurityViews() { |
| int children = mSecurityViewContainer.getChildCount(); |
| for (int i = 0; i < children; i++) { |
| updateSecurityView(mSecurityViewContainer.getChildAt(i)); |
| } |
| } |
| |
| private void updateSecurityView(View view) { |
| if (view instanceof KeyguardSecurityView) { |
| KeyguardSecurityView ksv = (KeyguardSecurityView) view; |
| ksv.setKeyguardCallback(mCallback); |
| ksv.setLockPatternUtils(mLockPatternUtils); |
| } else { |
| Log.w(TAG, "View " + view + " is not a KeyguardSecurityView"); |
| } |
| } |
| |
| void setLockPatternUtils(LockPatternUtils utils) { |
| mSecurityModel.setLockPatternUtils(utils); |
| mLockPatternUtils = utils; |
| updateSecurityViews(); |
| } |
| |
| @Override |
| protected void onAttachedToWindow() { |
| super.onAttachedToWindow(); |
| mAppWidgetHost.startListening(); |
| // TODO: Re-enable when we have layouts that can support a better variety of widgets. |
| // maybePopulateWidgets(); |
| disableStatusViewInteraction(); |
| post(mSwitchPageRunnable); |
| } |
| |
| private void disableStatusViewInteraction() { |
| // Disable all user interaction on status view. This is done to prevent falsing in the |
| // pocket from triggering useractivity and prevents 3rd party replacement widgets |
| // from responding to user interaction while in this position. |
| View statusView = findViewById(R.id.keyguard_status_view); |
| if (statusView instanceof KeyguardWidgetFrame) { |
| ((KeyguardWidgetFrame) statusView).setDisableUserInteraction(true); |
| } |
| } |
| |
| @Override |
| protected void onDetachedFromWindow() { |
| super.onDetachedFromWindow(); |
| mAppWidgetHost.stopListening(); |
| } |
| |
| private AppWidgetHost getAppWidgetHost() { |
| return mAppWidgetHost; |
| } |
| |
| void addWidget(AppWidgetHostView view) { |
| mAppWidgetContainer.addWidget(view); |
| } |
| |
| private KeyguardWidgetRegion.Callbacks mWidgetCallbacks |
| = new KeyguardWidgetRegion.Callbacks() { |
| @Override |
| public void userActivity() { |
| if (mViewMediatorCallback != null) { |
| mViewMediatorCallback.userActivity(); |
| } |
| } |
| |
| @Override |
| public void onUserActivityTimeoutChanged() { |
| if (mViewMediatorCallback != null) { |
| mViewMediatorCallback.onUserActivityTimeoutChanged(); |
| } |
| } |
| }; |
| |
| @Override |
| public long getUserActivityTimeout() { |
| // Currently only considering user activity timeouts needed by widgets. |
| // Could also take into account longer timeouts for certain security views. |
| if (mAppWidgetRegion != null) { |
| return mAppWidgetRegion.getUserActivityTimeout(); |
| } |
| return -1; |
| } |
| |
| private KeyguardSecurityCallback mCallback = new KeyguardSecurityCallback() { |
| |
| public void userActivity(long timeout) { |
| if (mViewMediatorCallback != null) { |
| mViewMediatorCallback.userActivity(timeout); |
| } |
| } |
| |
| public void dismiss(boolean authenticated) { |
| showNextSecurityScreenOrFinish(authenticated); |
| } |
| |
| public boolean isVerifyUnlockOnly() { |
| return mIsVerifyUnlockOnly; |
| } |
| |
| public void reportSuccessfulUnlockAttempt() { |
| KeyguardUpdateMonitor.getInstance(mContext).clearFailedUnlockAttempts(); |
| mLockPatternUtils.reportSuccessfulPasswordAttempt(); |
| } |
| |
| public void reportFailedUnlockAttempt() { |
| if (mCurrentSecuritySelection == SecurityMode.Biometric) { |
| KeyguardUpdateMonitor.getInstance(mContext).reportFailedBiometricUnlockAttempt(); |
| } else { |
| KeyguardHostView.this.reportFailedUnlockAttempt(); |
| } |
| } |
| |
| public int getFailedAttempts() { |
| return KeyguardUpdateMonitor.getInstance(mContext).getFailedUnlockAttempts(); |
| } |
| |
| @Override |
| public void showBackupSecurity() { |
| KeyguardHostView.this.showBackupSecurityScreen(); |
| } |
| |
| @Override |
| public void setOnDismissRunnable(Runnable runnable) { |
| KeyguardHostView.this.setOnDismissRunnable(runnable); |
| } |
| |
| }; |
| |
| private void showDialog(String title, String message) { |
| final AlertDialog dialog = new AlertDialog.Builder(mContext) |
| .setTitle(title) |
| .setMessage(message) |
| .setNeutralButton(com.android.internal.R.string.ok, null) |
| .create(); |
| if (!(mContext instanceof Activity)) { |
| dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); |
| } |
| dialog.show(); |
| } |
| |
| private void showTimeoutDialog() { |
| int timeoutInSeconds = (int) LockPatternUtils.FAILED_ATTEMPT_TIMEOUT_MS / 1000; |
| int messageId = 0; |
| |
| switch (mSecurityModel.getSecurityMode()) { |
| case Pattern: |
| messageId = R.string.kg_too_many_failed_pattern_attempts_dialog_message; |
| break; |
| |
| case Password: { |
| final boolean isPin = mLockPatternUtils.getKeyguardStoredPasswordQuality() == |
| DevicePolicyManager.PASSWORD_QUALITY_NUMERIC; |
| messageId = isPin ? R.string.kg_too_many_failed_pin_attempts_dialog_message |
| : R.string.kg_too_many_failed_password_attempts_dialog_message; |
| } |
| break; |
| } |
| |
| if (messageId != 0) { |
| final String message = mContext.getString(messageId, |
| KeyguardUpdateMonitor.getInstance(mContext).getFailedUnlockAttempts(), |
| timeoutInSeconds); |
| showDialog(null, message); |
| } |
| } |
| |
| private void showAlmostAtWipeDialog(int attempts, int remaining) { |
| int timeoutInSeconds = (int) LockPatternUtils.FAILED_ATTEMPT_TIMEOUT_MS / 1000; |
| String message = mContext.getString(R.string.kg_failed_attempts_almost_at_wipe, |
| attempts, remaining); |
| showDialog(null, message); |
| } |
| |
| private void showWipeDialog(int attempts) { |
| String message = mContext.getString(R.string.kg_failed_attempts_now_wiping, attempts); |
| showDialog(null, message); |
| } |
| |
| private void showAlmostAtAccountLoginDialog() { |
| final int timeoutInSeconds = (int) LockPatternUtils.FAILED_ATTEMPT_TIMEOUT_MS / 1000; |
| final int count = LockPatternUtils.FAILED_ATTEMPTS_BEFORE_RESET |
| - LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT; |
| String message = mContext.getString(R.string.kg_failed_attempts_almost_at_login, |
| count, LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT, timeoutInSeconds); |
| showDialog(null, message); |
| } |
| |
| private void reportFailedUnlockAttempt() { |
| final KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext); |
| final int failedAttempts = monitor.getFailedUnlockAttempts() + 1; // +1 for this time |
| |
| if (DEBUG) Log.d(TAG, "reportFailedPatternAttempt: #" + failedAttempts); |
| |
| SecurityMode mode = mSecurityModel.getSecurityMode(); |
| final boolean usingPattern = mode == KeyguardSecurityModel.SecurityMode.Pattern; |
| |
| final int failedAttemptsBeforeWipe = mLockPatternUtils.getDevicePolicyManager() |
| .getMaximumFailedPasswordsForWipe(null, mLockPatternUtils.getCurrentUser()); |
| |
| final int failedAttemptWarning = LockPatternUtils.FAILED_ATTEMPTS_BEFORE_RESET |
| - LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT; |
| |
| final int remainingBeforeWipe = failedAttemptsBeforeWipe > 0 ? |
| (failedAttemptsBeforeWipe - failedAttempts) |
| : Integer.MAX_VALUE; // because DPM returns 0 if no restriction |
| |
| boolean showTimeout = false; |
| if (remainingBeforeWipe < LockPatternUtils.FAILED_ATTEMPTS_BEFORE_WIPE_GRACE) { |
| // If we reach this code, it means the user has installed a DevicePolicyManager |
| // that requests device wipe after N attempts. Once we get below the grace |
| // period, we'll post this dialog every time as a clear warning until the |
| // bombshell hits and the device is wiped. |
| if (remainingBeforeWipe > 0) { |
| showAlmostAtWipeDialog(failedAttempts, remainingBeforeWipe); |
| } else { |
| // Too many attempts. The device will be wiped shortly. |
| Slog.i(TAG, "Too many unlock attempts; device will be wiped!"); |
| showWipeDialog(failedAttempts); |
| } |
| } else { |
| showTimeout = |
| (failedAttempts % LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT) == 0; |
| if (usingPattern && mEnableFallback) { |
| if (failedAttempts == failedAttemptWarning) { |
| showAlmostAtAccountLoginDialog(); |
| showTimeout = false; // don't show both dialogs |
| } else if (failedAttempts >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_RESET) { |
| mLockPatternUtils.setPermanentlyLocked(true); |
| showSecurityScreen(SecurityMode.Account); |
| // don't show timeout dialog because we show account unlock screen next |
| showTimeout = false; |
| } |
| } |
| } |
| monitor.reportFailedUnlockAttempt(); |
| mLockPatternUtils.reportFailedPasswordAttempt(); |
| if (showTimeout) { |
| showTimeoutDialog(); |
| } |
| } |
| |
| /** |
| * Shows the primary security screen for the user. This will be either the multi-selector |
| * or the user's security method. |
| * @param turningOff true if the device is being turned off |
| */ |
| void showPrimarySecurityScreen(boolean turningOff) { |
| SecurityMode securityMode = mSecurityModel.getSecurityMode(); |
| if (DEBUG) Log.v(TAG, "showPrimarySecurityScreen(turningOff=" + turningOff + ")"); |
| if (!turningOff && KeyguardUpdateMonitor.getInstance(mContext).isAlternateUnlockEnabled()) { |
| // If we're not turning off, then allow biometric alternate. |
| // We'll reload it when the device comes back on. |
| securityMode = mSecurityModel.getAlternateFor(securityMode); |
| } |
| showSecurityScreen(securityMode); |
| } |
| |
| /** |
| * Shows the backup security screen for the current security mode. This could be used for |
| * password recovery screens but is currently only used for pattern unlock to show the |
| * account unlock screen and biometric unlock to show the user's normal unlock. |
| */ |
| private void showBackupSecurityScreen() { |
| if (DEBUG) Log.d(TAG, "showBackupSecurity()"); |
| SecurityMode backup = mSecurityModel.getBackupSecurityMode(mCurrentSecuritySelection); |
| showSecurityScreen(backup); |
| } |
| |
| public boolean showNextSecurityScreenIfPresent() { |
| SecurityMode securityMode = mSecurityModel.getSecurityMode(); |
| // Allow an alternate, such as biometric unlock |
| securityMode = mSecurityModel.getAlternateFor(securityMode); |
| if (SecurityMode.None == securityMode) { |
| return false; |
| } else { |
| showSecurityScreen(securityMode); // switch to the alternate security view |
| return true; |
| } |
| } |
| |
| private void showNextSecurityScreenOrFinish(boolean authenticated) { |
| if (DEBUG) Log.d(TAG, "showNextSecurityScreenOrFinish(" + authenticated + ")"); |
| boolean finish = false; |
| if (SecurityMode.None == mCurrentSecuritySelection) { |
| SecurityMode securityMode = mSecurityModel.getSecurityMode(); |
| // Allow an alternate, such as biometric unlock |
| securityMode = mSecurityModel.getAlternateFor(securityMode); |
| if (SecurityMode.None == securityMode) { |
| finish = true; // no security required |
| } else { |
| showSecurityScreen(securityMode); // switch to the alternate security view |
| } |
| } else if (authenticated) { |
| switch (mCurrentSecuritySelection) { |
| case Pattern: |
| case Password: |
| case Account: |
| case Biometric: |
| finish = true; |
| break; |
| |
| case SimPin: |
| case SimPuk: |
| // Shortcut for SIM PIN/PUK to go to directly to user's security screen or home |
| SecurityMode securityMode = mSecurityModel.getSecurityMode(); |
| if (securityMode != SecurityMode.None) { |
| showSecurityScreen(securityMode); |
| } else { |
| finish = true; |
| } |
| break; |
| |
| default: |
| Log.v(TAG, "Bad security screen " + mCurrentSecuritySelection + ", fail safe"); |
| showPrimarySecurityScreen(false); |
| break; |
| } |
| } else { |
| showPrimarySecurityScreen(false); |
| } |
| if (finish) { |
| // If the alternate unlock was suppressed, it can now be safely |
| // enabled because the user has left keyguard. |
| KeyguardUpdateMonitor.getInstance(mContext).setAlternateUnlockEnabled(true); |
| KeyguardUpdateMonitor.getInstance(mContext).setIsFirstBoot(false); |
| |
| // If there's a pending runnable because the user interacted with a widget |
| // and we're leaving keyguard, then run it. |
| if (mLaunchRunnable != null) { |
| mLaunchRunnable.run(); |
| mLaunchRunnable = null; |
| } |
| if (mViewMediatorCallback != null) { |
| mViewMediatorCallback.keyguardDone(true); |
| } |
| } |
| } |
| |
| private OnClickHandler mOnClickHandler = new OnClickHandler() { |
| @Override |
| public boolean onClickHandler(final View view, |
| final android.app.PendingIntent pendingIntent, |
| final Intent fillInIntent) { |
| if (pendingIntent.isActivity()) { |
| setOnDismissRunnable(new Runnable() { |
| public void run() { |
| try { |
| // TODO: Unregister this handler if PendingIntent.FLAG_ONE_SHOT? |
| Context context = view.getContext(); |
| ActivityOptions opts = ActivityOptions.makeScaleUpAnimation(view, |
| 0, 0, |
| view.getMeasuredWidth(), view.getMeasuredHeight()); |
| context.startIntentSender( |
| pendingIntent.getIntentSender(), fillInIntent, |
| Intent.FLAG_ACTIVITY_NEW_TASK, |
| Intent.FLAG_ACTIVITY_NEW_TASK, 0, opts.toBundle()); |
| } catch (IntentSender.SendIntentException e) { |
| android.util.Log.e(TAG, "Cannot send pending intent: ", e); |
| } catch (Exception e) { |
| android.util.Log.e(TAG, "Cannot send pending intent due to " + |
| "unknown exception: ", e); |
| } |
| } |
| }); |
| |
| mCallback.dismiss(false); |
| return true; |
| } else { |
| return super.onClickHandler(view, pendingIntent, fillInIntent); |
| } |
| }; |
| }; |
| |
| private KeyguardStatusViewManager mKeyguardStatusViewManager; |
| |
| // Used to ignore callbacks from methods that are no longer current (e.g. face unlock). |
| // This avoids unwanted asynchronous events from messing with the state. |
| private KeyguardSecurityCallback mNullCallback = new KeyguardSecurityCallback() { |
| |
| @Override |
| public void userActivity(long timeout) { |
| } |
| |
| @Override |
| public void showBackupSecurity() { |
| } |
| |
| @Override |
| public void setOnDismissRunnable(Runnable runnable) { |
| } |
| |
| @Override |
| public void reportSuccessfulUnlockAttempt() { |
| } |
| |
| @Override |
| public void reportFailedUnlockAttempt() { |
| } |
| |
| @Override |
| public boolean isVerifyUnlockOnly() { |
| return false; |
| } |
| |
| @Override |
| public int getFailedAttempts() { |
| return 0; |
| } |
| |
| @Override |
| public void dismiss(boolean securityVerified) { |
| } |
| }; |
| |
| @Override |
| public void reset() { |
| mIsVerifyUnlockOnly = false; |
| mAppWidgetContainer.setCurrentPage(getWidgetPosition(R.id.keyguard_status_view)); |
| } |
| |
| /** |
| * Sets a runnable to run when keyguard is dismissed |
| * @param runnable |
| */ |
| protected void setOnDismissRunnable(Runnable runnable) { |
| mLaunchRunnable = runnable; |
| } |
| |
| private KeyguardSecurityView getSecurityView(SecurityMode securityMode) { |
| final int securityViewIdForMode = getSecurityViewIdForMode(securityMode); |
| KeyguardSecurityView view = null; |
| final int children = mSecurityViewContainer.getChildCount(); |
| for (int child = 0; child < children; child++) { |
| if (mSecurityViewContainer.getChildAt(child).getId() == securityViewIdForMode) { |
| view = ((KeyguardSecurityView)mSecurityViewContainer.getChildAt(child)); |
| break; |
| } |
| } |
| boolean simPukFullScreen = getResources().getBoolean(R.bool.kg_sim_puk_account_full_screen); |
| int layoutId = getLayoutIdFor(securityMode); |
| if (view == null && layoutId != 0) { |
| final LayoutInflater inflater = LayoutInflater.from(mContext); |
| View v = inflater.inflate(layoutId, this, false); |
| mSecurityViewContainer.addView(v); |
| updateSecurityView(v); |
| |
| view = (KeyguardSecurityView) v; |
| TextView navigationText = ((TextView) findViewById(R.id.keyguard_message_area)); |
| |
| // Some devices can fit a navigation area, others cannot. On devices that cannot, |
| // we display the security message in status area. |
| if (navigationText != null) { |
| view.setSecurityMessageDisplay(new KeyguardNavigationManager(navigationText)); |
| } else { |
| view.setSecurityMessageDisplay(mKeyguardStatusViewManager); |
| } |
| } |
| |
| if (securityMode == SecurityMode.SimPin || securityMode == SecurityMode.SimPuk || |
| securityMode == SecurityMode.Account) { |
| if (simPukFullScreen) { |
| mAppWidgetRegion.setVisibility(View.GONE); |
| } |
| } |
| |
| if (view instanceof KeyguardSelectorView) { |
| KeyguardSelectorView selectorView = (KeyguardSelectorView) view; |
| View carrierText = selectorView.findViewById(R.id.keyguard_selector_fade_container); |
| selectorView.setCarrierArea(carrierText); |
| } |
| |
| return view; |
| } |
| |
| /** |
| * Switches to the given security view unless it's already being shown, in which case |
| * this is a no-op. |
| * |
| * @param securityMode |
| */ |
| private void showSecurityScreen(SecurityMode securityMode) { |
| if (DEBUG) Log.d(TAG, "showSecurityScreen(" + securityMode + ")"); |
| |
| if (securityMode == mCurrentSecuritySelection) return; |
| |
| KeyguardSecurityView oldView = getSecurityView(mCurrentSecuritySelection); |
| KeyguardSecurityView newView = getSecurityView(securityMode); |
| |
| // Emulate Activity life cycle |
| if (oldView != null) { |
| oldView.onPause(); |
| oldView.setKeyguardCallback(mNullCallback); // ignore requests from old view |
| } |
| newView.onResume(); |
| newView.setKeyguardCallback(mCallback); |
| |
| final boolean needsInput = newView.needsInput(); |
| if (mViewMediatorCallback != null) { |
| mViewMediatorCallback.setNeedsInput(needsInput); |
| } |
| |
| // Find and show this child. |
| final int childCount = mSecurityViewContainer.getChildCount(); |
| |
| // Do flip animation to the next screen |
| if (false) { |
| mSecurityViewContainer.setInAnimation( |
| AnimationUtils.loadAnimation(mContext, R.anim.keyguard_security_animate_in)); |
| mSecurityViewContainer.setOutAnimation( |
| AnimationUtils.loadAnimation(mContext, R.anim.keyguard_security_animate_out)); |
| } |
| final int securityViewIdForMode = getSecurityViewIdForMode(securityMode); |
| for (int i = 0; i < childCount; i++) { |
| if (mSecurityViewContainer.getChildAt(i).getId() == securityViewIdForMode) { |
| mSecurityViewContainer.setDisplayedChild(i); |
| break; |
| } |
| } |
| |
| if (securityMode == SecurityMode.None) { |
| // Discard current runnable if we're switching back to the selector view |
| setOnDismissRunnable(null); |
| } |
| mCurrentSecuritySelection = securityMode; |
| } |
| |
| @Override |
| public void onScreenTurnedOn() { |
| if (DEBUG) Log.d(TAG, "screen on"); |
| showPrimarySecurityScreen(false); |
| getSecurityView(mCurrentSecuritySelection).onResume(); |
| |
| // 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(); |
| } |
| |
| @Override |
| public void onScreenTurnedOff() { |
| if (DEBUG) Log.d(TAG, "screen off"); |
| showPrimarySecurityScreen(true); |
| getSecurityView(mCurrentSecuritySelection).onPause(); |
| } |
| |
| @Override |
| public void show() { |
| onScreenTurnedOn(); |
| } |
| |
| private boolean isSecure() { |
| SecurityMode mode = mSecurityModel.getSecurityMode(); |
| switch (mode) { |
| case Pattern: |
| return mLockPatternUtils.isLockPatternEnabled(); |
| case Password: |
| return mLockPatternUtils.isLockPasswordEnabled(); |
| case SimPin: |
| case SimPuk: |
| case Account: |
| return true; |
| case None: |
| return false; |
| default: |
| throw new IllegalStateException("Unknown security mode " + mode); |
| } |
| } |
| |
| @Override |
| public void wakeWhenReadyTq(int keyCode) { |
| if (DEBUG) Log.d(TAG, "onWakeKey"); |
| if (keyCode == KeyEvent.KEYCODE_MENU && isSecure()) { |
| if (DEBUG) Log.d(TAG, "switching screens to unlock screen because wake key was MENU"); |
| showSecurityScreen(SecurityMode.None); |
| } else { |
| if (DEBUG) Log.d(TAG, "poking wake lock immediately"); |
| } |
| if (mViewMediatorCallback != null) { |
| mViewMediatorCallback.wakeUp(); |
| } |
| } |
| |
| @Override |
| public void verifyUnlock() { |
| SecurityMode securityMode = mSecurityModel.getSecurityMode(); |
| if (securityMode == KeyguardSecurityModel.SecurityMode.None) { |
| if (mViewMediatorCallback != null) { |
| mViewMediatorCallback.keyguardDone(true); |
| } |
| } else if (securityMode != KeyguardSecurityModel.SecurityMode.Pattern |
| && 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 |
| mIsVerifyUnlockOnly = true; |
| showSecurityScreen(securityMode); |
| } |
| } |
| |
| private int getSecurityViewIdForMode(SecurityMode securityMode) { |
| switch (securityMode) { |
| case None: return R.id.keyguard_selector_view; |
| case Pattern: return R.id.keyguard_pattern_view; |
| case Password: return R.id.keyguard_password_view; |
| case Biometric: return R.id.keyguard_face_unlock_view; |
| case Account: return R.id.keyguard_account_view; |
| case SimPin: return R.id.keyguard_sim_pin_view; |
| case SimPuk: return R.id.keyguard_sim_puk_view; |
| } |
| return 0; |
| } |
| |
| private int getLayoutIdFor(SecurityMode securityMode) { |
| switch (securityMode) { |
| case None: return R.layout.keyguard_selector_view; |
| case Pattern: return R.layout.keyguard_pattern_view; |
| case Password: return R.layout.keyguard_password_view; |
| case Biometric: return R.layout.keyguard_face_unlock_view; |
| case Account: return R.layout.keyguard_account_view; |
| case SimPin: return R.layout.keyguard_sim_pin_view; |
| case SimPuk: return R.layout.keyguard_sim_puk_view; |
| default: |
| return 0; |
| } |
| } |
| |
| private void addWidget(int appId) { |
| AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext); |
| AppWidgetProviderInfo appWidgetInfo = appWidgetManager.getAppWidgetInfo(appId); |
| if (appWidgetInfo != null) { |
| AppWidgetHostView view = getAppWidgetHost().createView(mContext, appId, appWidgetInfo); |
| addWidget(view); |
| } else { |
| Log.w(TAG, "AppWidgetInfo was null; not adding widget id " + appId); |
| } |
| } |
| |
| private void addDefaultWidgets() { |
| LayoutInflater inflater = LayoutInflater.from(mContext); |
| inflater.inflate(R.layout.keyguard_status_view, mAppWidgetContainer, true); |
| inflater.inflate(R.layout.keyguard_transport_control_view, this, true); |
| |
| inflateAndAddUserSelectorWidgetIfNecessary(); |
| initializeTransportControl(); |
| } |
| |
| private void initializeTransportControl() { |
| mTransportControl = |
| (KeyguardTransportControlView) findViewById(R.id.keyguard_transport_control); |
| mTransportControl.setVisibility(View.GONE); |
| |
| // This code manages showing/hiding the transport control. We keep it around and only |
| // add it to the hierarchy if it needs to be present. |
| if (mTransportControl != null) { |
| mTransportControl.setKeyguardCallback(new TransportCallback() { |
| @Override |
| public void onListenerDetached() { |
| int page = getWidgetPosition(R.id.keyguard_transport_control); |
| if (page != -1) { |
| mAppWidgetContainer.removeView(mTransportControl); |
| // XXX keep view attached so we still get show/hide events from AudioManager |
| KeyguardHostView.this.addView(mTransportControl); |
| mTransportControl.setVisibility(View.GONE); |
| mTransportState = TRANSPORT_GONE; |
| mTransportControl.post(mSwitchPageRunnable); |
| } |
| } |
| |
| @Override |
| public void onListenerAttached() { |
| if (getWidgetPosition(R.id.keyguard_transport_control) == -1) { |
| KeyguardHostView.this.removeView(mTransportControl); |
| mAppWidgetContainer.addView(mTransportControl, 0); |
| mTransportControl.setVisibility(View.VISIBLE); |
| } |
| } |
| |
| @Override |
| public void onPlayStateChanged() { |
| mTransportControl.post(mSwitchPageRunnable); |
| } |
| }); |
| } |
| |
| mKeyguardStatusViewManager = ((KeyguardStatusView) |
| findViewById(R.id.keyguard_status_view_face_palm)).getManager(); |
| |
| } |
| |
| private void maybePopulateWidgets() { |
| DevicePolicyManager dpm = |
| (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE); |
| if (dpm != null) { |
| final int currentUser = mLockPatternUtils.getCurrentUser(); |
| final int disabledFeatures = dpm.getKeyguardDisabledFeatures(null, currentUser); |
| if ((disabledFeatures & DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL) != 0) { |
| Log.v(TAG, "Keyguard widgets disabled because of device policy admin"); |
| return; |
| } |
| } |
| |
| // Replace status widget if selected by user in Settings |
| int statusWidgetId = mLockPatternUtils.getStatusWidget(); |
| if (statusWidgetId != -1) { |
| addWidget(statusWidgetId); |
| View newStatusWidget = mAppWidgetContainer.getChildAt( |
| mAppWidgetContainer.getChildCount() - 1); |
| |
| int oldStatusWidgetPosition = getWidgetPosition(R.id.keyguard_status_view); |
| mAppWidgetContainer.removeViewAt(oldStatusWidgetPosition); |
| |
| // Re-add new status widget at position of old one |
| mAppWidgetContainer.removeView(newStatusWidget); |
| newStatusWidget.setId(R.id.keyguard_status_view); |
| mAppWidgetContainer.addView(newStatusWidget, oldStatusWidgetPosition); |
| } |
| |
| // Add user-selected widget |
| final int[] widgets = mLockPatternUtils.getUserDefinedWidgets(); |
| for (int i = 0; i < widgets.length; i++) { |
| if (widgets[i] != -1) { |
| addWidget(widgets[i]); |
| } |
| } |
| } |
| |
| Runnable mSwitchPageRunnable = new Runnable() { |
| @Override |
| public void run() { |
| showAppropriateWidgetPage(); |
| } |
| }; |
| |
| static class SavedState extends BaseSavedState { |
| int transportState; |
| |
| SavedState(Parcelable superState) { |
| super(superState); |
| } |
| |
| private SavedState(Parcel in) { |
| super(in); |
| this.transportState = in.readInt(); |
| } |
| |
| @Override |
| public void writeToParcel(Parcel out, int flags) { |
| super.writeToParcel(out, flags); |
| out.writeInt(this.transportState); |
| } |
| |
| public static final Parcelable.Creator<SavedState> CREATOR |
| = new Parcelable.Creator<SavedState>() { |
| public SavedState createFromParcel(Parcel in) { |
| return new SavedState(in); |
| } |
| |
| public SavedState[] newArray(int size) { |
| return new SavedState[size]; |
| } |
| }; |
| } |
| |
| @Override |
| public Parcelable onSaveInstanceState() { |
| Parcelable superState = super.onSaveInstanceState(); |
| SavedState ss = new SavedState(superState); |
| ss.transportState = mTransportState; |
| return ss; |
| } |
| |
| @Override |
| public void onRestoreInstanceState(Parcelable state) { |
| if (!(state instanceof SavedState)) { |
| super.onRestoreInstanceState(state); |
| return; |
| } |
| SavedState ss = (SavedState) state; |
| super.onRestoreInstanceState(ss.getSuperState()); |
| mTransportState = ss.transportState; |
| post(mSwitchPageRunnable); |
| } |
| |
| private void showAppropriateWidgetPage() { |
| |
| // The following sets the priority for showing widgets. Transport should be shown if |
| // music is playing, followed by the multi-user widget if enabled, followed by the |
| // status widget. |
| final int pageToShow; |
| if (mTransportControl.isMusicPlaying() || mTransportState == TRANSPORT_VISIBLE) { |
| mTransportState = TRANSPORT_VISIBLE; |
| pageToShow = mAppWidgetContainer.indexOfChild(mTransportControl); |
| } else { |
| UserManager mUm = (UserManager) mContext.getSystemService(Context.USER_SERVICE); |
| final View multiUserView = findViewById(R.id.keyguard_multi_user_selector); |
| final int multiUserPosition = mAppWidgetContainer.indexOfChild(multiUserView); |
| if (multiUserPosition != -1 && mUm.getUsers(true).size() > 1) { |
| pageToShow = multiUserPosition; |
| } else { |
| final View statusView = findViewById(R.id.keyguard_status_view); |
| pageToShow = mAppWidgetContainer.indexOfChild(statusView); |
| } |
| if (mTransportState == TRANSPORT_VISIBLE) { |
| mTransportState = TRANSPORT_INVISIBLE; |
| } |
| } |
| mAppWidgetContainer.setCurrentPage(pageToShow); |
| } |
| |
| private void inflateAndAddUserSelectorWidgetIfNecessary() { |
| // if there are multiple users, we need to add the multi-user switcher widget to the |
| // keyguard. |
| UserManager mUm = (UserManager) mContext.getSystemService(Context.USER_SERVICE); |
| List<UserInfo> users = mUm.getUsers(true); |
| |
| if (users.size() > 1) { |
| KeyguardWidgetFrame userSwitcher = (KeyguardWidgetFrame) |
| LayoutInflater.from(mContext).inflate(R.layout.keyguard_multi_user_selector_widget, |
| mAppWidgetContainer, false); |
| |
| // add the switcher in the first position |
| mAppWidgetContainer.addView(userSwitcher, getWidgetPosition(R.id.keyguard_status_view)); |
| KeyguardMultiUserSelectorView multiUser = (KeyguardMultiUserSelectorView) |
| userSwitcher.getChildAt(0); |
| |
| UserSwitcherCallback callback = new UserSwitcherCallback() { |
| @Override |
| public void hideSecurityView(int duration) { |
| mSecurityViewContainer.animate().alpha(0).setDuration(duration); |
| } |
| |
| @Override |
| public void showSecurityView() { |
| mSecurityViewContainer.setAlpha(1.0f); |
| } |
| |
| @Override |
| public void showUnlockHint() { |
| if (mKeyguardSelectorView != null) { |
| mKeyguardSelectorView.ping(); |
| } |
| } |
| |
| }; |
| multiUser.setCallback(callback); |
| } |
| } |
| |
| @Override |
| public void cleanUp() { |
| |
| } |
| |
| /** |
| * 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( |
| com.android.internal.R.bool.config_disableMenuKeyInLockScreen); |
| final boolean isTestHarness = ActivityManager.isRunningInTestHarness(); |
| final boolean fileOverride = (new File(ENABLE_MENU_KEY_FILE)).exists(); |
| return !configDisabled || isTestHarness || fileOverride; |
| } |
| |
| @Override |
| public boolean onKeyDown(int keyCode, KeyEvent event) { |
| if (keyCode == KeyEvent.KEYCODE_MENU && mEnableMenuKey) { |
| showNextSecurityScreenOrFinish(false); |
| return true; |
| } else { |
| return super.onKeyDown(keyCode, event); |
| } |
| } |
| |
| public void goToUserSwitcher() { |
| mAppWidgetContainer.setCurrentPage(getWidgetPosition(R.id.keyguard_multi_user_selector)); |
| } |
| |
| public boolean handleBackKey() { |
| if (mCurrentSecuritySelection != SecurityMode.None) { |
| mCallback.dismiss(false); |
| return true; |
| } |
| return false; |
| } |
| |
| } |