diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:32:13 -0800 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:32:13 -0800 |
commit | c5d93b3b6a0ba4831903f8e8d1664c4470cf15d7 (patch) | |
tree | 0a927a9daf4d72d6f048d2d20d769cb290091544 /phone/com/android/internal/policy/impl/LockPatternKeyguardView.java | |
parent | 461429674b80b8c0452df6f69376c6394014e6a8 (diff) | |
download | base-c5d93b3b6a0ba4831903f8e8d1664c4470cf15d7.tar.gz |
auto import from //depot/cupcake/@135843
Diffstat (limited to 'phone/com/android/internal/policy/impl/LockPatternKeyguardView.java')
-rw-r--r-- | phone/com/android/internal/policy/impl/LockPatternKeyguardView.java | 639 |
1 files changed, 639 insertions, 0 deletions
diff --git a/phone/com/android/internal/policy/impl/LockPatternKeyguardView.java b/phone/com/android/internal/policy/impl/LockPatternKeyguardView.java new file mode 100644 index 0000000..cb3131d --- /dev/null +++ b/phone/com/android/internal/policy/impl/LockPatternKeyguardView.java @@ -0,0 +1,639 @@ +/* + * 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.internal.policy.impl; + +import android.accounts.AccountsServiceConstants; +import android.accounts.IAccountsService; +import android.app.AlertDialog; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.SystemProperties; +import com.android.internal.telephony.SimCard; +import android.text.TextUtils; +import android.util.Log; +import android.view.KeyEvent; +import android.view.View; +import android.view.WindowManager; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.PixelFormat; +import android.graphics.ColorFilter; +import com.android.internal.R; +import com.android.internal.widget.LockPatternUtils; + +/** + * The host view for all of the screens of the pattern unlock screen. There are + * two {@link Mode}s of operation, lock and unlock. This will show the appropriate + * screen, and listen for callbacks via {@link com.android.internal.policy.impl.KeyguardScreenCallback + * from the current screen. + * + * This view, in turn, communicates back to {@link com.android.internal.policy.impl.KeyguardViewManager} + * via its {@link com.android.internal.policy.impl.KeyguardViewCallback}, as appropriate. + */ +public class LockPatternKeyguardView extends KeyguardViewBase { + + // intent action for launching emergency dialer activity. + static final String ACTION_EMERGENCY_DIAL = "com.android.phone.EmergencyDialer.DIAL"; + + private static final boolean DEBUG = false; + private static final String TAG = "LockPatternKeyguardView"; + + private final KeyguardUpdateMonitor mUpdateMonitor; + private final KeyguardWindowController mWindowController; + + private View mLockScreen; + private View mUnlockScreen; + + private boolean mScreenOn = false; + private boolean mHasAccount = false; // assume they don't have an account until we know better + + + /** + * The current {@link KeyguardScreen} will use this to communicate back to us. + */ + KeyguardScreenCallback mKeyguardScreenCallback; + + + private boolean mRequiresSim; + + + /** + * Either a lock screen (an informational keyguard screen), or an unlock + * screen (a means for unlocking the device) is shown at any given time. + */ + enum Mode { + LockScreen, + UnlockScreen + } + + /** + * The different types screens available for {@link Mode#UnlockScreen}. + * @see com.android.internal.policy.impl.LockPatternKeyguardView#getUnlockMode() + */ + enum UnlockMode { + + /** + * Unlock by drawing a pattern. + */ + Pattern, + + /** + * Unlock by entering a sim pin. + */ + SimPin, + + /** + * Unlock by entering an account's login and password. + */ + Account + } + + /** + * The current mode. + */ + private Mode mMode = Mode.LockScreen; + + /** + * Keeps track of what mode the current unlock screen is + */ + private UnlockMode mUnlockScreenMode; + + /** + * If true, it means we are in the process of verifying that the user + * can get past the lock screen per {@link #verifyUnlock()} + */ + private boolean mIsVerifyUnlockOnly = false; + + + /** + * Used to lookup the state of the lock pattern + */ + private final LockPatternUtils mLockPatternUtils; + + /** + * Used to fetch accounts from GLS. + */ + private ServiceConnection mServiceConnection; + + /** + * @return Whether we are stuck on the lock screen because the sim is + * missing. + */ + private boolean stuckOnLockScreenBecauseSimMissing() { + return mRequiresSim + && (!mUpdateMonitor.isDeviceProvisioned()) + && (mUpdateMonitor.getSimState() == SimCard.State.ABSENT); + } + + /** + * @param context Used to inflate, and create views. + * @param updateMonitor Knows the state of the world, and passed along to each + * screen so they can use the knowledge, and also register for callbacks + * on dynamic information. + * @param lockPatternUtils Used to look up state of lock pattern. + */ + public LockPatternKeyguardView( + Context context, + KeyguardUpdateMonitor updateMonitor, + LockPatternUtils lockPatternUtils, + KeyguardWindowController controller) { + super(context); + + asyncCheckForAccount(); + + mRequiresSim = + TextUtils.isEmpty(SystemProperties.get("keyguard.no_require_sim")); + + mUpdateMonitor = updateMonitor; + mLockPatternUtils = lockPatternUtils; + mWindowController = controller; + + mMode = getInitialMode(); + + mKeyguardScreenCallback = new KeyguardScreenCallback() { + + public void goToLockScreen() { + if (mIsVerifyUnlockOnly) { + // navigating away from unlock screen during verify mode means + // we are done and the user failed to authenticate. + mIsVerifyUnlockOnly = false; + getCallback().keyguardDone(false); + } else { + updateScreen(Mode.LockScreen); + } + } + + public void goToUnlockScreen() { + final SimCard.State simState = mUpdateMonitor.getSimState(); + if (stuckOnLockScreenBecauseSimMissing() + || (simState == SimCard.State.PUK_REQUIRED)){ + // stuck on lock screen when sim missing or puk'd + return; + } + if (!isSecure()) { + getCallback().keyguardDone(true); + } else { + updateScreen(Mode.UnlockScreen); + } + } + + public boolean isSecure() { + return LockPatternKeyguardView.this.isSecure(); + } + + public boolean isVerifyUnlockOnly() { + return mIsVerifyUnlockOnly; + } + + public void recreateMe() { + recreateScreens(); + } + + public void takeEmergencyCallAction() { + Intent intent = new Intent(ACTION_EMERGENCY_DIAL); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); + getContext().startActivity(intent); + } + + public void pokeWakelock() { + getCallback().pokeWakelock(); + } + + public void pokeWakelock(int millis) { + getCallback().pokeWakelock(millis); + } + + public void keyguardDone(boolean authenticated) { + getCallback().keyguardDone(authenticated); + } + + public void keyguardDoneDrawing() { + // irrelevant to keyguard screen, they shouldn't be calling this + } + + public void reportFailedPatternAttempt() { + mUpdateMonitor.reportFailedAttempt(); + final int failedAttempts = mUpdateMonitor.getFailedAttempts(); + if (mHasAccount && failedAttempts == + (LockPatternUtils.FAILED_ATTEMPTS_BEFORE_RESET + - LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT)) { + showAlmostAtAccountLoginDialog(); + } else if (mHasAccount + && failedAttempts >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_RESET) { + mLockPatternUtils.setPermanentlyLocked(true); + updateScreen(mMode); + } else if ((failedAttempts % LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT) + == 0) { + showTimeoutDialog(); + } + } + + public boolean doesFallbackUnlockScreenExist() { + return mHasAccount; + } + }; + + /** + * We'll get key events the current screen doesn't use. see + * {@link KeyguardViewBase#onKeyDown(int, android.view.KeyEvent)} + */ + setFocusableInTouchMode(true); + setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); + + // wall paper background + final BitmapDrawable drawable = (BitmapDrawable) context.getWallpaper(); + setBackgroundDrawable( + new FastBitmapDrawable(drawable.getBitmap())); + + // create both the lock and unlock screen so they are quickly available + // when the screen turns on + mLockScreen = createLockScreen(); + addView(mLockScreen); + final UnlockMode unlockMode = getUnlockMode(); + mUnlockScreen = createUnlockScreenFor(unlockMode); + mUnlockScreenMode = unlockMode; + addView(mUnlockScreen); + updateScreen(mMode); + } + + /** + * Asynchronously checks for at least one account. This will set mHasAccount + * to true if an account is found. + */ + private void asyncCheckForAccount() { + + mServiceConnection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, IBinder service) { + try { + IAccountsService accountsService = IAccountsService.Stub.asInterface(service); + String accounts[] = accountsService.getAccounts(); + mHasAccount = (accounts.length > 0); + } catch (RemoteException e) { + // Not much we can do here... + Log.e(TAG, "Gls died while attempting to get accounts: " + e); + } finally { + getContext().unbindService(mServiceConnection); + mServiceConnection = null; + } + } + + public void onServiceDisconnected(ComponentName className) { + // nothing to do here + } + }; + boolean status = getContext().bindService(AccountsServiceConstants.SERVICE_INTENT, + mServiceConnection, Context.BIND_AUTO_CREATE); + if (!status) Log.e(TAG, "Failed to bind to GLS while checking for account"); + } + + @Override + public void reset() { + mIsVerifyUnlockOnly = false; + updateScreen(getInitialMode()); + } + + @Override + public void onScreenTurnedOff() { + mScreenOn = false; + if (mMode == Mode.LockScreen) { + ((KeyguardScreen) mLockScreen).onPause(); + } else { + ((KeyguardScreen) mUnlockScreen).onPause(); + } + } + + @Override + public void onScreenTurnedOn() { + mScreenOn = true; + if (mMode == Mode.LockScreen) { + ((KeyguardScreen) mLockScreen).onResume(); + } else { + ((KeyguardScreen) mUnlockScreen).onResume(); + } + } + + + private void recreateScreens() { + if (mLockScreen.getVisibility() == View.VISIBLE) { + ((KeyguardScreen) mLockScreen).onPause(); + } + ((KeyguardScreen) mLockScreen).cleanUp(); + removeViewInLayout(mLockScreen); + + mLockScreen = createLockScreen(); + mLockScreen.setVisibility(View.INVISIBLE); + addView(mLockScreen); + + if (mUnlockScreen.getVisibility() == View.VISIBLE) { + ((KeyguardScreen) mUnlockScreen).onPause(); + } + ((KeyguardScreen) mUnlockScreen).cleanUp(); + removeViewInLayout(mUnlockScreen); + + final UnlockMode unlockMode = getUnlockMode(); + mUnlockScreen = createUnlockScreenFor(unlockMode); + mUnlockScreen.setVisibility(View.INVISIBLE); + mUnlockScreenMode = unlockMode; + addView(mUnlockScreen); + + updateScreen(mMode); + } + + + @Override + public void wakeWhenReadyTq(int keyCode) { + if (DEBUG) Log.d(TAG, "onWakeKey"); + if (keyCode == KeyEvent.KEYCODE_MENU && isSecure() && (mMode == Mode.LockScreen) + && (mUpdateMonitor.getSimState() != SimCard.State.PUK_REQUIRED)) { + if (DEBUG) Log.d(TAG, "switching screens to unlock screen because wake key was MENU"); + updateScreen(Mode.UnlockScreen); + getCallback().pokeWakelock(); + } else { + if (DEBUG) Log.d(TAG, "poking wake lock immediately"); + getCallback().pokeWakelock(); + } + } + + @Override + public void verifyUnlock() { + if (!isSecure()) { + // non-secure keyguard screens are successfull by default + getCallback().keyguardDone(true); + } else if (mUnlockScreenMode != UnlockMode.Pattern) { + // can only verify unlock when in pattern mode + getCallback().keyguardDone(false); + } else { + // otherwise, go to the unlock screen, see if they can verify it + mIsVerifyUnlockOnly = true; + updateScreen(Mode.UnlockScreen); + } + } + + @Override + public void cleanUp() { + ((KeyguardScreen) mLockScreen).onPause(); + ((KeyguardScreen) mLockScreen).cleanUp(); + ((KeyguardScreen) mUnlockScreen).onPause(); + ((KeyguardScreen) mUnlockScreen).cleanUp(); + } + + private boolean isSecure() { + UnlockMode unlockMode = getUnlockMode(); + if (unlockMode == UnlockMode.Pattern) { + return mLockPatternUtils.isLockPatternEnabled(); + } else if (unlockMode == UnlockMode.SimPin) { + return mUpdateMonitor.getSimState() == SimCard.State.PIN_REQUIRED + || mUpdateMonitor.getSimState() == SimCard.State.PUK_REQUIRED; + } else if (unlockMode == UnlockMode.Account) { + return true; + } else { + throw new IllegalStateException("unknown unlock mode " + unlockMode); + } + } + + private void updateScreen(final Mode mode) { + + mMode = mode; + + final View goneScreen = (mode == Mode.LockScreen) ? mUnlockScreen : mLockScreen; + final View visibleScreen = (mode == Mode.LockScreen) + ? mLockScreen : getUnlockScreenForCurrentUnlockMode(); + + // do this before changing visibility so focus isn't requested before the input + // flag is set + mWindowController.setNeedsInput(((KeyguardScreen)visibleScreen).needsInput()); + + + if (mScreenOn) { + if (goneScreen.getVisibility() == View.VISIBLE) { + ((KeyguardScreen) goneScreen).onPause(); + } + if (visibleScreen.getVisibility() != View.VISIBLE) { + ((KeyguardScreen) visibleScreen).onResume(); + } + } + + goneScreen.setVisibility(View.GONE); + visibleScreen.setVisibility(View.VISIBLE); + + + if (!visibleScreen.requestFocus()) { + throw new IllegalStateException("keyguard screen must be able to take " + + "focus when shown " + visibleScreen.getClass().getCanonicalName()); + } + } + + View createLockScreen() { + return new LockScreen( + mContext, + mLockPatternUtils, + mUpdateMonitor, + mKeyguardScreenCallback); + } + + View createUnlockScreenFor(UnlockMode unlockMode) { + if (unlockMode == UnlockMode.Pattern) { + return new UnlockScreen( + mContext, + mLockPatternUtils, + mUpdateMonitor, + mKeyguardScreenCallback, + mUpdateMonitor.getFailedAttempts()); + } else if (unlockMode == UnlockMode.SimPin) { + return new SimUnlockScreen( + mContext, + mUpdateMonitor, + mKeyguardScreenCallback); + } else if (unlockMode == UnlockMode.Account) { + try { + return new AccountUnlockScreen( + mContext, + mKeyguardScreenCallback, + mLockPatternUtils); + } catch (IllegalStateException e) { + Log.i(TAG, "Couldn't instantiate AccountUnlockScreen" + + " (IAccountsService isn't available)"); + // TODO: Need a more general way to provide a + // platform-specific fallback UI here. + // For now, if we can't display the account login + // unlock UI, just bring back the regular "Pattern" unlock mode. + + // (We do this by simply returning a regular UnlockScreen + // here. This means that the user will still see the + // regular pattern unlock UI, regardless of the value of + // mUnlockScreenMode or whether or not we're in the + // "permanently locked" state.) + return createUnlockScreenFor(UnlockMode.Pattern); + } + } else { + throw new IllegalArgumentException("unknown unlock mode " + unlockMode); + } + } + + private View getUnlockScreenForCurrentUnlockMode() { + final UnlockMode unlockMode = getUnlockMode(); + + // if a screen exists for the correct mode, we're done + if (unlockMode == mUnlockScreenMode) { + return mUnlockScreen; + } + + // remember the mode + mUnlockScreenMode = unlockMode; + + // unlock mode has changed and we have an existing old unlock screen + // to clean up + if (mScreenOn && (mUnlockScreen.getVisibility() == View.VISIBLE)) { + ((KeyguardScreen) mUnlockScreen).onPause(); + } + ((KeyguardScreen) mUnlockScreen).cleanUp(); + removeViewInLayout(mUnlockScreen); + + // create the new one + mUnlockScreen = createUnlockScreenFor(unlockMode); + mUnlockScreen.setVisibility(View.INVISIBLE); + addView(mUnlockScreen); + return mUnlockScreen; + } + + /** + * Given the current state of things, what should be the initial mode of + * the lock screen (lock or unlock). + */ + private Mode getInitialMode() { + final SimCard.State simState = mUpdateMonitor.getSimState(); + if (stuckOnLockScreenBecauseSimMissing() || (simState == SimCard.State.PUK_REQUIRED)) { + return Mode.LockScreen; + } else if (mUpdateMonitor.isKeyboardOpen() && isSecure()) { + return Mode.UnlockScreen; + } else { + return Mode.LockScreen; + } + } + + /** + * Given the current state of things, what should the unlock screen be? + */ + private UnlockMode getUnlockMode() { + final SimCard.State simState = mUpdateMonitor.getSimState(); + if (simState == SimCard.State.PIN_REQUIRED || simState == SimCard.State.PUK_REQUIRED) { + return UnlockMode.SimPin; + } else { + return mLockPatternUtils.isPermanentlyLocked() ? + UnlockMode.Account: + UnlockMode.Pattern; + } + } + + private void showTimeoutDialog() { + int timeoutInSeconds = (int) LockPatternUtils.FAILED_ATTEMPT_TIMEOUT_MS / 1000; + String message = mContext.getString( + R.string.lockscreen_too_many_failed_attempts_dialog_message, + mUpdateMonitor.getFailedAttempts(), + timeoutInSeconds); + final AlertDialog dialog = new AlertDialog.Builder(mContext) + .setTitle(null) + .setMessage(message) + .setNeutralButton(R.string.ok, null) + .create(); + dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + dialog.getWindow().setFlags( + WindowManager.LayoutParams.FLAG_BLUR_BEHIND, + WindowManager.LayoutParams.FLAG_BLUR_BEHIND); + dialog.show(); + } + + private void showAlmostAtAccountLoginDialog() { + int timeoutInSeconds = (int) LockPatternUtils.FAILED_ATTEMPT_TIMEOUT_MS / 1000; + String message = mContext.getString( + R.string.lockscreen_failed_attempts_almost_glogin, + LockPatternUtils.FAILED_ATTEMPTS_BEFORE_RESET - LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT, + LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT, + timeoutInSeconds); + final AlertDialog dialog = new AlertDialog.Builder(mContext) + .setTitle(null) + .setMessage(message) + .setNeutralButton(R.string.ok, null) + .create(); + dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + dialog.getWindow().setFlags( + WindowManager.LayoutParams.FLAG_BLUR_BEHIND, + WindowManager.LayoutParams.FLAG_BLUR_BEHIND); + dialog.show(); + } + + /** + * Used to put wallpaper on the background of the lock screen. Centers it Horizontally and + * vertically. + */ + static private class FastBitmapDrawable extends Drawable { + private Bitmap mBitmap; + + private FastBitmapDrawable(Bitmap bitmap) { + mBitmap = bitmap; + } + + @Override + public void draw(Canvas canvas) { + canvas.drawBitmap( + mBitmap, + (getBounds().width() - mBitmap.getWidth()) / 2, + (getBounds().height() - mBitmap.getHeight()) / 2, + null); + } + + @Override + public int getOpacity() { + return PixelFormat.TRANSLUCENT; + } + + @Override + public void setAlpha(int alpha) { + } + + @Override + public void setColorFilter(ColorFilter cf) { + } + + @Override + public int getIntrinsicWidth() { + return mBitmap.getWidth(); + } + + @Override + public int getIntrinsicHeight() { + return mBitmap.getHeight(); + } + + @Override + public int getMinimumWidth() { + return mBitmap.getWidth(); + } + + @Override + public int getMinimumHeight() { + return mBitmap.getHeight(); + } + } +} |