diff options
Diffstat (limited to 'policy/src/com/android')
45 files changed, 8562 insertions, 32 deletions
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java index 65e8560..ef31dd6 100755 --- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java @@ -61,6 +61,8 @@ import android.provider.Settings; import com.android.internal.R; import com.android.internal.policy.PolicyManager; +import com.android.internal.policy.impl.keyguard.KeyguardViewManager; +import com.android.internal.policy.impl.keyguard.KeyguardViewMediator; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.telephony.ITelephony; import com.android.internal.widget.PointerLocationView; @@ -864,7 +866,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { mHeadless = "1".equals(SystemProperties.get("ro.config.headless", "0")); if (!mHeadless) { // don't create KeyguardViewMediator if headless - mKeyguardMediator = new KeyguardViewMediator(context, this); + mKeyguardMediator = new KeyguardViewMediator(context, null); } mHandler = new PolicyHandler(); mOrientationListener = new MyOrientationListener(mContext); diff --git a/policy/src/com/android/internal/policy/impl/BiometricSensorUnlock.java b/policy/src/com/android/internal/policy/impl/keyguard/BiometricSensorUnlock.java index f476f82..39afaa2 100644 --- a/policy/src/com/android/internal/policy/impl/BiometricSensorUnlock.java +++ b/policy/src/com/android/internal/policy/impl/keyguard/BiometricSensorUnlock.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.internal.policy.impl; +package com.android.internal.policy.impl.keyguard; import android.view.View; diff --git a/policy/src/com/android/internal/policy/impl/keyguard/FaceUnlock.java b/policy/src/com/android/internal/policy/impl/keyguard/FaceUnlock.java new file mode 100644 index 0000000..31d138b --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/keyguard/FaceUnlock.java @@ -0,0 +1,542 @@ +/* + * 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 com.android.internal.policy.IFaceLockCallback; +import com.android.internal.policy.IFaceLockInterface; +import com.android.internal.widget.LockPatternUtils; + +import android.app.admin.DevicePolicyManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.util.Log; +import android.view.View; + +public class FaceUnlock implements BiometricSensorUnlock, Handler.Callback { + + private static final boolean DEBUG = false; + private static final String TAG = "FULLockscreen"; + + private final Context mContext; + private final LockPatternUtils mLockPatternUtils; + + // TODO: is mServiceRunning needed or can we just use mIsRunning or check if mService is null? + private boolean mServiceRunning = false; + // TODO: now that the code has been restructure to do almost all operations from a handler, this + // lock may no longer be necessary. + private final Object mServiceRunningLock = new Object(); + private IFaceLockInterface mService; + private boolean mBoundToService = false; + private View mFaceUnlockView; + + private Handler mHandler; + private final int MSG_SHOW_FACE_UNLOCK_VIEW = 0; + private final int MSG_HIDE_FACE_UNLOCK_VIEW = 1; + private final int MSG_SERVICE_CONNECTED = 2; + private final int MSG_SERVICE_DISCONNECTED = 3; + private final int MSG_UNLOCK = 4; + private final int MSG_CANCEL = 5; + private final int MSG_REPORT_FAILED_ATTEMPT = 6; + private final int MSG_EXPOSE_FALLBACK = 7; + private final int MSG_POKE_WAKELOCK = 8; + + // TODO: This was added for the purpose of adhering to what the biometric interface expects + // the isRunning() function to return. However, it is probably not necessary to have both + // mRunning and mServiceRunning. I'd just rather wait to change that logic. + private volatile boolean mIsRunning = false; + + // Long enough to stay visible while the service starts + // Short enough to not have to wait long for backup if service fails to start or crashes + // The service can take a couple of seconds to start on the first try after boot + private final int SERVICE_STARTUP_VIEW_TIMEOUT = 3000; + + // So the user has a consistent amount of time when brought to the backup method from Face + // Unlock + private final int BACKUP_LOCK_TIMEOUT = 5000; + + KeyguardSecurityCallback mKeyguardScreenCallback; + + /** + * Stores some of the structures that Face Unlock will need to access and creates the handler + * will be used to execute messages on the UI thread. + */ + public FaceUnlock(Context context, KeyguardSecurityCallback keyguardScreenCallback) { + mContext = context; + mLockPatternUtils = new LockPatternUtils(context); + mKeyguardScreenCallback = keyguardScreenCallback; + mHandler = new Handler(this); + } + + /** + * Stores and displays the view that Face Unlock is allowed to draw within. + * TODO: since the layout object will eventually be shared by multiple biometric unlock + * methods, we will have to add our other views (background, cancel button) here. + */ + public void initializeView(View biometricUnlockView) { + Log.d(TAG, "initializeView()"); + mFaceUnlockView = biometricUnlockView; + } + + /** + * Indicates whether Face Unlock is currently running. + */ + public boolean isRunning() { + return mIsRunning; + } + + /** + * Sets the Face Unlock view to visible, hiding it after the specified amount of time. If + * timeoutMillis is 0, no hide is performed. Called on the UI thread. + */ + public void show(long timeoutMillis) { + if (DEBUG) Log.d(TAG, "show()"); + if (mHandler.getLooper() != Looper.myLooper()) { + Log.e(TAG, "show() called off of the UI thread"); + } + + removeDisplayMessages(); + if (mFaceUnlockView != null) { + mFaceUnlockView.setVisibility(View.VISIBLE); + } + if (timeoutMillis > 0) { + mHandler.sendEmptyMessageDelayed(MSG_HIDE_FACE_UNLOCK_VIEW, timeoutMillis); + } + } + + /** + * Hides the Face Unlock view. + */ + public void hide() { + if (DEBUG) Log.d(TAG, "hide()"); + // Remove messages to prevent a delayed show message from undo-ing the hide + removeDisplayMessages(); + mHandler.sendEmptyMessage(MSG_HIDE_FACE_UNLOCK_VIEW); + } + + /** + * Binds to the Face Unlock service. Face Unlock will be started when the bind completes. The + * Face Unlock view is displayed to hide the backup lock while the service is starting up. + * Called on the UI thread. + */ + public boolean start() { + if (DEBUG) Log.d(TAG, "start()"); + if (mHandler.getLooper() != Looper.myLooper()) { + Log.e(TAG, "start() called off of the UI thread"); + } + + if (mIsRunning) { + Log.w(TAG, "start() called when already running"); + } + + // Show Face Unlock view, but only for a little bit so lockpattern will become visible if + // Face Unlock fails to start or crashes + // This must show before bind to guarantee that Face Unlock has a place to display + show(SERVICE_STARTUP_VIEW_TIMEOUT); + if (!mBoundToService) { + Log.d(TAG, "Binding to Face Unlock service"); + mContext.bindService(new Intent(IFaceLockInterface.class.getName()), + mConnection, + Context.BIND_AUTO_CREATE, + mLockPatternUtils.getCurrentUser()); + mBoundToService = true; + } else { + Log.w(TAG, "Attempt to bind to Face Unlock when already bound"); + } + + mIsRunning = true; + return true; + } + + /** + * Stops Face Unlock and unbinds from the service. Called on the UI thread. + */ + public boolean stop() { + if (DEBUG) Log.d(TAG, "stop()"); + if (mHandler.getLooper() != Looper.myLooper()) { + Log.e(TAG, "stop() called off of the UI thread"); + } + + boolean mWasRunning = mIsRunning; + stopUi(); + + if (mBoundToService) { + if (mService != null) { + try { + mService.unregisterCallback(mFaceUnlockCallback); + } catch (RemoteException e) { + // Not much we can do + } + } + Log.d(TAG, "Unbinding from Face Unlock service"); + mContext.unbindService(mConnection); + mBoundToService = false; + } else { + // This is usually not an error when this happens. Sometimes we will tell it to + // unbind multiple times because it's called from both onWindowFocusChanged and + // onDetachedFromWindow. + if (DEBUG) Log.d(TAG, "Attempt to unbind from Face Unlock when not bound"); + } + mIsRunning = false; + return mWasRunning; + } + + /** + * Frees up resources used by Face Unlock and stops it if it is still running. + */ + public void cleanUp() { + if (DEBUG) Log.d(TAG, "cleanUp()"); + if (mService != null) { + try { + mService.unregisterCallback(mFaceUnlockCallback); + } catch (RemoteException e) { + // Not much we can do + } + stopUi(); + mService = null; + } + } + + /** + * Returns the Device Policy Manager quality for Face Unlock, which is BIOMETRIC_WEAK. + */ + public int getQuality() { + return DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK; + } + + /** + * Handles messages such that everything happens on the UI thread in a deterministic order. + * Calls from the Face Unlock service come from binder threads. Calls from lockscreen typically + * come from the UI thread. This makes sure there are no race conditions between those calls. + */ + public boolean handleMessage(Message msg) { + switch (msg.what) { + case MSG_SHOW_FACE_UNLOCK_VIEW: + handleShowFaceUnlockView(); + break; + case MSG_HIDE_FACE_UNLOCK_VIEW: + handleHideFaceUnlockView(); + break; + case MSG_SERVICE_CONNECTED: + handleServiceConnected(); + break; + case MSG_SERVICE_DISCONNECTED: + handleServiceDisconnected(); + break; + case MSG_UNLOCK: + handleUnlock(); + break; + case MSG_CANCEL: + handleCancel(); + break; + case MSG_REPORT_FAILED_ATTEMPT: + handleReportFailedAttempt(); + break; + case MSG_EXPOSE_FALLBACK: + handleExposeFallback(); + break; + case MSG_POKE_WAKELOCK: + handlePokeWakelock(msg.arg1); + break; + default: + Log.e(TAG, "Unhandled message"); + return false; + } + return true; + } + + /** + * Sets the Face Unlock view to visible, thus covering the backup lock. + */ + void handleShowFaceUnlockView() { + if (DEBUG) Log.d(TAG, "handleShowFaceUnlockView()"); + if (mFaceUnlockView != null) { + mFaceUnlockView.setVisibility(View.VISIBLE); + } else { + Log.e(TAG, "mFaceUnlockView is null in handleShowFaceUnlockView()"); + } + } + + /** + * Sets the Face Unlock view to invisible, thus exposing the backup lock. + */ + void handleHideFaceUnlockView() { + if (DEBUG) Log.d(TAG, "handleHideFaceUnlockView()"); + if (mFaceUnlockView != null) { + mFaceUnlockView.setVisibility(View.INVISIBLE); + } else { + Log.e(TAG, "mFaceUnlockView is null in handleHideFaceUnlockView()"); + } + } + + /** + * Tells the service to start its UI via an AIDL interface. Called when the + * onServiceConnected() callback is received. + */ + void handleServiceConnected() { + Log.d(TAG, "handleServiceConnected()"); + + // It is possible that an unbind has occurred in the time between the bind and when this + // function is reached. If an unbind has already occurred, proceeding on to call startUi() + // can result in a fatal error. Note that the onServiceConnected() callback is + // asynchronous, so this possibility would still exist if we executed this directly in + // onServiceConnected() rather than using a handler. + if (!mBoundToService) { + Log.d(TAG, "Dropping startUi() in handleServiceConnected() because no longer bound"); + return; + } + + try { + mService.registerCallback(mFaceUnlockCallback); + } catch (RemoteException e) { + Log.e(TAG, "Caught exception connecting to Face Unlock: " + e.toString()); + mService = null; + mBoundToService = false; + mIsRunning = false; + return; + } + + if (mFaceUnlockView != null) { + IBinder windowToken = mFaceUnlockView.getWindowToken(); + if (windowToken != null) { + // When switching between portrait and landscape view while Face Unlock is running, + // the screen will eventually go dark unless we poke the wakelock when Face Unlock + // is restarted. + mKeyguardScreenCallback.userActivity(0); + + int[] position; + position = new int[2]; + mFaceUnlockView.getLocationInWindow(position); + startUi(windowToken, position[0], position[1], mFaceUnlockView.getWidth(), + mFaceUnlockView.getHeight()); + } else { + Log.e(TAG, "windowToken is null in handleServiceConnected()"); + } + } + } + + /** + * Called when the onServiceDisconnected() callback is received. This should not happen during + * normal operation. It indicates an error has occurred. + */ + void handleServiceDisconnected() { + Log.e(TAG, "handleServiceDisconnected()"); + // TODO: this lock may no longer be needed now that everything is being called from a + // handler + synchronized (mServiceRunningLock) { + mService = null; + mServiceRunning = false; + } + mBoundToService = false; + mIsRunning = false; + } + + /** + * Stops the Face Unlock service and tells the device to grant access to the user. Shows the + * Face Unlock view to keep the backup lock covered while the device unlocks. + */ + void handleUnlock() { + if (DEBUG) Log.d(TAG, "handleUnlock()"); + removeDisplayMessages(); + if (mFaceUnlockView != null) { + mFaceUnlockView.setVisibility(View.VISIBLE); + } else { + Log.e(TAG, "mFaceUnlockView is null in handleUnlock()"); + } + stop(); + mKeyguardScreenCallback.reportSuccessfulUnlockAttempt(); + mKeyguardScreenCallback.dismiss(true); + } + + /** + * Stops the Face Unlock service and exposes the backup lock. + */ + void handleCancel() { + if (DEBUG) Log.d(TAG, "handleCancel()"); + if (mFaceUnlockView != null) { + mFaceUnlockView.setVisibility(View.INVISIBLE); + } else { + Log.e(TAG, "mFaceUnlockView is null in handleCancel()"); + } + stop(); + mKeyguardScreenCallback.userActivity(BACKUP_LOCK_TIMEOUT); + } + + /** + * Increments the number of failed Face Unlock attempts. + */ + void handleReportFailedAttempt() { + if (DEBUG) Log.d(TAG, "handleReportFailedAttempt()"); + mKeyguardScreenCallback.reportFailedUnlockAttempt(); + } + + /** + * Hides the Face Unlock view to expose the backup lock. Called when the Face Unlock service UI + * is started, indicating there is no need to continue displaying the underlying view because + * the service UI is now covering the backup lock. + */ + void handleExposeFallback() { + if (DEBUG) Log.d(TAG, "handleExposeFallback()"); + if (mFaceUnlockView != null) { + mFaceUnlockView.setVisibility(View.INVISIBLE); + } else { + Log.e(TAG, "mFaceUnlockView is null in handleExposeFallback()"); + } + } + + /** + * Pokes the wakelock to keep the screen alive and active for a specific amount of time. + */ + void handlePokeWakelock(int millis) { + mKeyguardScreenCallback.userActivity(millis); + } + + /** + * Removes show and hide messages from the message queue. Called to prevent delayed show/hide + * messages from undoing a new message. + */ + private void removeDisplayMessages() { + mHandler.removeMessages(MSG_SHOW_FACE_UNLOCK_VIEW); + mHandler.removeMessages(MSG_HIDE_FACE_UNLOCK_VIEW); + } + + /** + * Implements service connection methods. + */ + private ServiceConnection mConnection = new ServiceConnection() { + /** + * Called when the Face Unlock service connects after calling bind(). + */ + public void onServiceConnected(ComponentName className, IBinder iservice) { + Log.d(TAG, "Connected to Face Unlock service"); + mService = IFaceLockInterface.Stub.asInterface(iservice); + mHandler.sendEmptyMessage(MSG_SERVICE_CONNECTED); + } + + /** + * Called if the Face Unlock service unexpectedly disconnects. This indicates an error. + */ + public void onServiceDisconnected(ComponentName className) { + Log.e(TAG, "Unexpected disconnect from Face Unlock service"); + mHandler.sendEmptyMessage(MSG_SERVICE_DISCONNECTED); + } + }; + + /** + * Tells the Face Unlock service to start displaying its UI and start processing. + */ + private void startUi(IBinder windowToken, int x, int y, int w, int h) { + if (DEBUG) Log.d(TAG, "startUi()"); + synchronized (mServiceRunningLock) { + if (!mServiceRunning) { + Log.d(TAG, "Starting Face Unlock"); + try { + mService.startUi(windowToken, x, y, w, h, + mLockPatternUtils.isBiometricWeakLivelinessEnabled()); + } catch (RemoteException e) { + Log.e(TAG, "Caught exception starting Face Unlock: " + e.toString()); + return; + } + mServiceRunning = true; + } else { + Log.w(TAG, "startUi() attempted while running"); + } + } + } + + /** + * Tells the Face Unlock service to stop displaying its UI and stop processing. + */ + private void stopUi() { + if (DEBUG) Log.d(TAG, "stopUi()"); + // Note that attempting to stop Face Unlock when it's not running is not an issue. + // Face Unlock can return, which stops it and then we try to stop it when the + // screen is turned off. That's why we check. + synchronized (mServiceRunningLock) { + if (mServiceRunning) { + Log.d(TAG, "Stopping Face Unlock"); + try { + mService.stopUi(); + } catch (RemoteException e) { + Log.e(TAG, "Caught exception stopping Face Unlock: " + e.toString()); + } + mServiceRunning = false; + } else { + // This is usually not an error when this happens. Sometimes we will tell it to + // stop multiple times because it's called from both onWindowFocusChanged and + // onDetachedFromWindow. + if (DEBUG) Log.d(TAG, "stopUi() attempted while not running"); + } + } + } + + /** + * Implements the AIDL biometric unlock service callback interface. + */ + private final IFaceLockCallback mFaceUnlockCallback = new IFaceLockCallback.Stub() { + /** + * Called when Face Unlock wants to grant access to the user. + */ + public void unlock() { + if (DEBUG) Log.d(TAG, "unlock()"); + mHandler.sendEmptyMessage(MSG_UNLOCK); + } + + /** + * Called when Face Unlock wants to go to the backup. + */ + public void cancel() { + if (DEBUG) Log.d(TAG, "cancel()"); + mHandler.sendEmptyMessage(MSG_CANCEL); + } + + /** + * Called when Face Unlock wants to increment the number of failed attempts. + */ + public void reportFailedAttempt() { + if (DEBUG) Log.d(TAG, "reportFailedAttempt()"); + mHandler.sendEmptyMessage(MSG_REPORT_FAILED_ATTEMPT); + } + + /** + * Called when the Face Unlock service starts displaying the UI, indicating that the backup + * unlock can be exposed because the Face Unlock service is now covering the backup with its + * UI. + **/ + public void exposeFallback() { + if (DEBUG) Log.d(TAG, "exposeFallback()"); + mHandler.sendEmptyMessage(MSG_EXPOSE_FALLBACK); + } + + /** + * Called when Face Unlock wants to keep the screen alive and active for a specific amount + * of time. + */ + public void pokeWakelock(int millis) { + if (DEBUG) Log.d(TAG, "pokeWakelock() for " + millis + "ms"); + Message message = mHandler.obtainMessage(MSG_POKE_WAKELOCK, millis, -1); + mHandler.sendMessage(message); + } + + }; +} diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardAccountView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardAccountView.java new file mode 100644 index 0000000..1e73c5b --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardAccountView.java @@ -0,0 +1,317 @@ +/* + * 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.accounts.Account; +import android.accounts.AccountManager; +import android.accounts.AccountManagerCallback; +import android.accounts.AccountManagerFuture; +import android.accounts.AuthenticatorException; +import android.accounts.OperationCanceledException; +import android.app.Dialog; +import android.app.ProgressDialog; +import android.content.Context; +import android.content.Intent; +import android.graphics.Rect; +import android.os.Bundle; +import android.text.Editable; +import android.text.InputFilter; +import android.text.LoginFilter; +import android.text.TextWatcher; +import android.util.AttributeSet; +import android.view.KeyEvent; +import android.view.View; +import android.view.WindowManager; +import android.widget.Button; +import android.widget.EditText; +import android.widget.LinearLayout; + +import com.android.internal.widget.LockPatternUtils; +import com.android.internal.R; + +import java.io.IOException; + +/** + * When the user forgets their password a bunch of times, we fall back on their + * account's login/password to unlock the phone (and reset their lock pattern). + */ +public class KeyguardAccountView extends LinearLayout implements KeyguardSecurityView, + View.OnClickListener, TextWatcher { + private static final int AWAKE_POKE_MILLIS = 30000; + private static final String LOCK_PATTERN_PACKAGE = "com.android.settings"; + private static final String LOCK_PATTERN_CLASS = LOCK_PATTERN_PACKAGE + ".ChooseLockGeneric"; + + private KeyguardSecurityCallback mCallback; + private LockPatternUtils mLockPatternUtils; + private EditText mLogin; + private EditText mPassword; + private Button mOk; + public boolean mEnableFallback; + private KeyguardNavigationManager mNavigationManager; + + /** + * Shown while making asynchronous check of password. + */ + private ProgressDialog mCheckingDialog; + + public KeyguardAccountView(Context context) { + this(context, null, 0); + } + + public KeyguardAccountView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public KeyguardAccountView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + mLockPatternUtils = new LockPatternUtils(getContext()); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mNavigationManager = new KeyguardNavigationManager(this); + + mLogin = (EditText) findViewById(R.id.login); + mLogin.setFilters(new InputFilter[] { new LoginFilter.UsernameFilterGeneric() } ); + mLogin.addTextChangedListener(this); + + mPassword = (EditText) findViewById(R.id.password); + mPassword.addTextChangedListener(this); + + mOk = (Button) findViewById(R.id.ok); + mOk.setOnClickListener(this); + reset(); + } + + public void setKeyguardCallback(KeyguardSecurityCallback callback) { + mCallback = callback; + } + + public void setLockPatternUtils(LockPatternUtils utils) { + mLockPatternUtils = utils; + } + + public KeyguardSecurityCallback getCallback() { + return mCallback; + } + + + public void afterTextChanged(Editable s) { + } + + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + public void onTextChanged(CharSequence s, int start, int before, int count) { + if (mCallback != null) { + mCallback.userActivity(AWAKE_POKE_MILLIS); + } + } + + @Override + protected boolean onRequestFocusInDescendants(int direction, + Rect previouslyFocusedRect) { + // send focus to the login field + return mLogin.requestFocus(direction, previouslyFocusedRect); + } + + public boolean needsInput() { + return true; + } + + public void reset() { + // start fresh + mLogin.setText(""); + mPassword.setText(""); + mLogin.requestFocus(); + mNavigationManager.setMessage(mLockPatternUtils.isPermanentlyLocked() ? + R.string.kg_login_too_many_attempts : R.string.kg_login_instructions); + } + + /** {@inheritDoc} */ + public void cleanUp() { + if (mCheckingDialog != null) { + mCheckingDialog.hide(); + } + mCallback = null; + mLockPatternUtils = null; + } + + public void onClick(View v) { + mCallback.userActivity(0); + if (v == mOk) { + asyncCheckPassword(); + } + } + + private void postOnCheckPasswordResult(final boolean success) { + // ensure this runs on UI thread + mLogin.post(new Runnable() { + public void run() { + if (success) { + // clear out forgotten password + mLockPatternUtils.setPermanentlyLocked(false); + mLockPatternUtils.setLockPatternEnabled(false); + mLockPatternUtils.saveLockPattern(null); + + // launch the 'choose lock pattern' activity so + // the user can pick a new one if they want to + Intent intent = new Intent(); + intent.setClassName(LOCK_PATTERN_PACKAGE, LOCK_PATTERN_CLASS); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + mContext.startActivity(intent); + mCallback.reportSuccessfulUnlockAttempt(); + + // dismiss keyguard + mCallback.dismiss(true); + } else { + mNavigationManager.setMessage(R.string.kg_login_invalid_input); + mPassword.setText(""); + mCallback.reportFailedUnlockAttempt(); + } + } + }); + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + if (event.getAction() == KeyEvent.ACTION_DOWN + && event.getKeyCode() == KeyEvent.KEYCODE_BACK) { + if (mLockPatternUtils.isPermanentlyLocked()) { + mCallback.dismiss(false); + } else { + // TODO: mCallback.forgotPattern(false); + } + return true; + } + return super.dispatchKeyEvent(event); + } + + /** + * Given the string the user entered in the 'username' field, find + * the stored account that they probably intended. Prefer, in order: + * + * - an exact match for what was typed, or + * - a case-insensitive match for what was typed, or + * - if they didn't include a domain, an exact match of the username, or + * - if they didn't include a domain, a case-insensitive + * match of the username. + * + * If there is a tie for the best match, choose neither -- + * the user needs to be more specific. + * + * @return an account name from the database, or null if we can't + * find a single best match. + */ + private Account findIntendedAccount(String username) { + Account[] accounts = AccountManager.get(mContext).getAccountsByType("com.google"); + + // Try to figure out which account they meant if they + // typed only the username (and not the domain), or got + // the case wrong. + + Account bestAccount = null; + int bestScore = 0; + for (Account a: accounts) { + int score = 0; + if (username.equals(a.name)) { + score = 4; + } else if (username.equalsIgnoreCase(a.name)) { + score = 3; + } else if (username.indexOf('@') < 0) { + int i = a.name.indexOf('@'); + if (i >= 0) { + String aUsername = a.name.substring(0, i); + if (username.equals(aUsername)) { + score = 2; + } else if (username.equalsIgnoreCase(aUsername)) { + score = 1; + } + } + } + if (score > bestScore) { + bestAccount = a; + bestScore = score; + } else if (score == bestScore) { + bestAccount = null; + } + } + return bestAccount; + } + + private void asyncCheckPassword() { + mCallback.userActivity(AWAKE_POKE_MILLIS); + final String login = mLogin.getText().toString(); + final String password = mPassword.getText().toString(); + Account account = findIntendedAccount(login); + if (account == null) { + postOnCheckPasswordResult(false); + return; + } + getProgressDialog().show(); + Bundle options = new Bundle(); + options.putString(AccountManager.KEY_PASSWORD, password); + AccountManager.get(mContext).confirmCredentials(account, options, null /* activity */, + new AccountManagerCallback<Bundle>() { + public void run(AccountManagerFuture<Bundle> future) { + try { + mCallback.userActivity(AWAKE_POKE_MILLIS); + final Bundle result = future.getResult(); + final boolean verified = result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT); + postOnCheckPasswordResult(verified); + } catch (OperationCanceledException e) { + postOnCheckPasswordResult(false); + } catch (IOException e) { + postOnCheckPasswordResult(false); + } catch (AuthenticatorException e) { + postOnCheckPasswordResult(false); + } finally { + mLogin.post(new Runnable() { + public void run() { + getProgressDialog().hide(); + } + }); + } + } + }, null /* handler */); + } + + private Dialog getProgressDialog() { + if (mCheckingDialog == null) { + mCheckingDialog = new ProgressDialog(mContext); + mCheckingDialog.setMessage( + mContext.getString(R.string.kg_login_checking_password)); + mCheckingDialog.setIndeterminate(true); + mCheckingDialog.setCancelable(false); + mCheckingDialog.getWindow().setType( + WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + } + return mCheckingDialog; + } + + @Override + public void onPause() { + + } + + @Override + public void onResume() { + reset(); + } + +} + diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardFaceUnlockView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardFaceUnlockView.java new file mode 100644 index 0000000..843151b --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardFaceUnlockView.java @@ -0,0 +1,194 @@ +/* + * 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.content.Context; +import android.util.AttributeSet; +import android.widget.LinearLayout; + +import com.android.internal.widget.LockPatternUtils; + +public class KeyguardFaceUnlockView extends LinearLayout implements KeyguardSecurityView { + + // Long enough to stay visible while dialer comes up + // Short enough to not be visible if the user goes back immediately + private static final int BIOMETRIC_AREA_EMERGENCY_DIALER_TIMEOUT = 1000; + private KeyguardSecurityCallback mKeyguardSecurityCallback; + private LockPatternUtils mLockPatternUtils; + + public KeyguardFaceUnlockView(Context context) { + this(context, null); + } + + public KeyguardFaceUnlockView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + public void setKeyguardCallback(KeyguardSecurityCallback callback) { + mKeyguardSecurityCallback = callback; + } + + @Override + public void setLockPatternUtils(LockPatternUtils utils) { + mLockPatternUtils = utils; + } + + @Override + public void reset() { + + } + + @Override + public void onPause() { + + } + + @Override + public void onResume() { + + } + + @Override + public boolean needsInput() { + return false; + } + + @Override + public KeyguardSecurityCallback getCallback() { + return mKeyguardSecurityCallback; + } + + // TODO + // public void onRefreshBatteryInfo(BatteryStatus status) { + // // When someone plugs in or unplugs the device, we hide the biometric sensor area and + // // suppress its startup for the next onScreenTurnedOn(). Since plugging/unplugging + // // causes the screen to turn on, the biometric unlock would start if it wasn't + // // suppressed. + // // + // // However, if the biometric unlock is already running, we do not want to interrupt it. + // final boolean pluggedIn = status.isPluggedIn(); + // if (mBiometricUnlock != null && mPluggedIn != pluggedIn + // && !mBiometricUnlock.isRunning()) { + // mBiometricUnlock.stop(); + // mBiometricUnlock.hide(); + // mSuppressBiometricUnlock = true; + // } + // mPluggedIn = pluggedIn; + // } + + // We need to stop the biometric unlock when a phone call comes in + // @Override + // public void onPhoneStateChanged(int phoneState) { + // if (DEBUG) Log.d(TAG, "phone state: " + phoneState); + // if (phoneState == TelephonyManager.CALL_STATE_RINGING) { + // mSuppressBiometricUnlock = true; + // mBiometricUnlock.stop(); + // mBiometricUnlock.hide(); + // } + // } + + // @Override + // public void onUserSwitched(int userId) { + // if (mBiometricUnlock != null) { + // mBiometricUnlock.stop(); + // } + // mLockPatternUtils.setCurrentUser(userId); + // updateScreen(getInitialMode(), true); + // } + + // /** + // * This returns false if there is any condition that indicates that the biometric unlock should + // * not be used before the next time the unlock screen is recreated. In other words, if this + // * returns false there is no need to even construct the biometric unlock. + // */ + // private boolean useBiometricUnlock() { + // final ShowingMode unlockMode = getUnlockMode(); + // final boolean backupIsTimedOut = (mUpdateMonitor.getFailedAttempts() >= + // LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT); + // return (mLockPatternUtils.usingBiometricWeak() && + // mLockPatternUtils.isBiometricWeakInstalled() && + // !mUpdateMonitor.getMaxBiometricUnlockAttemptsReached() && + // !backupIsTimedOut && + // (unlockMode == ShowingMode.Pattern || unlockMode == ShowingMode.Password)); + // } + + // private void initializeBiometricUnlockView(View view) { + // boolean restartBiometricUnlock = false; + // + // if (mBiometricUnlock != null) { + // restartBiometricUnlock = mBiometricUnlock.stop(); + // } + // + // // Prevents biometric unlock from coming up immediately after a phone call or if there + // // is a dialog on top of lockscreen. It is only updated if the screen is off because if the + // // screen is on it's either because of an orientation change, or when it first boots. + // // In both those cases, we don't want to override the current value of + // // mSuppressBiometricUnlock and instead want to use the previous value. + // if (!mScreenOn) { + // mSuppressBiometricUnlock = + // mUpdateMonitor.getPhoneState() != TelephonyManager.CALL_STATE_IDLE + // || mHasDialog; + // } + // + // // If the biometric unlock is not being used, we don't bother constructing it. Then we can + // // simply check if it is null when deciding whether we should make calls to it. + // mBiometricUnlock = null; + // if (useBiometricUnlock()) { + // // TODO: make faceLockAreaView a more general biometricUnlockView + // // We will need to add our Face Unlock specific child views programmatically in + // // initializeView rather than having them in the XML files. + // View biometricUnlockView = view.findViewById( + // com.android.internal.R.id.faceLockAreaView); + // if (biometricUnlockView != null) { + // mBiometricUnlock = new FaceUnlock(mContext, mUpdateMonitor, mLockPatternUtils, + // mKeyguardScreenCallback); + // mBiometricUnlock.initializeView(biometricUnlockView); + // + // // If this is being called because the screen turned off, we want to cover the + // // backup lock so it is covered when the screen turns back on. + // if (!mScreenOn) mBiometricUnlock.show(0); + // } else { + // Log.w(TAG, "Couldn't find biometric unlock view"); + // } + // } + // + // if (mBiometricUnlock != null && restartBiometricUnlock) { + // maybeStartBiometricUnlock(); + // } + // } + + // /** + // * Starts the biometric unlock if it should be started based on a number of factors including + // * the mSuppressBiometricUnlock flag. If it should not be started, it hides the biometric + // * unlock area. + // */ + // private void maybeStartBiometricUnlock() { + // if (mBiometricUnlock != null) { + // final boolean backupIsTimedOut = (mUpdateMonitor.getFailedAttempts() >= + // LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT); + // if (!mSuppressBiometricUnlock + // && mUpdateMonitor.getPhoneState() == TelephonyManager.CALL_STATE_IDLE + // && !mUpdateMonitor.getMaxBiometricUnlockAttemptsReached() + // && !backupIsTimedOut) { + // mBiometricUnlock.start(); + // } else { + // mBiometricUnlock.hide(); + // } + // } + //} + +} diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java new file mode 100644 index 0000000..cd9a800 --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java @@ -0,0 +1,443 @@ +/* + * 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.ActivityOptions; +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.SharedPreferences; +import android.content.SharedPreferences.Editor; +import android.content.res.Configuration; +import android.graphics.Canvas; +import android.telephony.TelephonyManager; +import android.util.AttributeSet; +import android.util.Log; +import android.view.KeyEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.AnimationUtils; +import android.widget.Button; +import android.widget.ViewFlipper; +import android.widget.RemoteViews.OnClickHandler; + +import com.android.internal.policy.impl.keyguard.KeyguardSecurityModel.SecurityMode; +import com.android.internal.widget.LockPatternUtils; +import com.android.internal.R; + +import java.util.ArrayList; + +public class KeyguardHostView extends KeyguardViewBase { + // Use this to debug all of keyguard + public static boolean DEBUG; + + static final int APPWIDGET_HOST_ID = 0x4B455947; + private static final String KEYGUARD_WIDGET_PREFS = "keyguard_widget_prefs"; + + // time after launching EmergencyDialer before the screen goes blank. + private static final int EMERGENCY_CALL_TIMEOUT = 10000; + + // intent action for launching emergency dialer activity. + static final String ACTION_EMERGENCY_DIAL = "com.android.phone.EmergencyDialer.DIAL"; + + private static final String TAG = "KeyguardViewHost"; + + private static final int SECURITY_SELECTOR_ID = R.id.keyguard_selector_view; + private static final int SECURITY_PATTERN_ID = R.id.keyguard_pattern_view; + private static final int SECURITY_PASSWORD_ID = R.id.keyguard_password_view; + private static final int SECURITY_BIOMETRIC_ID = R.id.keyguard_face_unlock_view; + private static final int SECURITY_SIM_PIN_ID = R.id.keyguard_sim_pin_view; + private static final int SECURITY_SIM_PUK_ID = R.id.keyguard_sim_puk_view; + private static final int SECURITY_ACCOUNT_ID = R.id.keyguard_account_view; + + private AppWidgetHost mAppWidgetHost; + private ViewGroup mAppWidgetContainer; + private ViewFlipper mViewFlipper; + private Button mEmergencyDialerButton; + + private boolean mScreenOn; + private boolean mIsVerifyUnlockOnly; + private int mCurrentSecurityId = SECURITY_SELECTOR_ID; + + // KeyguardSecurityViews + final private int [] mViewIds = { + SECURITY_SELECTOR_ID, + SECURITY_PATTERN_ID, + SECURITY_PASSWORD_ID, + SECURITY_BIOMETRIC_ID, + SECURITY_SIM_PIN_ID, + SECURITY_SIM_PUK_ID, + SECURITY_ACCOUNT_ID, + }; + + private ArrayList<View> mViews = new ArrayList<View>(mViewIds.length); + + protected Runnable mLaunchRunnable; + + protected int mFailedAttempts; + private LockPatternUtils mLockPatternUtils; + + private KeyguardSecurityModel mSecurityModel; + + public KeyguardHostView(Context context) { + this(context, null); + } + + public KeyguardHostView(Context context, AttributeSet attrs) { + super(context, attrs); + mAppWidgetHost = new AppWidgetHost(mContext, APPWIDGET_HOST_ID, mOnClickHandler); + mSecurityModel = new KeyguardSecurityModel(mContext); + } + + @Override + protected void dispatchDraw(Canvas canvas) { + super.dispatchDraw(canvas); + mCallback.keyguardDoneDrawing(); + } + + @Override + protected void onFinishInflate() { + mAppWidgetContainer = (ViewGroup) findViewById(R.id.app_widget_container); + mAppWidgetContainer.setVisibility(VISIBLE); + + // View Flipper + mViewFlipper = (ViewFlipper) findViewById(R.id.view_flipper); + mViewFlipper.setInAnimation(AnimationUtils.loadAnimation(mContext, + R.anim.keyguard_security_animate_in)); + mViewFlipper.setOutAnimation(AnimationUtils.loadAnimation(mContext, + R.anim.keyguard_security_animate_out)); + + // Initialize all security views + for (int i = 0; i < mViewIds.length; i++) { + View view = findViewById(mViewIds[i]); + mViews.add(view); + if (view != null) { + ((KeyguardSecurityView) view).setKeyguardCallback(mCallback); + } else { + Log.v("*********", "Can't find view id " + mViewIds[i]); + } + } + + // Enable emergency dialer button + mEmergencyDialerButton = (Button) findViewById(R.id.emergency_call_button); + mEmergencyDialerButton.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + takeEmergencyCallAction(); + } + }); + } + + void setLockPatternUtils(LockPatternUtils utils) { + mSecurityModel.setLockPatternUtils(utils); + mLockPatternUtils = utils; + for (int i = 0; i < mViews.size(); i++) { + KeyguardSecurityView ksv = (KeyguardSecurityView) mViews.get(i); + if (ksv != null) { + ksv.setLockPatternUtils(utils); + } else { + Log.w(TAG, "**** ksv was null at " + i); + } + } + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + mAppWidgetHost.startListening(); + populateWidgets(); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + mAppWidgetHost.stopListening(); + } + + AppWidgetHost getAppWidgetHost() { + return mAppWidgetHost; + } + + void addWidget(AppWidgetHostView view) { + mAppWidgetContainer.addView(view); + } + + private KeyguardSecurityCallback mCallback = new KeyguardSecurityCallback() { + + public void userActivity(long timeout) { + mViewMediatorCallback.pokeWakelock(timeout); + } + + public void dismiss(boolean authenticated) { + showNextSecurityScreenOrFinish(authenticated); + } + + public boolean isVerifyUnlockOnly() { + // TODO + return false; + } + + public void reportSuccessfulUnlockAttempt() { + KeyguardUpdateMonitor.getInstance(mContext).clearFailedAttempts(); + } + + public void reportFailedUnlockAttempt() { + // TODO: handle biometric attempt differently. + KeyguardUpdateMonitor.getInstance(mContext).reportFailedAttempt(); + } + + public int getFailedAttempts() { + return KeyguardUpdateMonitor.getInstance(mContext).getFailedAttempts(); + } + + public void showBackupUnlock() { + // TODO + } + + public void keyguardDoneDrawing() { + mViewMediatorCallback.keyguardDoneDrawing(); + } + + }; + + public void takeEmergencyCallAction() { + mCallback.userActivity(EMERGENCY_CALL_TIMEOUT); + if (TelephonyManager.getDefault().getCallState() + == TelephonyManager.CALL_STATE_OFFHOOK) { + mLockPatternUtils.resumeCall(); + } else { + Intent intent = new Intent(ACTION_EMERGENCY_DIAL); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); + getContext().startActivity(intent); + } + } + + protected void showNextSecurityScreenOrFinish(boolean authenticated) { + boolean finish = false; + if (SECURITY_SELECTOR_ID == mCurrentSecurityId) { + int realSecurityId = getSecurityViewIdForMode(mSecurityModel.getSecurityMode()); + if (realSecurityId == mCurrentSecurityId) { + finish = true; // no security required + } else { + showSecurityScreen(realSecurityId); // switch to the "real" security view + } + } else if (authenticated) { + if ((mCurrentSecurityId == SECURITY_PATTERN_ID + || mCurrentSecurityId == SECURITY_PASSWORD_ID + || mCurrentSecurityId == SECURITY_ACCOUNT_ID)) { + finish = true; + } + } else { + // Not authenticated but we were asked to dismiss so go back to selector screen. + showSecurityScreen(SECURITY_SELECTOR_ID); + } + if (finish) { + // 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(); + mViewFlipper.setDisplayedChild(0); + mLaunchRunnable = 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()) { + mLaunchRunnable = 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); + } + }; + }; + + @Override + public void reset() { + + } + + private KeyguardSecurityView getSecurityView(int securitySelectorId) { + final int children = mViewFlipper.getChildCount(); + for (int child = 0; child < children; child++) { + if (mViewFlipper.getChildAt(child).getId() == securitySelectorId) { + return ((KeyguardSecurityView)mViewFlipper.getChildAt(child)); + } + } + return null; + } + + private void showSecurityScreen(int securityViewId) { + + if (securityViewId == mCurrentSecurityId) return; + + KeyguardSecurityView oldView = getSecurityView(mCurrentSecurityId); + KeyguardSecurityView newView = getSecurityView(securityViewId); + + // Emulate Activity life cycle + oldView.onPause(); + newView.onResume(); + + mViewMediatorCallback.setNeedsInput(newView.needsInput()); + mCurrentSecurityId = securityViewId; + + // Find and show this child. + final int childCount = mViewFlipper.getChildCount(); + for (int i = 0; i < childCount; i++) { + if (securityViewId == mViewFlipper.getChildAt(i).getId()) { + mViewFlipper.setDisplayedChild(i); + break; + } + } + } + + @Override + public void onScreenTurnedOn() { + if (DEBUG) Log.d(TAG, "screen on"); + mScreenOn = true; + showSecurityScreen(mCurrentSecurityId); + } + + @Override + public void onScreenTurnedOff() { + if (DEBUG) Log.d(TAG, "screen off"); + mScreenOn = false; + showSecurityScreen(SECURITY_SELECTOR_ID); + } + + @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(SECURITY_SELECTOR_ID); + mViewMediatorCallback.pokeWakelock(); + } else { + if (DEBUG) Log.d(TAG, "poking wake lock immediately"); + mViewMediatorCallback.pokeWakelock(); + } + } + + @Override + public void verifyUnlock() { + SecurityMode securityMode = mSecurityModel.getSecurityMode(); + if (securityMode == KeyguardSecurityModel.SecurityMode.None) { + mViewMediatorCallback.keyguardDone(true); + } else if (securityMode != KeyguardSecurityModel.SecurityMode.Pattern + && securityMode != KeyguardSecurityModel.SecurityMode.Password) { + // can only verify unlock when in pattern/password mode + mViewMediatorCallback.keyguardDone(false); + } else { + // otherwise, go to the unlock screen, see if they can verify it + mIsVerifyUnlockOnly = true; + showSecurityScreen(getSecurityViewIdForMode(securityMode)); + } + } + + private int getSecurityViewIdForMode(SecurityMode securityMode) { + switch (securityMode) { + case None: return SECURITY_SELECTOR_ID; + case Pattern: return SECURITY_PATTERN_ID; + case Password: return SECURITY_PASSWORD_ID; + case Biometric: return SECURITY_BIOMETRIC_ID; + case Account: return SECURITY_ACCOUNT_ID; + case SimPin: return SECURITY_SIM_PIN_ID; + case SimPuk: return SECURITY_SIM_PUK_ID; + } + return 0; + } + + private void addWidget(int appId) { + AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext); + AppWidgetProviderInfo appWidgetInfo = appWidgetManager.getAppWidgetInfo(appId); + AppWidgetHostView view = getAppWidgetHost().createView(mContext, appId, appWidgetInfo); + addWidget(view); + } + + private void populateWidgets() { + SharedPreferences prefs = mContext.getSharedPreferences( + KEYGUARD_WIDGET_PREFS, Context.MODE_PRIVATE); + for (String key : prefs.getAll().keySet()) { + int appId = prefs.getInt(key, -1); + if (appId != -1) { + Log.w(TAG, "populate: adding " + key); + addWidget(appId); + } else { + Log.w(TAG, "populate: can't find " + key); + } + } + } + + @Override + public void cleanUp() { + + } + +} diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardNavigationManager.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardNavigationManager.java new file mode 100644 index 0000000..d3feced --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardNavigationManager.java @@ -0,0 +1,59 @@ +/* + * 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.view.View; +import android.widget.TextView; + +import com.android.internal.R; + +public class KeyguardNavigationManager { + + private TextView mMessageArea; + private KeyguardSecurityView mKeyguardSecurityView; + + public KeyguardNavigationManager(KeyguardSecurityView view) { + mKeyguardSecurityView = view; + mMessageArea = (TextView) ((View) view).findViewById(R.id.message_area); + mMessageArea.setSelected(true); // Make marquee work + mMessageArea.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + mKeyguardSecurityView.getCallback().dismiss(false); + } + }); + } + + public void setMessage(CharSequence msg) { + mMessageArea.setText(msg); + } + + public void setMessage(int resId) { + if (resId != 0) { + mMessageArea.setText(resId); + } else { + mMessageArea.setText(""); + } + } + + public void setMessage(int resId, Object... formatArgs) { + if (resId != 0) { + mMessageArea.setText(mMessageArea.getContext().getString(resId, formatArgs)); + } else { + mMessageArea.setText(""); + } + } +} diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardPasswordView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardPasswordView.java new file mode 100644 index 0000000..6938561 --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardPasswordView.java @@ -0,0 +1,340 @@ +/* + * 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.content.Context; +import android.util.AttributeSet; +import android.view.View; + +import com.android.internal.R; +import com.android.internal.widget.LockPatternUtils; +import java.util.List; + +import android.app.admin.DevicePolicyManager; +import android.content.res.Configuration; +import android.graphics.Rect; + +import com.android.internal.widget.PasswordEntryKeyboardView; + +import android.os.CountDownTimer; +import android.os.SystemClock; +import android.security.KeyStore; +import android.text.Editable; +import android.text.InputType; +import android.text.TextWatcher; +import android.text.method.DigitsKeyListener; +import android.text.method.TextKeyListener; +import android.view.KeyEvent; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodInfo; +import android.view.inputmethod.InputMethodManager; +import android.view.inputmethod.InputMethodSubtype; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.widget.TextView.OnEditorActionListener; + +import com.android.internal.widget.PasswordEntryKeyboardHelper; +/** + * Displays a dialer-like interface or alphanumeric (latin-1) key entry for the user to enter + * an unlock password + */ + +public class KeyguardPasswordView extends LinearLayout + implements KeyguardSecurityView, OnEditorActionListener { + private KeyguardSecurityCallback mCallback; + private EditText mPasswordEntry; + private LockPatternUtils mLockPatternUtils; + private PasswordEntryKeyboardView mKeyboardView; + private PasswordEntryKeyboardHelper mKeyboardHelper; + private boolean mIsAlpha; + private KeyguardNavigationManager mNavigationManager; + + // To avoid accidental lockout due to events while the device in in the pocket, ignore + // any passwords with length less than or equal to this length. + private static final int MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT = 3; + + public KeyguardPasswordView(Context context) { + super(context); + } + + public KeyguardPasswordView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public void setKeyguardCallback(KeyguardSecurityCallback callback) { + mCallback = callback; + } + + public void setLockPatternUtils(LockPatternUtils utils) { + mLockPatternUtils = utils; + } + + public void reset() { + // start fresh + mPasswordEntry.setText(""); + mPasswordEntry.requestFocus(); + + // if the user is currently locked out, enforce it. + long deadline = mLockPatternUtils.getLockoutAttemptDeadline(); + if (deadline != 0) { + handleAttemptLockout(deadline); + } else { + mNavigationManager.setMessage( + mIsAlpha ? R.string.kg_password_instructions : R.string.kg_pin_instructions); + } + } + + @Override + protected void onFinishInflate() { + mLockPatternUtils = new LockPatternUtils(mContext); // TODO: use common one + + mNavigationManager = new KeyguardNavigationManager(this); + + final int quality = mLockPatternUtils.getKeyguardStoredPasswordQuality(); + mIsAlpha = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC == quality + || DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC == quality + || DevicePolicyManager.PASSWORD_QUALITY_COMPLEX == quality; + + mKeyboardView = (PasswordEntryKeyboardView) findViewById(R.id.keyboard); + mPasswordEntry = (EditText) findViewById(R.id.passwordEntry); + mPasswordEntry.setOnEditorActionListener(this); + + mKeyboardHelper = new PasswordEntryKeyboardHelper(mContext, mKeyboardView, this, false); + mKeyboardHelper.setEnableHaptics(mLockPatternUtils.isTactileFeedbackEnabled()); + + boolean imeOrDeleteButtonVisible = false; + if (mIsAlpha) { + // We always use the system IME for alpha keyboard, so hide lockscreen's soft keyboard + mKeyboardHelper.setKeyboardMode(PasswordEntryKeyboardHelper.KEYBOARD_MODE_ALPHA); + mKeyboardView.setVisibility(View.GONE); + } else { + // Use lockscreen's numeric keyboard if the physical keyboard isn't showing + mKeyboardHelper.setKeyboardMode(PasswordEntryKeyboardHelper.KEYBOARD_MODE_NUMERIC); + mKeyboardView.setVisibility(getResources().getConfiguration().hardKeyboardHidden + == Configuration.HARDKEYBOARDHIDDEN_NO ? View.INVISIBLE : View.VISIBLE); + + // The delete button is of the PIN keyboard itself in some (e.g. tablet) layouts, + // not a separate view + View pinDelete = findViewById(R.id.delete_button); + if (pinDelete != null) { + pinDelete.setVisibility(View.VISIBLE); + imeOrDeleteButtonVisible = true; + pinDelete.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + mKeyboardHelper.handleBackspace(); + } + }); + } + } + + mPasswordEntry.requestFocus(); + + // This allows keyboards with overlapping qwerty/numeric keys to choose just numeric keys. + if (mIsAlpha) { + mPasswordEntry.setKeyListener(TextKeyListener.getInstance()); + mPasswordEntry.setInputType(InputType.TYPE_CLASS_TEXT + | InputType.TYPE_TEXT_VARIATION_PASSWORD); + } else { + mPasswordEntry.setKeyListener(DigitsKeyListener.getInstance()); + mPasswordEntry.setInputType(InputType.TYPE_CLASS_NUMBER + | InputType.TYPE_NUMBER_VARIATION_PASSWORD); + } + + // Poke the wakelock any time the text is selected or modified + mPasswordEntry.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + mCallback.userActivity(0); // TODO: customize timeout for text? + } + }); + + mPasswordEntry.addTextChangedListener(new TextWatcher() { + public void onTextChanged(CharSequence s, int start, int before, int count) { + } + + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + public void afterTextChanged(Editable s) { + mCallback.userActivity(0); + } + }); + + // If there's more than one IME, enable the IME switcher button + View switchImeButton = findViewById(R.id.switch_ime_button); + final InputMethodManager imm = (InputMethodManager) getContext().getSystemService( + Context.INPUT_METHOD_SERVICE); + if (mIsAlpha && switchImeButton != null && hasMultipleEnabledIMEsOrSubtypes(imm, false)) { + switchImeButton.setVisibility(View.VISIBLE); + imeOrDeleteButtonVisible = true; + switchImeButton.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + mCallback.userActivity(0); // Leave the screen on a bit longer + imm.showInputMethodPicker(); + } + }); + } + + // If no icon is visible, reset the left margin on the password field so the text is + // still centered. + if (!imeOrDeleteButtonVisible) { + android.view.ViewGroup.LayoutParams params = mPasswordEntry.getLayoutParams(); + if (params instanceof MarginLayoutParams) { + ((MarginLayoutParams)params).leftMargin = 0; + mPasswordEntry.setLayoutParams(params); + } + } + } + + /** + * Method adapted from com.android.inputmethod.latin.Utils + * + * @param imm The input method manager + * @param shouldIncludeAuxiliarySubtypes + * @return true if we have multiple IMEs to choose from + */ + private boolean hasMultipleEnabledIMEsOrSubtypes(InputMethodManager imm, + final boolean shouldIncludeAuxiliarySubtypes) { + final List<InputMethodInfo> enabledImis = imm.getEnabledInputMethodList(); + + // Number of the filtered IMEs + int filteredImisCount = 0; + + for (InputMethodInfo imi : enabledImis) { + // We can return true immediately after we find two or more filtered IMEs. + if (filteredImisCount > 1) return true; + final List<InputMethodSubtype> subtypes = + imm.getEnabledInputMethodSubtypeList(imi, true); + // IMEs that have no subtypes should be counted. + if (subtypes.isEmpty()) { + ++filteredImisCount; + continue; + } + + int auxCount = 0; + for (InputMethodSubtype subtype : subtypes) { + if (subtype.isAuxiliary()) { + ++auxCount; + } + } + final int nonAuxCount = subtypes.size() - auxCount; + + // IMEs that have one or more non-auxiliary subtypes should be counted. + // If shouldIncludeAuxiliarySubtypes is true, IMEs that have two or more auxiliary + // subtypes should be counted as well. + if (nonAuxCount > 0 || (shouldIncludeAuxiliarySubtypes && auxCount > 1)) { + ++filteredImisCount; + continue; + } + } + + return filteredImisCount > 1 + // imm.getEnabledInputMethodSubtypeList(null, false) will return the current IME's enabled + // input method subtype (The current IME should be LatinIME.) + || imm.getEnabledInputMethodSubtypeList(null, false).size() > 1; + } + + @Override + protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { + // send focus to the password field + return mPasswordEntry.requestFocus(direction, previouslyFocusedRect); + } + + private void verifyPasswordAndUnlock() { + String entry = mPasswordEntry.getText().toString(); + boolean wrongPassword = true; + if (mLockPatternUtils.checkPassword(entry)) { + mCallback.reportSuccessfulUnlockAttempt(); + KeyStore.getInstance().password(entry); + mCallback.dismiss(true); + wrongPassword = false; + } else if (entry.length() > MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT ) { + // to avoid accidental lockout, only count attempts that are long enough to be a + // real password. This may require some tweaking. + mCallback.reportFailedUnlockAttempt(); + if (0 == (mCallback.getFailedAttempts() + % LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT)) { + long deadline = mLockPatternUtils.setLockoutAttemptDeadline(); + handleAttemptLockout(deadline); + } + } + mNavigationManager.setMessage(wrongPassword ? + (mIsAlpha ? R.string.kg_wrong_password : R.string.kg_wrong_pin) : 0); + mPasswordEntry.setText(""); + } + + // Prevent user from using the PIN/Password entry until scheduled deadline. + private void handleAttemptLockout(long elapsedRealtimeDeadline) { + mPasswordEntry.setEnabled(false); + mKeyboardView.setEnabled(false); + long elapsedRealtime = SystemClock.elapsedRealtime(); + new CountDownTimer(elapsedRealtimeDeadline - elapsedRealtime, 1000) { + + @Override + public void onTick(long millisUntilFinished) { + int secondsRemaining = (int) (millisUntilFinished / 1000); + mNavigationManager.setMessage( + R.string.kg_too_many_failed_attempts_countdown, secondsRemaining); + } + + @Override + public void onFinish() { + mPasswordEntry.setEnabled(true); + mKeyboardView.setEnabled(true); + } + }.start(); + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + mCallback.userActivity(0); + return false; + } + + @Override + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + // Check if this was the result of hitting the enter key + if (actionId == EditorInfo.IME_NULL || actionId == EditorInfo.IME_ACTION_DONE + || actionId == EditorInfo.IME_ACTION_NEXT) { + verifyPasswordAndUnlock(); + return true; + } + return false; + } + + @Override + public boolean needsInput() { + return mIsAlpha; + } + + @Override + public void onPause() { + + } + + @Override + public void onResume() { + reset(); + } + + @Override + public KeyguardSecurityCallback getCallback() { + return mCallback; + } + +} + diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardPatternView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardPatternView.java new file mode 100644 index 0000000..a95cfcb --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardPatternView.java @@ -0,0 +1,367 @@ +/* + * 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.accounts.Account; +import android.accounts.AccountManager; +import android.accounts.AccountManagerCallback; +import android.accounts.AccountManagerFuture; +import android.accounts.AuthenticatorException; +import android.accounts.OperationCanceledException; +import android.content.Context; +import android.os.Bundle; +import android.os.CountDownTimer; +import android.os.SystemClock; +import android.security.KeyStore; +import android.util.AttributeSet; +import android.util.Log; +import android.view.MotionEvent; +import android.view.View; +import android.widget.Button; +import android.widget.LinearLayout; + +import com.android.internal.widget.LockPatternUtils; +import com.android.internal.widget.LockPatternView; +import com.android.internal.R; + +import java.io.IOException; +import java.util.List; + +public class KeyguardPatternView extends LinearLayout implements KeyguardSecurityView { + + private static final String TAG = "SecurityPatternView"; + private static final boolean DEBUG = false; + + // how long before we clear the wrong pattern + private static final int PATTERN_CLEAR_TIMEOUT_MS = 2000; + + // how long we stay awake after each key beyond MIN_PATTERN_BEFORE_POKE_WAKELOCK + private static final int UNLOCK_PATTERN_WAKE_INTERVAL_MS = 7000; + + // how long we stay awake after the user hits the first dot. + private static final int UNLOCK_PATTERN_WAKE_INTERVAL_FIRST_DOTS_MS = 2000; + + // how many cells the user has to cross before we poke the wakelock + private static final int MIN_PATTERN_BEFORE_POKE_WAKELOCK = 2; + + private int mFailedPatternAttemptsSinceLastTimeout = 0; + private int mTotalFailedPatternAttempts = 0; + private CountDownTimer mCountdownTimer = null; + private LockPatternUtils mLockPatternUtils; + private LockPatternView mLockPatternView; + private Button mForgotPatternButton; + private KeyguardSecurityCallback mCallback; + private boolean mEnableFallback; + private KeyguardNavigationManager mNavigationManager; + + /** + * Keeps track of the last time we poked the wake lock during dispatching of the touch event. + * Initialized to something guaranteed to make us poke the wakelock when the user starts + * drawing the pattern. + * @see #dispatchTouchEvent(android.view.MotionEvent) + */ + private long mLastPokeTime = -UNLOCK_PATTERN_WAKE_INTERVAL_MS; + + /** + * Useful for clearing out the wrong pattern after a delay + */ + private Runnable mCancelPatternRunnable = new Runnable() { + public void run() { + mLockPatternView.clearPattern(); + } + }; + + enum FooterMode { + Normal, + ForgotLockPattern, + VerifyUnlocked + } + + public KeyguardPatternView(Context context) { + this(context, null); + } + + public KeyguardPatternView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public void setKeyguardCallback(KeyguardSecurityCallback callback) { + mCallback = callback; + } + + public void setLockPatternUtils(LockPatternUtils utils) { + mLockPatternUtils = utils; + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + + mNavigationManager = new KeyguardNavigationManager(this); + mLockPatternUtils = mLockPatternUtils == null + ? new LockPatternUtils(mContext) : mLockPatternUtils; + + mLockPatternView = (LockPatternView) findViewById(R.id.lockPatternView); + mLockPatternView.setSaveEnabled(false); + mLockPatternView.setFocusable(false); + mLockPatternView.setOnPatternListener(new UnlockPatternListener()); + + // stealth mode will be the same for the life of this screen + mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled()); + + // vibrate mode will be the same for the life of this screen + mLockPatternView.setTactileFeedbackEnabled(mLockPatternUtils.isTactileFeedbackEnabled()); + + mForgotPatternButton = (Button) findViewById(R.id.forgot_password_button); + mForgotPatternButton.setText(R.string.kg_forgot_pattern_button_text); + mForgotPatternButton.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + mCallback.showBackupUnlock(); + } + }); + + setFocusableInTouchMode(true); + + maybeEnableFallback(mContext); + } + + private void updateFooter(FooterMode mode) { + switch (mode) { + case Normal: + if (DEBUG) Log.d(TAG, "mode normal"); + mForgotPatternButton.setVisibility(View.GONE); + break; + case ForgotLockPattern: + if (DEBUG) Log.d(TAG, "mode ForgotLockPattern"); + mForgotPatternButton.setVisibility(View.VISIBLE); + break; + case VerifyUnlocked: + if (DEBUG) Log.d(TAG, "mode VerifyUnlocked"); + mForgotPatternButton.setVisibility(View.GONE); + } + } + + @Override + public boolean dispatchTouchEvent(MotionEvent ev) { + final boolean result = super.dispatchTouchEvent(ev); + // as long as the user is entering a pattern (i.e sending a touch event that was handled + // by this screen), keep poking the wake lock so that the screen will stay on. + final long elapsed = SystemClock.elapsedRealtime() - mLastPokeTime; + if (result && (elapsed > (UNLOCK_PATTERN_WAKE_INTERVAL_MS - 100))) { + mLastPokeTime = SystemClock.elapsedRealtime(); + } + return result; + } + + public void reset() { + // reset lock pattern + mLockPatternView.enableInput(); + mLockPatternView.setEnabled(true); + mLockPatternView.clearPattern(); + + // if the user is currently locked out, enforce it. + long deadline = mLockPatternUtils.getLockoutAttemptDeadline(); + if (deadline != 0) { + handleAttemptLockout(deadline); + } else { + mNavigationManager.setMessage(R.string.kg_pattern_instructions); + } + + // the footer depends on how many total attempts the user has failed + if (mCallback.isVerifyUnlockOnly()) { + updateFooter(FooterMode.VerifyUnlocked); + } else if (mEnableFallback && + (mTotalFailedPatternAttempts >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT)) { + updateFooter(FooterMode.ForgotLockPattern); + } else { + updateFooter(FooterMode.Normal); + } + + } + + /** TODO: hook this up */ + public void cleanUp() { + if (DEBUG) Log.v(TAG, "Cleanup() called on " + this); + mLockPatternUtils = null; + mLockPatternView.setOnPatternListener(null); + } + + @Override + public void onWindowFocusChanged(boolean hasWindowFocus) { + super.onWindowFocusChanged(hasWindowFocus); + if (hasWindowFocus) { + // when timeout dialog closes we want to update our state + reset(); + } + } + + private class UnlockPatternListener implements LockPatternView.OnPatternListener { + + public void onPatternStart() { + mLockPatternView.removeCallbacks(mCancelPatternRunnable); + } + + public void onPatternCleared() { + } + + public void onPatternCellAdded(List<LockPatternView.Cell> pattern) { + // To guard against accidental poking of the wakelock, look for + // the user actually trying to draw a pattern of some minimal length. + if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) { + mCallback.userActivity(UNLOCK_PATTERN_WAKE_INTERVAL_MS); + } else { + // Give just a little extra time if they hit one of the first few dots + mCallback.userActivity(UNLOCK_PATTERN_WAKE_INTERVAL_FIRST_DOTS_MS); + } + } + + public void onPatternDetected(List<LockPatternView.Cell> pattern) { + if (mLockPatternUtils.checkPattern(pattern)) { + mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Correct); + mCallback.dismiss(true); // keyguardDone(true) + KeyStore.getInstance().password(LockPatternUtils.patternToString(pattern)); + } else { + if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) { + mCallback.userActivity(UNLOCK_PATTERN_WAKE_INTERVAL_MS); + } + mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong); + if (pattern.size() >= LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) { + mTotalFailedPatternAttempts++; + mFailedPatternAttemptsSinceLastTimeout++; + mCallback.reportFailedUnlockAttempt(); + } + if (mFailedPatternAttemptsSinceLastTimeout + >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT) { + long deadline = mLockPatternUtils.setLockoutAttemptDeadline(); + handleAttemptLockout(deadline); + } else { + mNavigationManager.setMessage(R.string.kg_wrong_pattern); + mLockPatternView.postDelayed(mCancelPatternRunnable, PATTERN_CLEAR_TIMEOUT_MS); + } + } + } + } + + private void maybeEnableFallback(Context context) { + // Ask the account manager if we have an account that can be used as a + // fallback in case the user forgets his pattern. + AccountAnalyzer accountAnalyzer = new AccountAnalyzer(AccountManager.get(context)); + accountAnalyzer.start(); + } + + private class AccountAnalyzer implements AccountManagerCallback<Bundle> { + private final AccountManager mAccountManager; + private final Account[] mAccounts; + private int mAccountIndex; + + private AccountAnalyzer(AccountManager accountManager) { + mAccountManager = accountManager; + mAccounts = accountManager.getAccountsByType("com.google"); + } + + private void next() { + // if we are ready to enable the fallback or if we depleted the list of accounts + // then finish and get out + if (mAccountIndex >= mAccounts.length) { + mEnableFallback = true; + return; + } + + // lookup the confirmCredentials intent for the current account + mAccountManager.confirmCredentials(mAccounts[mAccountIndex], null, null, this, null); + } + + public void start() { + mEnableFallback = false; + mAccountIndex = 0; + next(); + } + + public void run(AccountManagerFuture<Bundle> future) { + try { + Bundle result = future.getResult(); + if (result.getParcelable(AccountManager.KEY_INTENT) != null) { + mEnableFallback = true; + } + } catch (OperationCanceledException e) { + // just skip the account if we are unable to query it + } catch (IOException e) { + // just skip the account if we are unable to query it + } catch (AuthenticatorException e) { + // just skip the account if we are unable to query it + } finally { + mAccountIndex++; + next(); + } + } + } + + private void handleAttemptLockout(long elapsedRealtimeDeadline) { + mLockPatternView.clearPattern(); + mLockPatternView.setEnabled(false); + final long elapsedRealtime = SystemClock.elapsedRealtime(); + mCountdownTimer = new CountDownTimer(elapsedRealtimeDeadline - elapsedRealtime, 1000) { + + @Override + public void onTick(long millisUntilFinished) { + final int secondsRemaining = (int) (millisUntilFinished / 1000); + mNavigationManager.setMessage( + R.string.kg_too_many_failed_attempts_countdown,secondsRemaining); + } + + @Override + public void onFinish() { + mLockPatternView.setEnabled(true); + mNavigationManager.setMessage(R.string.kg_pattern_instructions); + // TODO mUnlockIcon.setVisibility(View.VISIBLE); + mFailedPatternAttemptsSinceLastTimeout = 0; + if (mEnableFallback) { + updateFooter(FooterMode.ForgotLockPattern); + } else { + updateFooter(FooterMode.Normal); + } + } + + }.start(); + } + + @Override + public boolean needsInput() { + return false; + } + + @Override + public void onPause() { + if (mCountdownTimer != null) { + mCountdownTimer.cancel(); + mCountdownTimer = null; + } + } + + @Override + public void onResume() { + reset(); + } + + @Override + public KeyguardSecurityCallback getCallback() { + return mCallback; + } + +} + + + diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityCallback.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityCallback.java new file mode 100644 index 0000000..1a4a40b --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityCallback.java @@ -0,0 +1,62 @@ +/* + * 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; + +public interface KeyguardSecurityCallback { + + /** + * Dismiss the given security screen. + * @param securityVerified true if the user correctly entered credentials for the given screen. + */ + void dismiss(boolean securityVerified); + + /** + * Manually report user activity to keep the device awake. If timeout is 0, + * uses user-defined timeout. + * @param timeout + */ + void userActivity(long timeout); + + /** + * Checks if keyguard is in "verify credentials" mode. + * @return true if user has been asked to verify security. + */ + boolean isVerifyUnlockOnly(); + + /** + * Call when user correctly enters their credentials + */ + void reportSuccessfulUnlockAttempt(); + + /** + * Call when the user incorrectly enters their credentials + */ + void reportFailedUnlockAttempt(); + + /** + * Gets the number of attempts thus far as reported by {@link #reportFailedUnlockAttempt()} + * @return number of failed attempts + */ + int getFailedAttempts(); + + /** + * Shows the backup unlock for the current method. If none available, this call is a NOP. + */ + void showBackupUnlock(); + + void keyguardDoneDrawing(); + +} diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityModel.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityModel.java new file mode 100644 index 0000000..d041dd3 --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityModel.java @@ -0,0 +1,85 @@ +/* + * 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.admin.DevicePolicyManager; +import android.content.Context; + +import com.android.internal.telephony.IccCardConstants; +import com.android.internal.widget.LockPatternUtils; + +public class KeyguardSecurityModel { + /** + * The different types of security available for {@link Mode#UnlockScreen}. + * @see com.android.internal.policy.impl.LockPatternKeyguardView#getUnlockMode() + */ + enum SecurityMode { + None, // No security enabled + Pattern, // Unlock by drawing a pattern. + Password, // Unlock by entering a password or PIN + Biometric, // Unlock with a biometric key (e.g. finger print or face unlock) + Account, // Unlock by entering an account's login and password. + SimPin, // Unlock by entering a sim pin. + SimPuk // Unlock by entering a sim puk + } + + private Context mContext; + private LockPatternUtils mLockPatternUtils; + + KeyguardSecurityModel(Context context) { + mContext = context; + mLockPatternUtils = new LockPatternUtils(context); + } + + void setLockPatternUtils(LockPatternUtils utils) { + mLockPatternUtils = utils; + } + + SecurityMode getSecurityMode() { + KeyguardUpdateMonitor mUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext); + final IccCardConstants.State simState = mUpdateMonitor.getSimState(); + if (simState == IccCardConstants.State.PIN_REQUIRED) { + return SecurityMode.SimPin; + } else if (simState == IccCardConstants.State.PUK_REQUIRED) { + return SecurityMode.SimPuk; + } else { + final int mode = mLockPatternUtils.getKeyguardStoredPasswordQuality(); + switch (mode) { + case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC: + case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC: + case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC: + case DevicePolicyManager.PASSWORD_QUALITY_COMPLEX: + return mLockPatternUtils.isLockPasswordEnabled() ? + SecurityMode.Password : SecurityMode.None; + + case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING: + case DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED: + if (mLockPatternUtils.isLockPatternEnabled()) { + return mLockPatternUtils.isPermanentlyLocked() ? + SecurityMode.Account : SecurityMode.Pattern; + } else { + return SecurityMode.None; + } + default: + throw new IllegalStateException("Unknown unlock mode:" + mode); + } + } + } + + SecurityMode getBackupFor(SecurityMode mode) { + return SecurityMode.None; // TODO: handle biometric unlock, etc. + } +} diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityView.java new file mode 100644 index 0000000..d80c1db --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityView.java @@ -0,0 +1,64 @@ +/* + * 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 com.android.internal.widget.LockPatternUtils; + +public interface KeyguardSecurityView { + /** + * Interface back to keyguard to tell it when security + * @param callback + */ + void setKeyguardCallback(KeyguardSecurityCallback callback); + + /** + * Set {@link LockPatternUtils} object. Useful for providing a mock interface. + * @param utils + */ + void setLockPatternUtils(LockPatternUtils utils); + + /** + * Reset the view and prepare to take input. This should do things like clearing the + * password or pattern and clear error messages. + */ + void reset(); + + /** + * Emulate activity life cycle within the view. When called, the view should clean up + * and prepare to be removed. + */ + void onPause(); + + /** + * Emulate activity life cycle within this view. When called, the view should prepare itself + * to be shown. + */ + void onResume(); + + /** + * Inquire whether this view requires IME (keyboard) interaction. + * + * @return true if IME interaction is required. + */ + boolean needsInput(); + + /** + * Get {@link KeyguardSecurityCallback} for the given object + * @return KeyguardSecurityCallback + */ + KeyguardSecurityCallback getCallback(); + +} diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSelectorView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSelectorView.java new file mode 100644 index 0000000..b69697f --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSelectorView.java @@ -0,0 +1,263 @@ +/* + * 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.animation.ObjectAnimator; +import android.app.ActivityManagerNative; +import android.app.SearchManager; +import android.content.ActivityNotFoundException; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.os.RemoteException; +import android.os.UserHandle; +import android.provider.MediaStore; +import android.util.AttributeSet; +import android.util.Log; +import android.util.Slog; +import android.view.View; +import android.widget.Button; +import android.widget.LinearLayout; + +import com.android.internal.telephony.IccCardConstants; +import com.android.internal.widget.LockPatternUtils; +import com.android.internal.widget.multiwaveview.GlowPadView; +import com.android.internal.widget.multiwaveview.GlowPadView.OnTriggerListener; +import com.android.internal.R; + +public class KeyguardSelectorView extends LinearLayout implements KeyguardSecurityView { + private static final boolean DEBUG = KeyguardHostView.DEBUG; + private static final String TAG = "SecuritySelectorView"; + private static final String ASSIST_ICON_METADATA_NAME = + "com.android.systemui.action_assist_icon"; + private KeyguardSecurityCallback mCallback; + private GlowPadView mGlowPadView; + private Button mEmergencyCallButton; + private ObjectAnimator mAnim; + private boolean mCameraDisabled; + private boolean mSearchDisabled; + private LockPatternUtils mLockPatternUtils; + + OnTriggerListener mOnTriggerListener = new OnTriggerListener() { + + public void onTrigger(View v, int target) { + final int resId = mGlowPadView.getResourceIdForTarget(target); + switch (resId) { + case com.android.internal.R.drawable.ic_action_assist_generic: + Intent assistIntent = + ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE)) + .getAssistIntent(mContext, UserHandle.USER_CURRENT); + if (assistIntent != null) { + launchActivity(assistIntent); + } else { + Log.w(TAG, "Failed to get intent for assist activity"); + } + mCallback.userActivity(0); + break; + + case com.android.internal.R.drawable.ic_lockscreen_camera: + launchActivity(new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA)); + mCallback.userActivity(0); + break; + + case com.android.internal.R.drawable.ic_lockscreen_unlock_phantom: + case com.android.internal.R.drawable.ic_lockscreen_unlock: + mCallback.dismiss(false); + break; + } + } + + public void onReleased(View v, int handle) { + doTransition(mEmergencyCallButton, 1.0f); + } + + public void onGrabbed(View v, int handle) { + doTransition(mEmergencyCallButton, 0.0f); + } + + public void onGrabbedStateChange(View v, int handle) { + + } + + public void onFinishFinalAnimation() { + + } + + }; + + KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() { + + private boolean mEmergencyDialerDisableBecauseSimLocked; + + @Override + public void onDevicePolicyManagerStateChanged() { + updateTargets(); + } + + @Override + public void onSimStateChanged(IccCardConstants.State simState) { + // Some carriers aren't capable of handling emergency calls while the SIM is locked + mEmergencyDialerDisableBecauseSimLocked = KeyguardUpdateMonitor.isSimLocked(simState) + && !mLockPatternUtils.isEmergencyCallEnabledWhileSimLocked(); + updateTargets(); + } + + void onPhoneStateChanged(int phoneState) { + if (mEmergencyCallButton != null) { + mLockPatternUtils.isEmergencyCallEnabledWhileSimLocked(); + mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCallButton, + phoneState, !mEmergencyDialerDisableBecauseSimLocked); + } + }; + }; + + public KeyguardSelectorView(Context context) { + this(context, null); + } + + public KeyguardSelectorView(Context context, AttributeSet attrs) { + super(context, attrs); + mLockPatternUtils = new LockPatternUtils(getContext()); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mGlowPadView = (GlowPadView) findViewById(R.id.glow_pad_view); + mGlowPadView.setOnTriggerListener(mOnTriggerListener); + mEmergencyCallButton = (Button) findViewById(R.id.emergency_call_button); + KeyguardUpdateMonitor.getInstance(getContext()).registerCallback(mInfoCallback); + updateTargets(); + } + + public boolean isTargetPresent(int resId) { + return mGlowPadView.getTargetPosition(resId) != -1; + } + + private void updateTargets() { + boolean disabledByAdmin = mLockPatternUtils.getDevicePolicyManager() + .getCameraDisabled(null); + final KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(getContext()); + boolean disabledBySimState = monitor.isSimLocked(); + boolean cameraTargetPresent = + isTargetPresent(com.android.internal.R.drawable.ic_lockscreen_camera); + boolean searchTargetPresent = + isTargetPresent(com.android.internal.R.drawable.ic_action_assist_generic); + + if (disabledByAdmin) { + Log.v(TAG, "Camera disabled by Device Policy"); + } else if (disabledBySimState) { + Log.v(TAG, "Camera disabled by Sim State"); + } + boolean searchActionAvailable = + ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE)) + .getAssistIntent(mContext, UserHandle.USER_CURRENT) != null; + mCameraDisabled = disabledByAdmin || disabledBySimState || !cameraTargetPresent; + mSearchDisabled = disabledBySimState || !searchActionAvailable || !searchTargetPresent; + updateResources(); + } + + public void updateResources() { + // Update the search icon with drawable from the search .apk + if (!mSearchDisabled) { + Intent intent = ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE)) + .getAssistIntent(mContext, UserHandle.USER_CURRENT); + if (intent != null) { + // XXX Hack. We need to substitute the icon here but haven't formalized + // the public API. The "_google" metadata will be going away, so + // DON'T USE IT! + ComponentName component = intent.getComponent(); + boolean replaced = mGlowPadView.replaceTargetDrawablesIfPresent(component, + ASSIST_ICON_METADATA_NAME + "_google", + com.android.internal.R.drawable.ic_action_assist_generic); + + if (!replaced && !mGlowPadView.replaceTargetDrawablesIfPresent(component, + ASSIST_ICON_METADATA_NAME, + com.android.internal.R.drawable.ic_action_assist_generic)) { + Slog.w(TAG, "Couldn't grab icon from package " + component); + } + } + } + + mGlowPadView.setEnableTarget(com.android.internal.R.drawable + .ic_lockscreen_camera, !mCameraDisabled); + mGlowPadView.setEnableTarget(com.android.internal.R.drawable + .ic_action_assist_generic, !mSearchDisabled); + } + + void doTransition(Object v, float to) { + if (mAnim != null) { + mAnim.cancel(); + } + mAnim = ObjectAnimator.ofFloat(mEmergencyCallButton, "alpha", to); + mAnim.start(); + } + + public void setKeyguardCallback(KeyguardSecurityCallback callback) { + mCallback = callback; + } + + public void setLockPatternUtils(LockPatternUtils utils) { + mLockPatternUtils = utils; + } + + /** + * Launches the said intent for the current foreground user. + * @param intent + */ + private void launchActivity(Intent intent) { + intent.setFlags( + Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_SINGLE_TOP + | Intent.FLAG_ACTIVITY_CLEAR_TOP); + try { + ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity(); + } catch (RemoteException e) { + Log.w(TAG, "can't dismiss keyguard on launch"); + } + try { + mContext.startActivityAsUser(intent, new UserHandle(UserHandle.USER_CURRENT)); + } catch (ActivityNotFoundException e) { + Log.w(TAG, "Activity not found for intent + " + intent.getAction()); + } + } + + @Override + public void reset() { + mGlowPadView.reset(false); + } + + @Override + public boolean needsInput() { + return false; + } + + @Override + public void onPause() { + KeyguardUpdateMonitor.getInstance(getContext()).removeCallback(mInfoCallback); + } + + @Override + public void onResume() { + KeyguardUpdateMonitor.getInstance(getContext()).registerCallback(mInfoCallback); + } + + @Override + public KeyguardSecurityCallback getCallback() { + return mCallback; + } + +} diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSimPinView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSimPinView.java new file mode 100644 index 0000000..294ea5c --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSimPinView.java @@ -0,0 +1,236 @@ +/* + * 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.Dialog; +import android.app.ProgressDialog; +import android.content.Context; +import android.graphics.Rect; +import android.os.RemoteException; +import android.os.ServiceManager; + +import com.android.internal.telephony.ITelephony; +import com.android.internal.widget.LockPatternUtils; +import com.android.internal.widget.PasswordEntryKeyboardHelper; +import com.android.internal.widget.PasswordEntryKeyboardView; +import com.android.internal.R; + +import android.util.AttributeSet; +import android.view.KeyEvent; +import android.view.View; +import android.view.WindowManager; +import android.view.inputmethod.EditorInfo; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.widget.TextView.OnEditorActionListener; + +/** + * Displays a dialer like interface to unlock the SIM PIN. + */ +public class KeyguardSimPinView extends LinearLayout + implements KeyguardSecurityView, OnEditorActionListener { + + private static final int DIGIT_PRESS_WAKE_MILLIS = 5000; + + private EditText mPinEntry; + private ProgressDialog mSimUnlockProgressDialog = null; + private KeyguardSecurityCallback mCallback; + private PasswordEntryKeyboardView mKeyboardView; + private PasswordEntryKeyboardHelper mKeyboardHelper; + private LockPatternUtils mLockPatternUtils; + private KeyguardNavigationManager mNavigationManager; + + public KeyguardSimPinView(Context context) { + this(context, null); + } + + public KeyguardSimPinView(Context context, AttributeSet attrs) { + super(context, attrs); + mLockPatternUtils = new LockPatternUtils(getContext()); + } + + public void setKeyguardCallback(KeyguardSecurityCallback callback) { + mCallback = callback; + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + + mNavigationManager = new KeyguardNavigationManager(this); + + mPinEntry = (EditText) findViewById(R.id.sim_pin_entry); + mPinEntry.setOnEditorActionListener(this); + + mKeyboardView = (PasswordEntryKeyboardView) findViewById(R.id.keyboard); + mKeyboardHelper = new PasswordEntryKeyboardHelper(mContext, mKeyboardView, this, false); + mKeyboardHelper.setKeyboardMode(PasswordEntryKeyboardHelper.KEYBOARD_MODE_NUMERIC); + mKeyboardHelper.setEnableHaptics(mLockPatternUtils.isTactileFeedbackEnabled()); + + final View deleteButton = findViewById(R.id.delete_button); + if (deleteButton != null) { + deleteButton.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + mKeyboardHelper.handleBackspace(); + } + }); + } + + setFocusableInTouchMode(true); + reset(); + } + + @Override + protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { + return mPinEntry.requestFocus(direction, previouslyFocusedRect); + } + + public void reset() { + // start fresh + mNavigationManager.setMessage(R.string.kg_sim_pin_instructions); + + // make sure that the number of entered digits is consistent when we + // erase the SIM unlock code, including orientation changes. + mPinEntry.setText(""); + mPinEntry.requestFocus(); + } + + /** {@inheritDoc} */ + public void cleanUp() { + // dismiss the dialog. + if (mSimUnlockProgressDialog != null) { + mSimUnlockProgressDialog.dismiss(); + mSimUnlockProgressDialog = null; + } + } + + /** + * Since the IPC can block, we want to run the request in a separate thread + * with a callback. + */ + private abstract class CheckSimPin extends Thread { + private final String mPin; + + protected CheckSimPin(String pin) { + mPin = pin; + } + + abstract void onSimLockChangedResponse(boolean success); + + @Override + public void run() { + try { + final boolean result = ITelephony.Stub.asInterface(ServiceManager + .checkService("phone")).supplyPin(mPin); + post(new Runnable() { + public void run() { + onSimLockChangedResponse(result); + } + }); + } catch (RemoteException e) { + post(new Runnable() { + public void run() { + onSimLockChangedResponse(false); + } + }); + } + } + } + + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + // Check if this was the result of hitting the enter key + mCallback.userActivity(DIGIT_PRESS_WAKE_MILLIS); + if (actionId == EditorInfo.IME_NULL || actionId == EditorInfo.IME_ACTION_DONE + || actionId == EditorInfo.IME_ACTION_NEXT) { + checkPin(); + return true; + } + return false; + } + + private Dialog getSimUnlockProgressDialog() { + if (mSimUnlockProgressDialog == null) { + mSimUnlockProgressDialog = new ProgressDialog(mContext); + mSimUnlockProgressDialog.setMessage( + mContext.getString(R.string.kg_sim_unlock_progress_dialog_message)); + mSimUnlockProgressDialog.setIndeterminate(true); + mSimUnlockProgressDialog.setCancelable(false); + if (!(mContext instanceof Activity)) { + mSimUnlockProgressDialog.getWindow().setType( + WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + } + } + return mSimUnlockProgressDialog; + } + + private void checkPin() { + if (mPinEntry.getText().length() < 4) { + // otherwise, display a message to the user, and don't submit. + mNavigationManager.setMessage(R.string.kg_invalid_sim_pin_hint); + mPinEntry.setText(""); + mCallback.userActivity(0); + return; + } + + getSimUnlockProgressDialog().show(); + + new CheckSimPin(mPinEntry.getText().toString()) { + void onSimLockChangedResponse(final boolean success) { + post(new Runnable() { + public void run() { + if (mSimUnlockProgressDialog != null) { + mSimUnlockProgressDialog.hide(); + } + if (success) { + // before closing the keyguard, report back that the sim is unlocked + // so it knows right away. + KeyguardUpdateMonitor.getInstance(getContext()).reportSimUnlocked(); + mCallback.dismiss(false); // + } else { + mNavigationManager.setMessage(R.string.kg_password_wrong_pin_code); + mPinEntry.setText(""); + } + mCallback.userActivity(0); + } + }); + } + }.start(); + } + + public void setLockPatternUtils(LockPatternUtils utils) { + mLockPatternUtils = utils; + } + + public boolean needsInput() { + return false; // This view provides its own keypad + } + + public void onPause() { + + } + + public void onResume() { + reset(); + } + + public KeyguardSecurityCallback getCallback() { + return mCallback; + } + +} diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSimPukView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSimPukView.java new file mode 100644 index 0000000..801dfc3 --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSimPukView.java @@ -0,0 +1,301 @@ +/* + * 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.Dialog; +import android.app.ProgressDialog; +import android.content.Context; +import android.content.res.Configuration; +import android.graphics.Rect; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.text.Editable; +import android.util.AttributeSet; +import android.util.Log; +import android.view.KeyEvent; +import android.view.View; +import android.view.WindowManager; +import android.view.inputmethod.EditorInfo; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.widget.TextView.OnEditorActionListener; + +import com.android.internal.telephony.ITelephony; +import com.android.internal.widget.LockPatternUtils; +import com.android.internal.widget.PasswordEntryKeyboardHelper; +import com.android.internal.widget.PasswordEntryKeyboardView; +import com.android.internal.R; + +public class KeyguardSimPukView extends LinearLayout implements View.OnClickListener, + View.OnFocusChangeListener, KeyguardSecurityView, OnEditorActionListener { + + private static final int DIGIT_PRESS_WAKE_MILLIS = 5000; + + private TextView mPukText; + private TextView mPinText; + private TextView mFocusedEntry; + + private View mDelPukButton; + private View mDelPinButton; + + private ProgressDialog mSimUnlockProgressDialog = null; + private KeyguardSecurityCallback mCallback; + + private KeyguardNavigationManager mNavigationManager; + + private PasswordEntryKeyboardView mKeyboardView; + + private PasswordEntryKeyboardHelper mKeyboardHelper; + + private LockPatternUtils mLockPatternUtils; + + public KeyguardSimPukView(Context context) { + this(context, null); + } + + public KeyguardSimPukView(Context context, AttributeSet attrs) { + super(context, attrs); + mLockPatternUtils = new LockPatternUtils(getContext()); + } + + public void setKeyguardCallback(KeyguardSecurityCallback callback) { + mCallback = callback; + mLockPatternUtils = new LockPatternUtils(getContext()); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + + mNavigationManager = new KeyguardNavigationManager(this); + + mPukText = (TextView) findViewById(R.id.sim_puk_entry); + mPukText.setOnEditorActionListener(this); + mPinText = (TextView) findViewById(R.id.sim_pin_entry); + mPinText.setOnEditorActionListener(this); + mDelPukButton = findViewById(R.id.puk_delete_button); + mDelPukButton.setOnClickListener(this); + mDelPinButton = findViewById(R.id.pin_delete_button); + mDelPinButton.setOnClickListener(this); + + mKeyboardView = (PasswordEntryKeyboardView) findViewById(R.id.keyboard); + mKeyboardHelper = new PasswordEntryKeyboardHelper(mContext, mKeyboardView, this, false); + mKeyboardHelper.setKeyboardMode(PasswordEntryKeyboardHelper.KEYBOARD_MODE_NUMERIC); + mKeyboardHelper.setEnableHaptics(mLockPatternUtils.isTactileFeedbackEnabled()); + + mNavigationManager.setMessage(R.string.kg_sim_puk_recovery_hint); + + mPinText.setFocusableInTouchMode(true); + mPinText.setOnFocusChangeListener(this); + mPukText.setFocusableInTouchMode(true); + mPukText.setOnFocusChangeListener(this); + + setFocusableInTouchMode(true); + + reset(); + } + + @Override + protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { + return mPukText.requestFocus(direction, previouslyFocusedRect); + } + + public boolean needsInput() { + return false; // This view provides its own keypad + } + + public void onPause() { + + } + + public void onResume() { + reset(); + } + + /** {@inheritDoc} */ + public void cleanUp() { + // dismiss the dialog. + if (mSimUnlockProgressDialog != null) { + mSimUnlockProgressDialog.dismiss(); + mSimUnlockProgressDialog = null; + } + } + + /** + * Since the IPC can block, we want to run the request in a separate thread + * with a callback. + */ + private abstract class CheckSimPuk extends Thread { + + private final String mPin, mPuk; + + protected CheckSimPuk(String puk, String pin) { + mPuk = puk; + mPin = pin; + } + + abstract void onSimLockChangedResponse(boolean success); + + @Override + public void run() { + try { + final boolean result = ITelephony.Stub.asInterface(ServiceManager + .checkService("phone")).supplyPuk(mPuk, mPin); + + post(new Runnable() { + public void run() { + onSimLockChangedResponse(result); + } + }); + } catch (RemoteException e) { + post(new Runnable() { + public void run() { + onSimLockChangedResponse(false); + } + }); + } + } + } + + public void onClick(View v) { + if (v == mDelPukButton) { + if (mFocusedEntry != mPukText) + mPukText.requestFocus(); + final Editable digits = mPukText.getEditableText(); + final int len = digits.length(); + if (len > 0) { + digits.delete(len-1, len); + } + } else if (v == mDelPinButton) { + if (mFocusedEntry != mPinText) + mPinText.requestFocus(); + final Editable digits = mPinText.getEditableText(); + final int len = digits.length(); + if (len > 0) { + digits.delete(len-1, len); + } + } + mCallback.userActivity(DIGIT_PRESS_WAKE_MILLIS); + } + + @Override + public void onFocusChange(View view, boolean hasFocus) { + if (hasFocus) + mFocusedEntry = (TextView) view; + } + + private Dialog getSimUnlockProgressDialog() { + if (mSimUnlockProgressDialog == null) { + mSimUnlockProgressDialog = new ProgressDialog(mContext); + mSimUnlockProgressDialog.setMessage(mContext.getString( + R.string.kg_sim_unlock_progress_dialog_message)); + mSimUnlockProgressDialog.setIndeterminate(true); + mSimUnlockProgressDialog.setCancelable(false); + if (!(mContext instanceof Activity)) { + mSimUnlockProgressDialog.getWindow().setType( + WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + } + } + return mSimUnlockProgressDialog; + } + + private void checkPuk() { + // make sure the puk is at least 8 digits long. + if (mPukText.getText().length() < 8) { + // otherwise, display a message to the user, and don't submit. + mNavigationManager.setMessage(R.string.kg_invalid_sim_puk_hint); + mPukText.setText(""); + return; + } + + // make sure the PIN is between 4 and 8 digits + if (mPinText.getText().length() < 4 + || mPinText.getText().length() > 8) { + // otherwise, display a message to the user, and don't submit. + mNavigationManager.setMessage(R.string.kg_invalid_sim_pin_hint); + mPinText.setText(""); + return; + } + + getSimUnlockProgressDialog().show(); + + new CheckSimPuk(mPukText.getText().toString(), + mPinText.getText().toString()) { + void onSimLockChangedResponse(final boolean success) { + mPinText.post(new Runnable() { + public void run() { + if (mSimUnlockProgressDialog != null) { + mSimUnlockProgressDialog.hide(); + } + if (success) { + mCallback.dismiss(true); + } else { + mNavigationManager.setMessage(R.string.kg_invalid_puk); + mPukText.setText(""); + mPinText.setText(""); + } + } + }); + } + }.start(); + } + + @Override + public boolean onEditorAction(TextView view, int actionId, KeyEvent event) { + // Check if this was the result of hitting the enter key + mCallback.userActivity(DIGIT_PRESS_WAKE_MILLIS); + if (actionId == EditorInfo.IME_NULL + || actionId == EditorInfo.IME_ACTION_DONE + || actionId == EditorInfo.IME_ACTION_NEXT) { + if (view == mPukText && mPukText.getText().length() < 8) { + mNavigationManager.setMessage(R.string.kg_invalid_sim_puk_hint); + mPukText.setText(""); + mPukText.requestFocus(); + return true; + } else if (view == mPinText) { + if (mPinText.getText().length() < 4 || mPinText.getText().length() > 8) { + mNavigationManager.setMessage(R.string.kg_invalid_sim_pin_hint); + mPinText.setText(""); + mPinText.requestFocus(); + } else { + checkPuk(); + } + return true; + } + } + return false; + } + + @Override + public void setLockPatternUtils(LockPatternUtils utils) { + mLockPatternUtils = utils; + } + + @Override + public void reset() { + mNavigationManager.setMessage(R.string.kg_sim_puk_recovery_hint); + mPinText.setText(""); + mPukText.setText(""); + mPukText.requestFocus(); + } + + @Override + public KeyguardSecurityCallback getCallback() { + return mCallback; + } + +} diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardStatusView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardStatusView.java new file mode 100644 index 0000000..d6ce967 --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardStatusView.java @@ -0,0 +1,44 @@ +/* + * 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.content.Context; +import android.util.AttributeSet; +import android.widget.GridLayout; + +public class KeyguardStatusView extends GridLayout { + public KeyguardStatusView(Context context) { + this(context, null, 0); + } + + public KeyguardStatusView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public KeyguardStatusView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + + // StatusView manages all of the widgets in this view. + new KeyguardStatusViewManager(this); + } + +} diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardStatusViewManager.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardStatusViewManager.java new file mode 100644 index 0000000..06ed88a --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardStatusViewManager.java @@ -0,0 +1,570 @@ +/* + * Copyright (C) 2011 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 com.android.internal.R; +import com.android.internal.telephony.IccCardConstants; +import com.android.internal.widget.DigitalClock; +import com.android.internal.widget.LockPatternUtils; + +import java.util.Date; + +import libcore.util.MutableInt; + +import android.content.ContentResolver; +import android.content.Context; +import android.provider.Settings; +import android.text.TextUtils; +import android.text.format.DateFormat; +import android.util.Log; +import android.view.View; +import android.widget.Button; +import android.widget.TextView; + +/*** + * Manages a number of views inside of LockScreen layouts. See below for a list of widgets + */ +class KeyguardStatusViewManager { + private static final boolean DEBUG = false; + private static final String TAG = "KeyguardStatusView"; + + public static final int LOCK_ICON = 0; // R.drawable.ic_lock_idle_lock; + public static final int ALARM_ICON = R.drawable.ic_lock_idle_alarm; + public static final int CHARGING_ICON = 0; //R.drawable.ic_lock_idle_charging; + public static final int BATTERY_LOW_ICON = 0; //R.drawable.ic_lock_idle_low_battery; + + private static final int INSTRUCTION_TEXT = 10; + private static final int CARRIER_TEXT = 11; + private static final int CARRIER_HELP_TEXT = 12; + private static final int HELP_MESSAGE_TEXT = 13; + private static final int OWNER_INFO = 14; + private static final int BATTERY_INFO = 15; + + private StatusMode mStatus; + private String mDateFormatString; + + // Views that this class controls. + // NOTE: These may be null in some LockScreen screens and should protect from NPE + private TextView mCarrierView; + private TextView mDateView; + private TextView mStatus1View; + private TextView mOwnerInfoView; + private TextView mAlarmStatusView; + + // Top-level container view for above views + private View mContainer; + + // are we showing battery information? + private boolean mShowingBatteryInfo = false; + + // last known plugged in state + private boolean mPluggedIn = false; + + // last known battery level + private int mBatteryLevel = 100; + + // last known SIM state + protected IccCardConstants.State mSimState; + + private LockPatternUtils mLockPatternUtils; + private KeyguardUpdateMonitor mUpdateMonitor; + + // Shadowed text values + private CharSequence mCarrierText; + private CharSequence mCarrierHelpText; + private String mHelpMessageText; + private String mInstructionText; + private CharSequence mOwnerInfoText; + private boolean mShowingStatus; + private CharSequence mPlmn; + private CharSequence mSpn; + protected int mPhoneState; + private DigitalClock mDigitalClock; + protected boolean mBatteryCharged; + protected boolean mBatteryIsLow; + private boolean mEmergencyButtonEnabledBecauseSimLocked; + private Button mEmergencyCallButton; + private boolean mEmergencyCallButtonEnabledInScreen; + + /** + * + * @param view the containing view of all widgets + * @param updateMonitor the update monitor to use + * @param lockPatternUtils lock pattern util object + * @param callback used to invoke emergency dialer + * @param emergencyButtonEnabledInScreen whether emergency button is enabled by default + */ + public KeyguardStatusViewManager(View view) { + if (DEBUG) Log.v(TAG, "KeyguardStatusViewManager()"); + mContainer = view; + mDateFormatString = getContext().getString(R.string.abbrev_wday_month_day_no_year); + mLockPatternUtils = new LockPatternUtils(view.getContext()); + mUpdateMonitor = KeyguardUpdateMonitor.getInstance(view.getContext()); + + mCarrierView = (TextView) findViewById(R.id.carrier); + mDateView = (TextView) findViewById(R.id.date); + mStatus1View = (TextView) findViewById(R.id.status1); + mAlarmStatusView = (TextView) findViewById(R.id.alarm_status); + mOwnerInfoView = (TextView) findViewById(R.id.propertyOf); + mDigitalClock = (DigitalClock) findViewById(R.id.time); + + // Registering this callback immediately updates the battery state, among other things. + mUpdateMonitor.registerCallback(mInfoCallback); + + resetStatusInfo(); + refreshDate(); + updateOwnerInfo(); + + // Required to get Marquee to work. + final View scrollableViews[] = { mCarrierView, mDateView, mStatus1View, mOwnerInfoView, + mAlarmStatusView }; + for (View v : scrollableViews) { + if (v != null) { + v.setSelected(true); + } + } + } + + void setInstructionText(String string) { + mInstructionText = string; + update(INSTRUCTION_TEXT, string); + } + + void setCarrierText(CharSequence string) { + mCarrierText = string; + update(CARRIER_TEXT, string); + } + + void setOwnerInfo(CharSequence string) { + mOwnerInfoText = string; + update(OWNER_INFO, string); + } + + /** + * Sets the carrier help text message, if view is present. Carrier help text messages are + * typically for help dealing with SIMS and connectivity. + * + * @param resId resource id of the message + */ + public void setCarrierHelpText(int resId) { + mCarrierHelpText = getText(resId); + update(CARRIER_HELP_TEXT, mCarrierHelpText); + } + + private CharSequence getText(int resId) { + return resId == 0 ? null : getContext().getText(resId); + } + + /** + * Unlock help message. This is typically for help with unlock widgets, e.g. "wrong password" + * or "try again." + * + * @param textResId + * @param lockIcon + */ + public void setHelpMessage(int textResId, int lockIcon) { + final CharSequence tmp = getText(textResId); + mHelpMessageText = tmp == null ? null : tmp.toString(); + update(HELP_MESSAGE_TEXT, mHelpMessageText); + } + + private void update(int what, CharSequence string) { + updateStatusLines(mShowingStatus); + } + + public void onPause() { + if (DEBUG) Log.v(TAG, "onPause()"); + mUpdateMonitor.removeCallback(mInfoCallback); + } + + /** {@inheritDoc} */ + public void onResume() { + if (DEBUG) Log.v(TAG, "onResume()"); + + // First update the clock, if present. + if (mDigitalClock != null) { + mDigitalClock.updateTime(); + } + + mUpdateMonitor.registerCallback(mInfoCallback); + resetStatusInfo(); + } + + void resetStatusInfo() { + mInstructionText = null; + updateStatusLines(true); + } + + /** + * Update the status lines based on these rules: + * AlarmStatus: Alarm state always gets it's own line. + * Status1 is shared between help, battery status and generic unlock instructions, + * prioritized in that order. + * @param showStatusLines status lines are shown if true + */ + void updateStatusLines(boolean showStatusLines) { + if (DEBUG) Log.v(TAG, "updateStatusLines(" + showStatusLines + ")"); + mShowingStatus = showStatusLines; + updateAlarmInfo(); + updateOwnerInfo(); + updateStatus1(); + updateCarrierText(); + } + + private void updateAlarmInfo() { + if (mAlarmStatusView != null) { + String nextAlarm = mLockPatternUtils.getNextAlarm(); + boolean showAlarm = mShowingStatus && !TextUtils.isEmpty(nextAlarm); + mAlarmStatusView.setText(nextAlarm); + mAlarmStatusView.setCompoundDrawablesWithIntrinsicBounds(ALARM_ICON, 0, 0, 0); + mAlarmStatusView.setVisibility(showAlarm ? View.VISIBLE : View.GONE); + } + } + + private void updateOwnerInfo() { + final ContentResolver res = getContext().getContentResolver(); + final boolean ownerInfoEnabled = Settings.Secure.getInt(res, + Settings.Secure.LOCK_SCREEN_OWNER_INFO_ENABLED, 1) != 0; + mOwnerInfoText = ownerInfoEnabled ? + Settings.Secure.getString(res, Settings.Secure.LOCK_SCREEN_OWNER_INFO) : null; + if (mOwnerInfoView != null) { + mOwnerInfoView.setText(mOwnerInfoText); + mOwnerInfoView.setVisibility(TextUtils.isEmpty(mOwnerInfoText) ? View.GONE:View.VISIBLE); + } + } + + private void updateStatus1() { + if (mStatus1View != null) { + MutableInt icon = new MutableInt(0); + CharSequence string = getPriorityTextMessage(icon); + mStatus1View.setText(string); + mStatus1View.setCompoundDrawablesWithIntrinsicBounds(icon.value, 0, 0, 0); + mStatus1View.setVisibility(mShowingStatus ? View.VISIBLE : View.INVISIBLE); + } + } + + private void updateCarrierText() { + mCarrierView.setText(mCarrierText); + } + + private CharSequence getAltTextMessage(MutableInt icon) { + // If we have replaced the status area with a single widget, then this code + // prioritizes what to show in that space when all transient messages are gone. + CharSequence string = null; + if (mShowingBatteryInfo) { + // Battery status + if (mPluggedIn) { + // Charging, charged or waiting to charge. + string = getContext().getString(mBatteryCharged ? R.string.lockscreen_charged + :R.string.lockscreen_plugged_in, mBatteryLevel); + icon.value = CHARGING_ICON; + } else if (mBatteryIsLow) { + // Battery is low + string = getContext().getString(R.string.lockscreen_low_battery); + icon.value = BATTERY_LOW_ICON; + } + } else { + string = mCarrierText; + } + return string; + } + + private CharSequence getPriorityTextMessage(MutableInt icon) { + CharSequence string = null; + if (!TextUtils.isEmpty(mInstructionText)) { + // Instructions only + string = mInstructionText; + icon.value = LOCK_ICON; + } else if (mShowingBatteryInfo) { + // Battery status + if (mPluggedIn) { + // Charging, charged or waiting to charge. + string = getContext().getString(mBatteryCharged ? R.string.lockscreen_charged + :R.string.lockscreen_plugged_in, mBatteryLevel); + icon.value = CHARGING_ICON; + } else if (mBatteryIsLow) { + // Battery is low + string = getContext().getString(R.string.lockscreen_low_battery); + icon.value = BATTERY_LOW_ICON; + } + } else if (mOwnerInfoView == null && mOwnerInfoText != null) { + string = mOwnerInfoText; + } + return string; + } + + void refreshDate() { + if (mDateView != null) { + mDateView.setText(DateFormat.format(mDateFormatString, new Date())); + } + } + + /** + * Determine the current status of the lock screen given the sim state and other stuff. + */ + public StatusMode getStatusForIccState(IccCardConstants.State simState) { + // Since reading the SIM may take a while, we assume it is present until told otherwise. + if (simState == null) { + return StatusMode.Normal; + } + + final boolean missingAndNotProvisioned = (!mUpdateMonitor.isDeviceProvisioned() + && (simState == IccCardConstants.State.ABSENT || + simState == IccCardConstants.State.PERM_DISABLED)); + + // Assume we're NETWORK_LOCKED if not provisioned + simState = missingAndNotProvisioned ? IccCardConstants.State.NETWORK_LOCKED : simState; + switch (simState) { + case ABSENT: + return StatusMode.SimMissing; + case NETWORK_LOCKED: + return StatusMode.SimMissingLocked; + case NOT_READY: + return StatusMode.SimMissing; + case PIN_REQUIRED: + return StatusMode.SimLocked; + case PUK_REQUIRED: + return StatusMode.SimPukLocked; + case READY: + return StatusMode.Normal; + case PERM_DISABLED: + return StatusMode.SimPermDisabled; + case UNKNOWN: + return StatusMode.SimMissing; + } + return StatusMode.SimMissing; + } + + private Context getContext() { + return mContainer.getContext(); + } + + /** + * Update carrier text, carrier help and emergency button to match the current status based + * on SIM state. + * + * @param simState + */ + private void updateCarrierStateWithSimStatus(IccCardConstants.State simState) { + if (DEBUG) Log.d(TAG, "updateCarrierTextWithSimStatus(), simState = " + simState); + + CharSequence carrierText = null; + int carrierHelpTextId = 0; + mEmergencyButtonEnabledBecauseSimLocked = false; + mStatus = getStatusForIccState(simState); + mSimState = simState; + switch (mStatus) { + case Normal: + carrierText = makeCarierString(mPlmn, mSpn); + break; + + case NetworkLocked: + carrierText = makeCarrierStringOnEmergencyCapable( + getContext().getText(R.string.lockscreen_network_locked_message), + mPlmn); + carrierHelpTextId = R.string.lockscreen_instructions_when_pattern_disabled; + break; + + case SimMissing: + // Shows "No SIM card | Emergency calls only" on devices that are voice-capable. + // This depends on mPlmn containing the text "Emergency calls only" when the radio + // has some connectivity. Otherwise, it should be null or empty and just show + // "No SIM card" + carrierText = makeCarrierStringOnEmergencyCapable( + getContext().getText(R.string.lockscreen_missing_sim_message_short), + mPlmn); + carrierHelpTextId = R.string.lockscreen_missing_sim_instructions_long; + break; + + case SimPermDisabled: + carrierText = getContext().getText( + R.string.lockscreen_permanent_disabled_sim_message_short); + carrierHelpTextId = R.string.lockscreen_permanent_disabled_sim_instructions; + mEmergencyButtonEnabledBecauseSimLocked = true; + break; + + case SimMissingLocked: + carrierText = makeCarrierStringOnEmergencyCapable( + getContext().getText(R.string.lockscreen_missing_sim_message_short), + mPlmn); + carrierHelpTextId = R.string.lockscreen_missing_sim_instructions; + mEmergencyButtonEnabledBecauseSimLocked = true; + break; + + case SimLocked: + carrierText = makeCarrierStringOnEmergencyCapable( + getContext().getText(R.string.lockscreen_sim_locked_message), + mPlmn); + mEmergencyButtonEnabledBecauseSimLocked = true; + break; + + case SimPukLocked: + carrierText = makeCarrierStringOnEmergencyCapable( + getContext().getText(R.string.lockscreen_sim_puk_locked_message), + mPlmn); + if (!mLockPatternUtils.isPukUnlockScreenEnable()) { + // This means we're showing the PUK unlock screen + mEmergencyButtonEnabledBecauseSimLocked = true; + } + break; + } + + setCarrierText(carrierText); + setCarrierHelpText(carrierHelpTextId); + updateEmergencyCallButtonState(mPhoneState); + } + + + /* + * Add emergencyCallMessage to carrier string only if phone supports emergency calls. + */ + private CharSequence makeCarrierStringOnEmergencyCapable( + CharSequence simMessage, CharSequence emergencyCallMessage) { + if (mLockPatternUtils.isEmergencyCallCapable()) { + return makeCarierString(simMessage, emergencyCallMessage); + } + return simMessage; + } + + private View findViewById(int id) { + return mContainer.findViewById(id); + } + + /** + * The status of this lock screen. Primarily used for widgets on LockScreen. + */ + enum StatusMode { + /** + * Normal case (sim card present, it's not locked) + */ + Normal(true), + + /** + * The sim card is 'network locked'. + */ + NetworkLocked(true), + + /** + * The sim card is missing. + */ + SimMissing(false), + + /** + * The sim card is missing, and this is the device isn't provisioned, so we don't let + * them get past the screen. + */ + SimMissingLocked(false), + + /** + * The sim card is PUK locked, meaning they've entered the wrong sim unlock code too many + * times. + */ + SimPukLocked(false), + + /** + * The sim card is locked. + */ + SimLocked(true), + + /** + * The sim card is permanently disabled due to puk unlock failure + */ + SimPermDisabled(false); + + private final boolean mShowStatusLines; + + StatusMode(boolean mShowStatusLines) { + this.mShowStatusLines = mShowStatusLines; + } + + /** + * @return Whether the status lines (battery level and / or next alarm) are shown while + * in this state. Mostly dictated by whether this is room for them. + */ + public boolean shouldShowStatusLines() { + return mShowStatusLines; + } + } + + private void updateEmergencyCallButtonState(int phoneState) { + if (mEmergencyCallButton != null) { + boolean enabledBecauseSimLocked = + mLockPatternUtils.isEmergencyCallEnabledWhileSimLocked() + && mEmergencyButtonEnabledBecauseSimLocked; + boolean shown = mEmergencyCallButtonEnabledInScreen || enabledBecauseSimLocked; + mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCallButton, + phoneState, shown); + } + } + + private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() { + + @Override + public void onRefreshBatteryInfo(KeyguardUpdateMonitor.BatteryStatus status) { + mShowingBatteryInfo = status.isPluggedIn() || status.isBatteryLow(); + mPluggedIn = status.isPluggedIn(); + mBatteryLevel = status.level; + mBatteryCharged = status.isCharged(); + mBatteryIsLow = status.isBatteryLow(); + final MutableInt tmpIcon = new MutableInt(0); + update(BATTERY_INFO, getAltTextMessage(tmpIcon)); + } + + @Override + public void onTimeChanged() { + refreshDate(); + } + + @Override + public void onRefreshCarrierInfo(CharSequence plmn, CharSequence spn) { + mPlmn = plmn; + mSpn = spn; + updateCarrierStateWithSimStatus(mSimState); + } + + @Override + public void onPhoneStateChanged(int phoneState) { + mPhoneState = phoneState; + updateEmergencyCallButtonState(phoneState); + } + + @Override + public void onSimStateChanged(IccCardConstants.State simState) { + updateCarrierStateWithSimStatus(simState); + } + }; + + /** + * Performs concentenation of PLMN/SPN + * @param plmn + * @param spn + * @return + */ + private static CharSequence makeCarierString(CharSequence plmn, CharSequence spn) { + final boolean plmnValid = !TextUtils.isEmpty(plmn); + final boolean spnValid = !TextUtils.isEmpty(spn); + if (plmnValid && spnValid) { + return plmn + "|" + spn; + } else if (plmnValid) { + return plmn; + } else if (spnValid) { + return spn; + } else { + return ""; + } + } +} diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardUpdateMonitor.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardUpdateMonitor.java new file mode 100644 index 0000000..dad0dff --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardUpdateMonitor.java @@ -0,0 +1,710 @@ +/* + * Copyright (C) 2008 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.admin.DevicePolicyManager; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.database.ContentObserver; +import static android.os.BatteryManager.BATTERY_STATUS_FULL; +import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN; +import static android.os.BatteryManager.BATTERY_HEALTH_UNKNOWN; +import static android.os.BatteryManager.EXTRA_STATUS; +import static android.os.BatteryManager.EXTRA_PLUGGED; +import static android.os.BatteryManager.EXTRA_LEVEL; +import static android.os.BatteryManager.EXTRA_HEALTH; +import android.media.AudioManager; +import android.os.BatteryManager; +import android.os.Handler; +import android.os.Message; +import android.provider.Settings; + +import com.android.internal.telephony.IccCardConstants; +import com.android.internal.telephony.TelephonyIntents; + +import android.telephony.TelephonyManager; +import android.util.Log; +import com.android.internal.R; +import com.google.android.collect.Lists; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; + +/** + * Watches for updates that may be interesting to the keyguard, and provides + * the up to date information as well as a registration for callbacks that care + * to be updated. + * + * Note: under time crunch, this has been extended to include some stuff that + * doesn't really belong here. see {@link #handleBatteryUpdate} where it shutdowns + * the device, and {@link #getFailedAttempts()}, {@link #reportFailedAttempt()} + * and {@link #clearFailedAttempts()}. Maybe we should rename this 'KeyguardContext'... + */ +public class KeyguardUpdateMonitor { + + private static final String TAG = "KeyguardUpdateMonitor"; + private static final boolean DEBUG = false; + private static final boolean DEBUG_SIM_STATES = DEBUG || false; + private static final int FAILED_BIOMETRIC_UNLOCK_ATTEMPTS_BEFORE_BACKUP = 3; + private static final int LOW_BATTERY_THRESHOLD = 20; + + // Callback messages + private static final int MSG_TIME_UPDATE = 301; + private static final int MSG_BATTERY_UPDATE = 302; + private static final int MSG_CARRIER_INFO_UPDATE = 303; + private static final int MSG_SIM_STATE_CHANGE = 304; + private static final int MSG_RINGER_MODE_CHANGED = 305; + private static final int MSG_PHONE_STATE_CHANGED = 306; + private static final int MSG_CLOCK_VISIBILITY_CHANGED = 307; + private static final int MSG_DEVICE_PROVISIONED = 308; + protected static final int MSG_DPM_STATE_CHANGED = 309; + protected static final int MSG_USER_SWITCHED = 310; + protected static final int MSG_USER_REMOVED = 311; + + private static KeyguardUpdateMonitor sInstance; + + private final Context mContext; + + // Telephony state + private IccCardConstants.State mSimState = IccCardConstants.State.READY; + private CharSequence mTelephonyPlmn; + private CharSequence mTelephonySpn; + private int mRingMode; + private int mPhoneState; + + // Device provisioning state + private boolean mDeviceProvisioned; + + // Battery status + private BatteryStatus mBatteryStatus; + + // Password attempts + private int mFailedAttempts = 0; + private int mFailedBiometricUnlockAttempts = 0; + + private boolean mClockVisible; + + private ArrayList<WeakReference<KeyguardUpdateMonitorCallback>> + mCallbacks = Lists.newArrayList(); + private ContentObserver mContentObserver; + + private final Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_TIME_UPDATE: + handleTimeUpdate(); + break; + case MSG_BATTERY_UPDATE: + handleBatteryUpdate((BatteryStatus) msg.obj); + break; + case MSG_CARRIER_INFO_UPDATE: + handleCarrierInfoUpdate(); + break; + case MSG_SIM_STATE_CHANGE: + handleSimStateChange((SimArgs) msg.obj); + break; + case MSG_RINGER_MODE_CHANGED: + handleRingerModeChange(msg.arg1); + break; + case MSG_PHONE_STATE_CHANGED: + handlePhoneStateChanged((String)msg.obj); + break; + case MSG_CLOCK_VISIBILITY_CHANGED: + handleClockVisibilityChanged(); + break; + case MSG_DEVICE_PROVISIONED: + handleDeviceProvisioned(); + break; + case MSG_DPM_STATE_CHANGED: + handleDevicePolicyManagerStateChanged(); + break; + case MSG_USER_SWITCHED: + handleUserSwitched(msg.arg1); + break; + case MSG_USER_REMOVED: + handleUserRemoved(msg.arg1); + break; + } + } + }; + + private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { + + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + if (DEBUG) Log.d(TAG, "received broadcast " + action); + + if (Intent.ACTION_TIME_TICK.equals(action) + || Intent.ACTION_TIME_CHANGED.equals(action) + || Intent.ACTION_TIMEZONE_CHANGED.equals(action)) { + mHandler.sendMessage(mHandler.obtainMessage(MSG_TIME_UPDATE)); + } else if (TelephonyIntents.SPN_STRINGS_UPDATED_ACTION.equals(action)) { + mTelephonyPlmn = getTelephonyPlmnFrom(intent); + mTelephonySpn = getTelephonySpnFrom(intent); + mHandler.sendMessage(mHandler.obtainMessage(MSG_CARRIER_INFO_UPDATE)); + } else if (Intent.ACTION_BATTERY_CHANGED.equals(action)) { + final int status = intent.getIntExtra(EXTRA_STATUS, BATTERY_STATUS_UNKNOWN); + final int plugged = intent.getIntExtra(EXTRA_PLUGGED, 0); + final int level = intent.getIntExtra(EXTRA_LEVEL, 0); + final int health = intent.getIntExtra(EXTRA_HEALTH, BATTERY_HEALTH_UNKNOWN); + final Message msg = mHandler.obtainMessage( + MSG_BATTERY_UPDATE, new BatteryStatus(status, level, plugged, health)); + mHandler.sendMessage(msg); + } else if (TelephonyIntents.ACTION_SIM_STATE_CHANGED.equals(action)) { + if (DEBUG_SIM_STATES) { + Log.v(TAG, "action " + action + " state" + + intent.getStringExtra(IccCardConstants.INTENT_KEY_ICC_STATE)); + } + mHandler.sendMessage(mHandler.obtainMessage( + MSG_SIM_STATE_CHANGE, SimArgs.fromIntent(intent))); + } else if (AudioManager.RINGER_MODE_CHANGED_ACTION.equals(action)) { + mHandler.sendMessage(mHandler.obtainMessage(MSG_RINGER_MODE_CHANGED, + intent.getIntExtra(AudioManager.EXTRA_RINGER_MODE, -1), 0)); + } else if (TelephonyManager.ACTION_PHONE_STATE_CHANGED.equals(action)) { + String state = intent.getStringExtra(TelephonyManager.EXTRA_STATE); + mHandler.sendMessage(mHandler.obtainMessage(MSG_PHONE_STATE_CHANGED, state)); + } else if (DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED + .equals(action)) { + mHandler.sendMessage(mHandler.obtainMessage(MSG_DPM_STATE_CHANGED)); + } else if (Intent.ACTION_USER_SWITCHED.equals(action)) { + mHandler.sendMessage(mHandler.obtainMessage(MSG_USER_SWITCHED, + intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0), 0)); + } else if (Intent.ACTION_USER_REMOVED.equals(action)) { + mHandler.sendMessage(mHandler.obtainMessage(MSG_USER_REMOVED, + intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0), 0)); + } + } + }; + + /** + * When we receive a + * {@link com.android.internal.telephony.TelephonyIntents#ACTION_SIM_STATE_CHANGED} broadcast, + * and then pass a result via our handler to {@link KeyguardUpdateMonitor#handleSimStateChange}, + * we need a single object to pass to the handler. This class helps decode + * the intent and provide a {@link SimCard.State} result. + */ + private static class SimArgs { + public final IccCardConstants.State simState; + + SimArgs(IccCardConstants.State state) { + simState = state; + } + + static SimArgs fromIntent(Intent intent) { + IccCardConstants.State state; + if (!TelephonyIntents.ACTION_SIM_STATE_CHANGED.equals(intent.getAction())) { + throw new IllegalArgumentException("only handles intent ACTION_SIM_STATE_CHANGED"); + } + String stateExtra = intent.getStringExtra(IccCardConstants.INTENT_KEY_ICC_STATE); + if (IccCardConstants.INTENT_VALUE_ICC_ABSENT.equals(stateExtra)) { + final String absentReason = intent + .getStringExtra(IccCardConstants.INTENT_KEY_LOCKED_REASON); + + if (IccCardConstants.INTENT_VALUE_ABSENT_ON_PERM_DISABLED.equals( + absentReason)) { + state = IccCardConstants.State.PERM_DISABLED; + } else { + state = IccCardConstants.State.ABSENT; + } + } else if (IccCardConstants.INTENT_VALUE_ICC_READY.equals(stateExtra)) { + state = IccCardConstants.State.READY; + } else if (IccCardConstants.INTENT_VALUE_ICC_LOCKED.equals(stateExtra)) { + final String lockedReason = intent + .getStringExtra(IccCardConstants.INTENT_KEY_LOCKED_REASON); + if (IccCardConstants.INTENT_VALUE_LOCKED_ON_PIN.equals(lockedReason)) { + state = IccCardConstants.State.PIN_REQUIRED; + } else if (IccCardConstants.INTENT_VALUE_LOCKED_ON_PUK.equals(lockedReason)) { + state = IccCardConstants.State.PUK_REQUIRED; + } else { + state = IccCardConstants.State.UNKNOWN; + } + } else if (IccCardConstants.INTENT_VALUE_LOCKED_NETWORK.equals(stateExtra)) { + state = IccCardConstants.State.NETWORK_LOCKED; + } else { + state = IccCardConstants.State.UNKNOWN; + } + return new SimArgs(state); + } + + public String toString() { + return simState.toString(); + } + } + + /* package */ static class BatteryStatus { + public final int status; + public final int level; + public final int plugged; + public final int health; + public BatteryStatus(int status, int level, int plugged, int health) { + this.status = status; + this.level = level; + this.plugged = plugged; + this.health = health; + } + + /** + * Determine whether the device is plugged in (USB or power). + * @return true if the device is plugged in. + */ + boolean isPluggedIn() { + return plugged == BatteryManager.BATTERY_PLUGGED_AC + || plugged == BatteryManager.BATTERY_PLUGGED_USB; + } + + /** + * Whether or not the device is charged. Note that some devices never return 100% for + * battery level, so this allows either battery level or status to determine if the + * battery is charged. + * @return true if the device is charged + */ + public boolean isCharged() { + return status == BATTERY_STATUS_FULL || level >= 100; + } + + /** + * Whether battery is low and needs to be charged. + * @return true if battery is low + */ + public boolean isBatteryLow() { + return level < LOW_BATTERY_THRESHOLD; + } + + } + + public static KeyguardUpdateMonitor getInstance(Context context) { + if (sInstance == null) { + sInstance = new KeyguardUpdateMonitor(context); + } + return sInstance; + } + + private KeyguardUpdateMonitor(Context context) { + mContext = context; + + mDeviceProvisioned = Settings.Secure.getInt( + mContext.getContentResolver(), Settings.Secure.DEVICE_PROVISIONED, 0) != 0; + + // Since device can't be un-provisioned, we only need to register a content observer + // to update mDeviceProvisioned when we are... + if (!mDeviceProvisioned) { + watchForDeviceProvisioning(); + } + + // Take a guess at initial SIM state, battery status and PLMN until we get an update + mSimState = IccCardConstants.State.NOT_READY; + mBatteryStatus = new BatteryStatus(BATTERY_STATUS_UNKNOWN, 100, 0, 0); + mTelephonyPlmn = getDefaultPlmn(); + + // Watch for interesting updates + final IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_TIME_TICK); + filter.addAction(Intent.ACTION_TIME_CHANGED); + filter.addAction(Intent.ACTION_BATTERY_CHANGED); + filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); + filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED); + filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED); + filter.addAction(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION); + filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION); + filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED); + filter.addAction(Intent.ACTION_USER_SWITCHED); + filter.addAction(Intent.ACTION_USER_REMOVED); + context.registerReceiver(mBroadcastReceiver, filter); + } + + private void watchForDeviceProvisioning() { + mContentObserver = new ContentObserver(mHandler) { + @Override + public void onChange(boolean selfChange) { + super.onChange(selfChange); + mDeviceProvisioned = Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.DEVICE_PROVISIONED, 0) != 0; + if (mDeviceProvisioned) { + mHandler.sendMessage(mHandler.obtainMessage(MSG_DEVICE_PROVISIONED)); + } + if (DEBUG) Log.d(TAG, "DEVICE_PROVISIONED state = " + mDeviceProvisioned); + } + }; + + mContext.getContentResolver().registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.DEVICE_PROVISIONED), + false, mContentObserver); + + // prevent a race condition between where we check the flag and where we register the + // observer by grabbing the value once again... + boolean provisioned = Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.DEVICE_PROVISIONED, 0) != 0; + if (provisioned != mDeviceProvisioned) { + mDeviceProvisioned = provisioned; + if (mDeviceProvisioned) { + mHandler.sendMessage(mHandler.obtainMessage(MSG_DEVICE_PROVISIONED)); + } + } + } + + /** + * Handle {@link #MSG_DPM_STATE_CHANGED} + */ + protected void handleDevicePolicyManagerStateChanged() { + for (int i = mCallbacks.size() - 1; i >= 0; i--) { + KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); + if (cb != null) { + cb.onDevicePolicyManagerStateChanged(); + } + } + } + + /** + * Handle {@link #MSG_USER_SWITCHED} + */ + protected void handleUserSwitched(int userId) { + for (int i = 0; i < mCallbacks.size(); i++) { + KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); + if (cb != null) { + cb.onUserSwitched(userId); + } + } + } + + /** + * Handle {@link #MSG_USER_SWITCHED} + */ + protected void handleUserRemoved(int userId) { + for (int i = 0; i < mCallbacks.size(); i++) { + KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); + if (cb != null) { + cb.onUserRemoved(userId); + } + } + } + + /** + * Handle {@link #MSG_DEVICE_PROVISIONED} + */ + protected void handleDeviceProvisioned() { + for (int i = 0; i < mCallbacks.size(); i++) { + KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); + if (cb != null) { + cb.onDeviceProvisioned(); + } + } + if (mContentObserver != null) { + // We don't need the observer anymore... + mContext.getContentResolver().unregisterContentObserver(mContentObserver); + mContentObserver = null; + } + } + + /** + * Handle {@link #MSG_PHONE_STATE_CHANGED} + */ + protected void handlePhoneStateChanged(String newState) { + if (DEBUG) Log.d(TAG, "handlePhoneStateChanged(" + newState + ")"); + if (TelephonyManager.EXTRA_STATE_IDLE.equals(newState)) { + mPhoneState = TelephonyManager.CALL_STATE_IDLE; + } else if (TelephonyManager.EXTRA_STATE_OFFHOOK.equals(newState)) { + mPhoneState = TelephonyManager.CALL_STATE_OFFHOOK; + } else if (TelephonyManager.EXTRA_STATE_RINGING.equals(newState)) { + mPhoneState = TelephonyManager.CALL_STATE_RINGING; + } + for (int i = 0; i < mCallbacks.size(); i++) { + KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); + if (cb != null) { + cb.onPhoneStateChanged(mPhoneState); + } + } + } + + /** + * Handle {@link #MSG_RINGER_MODE_CHANGED} + */ + protected void handleRingerModeChange(int mode) { + if (DEBUG) Log.d(TAG, "handleRingerModeChange(" + mode + ")"); + mRingMode = mode; + for (int i = 0; i < mCallbacks.size(); i++) { + KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); + if (cb != null) { + cb.onRingerModeChanged(mode); + } + } + } + + /** + * Handle {@link #MSG_TIME_UPDATE} + */ + private void handleTimeUpdate() { + if (DEBUG) Log.d(TAG, "handleTimeUpdate"); + for (int i = 0; i < mCallbacks.size(); i++) { + KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); + if (cb != null) { + cb.onTimeChanged(); + } + } + } + + /** + * Handle {@link #MSG_BATTERY_UPDATE} + */ + private void handleBatteryUpdate(BatteryStatus status) { + if (DEBUG) Log.d(TAG, "handleBatteryUpdate"); + final boolean batteryUpdateInteresting = isBatteryUpdateInteresting(mBatteryStatus, status); + mBatteryStatus = status; + if (batteryUpdateInteresting) { + for (int i = 0; i < mCallbacks.size(); i++) { + KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); + if (cb != null) { + cb.onRefreshBatteryInfo(status); + } + } + } + } + + /** + * Handle {@link #MSG_CARRIER_INFO_UPDATE} + */ + private void handleCarrierInfoUpdate() { + if (DEBUG) Log.d(TAG, "handleCarrierInfoUpdate: plmn = " + mTelephonyPlmn + + ", spn = " + mTelephonySpn); + + for (int i = 0; i < mCallbacks.size(); i++) { + KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); + if (cb != null) { + cb.onRefreshCarrierInfo(mTelephonyPlmn, mTelephonySpn); + } + } + } + + /** + * Handle {@link #MSG_SIM_STATE_CHANGE} + */ + private void handleSimStateChange(SimArgs simArgs) { + final IccCardConstants.State state = simArgs.simState; + + if (DEBUG) { + Log.d(TAG, "handleSimStateChange: intentValue = " + simArgs + " " + + "state resolved to " + state.toString()); + } + + if (state != IccCardConstants.State.UNKNOWN && state != mSimState) { + mSimState = state; + for (int i = 0; i < mCallbacks.size(); i++) { + KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); + if (cb != null) { + cb.onSimStateChanged(state); + } + } + } + } + + /** + * Handle {@link #MSG_CLOCK_VISIBILITY_CHANGED} + */ + private void handleClockVisibilityChanged() { + if (DEBUG) Log.d(TAG, "handleClockVisibilityChanged()"); + for (int i = 0; i < mCallbacks.size(); i++) { + KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); + if (cb != null) { + cb.onClockVisibilityChanged(); + } + } + } + + private static boolean isBatteryUpdateInteresting(BatteryStatus old, BatteryStatus current) { + final boolean nowPluggedIn = current.isPluggedIn(); + final boolean wasPluggedIn = old.isPluggedIn(); + final boolean stateChangedWhilePluggedIn = + wasPluggedIn == true && nowPluggedIn == true + && (old.status != current.status); + + // change in plug state is always interesting + if (wasPluggedIn != nowPluggedIn || stateChangedWhilePluggedIn) { + return true; + } + + // change in battery level while plugged in + if (nowPluggedIn && old.level != current.level) { + return true; + } + + // change where battery needs charging + if (!nowPluggedIn && current.isBatteryLow() && current.level != old.level) { + return true; + } + return false; + } + + /** + * @param intent The intent with action {@link TelephonyIntents#SPN_STRINGS_UPDATED_ACTION} + * @return The string to use for the plmn, or null if it should not be shown. + */ + private CharSequence getTelephonyPlmnFrom(Intent intent) { + if (intent.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_PLMN, false)) { + final String plmn = intent.getStringExtra(TelephonyIntents.EXTRA_PLMN); + return (plmn != null) ? plmn : getDefaultPlmn(); + } + return null; + } + + /** + * @return The default plmn (no service) + */ + private CharSequence getDefaultPlmn() { + return mContext.getResources().getText(R.string.lockscreen_carrier_default); + } + + /** + * @param intent The intent with action {@link Telephony.Intents#SPN_STRINGS_UPDATED_ACTION} + * @return The string to use for the plmn, or null if it should not be shown. + */ + private CharSequence getTelephonySpnFrom(Intent intent) { + if (intent.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_SPN, false)) { + final String spn = intent.getStringExtra(TelephonyIntents.EXTRA_SPN); + if (spn != null) { + return spn; + } + } + return null; + } + + /** + * Remove the given observer's callback. + * + * @param observer The observer to remove + */ + public void removeCallback(Object observer) { + mCallbacks.remove(observer); + } + + /** + * Register to receive notifications about general keyguard information + * (see {@link InfoCallback}. + * @param callback The callback. + */ + public void registerCallback(KeyguardUpdateMonitorCallback callback) { + if (!mCallbacks.contains(callback)) { + mCallbacks.add(new WeakReference<KeyguardUpdateMonitorCallback>(callback)); + // Notify listener of the current state + callback.onRefreshBatteryInfo(mBatteryStatus); + callback.onTimeChanged(); + callback.onRingerModeChanged(mRingMode); + callback.onPhoneStateChanged(mPhoneState); + callback.onRefreshCarrierInfo(mTelephonyPlmn, mTelephonySpn); + callback.onClockVisibilityChanged(); + callback.onSimStateChanged(mSimState); + } else { + if (DEBUG) Log.e(TAG, "Object tried to add another callback", + new Exception("Called by")); + } + + // Clean up any unused references + for (int i = mCallbacks.size() - 1; i >= 0; i--) { + if (mCallbacks.get(i).get() == null) { + mCallbacks.remove(i); + } + } + } + + public void reportClockVisible(boolean visible) { + mClockVisible = visible; + mHandler.obtainMessage(MSG_CLOCK_VISIBILITY_CHANGED).sendToTarget(); + } + + public IccCardConstants.State getSimState() { + return mSimState; + } + + /** + * Report that the user successfully entered the SIM PIN or PUK/SIM PIN so we + * have the information earlier than waiting for the intent + * broadcast from the telephony code. + * + * NOTE: Because handleSimStateChange() invokes callbacks immediately without going + * through mHandler, this *must* be called from the UI thread. + */ + public void reportSimUnlocked() { + handleSimStateChange(new SimArgs(IccCardConstants.State.READY)); + } + + public CharSequence getTelephonyPlmn() { + return mTelephonyPlmn; + } + + public CharSequence getTelephonySpn() { + return mTelephonySpn; + } + + /** + * @return Whether the device is provisioned (whether they have gone through + * the setup wizard) + */ + public boolean isDeviceProvisioned() { + return mDeviceProvisioned; + } + + public int getFailedAttempts() { + return mFailedAttempts; + } + + public void clearFailedAttempts() { + mFailedAttempts = 0; + mFailedBiometricUnlockAttempts = 0; + } + + public void reportFailedAttempt() { + mFailedAttempts++; + } + + public boolean isClockVisible() { + return mClockVisible; + } + + public int getPhoneState() { + return mPhoneState; + } + + public void reportFailedBiometricUnlockAttempt() { + mFailedBiometricUnlockAttempts++; + } + + public boolean getMaxBiometricUnlockAttemptsReached() { + return mFailedBiometricUnlockAttempts >= FAILED_BIOMETRIC_UNLOCK_ATTEMPTS_BEFORE_BACKUP; + } + + public boolean isSimLocked() { + return isSimLocked(mSimState); + } + + public static boolean isSimLocked(IccCardConstants.State state) { + return state == IccCardConstants.State.PIN_REQUIRED + || state == IccCardConstants.State.PUK_REQUIRED + || state == IccCardConstants.State.PERM_DISABLED; + } + + public boolean isSimPinSecure() { + return isSimPinSecure(mSimState); + } + + public static boolean isSimPinSecure(IccCardConstants.State state) { + final IccCardConstants.State simState = state; + return (simState == IccCardConstants.State.PIN_REQUIRED + || simState == IccCardConstants.State.PUK_REQUIRED + || simState == IccCardConstants.State.PERM_DISABLED); + } +} diff --git a/policy/src/com/android/internal/policy/impl/KeyguardUpdateMonitorCallback.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardUpdateMonitorCallback.java index d791419..3d65e68 100644 --- a/policy/src/com/android/internal/policy/impl/KeyguardUpdateMonitorCallback.java +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardUpdateMonitorCallback.java @@ -13,12 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.internal.policy.impl; +package com.android.internal.policy.impl.keyguard; import android.app.admin.DevicePolicyManager; import android.media.AudioManager; -import com.android.internal.policy.impl.KeyguardUpdateMonitor.BatteryStatus; import com.android.internal.telephony.IccCardConstants; /** @@ -31,7 +30,7 @@ class KeyguardUpdateMonitorCallback { * * @param status current battery status */ - void onRefreshBatteryInfo(BatteryStatus status) { } + void onRefreshBatteryInfo(KeyguardUpdateMonitor.BatteryStatus status) { } /** * Called once per minute or when the time changes. diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewBase.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewBase.java new file mode 100644 index 0000000..ad5de0e --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewBase.java @@ -0,0 +1,254 @@ +/* + * 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.keyguard; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.PixelFormat; +import android.graphics.PorterDuff; +import android.graphics.drawable.Drawable; +import android.media.AudioManager; +import android.media.IAudioService; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.telephony.TelephonyManager; +import android.view.KeyEvent; +import android.widget.LinearLayout; +import android.util.AttributeSet; +import android.util.Log; +import android.util.Slog; + +/** + * 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 LinearLayout { + + private static final int BACKGROUND_COLOR = 0x70000000; + private AudioManager mAudioManager; + private TelephonyManager mTelephonyManager = null; + protected KeyguardViewMediator.ViewMediatorCallback mViewMediatorCallback; + + // 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; + + // This is a faster way to draw the background on devices without hardware acceleration + private static final Drawable mBackgroundDrawable = new Drawable() { + @Override + public void draw(Canvas canvas) { + canvas.drawColor(BACKGROUND_COLOR, PorterDuff.Mode.SRC); + } + + @Override + public void setAlpha(int alpha) { + } + + @Override + public void setColorFilter(ColorFilter cf) { + } + + @Override + public int getOpacity() { + return PixelFormat.TRANSLUCENT; + } + }; + + public KeyguardViewBase(Context context) { + this(context, null); + } + + public KeyguardViewBase(Context context, AttributeSet attrs) { + super(context, attrs); + resetBackground(); + } + + public void resetBackground() { + setBackground(mBackgroundDrawable); + } + + /** + * Called when you need to reset the state of your view. + */ + abstract public void reset(); + + /** + * Called when the screen turned off. + */ + abstract public void onScreenTurnedOff(); + + /** + * Called when the screen turned on. + */ + abstract public void onScreenTurnedOn(); + + /** + * Called when the view needs to be shown. + */ + abstract public void show(); + + /** + * Called when a key has woken the device to give us a chance to adjust our + * state according the the key. We are responsible for waking the device + * (by poking the wake lock) once we are ready. + * + * The 'Tq' suffix is per the documentation in {@link android.view.WindowManagerPolicy}. + * Be sure not to take any action that takes a long time; any significant + * action should be posted to a handler. + * + * @param keyCode The wake key, which may be relevant for configuring the + * keyguard. May be {@link KeyEvent#KEYCODE_UNKNOWN} if waking for a reason + * other than a key press. + */ + abstract public void wakeWhenReadyTq(int keyCode); + + /** + * 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)} + */ + abstract public void verifyUnlock(); + + /** + * Called before this view is being removed. + */ + abstract public void cleanUp(); + + @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: { + 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: { + handleMediaKeyEvent(event); + return true; + } + } + } + return false; + } + + 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); + setSystemUiVisibility(STATUS_BAR_DISABLE_BACK); + } + + public void setViewMediatorCallback( + KeyguardViewMediator.ViewMediatorCallback viewMediatorCallback) { + mViewMediatorCallback = viewMediatorCallback; + } + +} diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewManager.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewManager.java new file mode 100644 index 0000000..61003bf --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewManager.java @@ -0,0 +1,318 @@ +/* + * 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.keyguard; + +import android.app.Activity; +import android.app.ActivityManager; +import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.graphics.PixelFormat; +import android.os.IBinder; +import android.os.SystemProperties; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewManager; +import android.view.WindowManager; +import android.widget.FrameLayout; + +import com.android.internal.widget.LockPatternUtils; +import com.android.internal.R; + +/** + * Manages creating, showing, hiding and resetting the keyguard. Calls back + * via {@link com.android.internal.policy.impl.KeyguardViewCallback} to poke + * the wake lock and report that the keyguard is done, which is in turn, + * reported to this class by the current {@link KeyguardViewBase}. + */ +public class KeyguardViewManager { + private final static boolean DEBUG = false; + private static String TAG = "KeyguardViewManager"; + + private final Context mContext; + private final ViewManager mViewManager; + private final KeyguardViewMediator.ViewMediatorCallback mViewMediatorCallback; + + private WindowManager.LayoutParams mWindowLayoutParams; + private boolean mNeedsInput = false; + + private FrameLayout mKeyguardHost; + private KeyguardHostView mKeyguardView; + + private boolean mScreenOn = false; + private LockPatternUtils mLockPatternUtils; + + public interface ShowListener { + void onShown(IBinder windowToken); + }; + + /** + * @param context Used to create views. + * @param viewManager Keyguard will be attached to this. + * @param callback Used to notify of changes. + * @param lockPatternUtils + */ + public KeyguardViewManager(Context context, ViewManager viewManager, + KeyguardViewMediator.ViewMediatorCallback callback, + LockPatternUtils lockPatternUtils) { + mContext = context; + mViewManager = viewManager; + mViewMediatorCallback = callback; + mLockPatternUtils = lockPatternUtils; + } + + /** + * Show the keyguard. Will handle creating and attaching to the view manager + * lazily. + */ + public synchronized void show() { + if (DEBUG) Log.d(TAG, "show(); mKeyguardView==" + mKeyguardView); + + boolean enableScreenRotation = shouldEnableScreenRotation(); + + maybeCreateKeyguardLocked(enableScreenRotation); + maybeEnableScreenRotation(enableScreenRotation); + + // Disable aspects of the system/status/navigation bars that are not appropriate or + // useful for the lockscreen but can be re-shown by dialogs or SHOW_WHEN_LOCKED activities. + // Other disabled bits are handled by the KeyguardViewMediator talking directly to the + // status bar service. + int visFlags = View.STATUS_BAR_DISABLE_BACK | View.STATUS_BAR_DISABLE_HOME; + if (DEBUG) Log.v(TAG, "KGVM: Set visibility on " + mKeyguardHost + " to " + visFlags); + mKeyguardHost.setSystemUiVisibility(visFlags); + + mViewManager.updateViewLayout(mKeyguardHost, mWindowLayoutParams); + mKeyguardHost.setVisibility(View.VISIBLE); + mKeyguardView.show(); + mKeyguardView.requestFocus(); + } + + private boolean shouldEnableScreenRotation() { + Resources res = mContext.getResources(); + return SystemProperties.getBoolean("lockscreen.rot_override",false) + || res.getBoolean(com.android.internal.R.bool.config_enableLockScreenRotation); + } + + class ViewManagerHost extends FrameLayout { + public ViewManagerHost(Context context) { + super(context); + } + + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + maybeCreateKeyguardLocked(shouldEnableScreenRotation()); + } + } + + private void maybeCreateKeyguardLocked(boolean enableScreenRotation) { + final boolean isActivity = (mContext instanceof Activity); // for test activity + + if (mKeyguardHost == null) { + if (DEBUG) Log.d(TAG, "keyguard host is null, creating it..."); + + mKeyguardHost = new ViewManagerHost(mContext); + + int flags = WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN + | WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER + | WindowManager.LayoutParams.FLAG_SLIPPERY; + + if (!mNeedsInput) { + flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; + } + if (ActivityManager.isHighEndGfx()) { + flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; + } + + final int stretch = ViewGroup.LayoutParams.MATCH_PARENT; + final int type = isActivity ? WindowManager.LayoutParams.TYPE_APPLICATION + : WindowManager.LayoutParams.TYPE_KEYGUARD; + WindowManager.LayoutParams lp = new WindowManager.LayoutParams( + stretch, stretch, type, flags, PixelFormat.TRANSLUCENT); + lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; + lp.windowAnimations = com.android.internal.R.style.Animation_LockScreen; + if (ActivityManager.isHighEndGfx()) { + lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; + lp.privateFlags |= + WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED; + } + lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SET_NEEDS_MENU_KEY; + lp.setTitle(isActivity ? "KeyguardMock" : "Keyguard"); + mWindowLayoutParams = lp; + mViewManager.addView(mKeyguardHost, lp); + } + inflateKeyguardView(); + mViewManager.updateViewLayout(mKeyguardHost, mWindowLayoutParams); + } + + private void inflateKeyguardView() { + if (mKeyguardView != null) { + mKeyguardHost.removeView(mKeyguardView); + } + final LayoutInflater inflater = LayoutInflater.from(mContext); + View view = inflater.inflate(R.layout.keyguard_host_view, mKeyguardHost, true); + mKeyguardView = (KeyguardHostView) view.findViewById(R.id.keyguard_host_view); + mKeyguardView.setLockPatternUtils(mLockPatternUtils); + mKeyguardView.setViewMediatorCallback(mViewMediatorCallback); + + if (mScreenOn) { + mKeyguardView.show(); + } + } + + private void maybeEnableScreenRotation(boolean enableScreenRotation) { + // TODO: move this outside + if (enableScreenRotation) { + if (DEBUG) Log.d(TAG, "Rotation sensor for lock screen On!"); + mWindowLayoutParams.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_USER; + } else { + if (DEBUG) Log.d(TAG, "Rotation sensor for lock screen Off!"); + mWindowLayoutParams.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; + } + mViewManager.updateViewLayout(mKeyguardHost, mWindowLayoutParams); + } + + public void setNeedsInput(boolean needsInput) { + mNeedsInput = needsInput; + if (mWindowLayoutParams != null) { + if (needsInput) { + mWindowLayoutParams.flags &= + ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; + } else { + mWindowLayoutParams.flags |= + WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; + } + + try { + mViewManager.updateViewLayout(mKeyguardHost, mWindowLayoutParams); + } catch (java.lang.IllegalArgumentException e) { + // TODO: Ensure this method isn't called on views that are changing... + Log.w(TAG,"Can't update input method on " + mKeyguardHost + " window not attached"); + } + } + } + + /** + * Reset the state of the view. + */ + public synchronized void reset() { + if (DEBUG) Log.d(TAG, "reset()"); + if (mKeyguardView != null) { + mKeyguardView.reset(); + } + } + + public synchronized void onScreenTurnedOff() { + if (DEBUG) Log.d(TAG, "onScreenTurnedOff()"); + mScreenOn = false; + if (mKeyguardView != null) { + mKeyguardView.onScreenTurnedOff(); + } + } + + public synchronized void onScreenTurnedOn( + final KeyguardViewManager.ShowListener showListener) { + if (DEBUG) Log.d(TAG, "onScreenTurnedOn()"); + mScreenOn = true; + if (mKeyguardView != null) { + mKeyguardView.onScreenTurnedOn(); + + // Caller should wait for this window to be shown before turning + // on the screen. + if (mKeyguardHost.getVisibility() == View.VISIBLE) { + // Keyguard may be in the process of being shown, but not yet + // updated with the window manager... give it a chance to do so. + mKeyguardHost.post(new Runnable() { + public void run() { + if (mKeyguardHost.getVisibility() == View.VISIBLE) { + showListener.onShown(mKeyguardHost.getWindowToken()); + } else { + showListener.onShown(null); + } + } + }); + } else { + showListener.onShown(null); + } + } else { + showListener.onShown(null); + } + } + + public synchronized void verifyUnlock() { + if (DEBUG) Log.d(TAG, "verifyUnlock()"); + show(); + mKeyguardView.verifyUnlock(); + } + + /** + * A key has woken the device. We use this to potentially adjust the state + * of the lock screen based on the key. + * + * The 'Tq' suffix is per the documentation in {@link android.view.WindowManagerPolicy}. + * Be sure not to take any action that takes a long time; any significant + * action should be posted to a handler. + * + * @param keyCode The wake key. May be {@link KeyEvent#KEYCODE_UNKNOWN} if waking + * for a reason other than a key press. + */ + public boolean wakeWhenReadyTq(int keyCode) { + if (DEBUG) Log.d(TAG, "wakeWhenReady(" + keyCode + ")"); + if (mKeyguardView != null) { + mKeyguardView.wakeWhenReadyTq(keyCode); + return true; + } else { + Log.w(TAG, "mKeyguardView is null in wakeWhenReadyTq"); + return false; + } + } + + /** + * Hides the keyguard view + */ + public synchronized void hide() { + if (DEBUG) Log.d(TAG, "hide()"); + + if (mKeyguardHost != null) { + mKeyguardHost.setVisibility(View.GONE); + // Don't do this right away, so we can let the view continue to animate + // as it goes away. + if (mKeyguardView != null) { + final KeyguardViewBase lastView = mKeyguardView; + mKeyguardView = null; + mKeyguardHost.postDelayed(new Runnable() { + public void run() { + synchronized (KeyguardViewManager.this) { + lastView.cleanUp(); + mKeyguardHost.removeView(lastView); + } + } + }, 500); + } + } + } + + /** + * @return Whether the keyguard is showing + */ + public synchronized boolean isShowing() { + return (mKeyguardHost != null && mKeyguardHost.getVisibility() == View.VISIBLE); + } +} diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewMediator.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewMediator.java new file mode 100644 index 0000000..d6733ea --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewMediator.java @@ -0,0 +1,1342 @@ +/* + * 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.keyguard; + +import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT; + +import com.android.internal.telephony.IccCardConstants; +import com.android.internal.widget.LockPatternUtils; + +import android.app.Activity; +import android.app.ActivityManagerNative; +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.app.StatusBarManager; +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.media.AudioManager; +import android.media.SoundPool; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.PowerManager; +import android.os.RemoteException; +import android.os.SystemClock; +import android.os.SystemProperties; +import android.os.UserHandle; +import android.provider.Settings; +import android.telephony.TelephonyManager; +import android.util.EventLog; +import android.util.Log; +import android.view.KeyEvent; +import android.view.WindowManager; +import android.view.WindowManagerPolicy; + + +/** + * Mediates requests related to the keyguard. This includes queries about the + * state of the keyguard, power management events that effect whether the keyguard + * should be shown or reset, callbacks to the phone window manager to notify + * it of when the keyguard is showing, and events from the keyguard view itself + * stating that the keyguard was succesfully unlocked. + * + * Note that the keyguard view is shown when the screen is off (as appropriate) + * so that once the screen comes on, it will be ready immediately. + * + * Example queries about the keyguard: + * - is {movement, key} one that should wake the keygaurd? + * - is the keyguard showing? + * - are input events restricted due to the state of the keyguard? + * + * Callbacks to the phone window manager: + * - the keyguard is showing + * + * Example external events that translate to keyguard view changes: + * - screen turned off -> reset the keyguard, and show it so it will be ready + * next time the screen turns on + * - keyboard is slid open -> if the keyguard is not secure, hide it + * + * Events from the keyguard view: + * - user succesfully unlocked keyguard -> hide keyguard view, and no longer + * restrict input events. + * + * Note: in addition to normal power managment events that effect the state of + * whether the keyguard should be showing, external apps and services may request + * that the keyguard be disabled via {@link #setKeyguardEnabled(boolean)}. When + * false, this will override all other conditions for turning on the keyguard. + * + * Threading and synchronization: + * This class is created by the initialization routine of the {@link WindowManagerPolicy}, + * and runs on its thread. The keyguard UI is created from that thread in the + * constructor of this class. The apis may be called from other threads, including the + * {@link com.android.server.input.InputManagerService}'s and {@link android.view.WindowManager}'s. + * Therefore, methods on this class are synchronized, and any action that is pointed + * directly to the keyguard UI is posted to a {@link Handler} to ensure it is taken on the UI + * thread of the keyguard. + */ +public class KeyguardViewMediator { + private static final int KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT = 30000; + private final static boolean DEBUG = false; + private final static boolean DBG_WAKE = false; + + private final static String TAG = "KeyguardViewMediator"; + + private static final String DELAYED_KEYGUARD_ACTION = + "com.android.internal.policy.impl.PhoneWindowManager.DELAYED_KEYGUARD"; + + // used for handler messages + private static final int TIMEOUT = 1; + private static final int SHOW = 2; + private static final int HIDE = 3; + private static final int RESET = 4; + private static final int VERIFY_UNLOCK = 5; + private static final int NOTIFY_SCREEN_OFF = 6; + private static final int NOTIFY_SCREEN_ON = 7; + private static final int WAKE_WHEN_READY = 8; + private static final int KEYGUARD_DONE = 9; + private static final int KEYGUARD_DONE_DRAWING = 10; + private static final int KEYGUARD_DONE_AUTHENTICATING = 11; + private static final int SET_HIDDEN = 12; + private static final int KEYGUARD_TIMEOUT = 13; + + /** + * The default amount of time we stay awake (used for all key input) + */ + protected static final int AWAKE_INTERVAL_DEFAULT_MS = 10000; + + /** + * How long to wait after the screen turns off due to timeout before + * turning on the keyguard (i.e, the user has this much time to turn + * the screen back on without having to face the keyguard). + */ + private static final int KEYGUARD_LOCK_AFTER_DELAY_DEFAULT = 5000; + + /** + * How long we'll wait for the {@link KeyguardViewCallback#keyguardDoneDrawing()} + * callback before unblocking a call to {@link #setKeyguardEnabled(boolean)} + * that is reenabling the keyguard. + */ + private static final int KEYGUARD_DONE_DRAWING_TIMEOUT_MS = 2000; + + /** + * Allow the user to expand the status bar when the keyguard is engaged + * (without a pattern or password). + */ + private static final boolean ENABLE_INSECURE_STATUS_BAR_EXPAND = true; + + /** The stream type that the lock sounds are tied to. */ + private int mMasterStreamType; + + private Context mContext; + private AlarmManager mAlarmManager; + private AudioManager mAudioManager; + private StatusBarManager mStatusBarManager; + private boolean mShowLockIcon; + private boolean mShowingLockIcon; + + private boolean mSystemReady; + + // Whether the next call to playSounds() should be skipped. Defaults to + // true because the first lock (on boot) should be silent. + private boolean mSuppressNextLockSound = true; + + + /** High level access to the power manager for WakeLocks */ + private PowerManager mPM; + + /** + * Used to keep the device awake while the keyguard is showing, i.e for + * calls to {@link #pokeWakelock()} + */ + private PowerManager.WakeLock mWakeLock; + + /** + * Used to keep the device awake while to ensure the keyguard finishes opening before + * we sleep. + */ + private PowerManager.WakeLock mShowKeyguardWakeLock; + + /** + * Does not turn on screen, held while a call to {@link KeyguardViewManager#wakeWhenReadyTq(int)} + * is called to make sure the device doesn't sleep before it has a chance to poke + * the wake lock. + * @see #wakeWhenReadyLocked(int) + */ + private PowerManager.WakeLock mWakeAndHandOff; + + private KeyguardViewManager mKeyguardViewManager; + + // these are protected by synchronized (this) + + /** + * External apps (like the phone app) can tell us to disable the keygaurd. + */ + private boolean mExternallyEnabled = true; + + /** + * Remember if an external call to {@link #setKeyguardEnabled} with value + * false caused us to hide the keyguard, so that we need to reshow it once + * the keygaurd is reenabled with another call with value true. + */ + private boolean mNeedToReshowWhenReenabled = false; + + // cached value of whether we are showing (need to know this to quickly + // answer whether the input should be restricted) + private boolean mShowing = false; + + // true if the keyguard is hidden by another window + private boolean mHidden = false; + + /** + * Helps remember whether the screen has turned on since the last time + * it turned off due to timeout. see {@link #onScreenTurnedOff(int)} + */ + private int mDelayedShowingSequence; + + private int mWakelockSequence; + + /** + * If the user has disabled the keyguard, then requests to exit, this is + * how we'll ultimately let them know whether it was successful. We use this + * var being non-null as an indicator that there is an in progress request. + */ + private WindowManagerPolicy.OnKeyguardExitResult mExitSecureCallback; + + // the properties of the keyguard + + private KeyguardUpdateMonitor mUpdateMonitor; + + private boolean mScreenOn; + + // last known state of the cellular connection + private String mPhoneState = TelephonyManager.EXTRA_STATE_IDLE; + + /** + * we send this intent when the keyguard is dismissed. + */ + private Intent mUserPresentIntent; + + /** + * {@link #setKeyguardEnabled} waits on this condition when it reenables + * the keyguard. + */ + private boolean mWaitingUntilKeyguardVisible = false; + private LockPatternUtils mLockPatternUtils; + + private SoundPool mLockSounds; + private int mLockSoundId; + private int mUnlockSoundId; + private int mLockSoundStreamId; + + /** + * The volume applied to the lock/unlock sounds. + */ + private final float mLockSoundVolume; + + /** + * The callback used by the keyguard view to tell the {@link KeyguardViewMediator} + * various things. + */ + public interface ViewMediatorCallback { + + /** + * Request the wakelock to be poked for the default amount of time. + */ + void pokeWakelock(); + + /** + * Request the wakelock to be poked for a specific amount of time. + * @param millis The amount of time in millis. + */ + void pokeWakelock(long millis); + + /** + * Report that the keyguard is done. + * @param authenticated Whether the user securely got past the keyguard. + * the only reason for this to be false is if the keyguard was instructed + * to appear temporarily to verify the user is supposed to get past the + * keyguard, and the user fails to do so. + */ + void keyguardDone(boolean authenticated); + + /** + * Report that the keyguard is done drawing. + */ + void keyguardDoneDrawing(); + + /** + * Tell ViewMediator that the current view needs IME input + * @param needsInput + */ + void setNeedsInput(boolean needsInput); + } + + KeyguardUpdateMonitorCallback mUpdateCallback = new KeyguardUpdateMonitorCallback() { + + @Override + public void onUserSwitched(int userId) { + mLockPatternUtils.setCurrentUser(userId); + synchronized (KeyguardViewMediator.this) { + resetStateLocked(); + } + } + + @Override + public void onUserRemoved(int userId) { + mLockPatternUtils.removeUser(userId); + } + + @Override + void onPhoneStateChanged(int phoneState) { + synchronized (KeyguardViewMediator.this) { + if (TelephonyManager.CALL_STATE_IDLE == phoneState // call ending + && !mScreenOn // screen off + && mExternallyEnabled) { // not disabled by any app + + // note: this is a way to gracefully reenable the keyguard when the call + // ends and the screen is off without always reenabling the keyguard + // each time the screen turns off while in call (and having an occasional ugly + // flicker while turning back on the screen and disabling the keyguard again). + if (DEBUG) Log.d(TAG, "screen is off and call ended, let's make sure the " + + "keyguard is showing"); + doKeyguardLocked(); + } + } + }; + + @Override + public void onClockVisibilityChanged() { + adjustStatusBarLocked(); + } + + @Override + public void onDeviceProvisioned() { + mContext.sendBroadcast(mUserPresentIntent); + } + + @Override + public void onSimStateChanged(IccCardConstants.State simState) { + if (DEBUG) Log.d(TAG, "onSimStateChanged: " + simState); + + switch (simState) { + case NOT_READY: + case ABSENT: + // only force lock screen in case of missing sim if user hasn't + // gone through setup wizard + synchronized (this) { + if (!mUpdateMonitor.isDeviceProvisioned()) { + if (!isShowing()) { + if (DEBUG) Log.d(TAG, "ICC_ABSENT isn't showing," + + " we need to show the keyguard since the " + + "device isn't provisioned yet."); + doKeyguardLocked(); + } else { + resetStateLocked(); + } + } + } + break; + case PIN_REQUIRED: + case PUK_REQUIRED: + synchronized (this) { + if (!isShowing()) { + if (DEBUG) Log.d(TAG, "INTENT_VALUE_ICC_LOCKED and keygaurd isn't " + + "showing; need to show keyguard so user can enter sim pin"); + doKeyguardLocked(); + } else { + resetStateLocked(); + } + } + break; + case PERM_DISABLED: + synchronized (this) { + if (!isShowing()) { + if (DEBUG) Log.d(TAG, "PERM_DISABLED and " + + "keygaurd isn't showing."); + doKeyguardLocked(); + } else { + if (DEBUG) Log.d(TAG, "PERM_DISABLED, resetStateLocked to" + + "show permanently disabled message in lockscreen."); + resetStateLocked(); + } + } + break; + case READY: + synchronized (this) { + if (isShowing()) { + resetStateLocked(); + } + } + break; + } + } + + }; + + ViewMediatorCallback mViewMediatorCallback = new ViewMediatorCallback() { + public void pokeWakelock() { + KeyguardViewMediator.this.pokeWakelock(); + } + + public void pokeWakelock(long holdMs) { + KeyguardViewMediator.this.pokeWakelock(holdMs); + } + + public void keyguardDone(boolean authenticated) { + KeyguardViewMediator.this.keyguardDone(authenticated, true); + } + + public void keyguardDoneDrawing() { + mHandler.sendEmptyMessage(KEYGUARD_DONE_DRAWING); + } + + @Override + public void setNeedsInput(boolean needsInput) { + mKeyguardViewManager.setNeedsInput(needsInput); + } + }; + + public void pokeWakelock() { + pokeWakelock(AWAKE_INTERVAL_DEFAULT_MS); + } + + public void pokeWakelock(long holdMs) { + synchronized (this) { + if (DBG_WAKE) Log.d(TAG, "pokeWakelock(" + holdMs + ")"); + mWakeLock.acquire(); + mHandler.removeMessages(TIMEOUT); + mWakelockSequence++; + Message msg = mHandler.obtainMessage(TIMEOUT, mWakelockSequence, 0); + mHandler.sendMessageDelayed(msg, holdMs); + } + } + + /** + * Construct a KeyguardViewMediator + * @param context + * @param lockPatternUtils optional mock interface for LockPatternUtils + */ + public KeyguardViewMediator(Context context, LockPatternUtils lockPatternUtils) { + mContext = context; + mPM = (PowerManager) context.getSystemService(Context.POWER_SERVICE); + mWakeLock = mPM.newWakeLock( + PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, "keyguard"); + mWakeLock.setReferenceCounted(false); + mShowKeyguardWakeLock = mPM.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "show keyguard"); + mShowKeyguardWakeLock.setReferenceCounted(false); + + mWakeAndHandOff = mPM.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "keyguardWakeAndHandOff"); + mWakeAndHandOff.setReferenceCounted(false); + + mContext.registerReceiver(mBroadcastReceiver, new IntentFilter(DELAYED_KEYGUARD_ACTION)); + + mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + + mUpdateMonitor = KeyguardUpdateMonitor.getInstance(context); + + mLockPatternUtils = lockPatternUtils != null + ? lockPatternUtils : new LockPatternUtils(mContext); + + WindowManager wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); + + mKeyguardViewManager = new KeyguardViewManager(context, wm, mViewMediatorCallback, + mLockPatternUtils); + + mUserPresentIntent = new Intent(Intent.ACTION_USER_PRESENT); + mUserPresentIntent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING + | Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + + final ContentResolver cr = mContext.getContentResolver(); + mShowLockIcon = (Settings.System.getInt(cr, "show_status_bar_lock", 0) == 1); + + mScreenOn = mPM.isScreenOn(); + + mLockSounds = new SoundPool(1, AudioManager.STREAM_SYSTEM, 0); + String soundPath = Settings.System.getString(cr, Settings.System.LOCK_SOUND); + if (soundPath != null) { + mLockSoundId = mLockSounds.load(soundPath, 1); + } + if (soundPath == null || mLockSoundId == 0) { + if (DEBUG) Log.d(TAG, "failed to load sound from " + soundPath); + } + soundPath = Settings.System.getString(cr, Settings.System.UNLOCK_SOUND); + if (soundPath != null) { + mUnlockSoundId = mLockSounds.load(soundPath, 1); + } + if (soundPath == null || mUnlockSoundId == 0) { + if (DEBUG) Log.d(TAG, "failed to load sound from " + soundPath); + } + int lockSoundDefaultAttenuation = context.getResources().getInteger( + com.android.internal.R.integer.config_lockSoundVolumeDb); + mLockSoundVolume = (float)Math.pow(10, (float)lockSoundDefaultAttenuation/20); + } + + /** + * Let us know that the system is ready after startup. + */ + public void onSystemReady() { + synchronized (this) { + if (DEBUG) Log.d(TAG, "onSystemReady"); + mSystemReady = true; + mUpdateMonitor.registerCallback(mUpdateCallback); + doKeyguardLocked(); + } + } + + /** + * Called to let us know the screen was turned off. + * @param why either {@link WindowManagerPolicy#OFF_BECAUSE_OF_USER}, + * {@link WindowManagerPolicy#OFF_BECAUSE_OF_TIMEOUT} or + * {@link WindowManagerPolicy#OFF_BECAUSE_OF_PROX_SENSOR}. + */ + public void onScreenTurnedOff(int why) { + synchronized (this) { + mScreenOn = false; + if (DEBUG) Log.d(TAG, "onScreenTurnedOff(" + why + ")"); + + // Lock immediately based on setting if secure (user has a pin/pattern/password). + // This also "locks" the device when not secure to provide easy access to the + // camera while preventing unwanted input. + final boolean lockImmediately = + mLockPatternUtils.getPowerButtonInstantlyLocks() || !mLockPatternUtils.isSecure(); + + if (mExitSecureCallback != null) { + if (DEBUG) Log.d(TAG, "pending exit secure callback cancelled"); + mExitSecureCallback.onKeyguardExitResult(false); + mExitSecureCallback = null; + if (!mExternallyEnabled) { + hideLocked(); + } + } else if (mShowing) { + notifyScreenOffLocked(); + resetStateLocked(); + } else if (why == WindowManagerPolicy.OFF_BECAUSE_OF_TIMEOUT + || (why == WindowManagerPolicy.OFF_BECAUSE_OF_USER && !lockImmediately)) { + // if the screen turned off because of timeout or the user hit the power button + // and we don't need to lock immediately, set an alarm + // to enable it a little bit later (i.e, give the user a chance + // to turn the screen back on within a certain window without + // having to unlock the screen) + final ContentResolver cr = mContext.getContentResolver(); + + // From DisplaySettings + long displayTimeout = Settings.System.getInt(cr, SCREEN_OFF_TIMEOUT, + KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT); + + // From SecuritySettings + final long lockAfterTimeout = Settings.Secure.getInt(cr, + Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT, + KEYGUARD_LOCK_AFTER_DELAY_DEFAULT); + + // From DevicePolicyAdmin + final long policyTimeout = mLockPatternUtils.getDevicePolicyManager() + .getMaximumTimeToLock(null); + + long timeout; + if (policyTimeout > 0) { + // policy in effect. Make sure we don't go beyond policy limit. + displayTimeout = Math.max(displayTimeout, 0); // ignore negative values + timeout = Math.min(policyTimeout - displayTimeout, lockAfterTimeout); + } else { + timeout = lockAfterTimeout; + } + + if (timeout <= 0) { + // Lock now + mSuppressNextLockSound = true; + doKeyguardLocked(); + } else { + // Lock in the future + long when = SystemClock.elapsedRealtime() + timeout; + Intent intent = new Intent(DELAYED_KEYGUARD_ACTION); + intent.putExtra("seq", mDelayedShowingSequence); + PendingIntent sender = PendingIntent.getBroadcast(mContext, + 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); + mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, when, sender); + if (DEBUG) Log.d(TAG, "setting alarm to turn off keyguard, seq = " + + mDelayedShowingSequence); + } + } else if (why == WindowManagerPolicy.OFF_BECAUSE_OF_PROX_SENSOR) { + // Do not enable the keyguard if the prox sensor forced the screen off. + } else { + doKeyguardLocked(); + } + } + } + + /** + * Let's us know the screen was turned on. + */ + public void onScreenTurnedOn(KeyguardViewManager.ShowListener showListener) { + synchronized (this) { + mScreenOn = true; + mDelayedShowingSequence++; + if (DEBUG) Log.d(TAG, "onScreenTurnedOn, seq = " + mDelayedShowingSequence); + if (showListener != null) { + notifyScreenOnLocked(showListener); + } + } + } + + /** + * Same semantics as {@link WindowManagerPolicy#enableKeyguard}; provide + * a way for external stuff to override normal keyguard behavior. For instance + * the phone app disables the keyguard when it receives incoming calls. + */ + public void setKeyguardEnabled(boolean enabled) { + synchronized (this) { + if (DEBUG) Log.d(TAG, "setKeyguardEnabled(" + enabled + ")"); + + mExternallyEnabled = enabled; + + if (!enabled && mShowing) { + if (mExitSecureCallback != null) { + if (DEBUG) Log.d(TAG, "in process of verifyUnlock request, ignoring"); + // we're in the process of handling a request to verify the user + // can get past the keyguard. ignore extraneous requests to disable / reenable + return; + } + + // hiding keyguard that is showing, remember to reshow later + if (DEBUG) Log.d(TAG, "remembering to reshow, hiding keyguard, " + + "disabling status bar expansion"); + mNeedToReshowWhenReenabled = true; + hideLocked(); + } else if (enabled && mNeedToReshowWhenReenabled) { + // reenabled after previously hidden, reshow + if (DEBUG) Log.d(TAG, "previously hidden, reshowing, reenabling " + + "status bar expansion"); + mNeedToReshowWhenReenabled = false; + + if (mExitSecureCallback != null) { + if (DEBUG) Log.d(TAG, "onKeyguardExitResult(false), resetting"); + mExitSecureCallback.onKeyguardExitResult(false); + mExitSecureCallback = null; + resetStateLocked(); + } else { + showLocked(); + + // block until we know the keygaurd is done drawing (and post a message + // to unblock us after a timeout so we don't risk blocking too long + // and causing an ANR). + mWaitingUntilKeyguardVisible = true; + mHandler.sendEmptyMessageDelayed(KEYGUARD_DONE_DRAWING, KEYGUARD_DONE_DRAWING_TIMEOUT_MS); + if (DEBUG) Log.d(TAG, "waiting until mWaitingUntilKeyguardVisible is false"); + while (mWaitingUntilKeyguardVisible) { + try { + wait(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + if (DEBUG) Log.d(TAG, "done waiting for mWaitingUntilKeyguardVisible"); + } + } + } + } + + /** + * @see android.app.KeyguardManager#exitKeyguardSecurely + */ + public void verifyUnlock(WindowManagerPolicy.OnKeyguardExitResult callback) { + synchronized (this) { + if (DEBUG) Log.d(TAG, "verifyUnlock"); + if (!mUpdateMonitor.isDeviceProvisioned()) { + // don't allow this api when the device isn't provisioned + if (DEBUG) Log.d(TAG, "ignoring because device isn't provisioned"); + callback.onKeyguardExitResult(false); + } else if (mExternallyEnabled) { + // this only applies when the user has externally disabled the + // keyguard. this is unexpected and means the user is not + // using the api properly. + Log.w(TAG, "verifyUnlock called when not externally disabled"); + callback.onKeyguardExitResult(false); + } else if (mExitSecureCallback != null) { + // already in progress with someone else + callback.onKeyguardExitResult(false); + } else { + mExitSecureCallback = callback; + verifyUnlockLocked(); + } + } + } + + /** + * Is the keyguard currently showing? + */ + public boolean isShowing() { + return mShowing; + } + + /** + * Is the keyguard currently showing and not being force hidden? + */ + public boolean isShowingAndNotHidden() { + return mShowing && !mHidden; + } + + /** + * Notify us when the keyguard is hidden by another window + */ + public void setHidden(boolean isHidden) { + if (DEBUG) Log.d(TAG, "setHidden " + isHidden); + mHandler.removeMessages(SET_HIDDEN); + Message msg = mHandler.obtainMessage(SET_HIDDEN, (isHidden ? 1 : 0), 0); + mHandler.sendMessage(msg); + } + + /** + * Handles SET_HIDDEN message sent by setHidden() + */ + private void handleSetHidden(boolean isHidden) { + synchronized (KeyguardViewMediator.this) { + if (mHidden != isHidden) { + mHidden = isHidden; + updateActivityLockScreenState(); + adjustUserActivityLocked(); + adjustStatusBarLocked(); + } + } + } + + /** + * Used by PhoneWindowManager to enable the keyguard due to a user activity timeout. + * This must be safe to call from any thread and with any window manager locks held. + */ + public void doKeyguardTimeout() { + mHandler.removeMessages(KEYGUARD_TIMEOUT); + Message msg = mHandler.obtainMessage(KEYGUARD_TIMEOUT); + mHandler.sendMessage(msg); + } + + /** + * Given the state of the keyguard, is the input restricted? + * Input is restricted when the keyguard is showing, or when the keyguard + * was suppressed by an app that disabled the keyguard or we haven't been provisioned yet. + */ + public boolean isInputRestricted() { + return mShowing || mNeedToReshowWhenReenabled || !mUpdateMonitor.isDeviceProvisioned(); + } + + /** + * Enable the keyguard if the settings are appropriate. Return true if all + * work that will happen is done; returns false if the caller can wait for + * the keyguard to be shown. + */ + private void doKeyguardLocked() { + // if another app is disabling us, don't show + if (!mExternallyEnabled) { + if (DEBUG) Log.d(TAG, "doKeyguard: not showing because externally disabled"); + + // note: we *should* set mNeedToReshowWhenReenabled=true here, but that makes + // for an occasional ugly flicker in this situation: + // 1) receive a call with the screen on (no keyguard) or make a call + // 2) screen times out + // 3) user hits key to turn screen back on + // instead, we reenable the keyguard when we know the screen is off and the call + // ends (see the broadcast receiver below) + // TODO: clean this up when we have better support at the window manager level + // for apps that wish to be on top of the keyguard + return; + } + + // if the keyguard is already showing, don't bother + if (mKeyguardViewManager.isShowing()) { + if (DEBUG) Log.d(TAG, "doKeyguard: not showing because it is already showing"); + return; + } + + // if the setup wizard hasn't run yet, don't show + final boolean requireSim = !SystemProperties.getBoolean("keyguard.no_require_sim", + false); + final boolean provisioned = mUpdateMonitor.isDeviceProvisioned(); + final IccCardConstants.State state = mUpdateMonitor.getSimState(); + final boolean lockedOrMissing = state.isPinLocked() + || ((state == IccCardConstants.State.ABSENT + || state == IccCardConstants.State.PERM_DISABLED) + && requireSim); + + if (!lockedOrMissing && !provisioned) { + if (DEBUG) Log.d(TAG, "doKeyguard: not showing because device isn't provisioned" + + " and the sim is not locked or missing"); + return; + } + + if (mLockPatternUtils.isLockScreenDisabled() && !lockedOrMissing) { + if (DEBUG) Log.d(TAG, "doKeyguard: not showing because lockscreen is off"); + return; + } + + if (DEBUG) Log.d(TAG, "doKeyguard: showing the lock screen"); + showLocked(); + } + + /** + * Send message to keyguard telling it to reset its state. + * @see #handleReset() + */ + private void resetStateLocked() { + if (DEBUG) Log.d(TAG, "resetStateLocked"); + Message msg = mHandler.obtainMessage(RESET); + mHandler.sendMessage(msg); + } + + /** + * Send message to keyguard telling it to verify unlock + * @see #handleVerifyUnlock() + */ + private void verifyUnlockLocked() { + if (DEBUG) Log.d(TAG, "verifyUnlockLocked"); + mHandler.sendEmptyMessage(VERIFY_UNLOCK); + } + + + /** + * Send a message to keyguard telling it the screen just turned on. + * @see #onScreenTurnedOff(int) + * @see #handleNotifyScreenOff + */ + private void notifyScreenOffLocked() { + if (DEBUG) Log.d(TAG, "notifyScreenOffLocked"); + mHandler.sendEmptyMessage(NOTIFY_SCREEN_OFF); + } + + /** + * Send a message to keyguard telling it the screen just turned on. + * @see #onScreenTurnedOn() + * @see #handleNotifyScreenOn + */ + private void notifyScreenOnLocked(KeyguardViewManager.ShowListener showListener) { + if (DEBUG) Log.d(TAG, "notifyScreenOnLocked"); + Message msg = mHandler.obtainMessage(NOTIFY_SCREEN_ON, showListener); + mHandler.sendMessage(msg); + } + + /** + * Send message to keyguard telling it about a wake key so it can adjust + * its state accordingly and then poke the wake lock when it is ready. + * @param keyCode The wake key. + * @see #handleWakeWhenReady + * @see #onWakeKeyWhenKeyguardShowingTq(int) + */ + private void wakeWhenReadyLocked(int keyCode) { + if (DBG_WAKE) Log.d(TAG, "wakeWhenReadyLocked(" + keyCode + ")"); + + /** + * acquire the handoff lock that will keep the cpu running. this will + * be released once the keyguard has set itself up and poked the other wakelock + * in {@link #handleWakeWhenReady(int)} + */ + mWakeAndHandOff.acquire(); + + Message msg = mHandler.obtainMessage(WAKE_WHEN_READY, keyCode, 0); + mHandler.sendMessage(msg); + } + + /** + * Send message to keyguard telling it to show itself + * @see #handleShow() + */ + private void showLocked() { + if (DEBUG) Log.d(TAG, "showLocked"); + // ensure we stay awake until we are finished displaying the keyguard + mShowKeyguardWakeLock.acquire(); + Message msg = mHandler.obtainMessage(SHOW); + mHandler.sendMessage(msg); + } + + /** + * Send message to keyguard telling it to hide itself + * @see #handleHide() + */ + private void hideLocked() { + if (DEBUG) Log.d(TAG, "hideLocked"); + Message msg = mHandler.obtainMessage(HIDE); + mHandler.sendMessage(msg); + } + + public boolean isSecure() { + return mLockPatternUtils.isSecure() + || KeyguardUpdateMonitor.getInstance(mContext).isSimPinSecure(); + } + + private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (DELAYED_KEYGUARD_ACTION.equals(intent.getAction())) { + final int sequence = intent.getIntExtra("seq", 0); + if (DEBUG) Log.d(TAG, "received DELAYED_KEYGUARD_ACTION with seq = " + + sequence + ", mDelayedShowingSequence = " + mDelayedShowingSequence); + synchronized (KeyguardViewMediator.this) { + if (mDelayedShowingSequence == sequence) { + // Don't play lockscreen SFX if the screen went off due to timeout. + mSuppressNextLockSound = true; + doKeyguardLocked(); + } + } + } + } + }; + + /** + * When a key is received when the screen is off and the keyguard is showing, + * we need to decide whether to actually turn on the screen, and if so, tell + * the keyguard to prepare itself and poke the wake lock when it is ready. + * + * The 'Tq' suffix is per the documentation in {@link WindowManagerPolicy}. + * Be sure not to take any action that takes a long time; any significant + * action should be posted to a handler. + * + * @param keyCode The keycode of the key that woke the device + * @param isDocked True if the device is in the dock + * @return Whether we poked the wake lock (and turned the screen on) + */ + public boolean onWakeKeyWhenKeyguardShowingTq(int keyCode, boolean isDocked) { + if (DEBUG) Log.d(TAG, "onWakeKeyWhenKeyguardShowing(" + keyCode + ")"); + + if (isWakeKeyWhenKeyguardShowing(keyCode, isDocked)) { + // give the keyguard view manager a chance to adjust the state of the + // keyguard based on the key that woke the device before poking + // the wake lock + wakeWhenReadyLocked(keyCode); + return true; + } else { + return false; + } + } + + /** + * When the keyguard is showing we ignore some keys that might otherwise typically + * be considered wake keys. We filter them out here. + * + * {@link KeyEvent#KEYCODE_POWER} is notably absent from this list because it + * is always considered a wake key. + */ + private boolean isWakeKeyWhenKeyguardShowing(int keyCode, boolean isDocked) { + switch (keyCode) { + // ignore volume keys unless docked + case KeyEvent.KEYCODE_VOLUME_UP: + case KeyEvent.KEYCODE_VOLUME_DOWN: + case KeyEvent.KEYCODE_VOLUME_MUTE: + return isDocked; + + // ignore media and camera keys + 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_CAMERA: + return false; + } + return true; + } + + /** + * When a wake motion such as an external mouse movement is received when the screen + * is off and the keyguard is showing, we need to decide whether to actually turn + * on the screen, and if so, tell the keyguard to prepare itself and poke the wake + * lock when it is ready. + * + * The 'Tq' suffix is per the documentation in {@link WindowManagerPolicy}. + * Be sure not to take any action that takes a long time; any significant + * action should be posted to a handler. + * + * @return Whether we poked the wake lock (and turned the screen on) + */ + public boolean onWakeMotionWhenKeyguardShowingTq() { + if (DEBUG) Log.d(TAG, "onWakeMotionWhenKeyguardShowing()"); + + // give the keyguard view manager a chance to adjust the state of the + // keyguard based on the key that woke the device before poking + // the wake lock + wakeWhenReadyLocked(KeyEvent.KEYCODE_UNKNOWN); + return true; + } + + public void keyguardDone(boolean authenticated, boolean wakeup) { + synchronized (this) { + EventLog.writeEvent(70000, 2); + if (DEBUG) Log.d(TAG, "keyguardDone(" + authenticated + ")"); + Message msg = mHandler.obtainMessage(KEYGUARD_DONE); + msg.arg1 = wakeup ? 1 : 0; + mHandler.sendMessage(msg); + + if (authenticated) { + mUpdateMonitor.clearFailedAttempts(); + } + + if (mExitSecureCallback != null) { + mExitSecureCallback.onKeyguardExitResult(authenticated); + mExitSecureCallback = null; + + if (authenticated) { + // after succesfully exiting securely, no need to reshow + // the keyguard when they've released the lock + mExternallyEnabled = true; + mNeedToReshowWhenReenabled = false; + } + } + } + } + + /** + * This handler will be associated with the policy thread, which will also + * be the UI thread of the keyguard. Since the apis of the policy, and therefore + * this class, can be called by other threads, any action that directly + * interacts with the keyguard ui should be posted to this handler, rather + * than called directly. + */ + private Handler mHandler = new Handler(Looper.myLooper(), null, true /*async*/) { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case TIMEOUT: + handleTimeout(msg.arg1); + return ; + case SHOW: + handleShow(); + return ; + case HIDE: + handleHide(); + return ; + case RESET: + handleReset(); + return ; + case VERIFY_UNLOCK: + handleVerifyUnlock(); + return; + case NOTIFY_SCREEN_OFF: + handleNotifyScreenOff(); + return; + case NOTIFY_SCREEN_ON: + handleNotifyScreenOn((KeyguardViewManager.ShowListener)msg.obj); + return; + case WAKE_WHEN_READY: + handleWakeWhenReady(msg.arg1); + return; + case KEYGUARD_DONE: + handleKeyguardDone(msg.arg1 != 0); + return; + case KEYGUARD_DONE_DRAWING: + handleKeyguardDoneDrawing(); + return; + case KEYGUARD_DONE_AUTHENTICATING: + keyguardDone(true, true); + return; + case SET_HIDDEN: + handleSetHidden(msg.arg1 != 0); + break; + case KEYGUARD_TIMEOUT: + synchronized (KeyguardViewMediator.this) { + doKeyguardLocked(); + } + break; + } + } + }; + + /** + * @see #keyguardDone + * @see #KEYGUARD_DONE + */ + private void handleKeyguardDone(boolean wakeup) { + if (DEBUG) Log.d(TAG, "handleKeyguardDone"); + handleHide(); + if (wakeup) { + mPM.wakeUp(SystemClock.uptimeMillis()); + } + mWakeLock.release(); + + if (!(mContext instanceof Activity)) { + final UserHandle currentUser = new UserHandle(mLockPatternUtils.getCurrentUser()); + mContext.sendBroadcastAsUser(mUserPresentIntent, currentUser); + } + } + + /** + * @see #keyguardDoneDrawing + * @see #KEYGUARD_DONE_DRAWING + */ + private void handleKeyguardDoneDrawing() { + synchronized(this) { + if (false) Log.d(TAG, "handleKeyguardDoneDrawing"); + if (mWaitingUntilKeyguardVisible) { + if (DEBUG) Log.d(TAG, "handleKeyguardDoneDrawing: notifying mWaitingUntilKeyguardVisible"); + mWaitingUntilKeyguardVisible = false; + notifyAll(); + + // there will usually be two of these sent, one as a timeout, and one + // as a result of the callback, so remove any remaining messages from + // the queue + mHandler.removeMessages(KEYGUARD_DONE_DRAWING); + } + } + } + + /** + * Handles the message sent by {@link #pokeWakelock} + * @param seq used to determine if anything has changed since the message + * was sent. + * @see #TIMEOUT + */ + private void handleTimeout(int seq) { + synchronized (KeyguardViewMediator.this) { + if (DEBUG) Log.d(TAG, "handleTimeout"); + if (seq == mWakelockSequence) { + mWakeLock.release(); + } + } + } + + private void playSounds(boolean locked) { + // User feedback for keyguard. + + if (mSuppressNextLockSound) { + mSuppressNextLockSound = false; + return; + } + + final ContentResolver cr = mContext.getContentResolver(); + if (Settings.System.getInt(cr, Settings.System.LOCKSCREEN_SOUNDS_ENABLED, 1) == 1) { + final int whichSound = locked + ? mLockSoundId + : mUnlockSoundId; + mLockSounds.stop(mLockSoundStreamId); + // Init mAudioManager + if (mAudioManager == null) { + mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); + if (mAudioManager == null) return; + mMasterStreamType = mAudioManager.getMasterStreamType(); + } + // If the stream is muted, don't play the sound + if (mAudioManager.isStreamMute(mMasterStreamType)) return; + + mLockSoundStreamId = mLockSounds.play(whichSound, + mLockSoundVolume, mLockSoundVolume, 1/*priortiy*/, 0/*loop*/, 1.0f/*rate*/); + } + } + + private void updateActivityLockScreenState() { + try { + ActivityManagerNative.getDefault().setLockScreenShown( + mShowing && !mHidden); + } catch (RemoteException e) { + } + } + + /** + * Handle message sent by {@link #showLocked}. + * @see #SHOW + */ + private void handleShow() { + synchronized (KeyguardViewMediator.this) { + if (DEBUG) Log.d(TAG, "handleShow"); + if (!mSystemReady) return; + + mKeyguardViewManager.show(); + mShowing = true; + updateActivityLockScreenState(); + adjustUserActivityLocked(); + adjustStatusBarLocked(); + try { + ActivityManagerNative.getDefault().closeSystemDialogs("lock"); + } catch (RemoteException e) { + } + + // Do this at the end to not slow down display of the keyguard. + playSounds(true); + + mShowKeyguardWakeLock.release(); + } + } + + /** + * Handle message sent by {@link #hideLocked()} + * @see #HIDE + */ + private void handleHide() { + synchronized (KeyguardViewMediator.this) { + if (DEBUG) Log.d(TAG, "handleHide"); + if (mWakeAndHandOff.isHeld()) { + Log.w(TAG, "attempt to hide the keyguard while waking, ignored"); + return; + } + + // only play "unlock" noises if not on a call (since the incall UI + // disables the keyguard) + if (TelephonyManager.EXTRA_STATE_IDLE.equals(mPhoneState)) { + playSounds(false); + } + + mKeyguardViewManager.hide(); + mShowing = false; + updateActivityLockScreenState(); + adjustUserActivityLocked(); + adjustStatusBarLocked(); + } + } + + private void adjustUserActivityLocked() { + // disable user activity if we are shown and not hidden + if (DEBUG) Log.d(TAG, "adjustUserActivityLocked mShowing: " + mShowing + " mHidden: " + mHidden); + boolean enabled = !mShowing || mHidden; + // FIXME: Replace this with a new timeout control mechanism. + //mRealPowerManager.enableUserActivity(enabled); + if (!enabled && mScreenOn) { + // reinstate our short screen timeout policy + pokeWakelock(); + } + } + + private void adjustStatusBarLocked() { + if (mStatusBarManager == null) { + mStatusBarManager = (StatusBarManager) + mContext.getSystemService(Context.STATUS_BAR_SERVICE); + } + if (mStatusBarManager == null) { + Log.w(TAG, "Could not get status bar manager"); + } else { + if (mShowLockIcon) { + // Give feedback to user when secure keyguard is active and engaged + if (mShowing && isSecure()) { + if (!mShowingLockIcon) { + String contentDescription = mContext.getString( + com.android.internal.R.string.status_bar_device_locked); + mStatusBarManager.setIcon("secure", + com.android.internal.R.drawable.stat_sys_secure, 0, + contentDescription); + mShowingLockIcon = true; + } + } else { + if (mShowingLockIcon) { + mStatusBarManager.removeIcon("secure"); + mShowingLockIcon = false; + } + } + } + + // Disable aspects of the system/status/navigation bars that must not be re-enabled by + // windows that appear on top, ever + int flags = StatusBarManager.DISABLE_NONE; + if (mShowing) { + // disable navigation status bar components (home, recents) if lock screen is up + flags |= StatusBarManager.DISABLE_RECENT; + if (isSecure() || !ENABLE_INSECURE_STATUS_BAR_EXPAND) { + // showing secure lockscreen; disable expanding. + flags |= StatusBarManager.DISABLE_EXPAND; + } + if (isSecure()) { + // showing secure lockscreen; disable ticker. + flags |= StatusBarManager.DISABLE_NOTIFICATION_TICKER; + } + } + + if (DEBUG) { + Log.d(TAG, "adjustStatusBarLocked: mShowing=" + mShowing + " mHidden=" + mHidden + + " isSecure=" + isSecure() + " --> flags=0x" + Integer.toHexString(flags)); + } + + mStatusBarManager.disable(flags); + } + } + + /** + * Handle message sent by {@link #wakeWhenReadyLocked(int)} + * @param keyCode The key that woke the device. + * @see #WAKE_WHEN_READY + */ + private void handleWakeWhenReady(int keyCode) { + synchronized (KeyguardViewMediator.this) { + if (DBG_WAKE) Log.d(TAG, "handleWakeWhenReady(" + keyCode + ")"); + + // this should result in a call to 'poke wakelock' which will set a timeout + // on releasing the wakelock + if (!mKeyguardViewManager.wakeWhenReadyTq(keyCode)) { + // poke wakelock ourselves if keyguard is no longer active + Log.w(TAG, "mKeyguardViewManager.wakeWhenReadyTq did not poke wake lock, so poke it ourselves"); + pokeWakelock(); + } + + /** + * Now that the keyguard is ready and has poked the wake lock, we can + * release the handoff wakelock + */ + mWakeAndHandOff.release(); + + if (!mWakeLock.isHeld()) { + Log.w(TAG, "mWakeLock not held in mKeyguardViewManager.wakeWhenReadyTq"); + } + } + } + + /** + * Handle message sent by {@link #resetStateLocked()} + * @see #RESET + */ + private void handleReset() { + synchronized (KeyguardViewMediator.this) { + if (DEBUG) Log.d(TAG, "handleReset"); + mKeyguardViewManager.reset(); + } + } + + /** + * Handle message sent by {@link #verifyUnlock} + * @see #RESET + */ + private void handleVerifyUnlock() { + synchronized (KeyguardViewMediator.this) { + if (DEBUG) Log.d(TAG, "handleVerifyUnlock"); + mKeyguardViewManager.verifyUnlock(); + mShowing = true; + updateActivityLockScreenState(); + } + } + + /** + * Handle message sent by {@link #notifyScreenOffLocked()} + * @see #NOTIFY_SCREEN_OFF + */ + private void handleNotifyScreenOff() { + synchronized (KeyguardViewMediator.this) { + if (DEBUG) Log.d(TAG, "handleNotifyScreenOff"); + mKeyguardViewManager.onScreenTurnedOff(); + } + } + + /** + * Handle message sent by {@link #notifyScreenOnLocked()} + * @see #NOTIFY_SCREEN_ON + */ + private void handleNotifyScreenOn(KeyguardViewManager.ShowListener showListener) { + synchronized (KeyguardViewMediator.this) { + if (DEBUG) Log.d(TAG, "handleNotifyScreenOn"); + mKeyguardViewManager.onScreenTurnedOn(showListener); + } + } + +} diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetView.java new file mode 100644 index 0000000..120f8f8 --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetView.java @@ -0,0 +1,140 @@ +/* + * 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.animation.TimeInterpolator; +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.DecelerateInterpolator; + +public class KeyguardWidgetView extends PagedView { + + public KeyguardWidgetView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public KeyguardWidgetView(Context context) { + this(null, null, 0); + } + + public KeyguardWidgetView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + } + + ZInterpolator mZInterpolator = new ZInterpolator(0.5f); + private static float CAMERA_DISTANCE = 1500; + private static float TRANSITION_SCALE_FACTOR = 0.74f; + private static float TRANSITION_PIVOT = 0.65f; + private static float TRANSITION_MAX_ROTATION = 30; + private static final boolean PERFORM_OVERSCROLL_ROTATION = true; + private AccelerateInterpolator mAlphaInterpolator = new AccelerateInterpolator(0.9f); + private DecelerateInterpolator mLeftScreenAlphaInterpolator = new DecelerateInterpolator(4); + /* + * This interpolator emulates the rate at which the perceived scale of an object changes + * as its distance from a camera increases. When this interpolator is applied to a scale + * animation on a view, it evokes the sense that the object is shrinking due to moving away + * from the camera. + */ + static class ZInterpolator implements TimeInterpolator { + private float focalLength; + + public ZInterpolator(float foc) { + focalLength = foc; + } + + public float getInterpolation(float input) { + return (1.0f - focalLength / (focalLength + input)) / + (1.0f - focalLength / (focalLength + 1.0f)); + } + } + + // In apps customize, we have a scrolling effect which emulates pulling cards off of a stack. + @Override + protected void screenScrolled(int screenCenter) { + super.screenScrolled(screenCenter); + + for (int i = 0; i < getChildCount(); i++) { + View v = getPageAt(i); + if (v != null) { + float scrollProgress = getScrollProgress(screenCenter, v, i); + + float interpolatedProgress = + mZInterpolator.getInterpolation(Math.abs(Math.min(scrollProgress, 0))); + float scale = (1 - interpolatedProgress) + + interpolatedProgress * TRANSITION_SCALE_FACTOR; + float translationX = Math.min(0, scrollProgress) * v.getMeasuredWidth(); + + float alpha; + + if (scrollProgress < 0) { + alpha = scrollProgress < 0 ? mAlphaInterpolator.getInterpolation( + 1 - Math.abs(scrollProgress)) : 1.0f; + } else { + // On large screens we need to fade the page as it nears its leftmost position + alpha = mLeftScreenAlphaInterpolator.getInterpolation(1 - scrollProgress); + } + + v.setCameraDistance(mDensity * CAMERA_DISTANCE); + int pageWidth = v.getMeasuredWidth(); + int pageHeight = v.getMeasuredHeight(); + + if (PERFORM_OVERSCROLL_ROTATION) { + if (i == 0 && scrollProgress < 0) { + // Overscroll to the left + v.setPivotX(TRANSITION_PIVOT * pageWidth); + v.setRotationY(-TRANSITION_MAX_ROTATION * scrollProgress); + scale = 1.0f; + alpha = 1.0f; + // On the first page, we don't want the page to have any lateral motion + translationX = 0; + } else if (i == getChildCount() - 1 && scrollProgress > 0) { + // Overscroll to the right + v.setPivotX((1 - TRANSITION_PIVOT) * pageWidth); + v.setRotationY(-TRANSITION_MAX_ROTATION * scrollProgress); + scale = 1.0f; + alpha = 1.0f; + // On the last page, we don't want the page to have any lateral motion. + translationX = 0; + } else { + v.setPivotY(pageHeight / 2.0f); + v.setPivotX(pageWidth / 2.0f); + v.setRotationY(0f); + } + } + + v.setTranslationX(translationX); + v.setScaleX(scale); + v.setScaleY(scale); + v.setAlpha(alpha); + + // If the view has 0 alpha, we set it to be invisible so as to prevent + // it from accepting touches + if (alpha == 0) { + v.setVisibility(INVISIBLE); + } else if (v.getVisibility() != VISIBLE) { + v.setVisibility(VISIBLE); + } + } + } + } +} diff --git a/policy/src/com/android/internal/policy/impl/keyguard/PagedView.java b/policy/src/com/android/internal/policy/impl/keyguard/PagedView.java new file mode 100644 index 0000000..1b46efa --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/keyguard/PagedView.java @@ -0,0 +1,1704 @@ +/* + * 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.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.animation.PropertyValuesHolder; +import android.animation.ValueAnimator; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.AttributeSet; +import android.util.Log; +import android.view.InputDevice; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.VelocityTracker; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewGroup; +import android.view.ViewParent; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityManager; +import android.view.accessibility.AccessibilityNodeInfo; +import android.view.animation.Interpolator; +import android.widget.Scroller; + +import com.android.internal.R; + +import java.util.ArrayList; + +/** + * An abstraction of the original Workspace which supports browsing through a + * sequential list of "pages" + */ +public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeListener { + private static final String TAG = "WidgetPagedView"; + private static final boolean DEBUG = false; + protected static final int INVALID_PAGE = -1; + + // the min drag distance for a fling to register, to prevent random page shifts + private static final int MIN_LENGTH_FOR_FLING = 25; + + protected static final int PAGE_SNAP_ANIMATION_DURATION = 550; + protected static final int SLOW_PAGE_SNAP_ANIMATION_DURATION = 950; + protected static final float NANOTIME_DIV = 1000000000.0f; + + private static final float OVERSCROLL_ACCELERATE_FACTOR = 2; + private static final float OVERSCROLL_DAMP_FACTOR = 0.14f; + + private static final float RETURN_TO_ORIGINAL_PAGE_THRESHOLD = 0.33f; + // The page is moved more than halfway, automatically move to the next page on touch up. + private static final float SIGNIFICANT_MOVE_THRESHOLD = 0.4f; + + // The following constants need to be scaled based on density. The scaled versions will be + // assigned to the corresponding member variables below. + private static final int FLING_THRESHOLD_VELOCITY = 500; + private static final int MIN_SNAP_VELOCITY = 1500; + private static final int MIN_FLING_VELOCITY = 250; + + static final int AUTOMATIC_PAGE_SPACING = -1; + + protected int mFlingThresholdVelocity; + protected int mMinFlingVelocity; + protected int mMinSnapVelocity; + + protected float mDensity; + protected float mSmoothingTime; + protected float mTouchX; + + protected boolean mFirstLayout = true; + + protected int mCurrentPage; + protected int mNextPage = INVALID_PAGE; + protected int mMaxScrollX; + protected Scroller mScroller; + private VelocityTracker mVelocityTracker; + + private float mDownMotionX; + protected float mLastMotionX; + protected float mLastMotionXRemainder; + protected float mLastMotionY; + protected float mTotalMotionX; + private int mLastScreenCenter = -1; + private int[] mChildOffsets; + private int[] mChildRelativeOffsets; + private int[] mChildOffsetsWithLayoutScale; + + protected final static int TOUCH_STATE_REST = 0; + protected final static int TOUCH_STATE_SCROLLING = 1; + protected final static int TOUCH_STATE_PREV_PAGE = 2; + protected final static int TOUCH_STATE_NEXT_PAGE = 3; + protected final static float ALPHA_QUANTIZE_LEVEL = 0.0001f; + + protected int mTouchState = TOUCH_STATE_REST; + protected boolean mForceScreenScrolled = false; + + protected OnLongClickListener mLongClickListener; + + protected boolean mAllowLongPress = true; + + protected int mTouchSlop; + private int mPagingTouchSlop; + private int mMaximumVelocity; + private int mMinimumWidth; + protected int mPageSpacing; + protected int mPageLayoutPaddingTop; + protected int mPageLayoutPaddingBottom; + protected int mPageLayoutPaddingLeft; + protected int mPageLayoutPaddingRight; + protected int mPageLayoutWidthGap; + protected int mPageLayoutHeightGap; + protected int mCellCountX = 0; + protected int mCellCountY = 0; + protected boolean mCenterPagesVertically; + protected boolean mAllowOverScroll = true; + protected int mUnboundedScrollX; + protected int[] mTempVisiblePagesRange = new int[2]; + protected boolean mForceDrawAllChildrenNextFrame; + + // mOverScrollX is equal to getScrollX() when we're within the normal scroll range. Otherwise + // it is equal to the scaled overscroll position. We use a separate value so as to prevent + // the screens from continuing to translate beyond the normal bounds. + protected int mOverScrollX; + + // parameter that adjusts the layout to be optimized for pages with that scale factor + protected float mLayoutScale = 1.0f; + + protected static final int INVALID_POINTER = -1; + + protected int mActivePointerId = INVALID_POINTER; + + private PageSwitchListener mPageSwitchListener; + + protected ArrayList<Boolean> mDirtyPageContent; + + // If true, syncPages and syncPageItems will be called to refresh pages + protected boolean mContentIsRefreshable = true; + + // If true, modify alpha of neighboring pages as user scrolls left/right + protected boolean mFadeInAdjacentScreens = true; + + // It true, use a different slop parameter (pagingTouchSlop = 2 * touchSlop) for deciding + // to switch to a new page + protected boolean mUsePagingTouchSlop = true; + + // If true, the subclass should directly update scrollX itself in its computeScroll method + // (SmoothPagedView does this) + protected boolean mDeferScrollUpdate = false; + + protected boolean mIsPageMoving = false; + + // All syncs and layout passes are deferred until data is ready. + protected boolean mIsDataReady = true; + + // Scrolling indicator + private ValueAnimator mScrollIndicatorAnimator; + private View mScrollIndicator; + private int mScrollIndicatorPaddingLeft; + private int mScrollIndicatorPaddingRight; + private boolean mShouldShowScrollIndicator = false; + private boolean mShouldShowScrollIndicatorImmediately = false; + protected static final int sScrollIndicatorFadeInDuration = 150; + protected static final int sScrollIndicatorFadeOutDuration = 650; + protected static final int sScrollIndicatorFlashDuration = 650; + + public interface PageSwitchListener { + void onPageSwitch(View newPage, int newPageIndex); + } + + public PagedView(Context context) { + this(context, null); + } + + public PagedView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public PagedView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + TypedArray a = context.obtainStyledAttributes(attrs, + R.styleable.PagedView, defStyle, 0); + setPageSpacing(a.getDimensionPixelSize(R.styleable.PagedView_pageSpacing, 0)); + mPageLayoutPaddingTop = a.getDimensionPixelSize( + R.styleable.PagedView_pageLayoutPaddingTop, 0); + mPageLayoutPaddingBottom = a.getDimensionPixelSize( + R.styleable.PagedView_pageLayoutPaddingBottom, 0); + mPageLayoutPaddingLeft = a.getDimensionPixelSize( + R.styleable.PagedView_pageLayoutPaddingLeft, 0); + mPageLayoutPaddingRight = a.getDimensionPixelSize( + R.styleable.PagedView_pageLayoutPaddingRight, 0); + mPageLayoutWidthGap = a.getDimensionPixelSize( + R.styleable.PagedView_pageLayoutWidthGap, 0); + mPageLayoutHeightGap = a.getDimensionPixelSize( + R.styleable.PagedView_pageLayoutHeightGap, 0); + mScrollIndicatorPaddingLeft = + a.getDimensionPixelSize(R.styleable.PagedView_scrollIndicatorPaddingLeft, 0); + mScrollIndicatorPaddingRight = + a.getDimensionPixelSize(R.styleable.PagedView_scrollIndicatorPaddingRight, 0); + a.recycle(); + + setHapticFeedbackEnabled(false); + init(); + } + + /** + * Initializes various states for this workspace. + */ + protected void init() { + mDirtyPageContent = new ArrayList<Boolean>(); + mDirtyPageContent.ensureCapacity(32); + mScroller = new Scroller(getContext(), new ScrollInterpolator()); + mCurrentPage = 0; + mCenterPagesVertically = true; + + final ViewConfiguration configuration = ViewConfiguration.get(getContext()); + mTouchSlop = configuration.getScaledTouchSlop(); + mPagingTouchSlop = configuration.getScaledPagingTouchSlop(); + mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); + mDensity = getResources().getDisplayMetrics().density; + + mFlingThresholdVelocity = (int) (FLING_THRESHOLD_VELOCITY * mDensity); + mMinFlingVelocity = (int) (MIN_FLING_VELOCITY * mDensity); + mMinSnapVelocity = (int) (MIN_SNAP_VELOCITY * mDensity); + setOnHierarchyChangeListener(this); + } + + public void setPageSwitchListener(PageSwitchListener pageSwitchListener) { + mPageSwitchListener = pageSwitchListener; + if (mPageSwitchListener != null) { + mPageSwitchListener.onPageSwitch(getPageAt(mCurrentPage), mCurrentPage); + } + } + + /** + * Called by subclasses to mark that data is ready, and that we can begin loading and laying + * out pages. + */ + protected void setDataIsReady() { + mIsDataReady = true; + } + + protected boolean isDataReady() { + return mIsDataReady; + } + + /** + * Returns the index of the currently displayed page. + * + * @return The index of the currently displayed page. + */ + int getCurrentPage() { + return mCurrentPage; + } + + int getNextPage() { + return (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage; + } + + int getPageCount() { + return getChildCount(); + } + + View getPageAt(int index) { + return getChildAt(index); + } + + protected int indexToPage(int index) { + return index; + } + + /** + * Updates the scroll of the current page immediately to its final scroll position. We use this + * in CustomizePagedView to allow tabs to share the same PagedView while resetting the scroll of + * the previous tab page. + */ + protected void updateCurrentPageScroll() { + int offset = getChildOffset(mCurrentPage); + int relOffset = getRelativeChildOffset(mCurrentPage); + int newX = offset - relOffset; + scrollTo(newX, 0); + mScroller.setFinalX(newX); + mScroller.forceFinished(true); + } + + /** + * Sets the current page. + */ + void setCurrentPage(int currentPage) { + if (!mScroller.isFinished()) { + mScroller.abortAnimation(); + } + // don't introduce any checks like mCurrentPage == currentPage here-- if we change the + // the default + if (getChildCount() == 0) { + return; + } + + mCurrentPage = Math.max(0, Math.min(currentPage, getPageCount() - 1)); + updateCurrentPageScroll(); + updateScrollingIndicator(); + notifyPageSwitchListener(); + invalidate(); + } + + protected void notifyPageSwitchListener() { + if (mPageSwitchListener != null) { + mPageSwitchListener.onPageSwitch(getPageAt(mCurrentPage), mCurrentPage); + } + } + + protected void pageBeginMoving() { + if (!mIsPageMoving) { + mIsPageMoving = true; + onPageBeginMoving(); + } + } + + protected void pageEndMoving() { + if (mIsPageMoving) { + mIsPageMoving = false; + onPageEndMoving(); + } + } + + protected boolean isPageMoving() { + return mIsPageMoving; + } + + // a method that subclasses can override to add behavior + protected void onPageBeginMoving() { + } + + // a method that subclasses can override to add behavior + protected void onPageEndMoving() { + } + + /** + * Registers the specified listener on each page contained in this workspace. + * + * @param l The listener used to respond to long clicks. + */ + @Override + public void setOnLongClickListener(OnLongClickListener l) { + mLongClickListener = l; + final int count = getPageCount(); + for (int i = 0; i < count; i++) { + getPageAt(i).setOnLongClickListener(l); + } + } + + @Override + public void scrollBy(int x, int y) { + scrollTo(mUnboundedScrollX + x, getScrollY() + y); + } + + @Override + public void scrollTo(int x, int y) { + mUnboundedScrollX = x; + + if (x < 0) { + super.scrollTo(0, y); + if (mAllowOverScroll) { + overScroll(x); + } + } else if (x > mMaxScrollX) { + super.scrollTo(mMaxScrollX, y); + if (mAllowOverScroll) { + overScroll(x - mMaxScrollX); + } + } else { + mOverScrollX = x; + super.scrollTo(x, y); + } + + mTouchX = x; + mSmoothingTime = System.nanoTime() / NANOTIME_DIV; + } + + // we moved this functionality to a helper function so SmoothPagedView can reuse it + protected boolean computeScrollHelper() { + if (mScroller.computeScrollOffset()) { + // Don't bother scrolling if the page does not need to be moved + if (getScrollX() != mScroller.getCurrX() + || getScrollY() != mScroller.getCurrY() + || mOverScrollX != mScroller.getCurrX()) { + scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); + } + invalidate(); + return true; + } else if (mNextPage != INVALID_PAGE) { + mCurrentPage = Math.max(0, Math.min(mNextPage, getPageCount() - 1)); + mNextPage = INVALID_PAGE; + notifyPageSwitchListener(); + + // We don't want to trigger a page end moving unless the page has settled + // and the user has stopped scrolling + if (mTouchState == TOUCH_STATE_REST) { + pageEndMoving(); + } + + // Notify the user when the page changes + AccessibilityManager accessibilityManager = (AccessibilityManager) + getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); + if (accessibilityManager.isEnabled()) { + AccessibilityEvent ev = + AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_SCROLLED); + ev.getText().add(getCurrentPageDescription()); + sendAccessibilityEventUnchecked(ev); + } + return true; + } + return false; + } + + public String getCurrentPageDescription() { + return ""; + } + + @Override + public void computeScroll() { + computeScrollHelper(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + if (!mIsDataReady || getChildCount() == 0) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + return; + } + + final int widthMode = MeasureSpec.getMode(widthMeasureSpec); + final int widthSize = MeasureSpec.getSize(widthMeasureSpec); + final int heightMode = MeasureSpec.getMode(heightMeasureSpec); + final int heightSize = MeasureSpec.getSize(heightMeasureSpec); + + if (widthMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.UNSPECIFIED) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + return; + } + + // Return early if we aren't given a proper dimension + if (widthSize <= 0 || heightSize <= 0) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + return; + } + + /* Allow the height to be set as WRAP_CONTENT. This allows the particular case + * of the All apps view on XLarge displays to not take up more space then it needs. Width + * is still not allowed to be set as WRAP_CONTENT since many parts of the code expect + * each page to have the same width. + */ + final int verticalPadding = getPaddingTop() + getPaddingBottom(); + final int horizontalPadding = getPaddingLeft() + getPaddingRight(); + + // The children are given the same width and height as the workspace + // unless they were set to WRAP_CONTENT + if (DEBUG) Log.d(TAG, "PagedView.onMeasure(): " + widthSize + ", " + heightSize); + final int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + // disallowing padding in paged view (just pass 0) + final View child = getPageAt(i); + + int childWidthMode = MeasureSpec.EXACTLY; + int childHeightMode = MeasureSpec.EXACTLY; + + final int childWidthMeasureSpec = + MeasureSpec.makeMeasureSpec(widthSize - horizontalPadding, childWidthMode); + final int childHeightMeasureSpec = + MeasureSpec.makeMeasureSpec(heightSize - verticalPadding, childHeightMode); + + child.measure(childWidthMeasureSpec, childHeightMeasureSpec); + } + + setMeasuredDimension(widthSize, heightSize); + + // We can't call getChildOffset/getRelativeChildOffset until we set the measured dimensions. + // We also wait until we set the measured dimensions before flushing the cache as well, to + // ensure that the cache is filled with good values. + invalidateCachedOffsets(); + + if (childCount > 0) { + if (DEBUG) Log.d(TAG, "getRelativeChildOffset(): " + getMeasuredWidth() + ", " + + getChildWidth(0)); + + // Calculate the variable page spacing if necessary + if (mPageSpacing == AUTOMATIC_PAGE_SPACING) { + // The gap between pages in the PagedView should be equal to the gap from the page + // to the edge of the screen (so it is not visible in the current screen). To + // account for unequal padding on each side of the paged view, we take the maximum + // of the left/right gap and use that as the gap between each page. + int offset = getRelativeChildOffset(0); + int spacing = Math.max(offset, widthSize - offset - + getChildAt(0).getMeasuredWidth()); + setPageSpacing(spacing); + } + } + + updateScrollingIndicatorPosition(); + + if (childCount > 0) { + mMaxScrollX = getChildOffset(childCount - 1) - getRelativeChildOffset(childCount - 1); + } else { + mMaxScrollX = 0; + } + } + + public void setPageSpacing(int pageSpacing) { + mPageSpacing = pageSpacing; + invalidateCachedOffsets(); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + if (!mIsDataReady || getChildCount() == 0) { + return; + } + + if (DEBUG) Log.d(TAG, "PagedView.onLayout()"); + final int verticalPadding = getPaddingTop() + getPaddingBottom(); + final int childCount = getChildCount(); + int childLeft = getRelativeChildOffset(0); + + for (int i = 0; i < childCount; i++) { + final View child = getPageAt(i); + if (child.getVisibility() != View.GONE) { + final int childWidth = getScaledMeasuredWidth(child); + final int childHeight = child.getMeasuredHeight(); + int childTop = getPaddingTop(); + if (mCenterPagesVertically) { + childTop += ((getMeasuredHeight() - verticalPadding) - childHeight) / 2; + } + + if (DEBUG) Log.d(TAG, "\tlayout-child" + i + ": " + childLeft + ", " + childTop); + child.layout(childLeft, childTop, + childLeft + child.getMeasuredWidth(), childTop + childHeight); + childLeft += childWidth + mPageSpacing; + } + } + + if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) { + setHorizontalScrollBarEnabled(false); + updateCurrentPageScroll(); + setHorizontalScrollBarEnabled(true); + mFirstLayout = false; + } + } + + protected void screenScrolled(int screenCenter) { + if (isScrollingIndicatorEnabled()) { + updateScrollingIndicator(); + } + boolean isInOverscroll = mOverScrollX < 0 || mOverScrollX > mMaxScrollX; + + if (mFadeInAdjacentScreens && !isInOverscroll) { + for (int i = 0; i < getChildCount(); i++) { + View child = getChildAt(i); + if (child != null) { + float scrollProgress = getScrollProgress(screenCenter, child, i); + float alpha = 1 - Math.abs(scrollProgress); + child.setAlpha(alpha); + } + } + invalidate(); + } + } + + @Override + public void onChildViewAdded(View parent, View child) { + // This ensures that when children are added, they get the correct transforms / alphas + // in accordance with any scroll effects. + mForceScreenScrolled = true; + invalidate(); + invalidateCachedOffsets(); + } + + @Override + public void onChildViewRemoved(View parent, View child) { + } + + protected void invalidateCachedOffsets() { + int count = getChildCount(); + if (count == 0) { + mChildOffsets = null; + mChildRelativeOffsets = null; + mChildOffsetsWithLayoutScale = null; + return; + } + + mChildOffsets = new int[count]; + mChildRelativeOffsets = new int[count]; + mChildOffsetsWithLayoutScale = new int[count]; + for (int i = 0; i < count; i++) { + mChildOffsets[i] = -1; + mChildRelativeOffsets[i] = -1; + mChildOffsetsWithLayoutScale[i] = -1; + } + } + + protected int getChildOffset(int index) { + int[] childOffsets = Float.compare(mLayoutScale, 1f) == 0 ? + mChildOffsets : mChildOffsetsWithLayoutScale; + + if (childOffsets != null && childOffsets[index] != -1) { + return childOffsets[index]; + } else { + if (getChildCount() == 0) + return 0; + + int offset = getRelativeChildOffset(0); + for (int i = 0; i < index; ++i) { + offset += getScaledMeasuredWidth(getPageAt(i)) + mPageSpacing; + } + if (childOffsets != null) { + childOffsets[index] = offset; + } + return offset; + } + } + + protected int getRelativeChildOffset(int index) { + if (mChildRelativeOffsets != null && mChildRelativeOffsets[index] != -1) { + return mChildRelativeOffsets[index]; + } else { + final int padding = getPaddingLeft() + getPaddingRight(); + final int offset = getPaddingLeft() + + (getMeasuredWidth() - padding - getChildWidth(index)) / 2; + if (mChildRelativeOffsets != null) { + mChildRelativeOffsets[index] = offset; + } + return offset; + } + } + + protected int getScaledMeasuredWidth(View child) { + // This functions are called enough times that it actually makes a difference in the + // profiler -- so just inline the max() here + final int measuredWidth = child.getMeasuredWidth(); + final int minWidth = mMinimumWidth; + final int maxWidth = (minWidth > measuredWidth) ? minWidth : measuredWidth; + return (int) (maxWidth * mLayoutScale + 0.5f); + } + + protected void getVisiblePages(int[] range) { + final int pageCount = getChildCount(); + + if (pageCount > 0) { + final int screenWidth = getMeasuredWidth(); + int leftScreen = 0; + int rightScreen = 0; + View currPage = getPageAt(leftScreen); + while (leftScreen < pageCount - 1 && + currPage.getX() + currPage.getWidth() - + currPage.getPaddingRight() < getScrollX()) { + leftScreen++; + currPage = getPageAt(leftScreen); + } + rightScreen = leftScreen; + currPage = getPageAt(rightScreen + 1); + while (rightScreen < pageCount - 1 && + currPage.getX() - currPage.getPaddingLeft() < getScrollX() + screenWidth) { + rightScreen++; + currPage = getPageAt(rightScreen + 1); + } + range[0] = leftScreen; + range[1] = rightScreen; + } else { + range[0] = -1; + range[1] = -1; + } + } + + protected boolean shouldDrawChild(View child) { + return child.getAlpha() > 0; + } + + @Override + protected void dispatchDraw(Canvas canvas) { + int halfScreenSize = getMeasuredWidth() / 2; + // mOverScrollX is equal to getScrollX() when we're within the normal scroll range. + // Otherwise it is equal to the scaled overscroll position. + int screenCenter = mOverScrollX + halfScreenSize; + + if (screenCenter != mLastScreenCenter || mForceScreenScrolled) { + // set mForceScreenScrolled before calling screenScrolled so that screenScrolled can + // set it for the next frame + mForceScreenScrolled = false; + screenScrolled(screenCenter); + mLastScreenCenter = screenCenter; + } + + // Find out which screens are visible; as an optimization we only call draw on them + final int pageCount = getChildCount(); + if (pageCount > 0) { + getVisiblePages(mTempVisiblePagesRange); + final int leftScreen = mTempVisiblePagesRange[0]; + final int rightScreen = mTempVisiblePagesRange[1]; + if (leftScreen != -1 && rightScreen != -1) { + final long drawingTime = getDrawingTime(); + // Clip to the bounds + canvas.save(); + canvas.clipRect(getScrollX(), getScrollY(), getScrollX() + getRight() - getLeft(), + getScrollY() + getBottom() - getTop()); + + for (int i = getChildCount() - 1; i >= 0; i--) { + final View v = getPageAt(i); + if (mForceDrawAllChildrenNextFrame || + (leftScreen <= i && i <= rightScreen && shouldDrawChild(v))) { + drawChild(canvas, v, drawingTime); + } + } + mForceDrawAllChildrenNextFrame = false; + canvas.restore(); + } + } + } + + @Override + public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) { + int page = indexToPage(indexOfChild(child)); + if (page != mCurrentPage || !mScroller.isFinished()) { + snapToPage(page); + return true; + } + return false; + } + + @Override + protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { + int focusablePage; + if (mNextPage != INVALID_PAGE) { + focusablePage = mNextPage; + } else { + focusablePage = mCurrentPage; + } + View v = getPageAt(focusablePage); + if (v != null) { + return v.requestFocus(direction, previouslyFocusedRect); + } + return false; + } + + @Override + public boolean dispatchUnhandledMove(View focused, int direction) { + if (direction == View.FOCUS_LEFT) { + if (getCurrentPage() > 0) { + snapToPage(getCurrentPage() - 1); + return true; + } + } else if (direction == View.FOCUS_RIGHT) { + if (getCurrentPage() < getPageCount() - 1) { + snapToPage(getCurrentPage() + 1); + return true; + } + } + return super.dispatchUnhandledMove(focused, direction); + } + + @Override + public void addFocusables(ArrayList<View> views, int direction, int focusableMode) { + if (mCurrentPage >= 0 && mCurrentPage < getPageCount()) { + getPageAt(mCurrentPage).addFocusables(views, direction, focusableMode); + } + if (direction == View.FOCUS_LEFT) { + if (mCurrentPage > 0) { + getPageAt(mCurrentPage - 1).addFocusables(views, direction, focusableMode); + } + } else if (direction == View.FOCUS_RIGHT){ + if (mCurrentPage < getPageCount() - 1) { + getPageAt(mCurrentPage + 1).addFocusables(views, direction, focusableMode); + } + } + } + + /** + * If one of our descendant views decides that it could be focused now, only + * pass that along if it's on the current page. + * + * This happens when live folders requery, and if they're off page, they + * end up calling requestFocus, which pulls it on page. + */ + @Override + public void focusableViewAvailable(View focused) { + View current = getPageAt(mCurrentPage); + View v = focused; + while (true) { + if (v == current) { + super.focusableViewAvailable(focused); + return; + } + if (v == this) { + return; + } + ViewParent parent = v.getParent(); + if (parent instanceof View) { + v = (View)v.getParent(); + } else { + return; + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { + if (disallowIntercept) { + // We need to make sure to cancel our long press if + // a scrollable widget takes over touch events + final View currentPage = getPageAt(mCurrentPage); + currentPage.cancelLongPress(); + } + super.requestDisallowInterceptTouchEvent(disallowIntercept); + } + + /** + * Return true if a tap at (x, y) should trigger a flip to the previous page. + */ + protected boolean hitsPreviousPage(float x, float y) { + return (x < getRelativeChildOffset(mCurrentPage) - mPageSpacing); + } + + /** + * Return true if a tap at (x, y) should trigger a flip to the next page. + */ + protected boolean hitsNextPage(float x, float y) { + return (x > (getMeasuredWidth() - getRelativeChildOffset(mCurrentPage) + mPageSpacing)); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + /* + * This method JUST determines whether we want to intercept the motion. + * If we return true, onTouchEvent will be called and we do the actual + * scrolling there. + */ + acquireVelocityTrackerAndAddMovement(ev); + + // Skip touch handling if there are no pages to swipe + if (getChildCount() <= 0) return super.onInterceptTouchEvent(ev); + + /* + * Shortcut the most recurring case: the user is in the dragging + * state and he is moving his finger. We want to intercept this + * motion. + */ + final int action = ev.getAction(); + if ((action == MotionEvent.ACTION_MOVE) && + (mTouchState == TOUCH_STATE_SCROLLING)) { + return true; + } + + switch (action & MotionEvent.ACTION_MASK) { + case MotionEvent.ACTION_MOVE: { + /* + * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check + * whether the user has moved far enough from his original down touch. + */ + if (mActivePointerId != INVALID_POINTER) { + determineScrollingStart(ev); + break; + } + // if mActivePointerId is INVALID_POINTER, then we must have missed an ACTION_DOWN + // event. in that case, treat the first occurence of a move event as a ACTION_DOWN + // i.e. fall through to the next case (don't break) + // (We sometimes miss ACTION_DOWN events in Workspace because it ignores all events + // while it's small- this was causing a crash before we checked for INVALID_POINTER) + } + + case MotionEvent.ACTION_DOWN: { + final float x = ev.getX(); + final float y = ev.getY(); + // Remember location of down touch + mDownMotionX = x; + mLastMotionX = x; + mLastMotionY = y; + mLastMotionXRemainder = 0; + mTotalMotionX = 0; + mActivePointerId = ev.getPointerId(0); + mAllowLongPress = true; + + /* + * If being flinged and user touches the screen, initiate drag; + * otherwise don't. mScroller.isFinished should be false when + * being flinged. + */ + final int xDist = Math.abs(mScroller.getFinalX() - mScroller.getCurrX()); + final boolean finishedScrolling = (mScroller.isFinished() || xDist < mTouchSlop); + if (finishedScrolling) { + mTouchState = TOUCH_STATE_REST; + mScroller.abortAnimation(); + } else { + mTouchState = TOUCH_STATE_SCROLLING; + } + + // check if this can be the beginning of a tap on the side of the pages + // to scroll the current page + if (mTouchState != TOUCH_STATE_PREV_PAGE && mTouchState != TOUCH_STATE_NEXT_PAGE) { + if (getChildCount() > 0) { + if (hitsPreviousPage(x, y)) { + mTouchState = TOUCH_STATE_PREV_PAGE; + } else if (hitsNextPage(x, y)) { + mTouchState = TOUCH_STATE_NEXT_PAGE; + } + } + } + break; + } + + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + mTouchState = TOUCH_STATE_REST; + mAllowLongPress = false; + mActivePointerId = INVALID_POINTER; + releaseVelocityTracker(); + break; + + case MotionEvent.ACTION_POINTER_UP: + onSecondaryPointerUp(ev); + releaseVelocityTracker(); + break; + } + + /* + * The only time we want to intercept motion events is if we are in the + * drag mode. + */ + return mTouchState != TOUCH_STATE_REST; + } + + protected void determineScrollingStart(MotionEvent ev) { + determineScrollingStart(ev, 1.0f); + } + + /* + * Determines if we should change the touch state to start scrolling after the + * user moves their touch point too far. + */ + protected void determineScrollingStart(MotionEvent ev, float touchSlopScale) { + /* + * Locally do absolute value. mLastMotionX is set to the y value + * of the down event. + */ + final int pointerIndex = ev.findPointerIndex(mActivePointerId); + if (pointerIndex == -1) { + return; + } + final float x = ev.getX(pointerIndex); + final float y = ev.getY(pointerIndex); + final int xDiff = (int) Math.abs(x - mLastMotionX); + final int yDiff = (int) Math.abs(y - mLastMotionY); + + final int touchSlop = Math.round(touchSlopScale * mTouchSlop); + boolean xPaged = xDiff > mPagingTouchSlop; + boolean xMoved = xDiff > touchSlop; + boolean yMoved = yDiff > touchSlop; + + if (xMoved || xPaged || yMoved) { + if (mUsePagingTouchSlop ? xPaged : xMoved) { + // Scroll if the user moved far enough along the X axis + mTouchState = TOUCH_STATE_SCROLLING; + mTotalMotionX += Math.abs(mLastMotionX - x); + mLastMotionX = x; + mLastMotionXRemainder = 0; + mTouchX = getScrollX(); + mSmoothingTime = System.nanoTime() / NANOTIME_DIV; + pageBeginMoving(); + } + // Either way, cancel any pending longpress + cancelCurrentPageLongPress(); + } + } + + protected void cancelCurrentPageLongPress() { + if (mAllowLongPress) { + mAllowLongPress = false; + // Try canceling the long press. It could also have been scheduled + // by a distant descendant, so use the mAllowLongPress flag to block + // everything + final View currentPage = getPageAt(mCurrentPage); + if (currentPage != null) { + currentPage.cancelLongPress(); + } + } + } + + protected float getScrollProgress(int screenCenter, View v, int page) { + final int halfScreenSize = getMeasuredWidth() / 2; + + int totalDistance = getScaledMeasuredWidth(v) + mPageSpacing; + int delta = screenCenter - (getChildOffset(page) - + getRelativeChildOffset(page) + halfScreenSize); + + float scrollProgress = delta / (totalDistance * 1.0f); + scrollProgress = Math.min(scrollProgress, 1.0f); + scrollProgress = Math.max(scrollProgress, -1.0f); + return scrollProgress; + } + + // This curve determines how the effect of scrolling over the limits of the page dimishes + // as the user pulls further and further from the bounds + private float overScrollInfluenceCurve(float f) { + f -= 1.0f; + return f * f * f + 1.0f; + } + + protected void acceleratedOverScroll(float amount) { + int screenSize = getMeasuredWidth(); + + // We want to reach the max over scroll effect when the user has + // over scrolled half the size of the screen + float f = OVERSCROLL_ACCELERATE_FACTOR * (amount / screenSize); + + if (f == 0) return; + + // Clamp this factor, f, to -1 < f < 1 + if (Math.abs(f) >= 1) { + f /= Math.abs(f); + } + + int overScrollAmount = (int) Math.round(f * screenSize); + if (amount < 0) { + mOverScrollX = overScrollAmount; + super.scrollTo(0, getScrollY()); + } else { + mOverScrollX = mMaxScrollX + overScrollAmount; + super.scrollTo(mMaxScrollX, getScrollY()); + } + invalidate(); + } + + protected void dampedOverScroll(float amount) { + int screenSize = getMeasuredWidth(); + + float f = (amount / screenSize); + + if (f == 0) return; + f = f / (Math.abs(f)) * (overScrollInfluenceCurve(Math.abs(f))); + + // Clamp this factor, f, to -1 < f < 1 + if (Math.abs(f) >= 1) { + f /= Math.abs(f); + } + + int overScrollAmount = (int) Math.round(OVERSCROLL_DAMP_FACTOR * f * screenSize); + if (amount < 0) { + mOverScrollX = overScrollAmount; + super.scrollTo(0, getScrollY()); + } else { + mOverScrollX = mMaxScrollX + overScrollAmount; + super.scrollTo(mMaxScrollX, getScrollY()); + } + invalidate(); + } + + protected void overScroll(float amount) { + dampedOverScroll(amount); + } + + protected float maxOverScroll() { + // Using the formula in overScroll, assuming that f = 1.0 (which it should generally not + // exceed). Used to find out how much extra wallpaper we need for the over scroll effect + float f = 1.0f; + f = f / (Math.abs(f)) * (overScrollInfluenceCurve(Math.abs(f))); + return OVERSCROLL_DAMP_FACTOR * f; + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + // Skip touch handling if there are no pages to swipe + if (getChildCount() <= 0) return super.onTouchEvent(ev); + + acquireVelocityTrackerAndAddMovement(ev); + + final int action = ev.getAction(); + + switch (action & MotionEvent.ACTION_MASK) { + case MotionEvent.ACTION_DOWN: + /* + * If being flinged and user touches, stop the fling. isFinished + * will be false if being flinged. + */ + if (!mScroller.isFinished()) { + mScroller.abortAnimation(); + } + + // Remember where the motion event started + mDownMotionX = mLastMotionX = ev.getX(); + mLastMotionXRemainder = 0; + mTotalMotionX = 0; + mActivePointerId = ev.getPointerId(0); + if (mTouchState == TOUCH_STATE_SCROLLING) { + pageBeginMoving(); + } + break; + + case MotionEvent.ACTION_MOVE: + if (mTouchState == TOUCH_STATE_SCROLLING) { + // Scroll to follow the motion event + final int pointerIndex = ev.findPointerIndex(mActivePointerId); + final float x = ev.getX(pointerIndex); + final float deltaX = mLastMotionX + mLastMotionXRemainder - x; + + mTotalMotionX += Math.abs(deltaX); + + // Only scroll and update mLastMotionX if we have moved some discrete amount. We + // keep the remainder because we are actually testing if we've moved from the last + // scrolled position (which is discrete). + if (Math.abs(deltaX) >= 1.0f) { + mTouchX += deltaX; + mSmoothingTime = System.nanoTime() / NANOTIME_DIV; + if (!mDeferScrollUpdate) { + scrollBy((int) deltaX, 0); + if (DEBUG) Log.d(TAG, "onTouchEvent().Scrolling: " + deltaX); + } else { + invalidate(); + } + mLastMotionX = x; + mLastMotionXRemainder = deltaX - (int) deltaX; + } else { + awakenScrollBars(); + } + } else { + determineScrollingStart(ev); + } + break; + + case MotionEvent.ACTION_UP: + if (mTouchState == TOUCH_STATE_SCROLLING) { + final int activePointerId = mActivePointerId; + final int pointerIndex = ev.findPointerIndex(activePointerId); + final float x = ev.getX(pointerIndex); + final VelocityTracker velocityTracker = mVelocityTracker; + velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); + int velocityX = (int) velocityTracker.getXVelocity(activePointerId); + final int deltaX = (int) (x - mDownMotionX); + final int pageWidth = getScaledMeasuredWidth(getPageAt(mCurrentPage)); + boolean isSignificantMove = Math.abs(deltaX) > pageWidth * + SIGNIFICANT_MOVE_THRESHOLD; + + mTotalMotionX += Math.abs(mLastMotionX + mLastMotionXRemainder - x); + + boolean isFling = mTotalMotionX > MIN_LENGTH_FOR_FLING && + Math.abs(velocityX) > mFlingThresholdVelocity; + + // In the case that the page is moved far to one direction and then is flung + // in the opposite direction, we use a threshold to determine whether we should + // just return to the starting page, or if we should skip one further. + boolean returnToOriginalPage = false; + if (Math.abs(deltaX) > pageWidth * RETURN_TO_ORIGINAL_PAGE_THRESHOLD && + Math.signum(velocityX) != Math.signum(deltaX) && isFling) { + returnToOriginalPage = true; + } + + int finalPage; + // We give flings precedence over large moves, which is why we short-circuit our + // test for a large move if a fling has been registered. That is, a large + // move to the left and fling to the right will register as a fling to the right. + if (((isSignificantMove && deltaX > 0 && !isFling) || + (isFling && velocityX > 0)) && mCurrentPage > 0) { + finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage - 1; + snapToPageWithVelocity(finalPage, velocityX); + } else if (((isSignificantMove && deltaX < 0 && !isFling) || + (isFling && velocityX < 0)) && + mCurrentPage < getChildCount() - 1) { + finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage + 1; + snapToPageWithVelocity(finalPage, velocityX); + } else { + snapToDestination(); + } + } else if (mTouchState == TOUCH_STATE_PREV_PAGE) { + // at this point we have not moved beyond the touch slop + // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so + // we can just page + int nextPage = Math.max(0, mCurrentPage - 1); + if (nextPage != mCurrentPage) { + snapToPage(nextPage); + } else { + snapToDestination(); + } + } else if (mTouchState == TOUCH_STATE_NEXT_PAGE) { + // at this point we have not moved beyond the touch slop + // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so + // we can just page + int nextPage = Math.min(getChildCount() - 1, mCurrentPage + 1); + if (nextPage != mCurrentPage) { + snapToPage(nextPage); + } else { + snapToDestination(); + } + } else { + onUnhandledTap(ev); + } + mTouchState = TOUCH_STATE_REST; + mActivePointerId = INVALID_POINTER; + releaseVelocityTracker(); + break; + + case MotionEvent.ACTION_CANCEL: + if (mTouchState == TOUCH_STATE_SCROLLING) { + snapToDestination(); + } + mTouchState = TOUCH_STATE_REST; + mActivePointerId = INVALID_POINTER; + releaseVelocityTracker(); + break; + + case MotionEvent.ACTION_POINTER_UP: + onSecondaryPointerUp(ev); + break; + } + + return true; + } + + @Override + public boolean onGenericMotionEvent(MotionEvent event) { + if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) { + switch (event.getAction()) { + case MotionEvent.ACTION_SCROLL: { + // Handle mouse (or ext. device) by shifting the page depending on the scroll + final float vscroll; + final float hscroll; + if ((event.getMetaState() & KeyEvent.META_SHIFT_ON) != 0) { + vscroll = 0; + hscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL); + } else { + vscroll = -event.getAxisValue(MotionEvent.AXIS_VSCROLL); + hscroll = event.getAxisValue(MotionEvent.AXIS_HSCROLL); + } + if (hscroll != 0 || vscroll != 0) { + if (hscroll > 0 || vscroll > 0) { + scrollRight(); + } else { + scrollLeft(); + } + return true; + } + } + } + } + return super.onGenericMotionEvent(event); + } + + private void acquireVelocityTrackerAndAddMovement(MotionEvent ev) { + if (mVelocityTracker == null) { + mVelocityTracker = VelocityTracker.obtain(); + } + mVelocityTracker.addMovement(ev); + } + + private void releaseVelocityTracker() { + if (mVelocityTracker != null) { + mVelocityTracker.recycle(); + mVelocityTracker = null; + } + } + + private void onSecondaryPointerUp(MotionEvent ev) { + final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> + MotionEvent.ACTION_POINTER_INDEX_SHIFT; + final int pointerId = ev.getPointerId(pointerIndex); + if (pointerId == mActivePointerId) { + // This was our active pointer going up. Choose a new + // active pointer and adjust accordingly. + // TODO: Make this decision more intelligent. + final int newPointerIndex = pointerIndex == 0 ? 1 : 0; + mLastMotionX = mDownMotionX = ev.getX(newPointerIndex); + mLastMotionY = ev.getY(newPointerIndex); + mLastMotionXRemainder = 0; + mActivePointerId = ev.getPointerId(newPointerIndex); + if (mVelocityTracker != null) { + mVelocityTracker.clear(); + } + } + } + + protected void onUnhandledTap(MotionEvent ev) {} + + @Override + public void requestChildFocus(View child, View focused) { + super.requestChildFocus(child, focused); + int page = indexToPage(indexOfChild(child)); + if (page >= 0 && page != getCurrentPage() && !isInTouchMode()) { + snapToPage(page); + } + } + + protected int getChildIndexForRelativeOffset(int relativeOffset) { + final int childCount = getChildCount(); + int left; + int right; + for (int i = 0; i < childCount; ++i) { + left = getRelativeChildOffset(i); + right = (left + getScaledMeasuredWidth(getPageAt(i))); + if (left <= relativeOffset && relativeOffset <= right) { + return i; + } + } + return -1; + } + + protected int getChildWidth(int index) { + // This functions are called enough times that it actually makes a difference in the + // profiler -- so just inline the max() here + final int measuredWidth = getPageAt(index).getMeasuredWidth(); + final int minWidth = mMinimumWidth; + return (minWidth > measuredWidth) ? minWidth : measuredWidth; + } + + int getPageNearestToCenterOfScreen() { + int minDistanceFromScreenCenter = Integer.MAX_VALUE; + int minDistanceFromScreenCenterIndex = -1; + int screenCenter = getScrollX() + (getMeasuredWidth() / 2); + final int childCount = getChildCount(); + for (int i = 0; i < childCount; ++i) { + View layout = (View) getPageAt(i); + int childWidth = getScaledMeasuredWidth(layout); + int halfChildWidth = (childWidth / 2); + int childCenter = getChildOffset(i) + halfChildWidth; + int distanceFromScreenCenter = Math.abs(childCenter - screenCenter); + if (distanceFromScreenCenter < minDistanceFromScreenCenter) { + minDistanceFromScreenCenter = distanceFromScreenCenter; + minDistanceFromScreenCenterIndex = i; + } + } + return minDistanceFromScreenCenterIndex; + } + + protected void snapToDestination() { + snapToPage(getPageNearestToCenterOfScreen(), PAGE_SNAP_ANIMATION_DURATION); + } + + private static class ScrollInterpolator implements Interpolator { + public ScrollInterpolator() { + } + + public float getInterpolation(float t) { + t -= 1.0f; + return t*t*t*t*t + 1; + } + } + + // We want the duration of the page snap animation to be influenced by the distance that + // the screen has to travel, however, we don't want this duration to be effected in a + // purely linear fashion. Instead, we use this method to moderate the effect that the distance + // of travel has on the overall snap duration. + float distanceInfluenceForSnapDuration(float f) { + f -= 0.5f; // center the values about 0. + f *= 0.3f * Math.PI / 2.0f; + return (float) Math.sin(f); + } + + protected void snapToPageWithVelocity(int whichPage, int velocity) { + whichPage = Math.max(0, Math.min(whichPage, getChildCount() - 1)); + int halfScreenSize = getMeasuredWidth() / 2; + + if (DEBUG) Log.d(TAG, "snapToPage.getChildOffset(): " + getChildOffset(whichPage)); + if (DEBUG) Log.d(TAG, "snapToPageWithVelocity.getRelativeChildOffset(): " + + getMeasuredWidth() + ", " + getChildWidth(whichPage)); + final int newX = getChildOffset(whichPage) - getRelativeChildOffset(whichPage); + int delta = newX - mUnboundedScrollX; + int duration = 0; + + if (Math.abs(velocity) < mMinFlingVelocity) { + // If the velocity is low enough, then treat this more as an automatic page advance + // as opposed to an apparent physical response to flinging + snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION); + return; + } + + // Here we compute a "distance" that will be used in the computation of the overall + // snap duration. This is a function of the actual distance that needs to be traveled; + // we keep this value close to half screen size in order to reduce the variance in snap + // duration as a function of the distance the page needs to travel. + float distanceRatio = Math.min(1f, 1.0f * Math.abs(delta) / (2 * halfScreenSize)); + float distance = halfScreenSize + halfScreenSize * + distanceInfluenceForSnapDuration(distanceRatio); + + velocity = Math.abs(velocity); + velocity = Math.max(mMinSnapVelocity, velocity); + + // we want the page's snap velocity to approximately match the velocity at which the + // user flings, so we scale the duration by a value near to the derivative of the scroll + // interpolator at zero, ie. 5. We use 4 to make it a little slower. + duration = 4 * Math.round(1000 * Math.abs(distance / velocity)); + + snapToPage(whichPage, delta, duration); + } + + protected void snapToPage(int whichPage) { + snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION); + } + + protected void snapToPage(int whichPage, int duration) { + whichPage = Math.max(0, Math.min(whichPage, getPageCount() - 1)); + + if (DEBUG) Log.d(TAG, "snapToPage.getChildOffset(): " + getChildOffset(whichPage)); + if (DEBUG) Log.d(TAG, "snapToPage.getRelativeChildOffset(): " + getMeasuredWidth() + ", " + + getChildWidth(whichPage)); + int newX = getChildOffset(whichPage) - getRelativeChildOffset(whichPage); + final int sX = mUnboundedScrollX; + final int delta = newX - sX; + snapToPage(whichPage, delta, duration); + } + + protected void snapToPage(int whichPage, int delta, int duration) { + mNextPage = whichPage; + + View focusedChild = getFocusedChild(); + if (focusedChild != null && whichPage != mCurrentPage && + focusedChild == getPageAt(mCurrentPage)) { + focusedChild.clearFocus(); + } + + pageBeginMoving(); + awakenScrollBars(duration); + if (duration == 0) { + duration = Math.abs(delta); + } + + if (!mScroller.isFinished()) mScroller.abortAnimation(); + mScroller.startScroll(mUnboundedScrollX, 0, delta, 0, duration); + + notifyPageSwitchListener(); + invalidate(); + } + + public void scrollLeft() { + if (mScroller.isFinished()) { + if (mCurrentPage > 0) snapToPage(mCurrentPage - 1); + } else { + if (mNextPage > 0) snapToPage(mNextPage - 1); + } + } + + public void scrollRight() { + if (mScroller.isFinished()) { + if (mCurrentPage < getChildCount() -1) snapToPage(mCurrentPage + 1); + } else { + if (mNextPage < getChildCount() -1) snapToPage(mNextPage + 1); + } + } + + public int getPageForView(View v) { + int result = -1; + if (v != null) { + ViewParent vp = v.getParent(); + int count = getChildCount(); + for (int i = 0; i < count; i++) { + if (vp == getPageAt(i)) { + return i; + } + } + } + return result; + } + + /** + * @return True is long presses are still allowed for the current touch + */ + public boolean allowLongPress() { + return mAllowLongPress; + } + + /** + * Set true to allow long-press events to be triggered, usually checked by + * {@link Launcher} to accept or block dpad-initiated long-presses. + */ + public void setAllowLongPress(boolean allowLongPress) { + mAllowLongPress = allowLongPress; + } + + public static class SavedState extends BaseSavedState { + int currentPage = -1; + + SavedState(Parcelable superState) { + super(superState); + } + + private SavedState(Parcel in) { + super(in); + currentPage = in.readInt(); + } + + @Override + public void writeToParcel(Parcel out, int flags) { + super.writeToParcel(out, flags); + out.writeInt(currentPage); + } + + 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]; + } + }; + } + + protected View getScrollingIndicator() { + return null; + } + + protected boolean isScrollingIndicatorEnabled() { + return false; + } + + Runnable hideScrollingIndicatorRunnable = new Runnable() { + @Override + public void run() { + hideScrollingIndicator(false); + } + }; + + protected void flashScrollingIndicator(boolean animated) { + removeCallbacks(hideScrollingIndicatorRunnable); + showScrollingIndicator(!animated); + postDelayed(hideScrollingIndicatorRunnable, sScrollIndicatorFlashDuration); + } + + protected void showScrollingIndicator(boolean immediately) { + mShouldShowScrollIndicator = true; + mShouldShowScrollIndicatorImmediately = true; + if (getChildCount() <= 1) return; + if (!isScrollingIndicatorEnabled()) return; + + mShouldShowScrollIndicator = false; + getScrollingIndicator(); + if (mScrollIndicator != null) { + // Fade the indicator in + updateScrollingIndicatorPosition(); + mScrollIndicator.setVisibility(View.VISIBLE); + cancelScrollingIndicatorAnimations(); + if (immediately) { + mScrollIndicator.setAlpha(1f); + } else { + mScrollIndicatorAnimator = ObjectAnimator.ofFloat(mScrollIndicator, "alpha", 1f); + mScrollIndicatorAnimator.setDuration(sScrollIndicatorFadeInDuration); + mScrollIndicatorAnimator.start(); + } + } + } + + protected void cancelScrollingIndicatorAnimations() { + if (mScrollIndicatorAnimator != null) { + mScrollIndicatorAnimator.cancel(); + } + } + + protected void hideScrollingIndicator(boolean immediately) { + if (getChildCount() <= 1) return; + if (!isScrollingIndicatorEnabled()) return; + + getScrollingIndicator(); + if (mScrollIndicator != null) { + // Fade the indicator out + updateScrollingIndicatorPosition(); + cancelScrollingIndicatorAnimations(); + if (immediately) { + mScrollIndicator.setVisibility(View.INVISIBLE); + mScrollIndicator.setAlpha(0f); + } else { + mScrollIndicatorAnimator = ObjectAnimator.ofFloat(mScrollIndicator, "alpha", 0f); + mScrollIndicatorAnimator.setDuration(sScrollIndicatorFadeOutDuration); + mScrollIndicatorAnimator.addListener(new AnimatorListenerAdapter() { + private boolean cancelled = false; + @Override + public void onAnimationCancel(android.animation.Animator animation) { + cancelled = true; + } + @Override + public void onAnimationEnd(Animator animation) { + if (!cancelled) { + mScrollIndicator.setVisibility(View.INVISIBLE); + } + } + }); + mScrollIndicatorAnimator.start(); + } + } + } + + /** + * To be overridden by subclasses to determine whether the scroll indicator should stretch to + * fill its space on the track or not. + */ + protected boolean hasElasticScrollIndicator() { + return true; + } + + private void updateScrollingIndicator() { + if (getChildCount() <= 1) return; + if (!isScrollingIndicatorEnabled()) return; + + getScrollingIndicator(); + if (mScrollIndicator != null) { + updateScrollingIndicatorPosition(); + } + if (mShouldShowScrollIndicator) { + showScrollingIndicator(mShouldShowScrollIndicatorImmediately); + } + } + + private void updateScrollingIndicatorPosition() { + if (!isScrollingIndicatorEnabled()) return; + if (mScrollIndicator == null) return; + int numPages = getChildCount(); + int pageWidth = getMeasuredWidth(); + int lastChildIndex = Math.max(0, getChildCount() - 1); + int maxScrollX = getChildOffset(lastChildIndex) - getRelativeChildOffset(lastChildIndex); + int trackWidth = pageWidth - mScrollIndicatorPaddingLeft - mScrollIndicatorPaddingRight; + int indicatorWidth = mScrollIndicator.getMeasuredWidth() - + mScrollIndicator.getPaddingLeft() - mScrollIndicator.getPaddingRight(); + + float offset = Math.max(0f, Math.min(1f, (float) getScrollX() / maxScrollX)); + int indicatorSpace = trackWidth / numPages; + int indicatorPos = (int) (offset * (trackWidth - indicatorSpace)) + mScrollIndicatorPaddingLeft; + if (hasElasticScrollIndicator()) { + if (mScrollIndicator.getMeasuredWidth() != indicatorSpace) { + mScrollIndicator.getLayoutParams().width = indicatorSpace; + mScrollIndicator.requestLayout(); + } + } else { + int indicatorCenterOffset = indicatorSpace / 2 - indicatorWidth / 2; + indicatorPos += indicatorCenterOffset; + } + mScrollIndicator.setTranslationX(indicatorPos); + } + + /* Accessibility */ + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setScrollable(getPageCount() > 1); + if (getCurrentPage() < getPageCount() - 1) { + info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD); + } + if (getCurrentPage() > 0) { + info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD); + } + } + + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setScrollable(true); + if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) { + event.setFromIndex(mCurrentPage); + event.setToIndex(mCurrentPage); + event.setItemCount(getChildCount()); + } + } + + @Override + public boolean performAccessibilityAction(int action, Bundle arguments) { + if (super.performAccessibilityAction(action, arguments)) { + return true; + } + switch (action) { + case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: { + if (getCurrentPage() < getPageCount() - 1) { + scrollRight(); + return true; + } + } break; + case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: { + if (getCurrentPage() > 0) { + scrollLeft(); + return true; + } + } break; + } + return false; + } + + @Override + public boolean onHoverEvent(android.view.MotionEvent event) { + return true; + } +} diff --git a/policy/src/com/android/internal/policy/impl/AccountUnlockScreen.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/AccountUnlockScreen.java index a4baeed..d6a31b8 100644 --- a/policy/src/com/android/internal/policy/impl/AccountUnlockScreen.java +++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/AccountUnlockScreen.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.internal.policy.impl; +package com.android.internal.policy.impl.keyguard_obsolete; import com.android.internal.R; import com.android.internal.widget.LockPatternUtils; diff --git a/policy/src/com/android/internal/policy/impl/keyguard_obsolete/BiometricSensorUnlock.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/BiometricSensorUnlock.java new file mode 100644 index 0000000..c38525e --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/BiometricSensorUnlock.java @@ -0,0 +1,80 @@ +/* + * 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_obsolete; + +import android.view.View; + +interface BiometricSensorUnlock { + /** + * Initializes the view provided for the biometric unlock UI to work within. The provided area + * completely covers the backup unlock mechanism. + * @param biometricUnlockView View provided for the biometric unlock UI. + */ + public void initializeView(View biometricUnlockView); + + /** + * Indicates whether the biometric unlock is running. Before + * {@link BiometricSensorUnlock#start} is called, isRunning() returns false. After a successful + * call to {@link BiometricSensorUnlock#start}, isRunning() returns true until the biometric + * unlock completes, {@link BiometricSensorUnlock#stop} has been called, or an error has + * forced the biometric unlock to stop. + * @return whether the biometric unlock is currently running. + */ + public boolean isRunning(); + + /** + * Covers the backup unlock mechanism by showing the contents of the view initialized in + * {@link BiometricSensorUnlock#initializeView(View)}. The view should disappear after the + * specified timeout. If the timeout is 0, the interface shows until another event, such as + * calling {@link BiometricSensorUnlock#hide()}, causes it to disappear. Called on the UI + * thread. + * @param timeoutMilliseconds Amount of time in milliseconds to display the view before + * disappearing. A value of 0 means the view should remain visible. + */ + public void show(long timeoutMilliseconds); + + /** + * Uncovers the backup unlock mechanism by hiding the contents of the view initialized in + * {@link BiometricSensorUnlock#initializeView(View)}. + */ + public void hide(); + + /** + * Binds to the biometric unlock service and starts the unlock procedure. Called on the UI + * thread. + * @return false if it can't be started or the backup should be used. + */ + public boolean start(); + + /** + * Stops the biometric unlock procedure and unbinds from the service. Called on the UI thread. + * @return whether the biometric unlock was running when called. + */ + public boolean stop(); + + /** + * Cleans up any resources used by the biometric unlock. + */ + public void cleanUp(); + + /** + * Gets the Device Policy Manager quality of the biometric unlock sensor + * (e.g., PASSWORD_QUALITY_BIOMETRIC_WEAK). + * @return biometric unlock sensor quality, as defined by Device Policy Manager. + */ + public int getQuality(); +} diff --git a/policy/src/com/android/internal/policy/impl/FaceUnlock.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/FaceUnlock.java index fda3c9d..e4d9215 100644 --- a/policy/src/com/android/internal/policy/impl/FaceUnlock.java +++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/FaceUnlock.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.internal.policy.impl; +package com.android.internal.policy.impl.keyguard_obsolete; import com.android.internal.R; import com.android.internal.policy.IFaceLockCallback; diff --git a/policy/src/com/android/internal/policy/impl/KeyguardScreen.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardScreen.java index bbb6875..ba5b7ff 100644 --- a/policy/src/com/android/internal/policy/impl/KeyguardScreen.java +++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardScreen.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.internal.policy.impl; +package com.android.internal.policy.impl.keyguard_obsolete; /** * Common interface of each {@link android.view.View} that is a screen of @@ -27,7 +27,7 @@ public interface KeyguardScreen { * keyboard to be displayed. */ boolean needsInput(); - + /** * This screen is no longer in front of the user. */ diff --git a/policy/src/com/android/internal/policy/impl/KeyguardScreenCallback.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardScreenCallback.java index a843603..be505a1 100644 --- a/policy/src/com/android/internal/policy/impl/KeyguardScreenCallback.java +++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardScreenCallback.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.internal.policy.impl; +package com.android.internal.policy.impl.keyguard_obsolete; import android.content.res.Configuration; diff --git a/policy/src/com/android/internal/policy/impl/KeyguardStatusViewManager.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardStatusViewManager.java index bb07a1d..409f87b 100644 --- a/policy/src/com/android/internal/policy/impl/KeyguardStatusViewManager.java +++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardStatusViewManager.java @@ -14,14 +14,13 @@ * limitations under the License. */ -package com.android.internal.policy.impl; +package com.android.internal.policy.impl.keyguard_obsolete; import com.android.internal.R; import com.android.internal.telephony.IccCardConstants; import com.android.internal.widget.DigitalClock; import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.TransportControlView; -import com.android.internal.policy.impl.KeyguardUpdateMonitor.BatteryStatus; import java.util.ArrayList; import java.util.Date; @@ -621,8 +620,7 @@ class KeyguardStatusViewManager implements OnClickListener { private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() { - @Override - public void onRefreshBatteryInfo(BatteryStatus status) { + public void onRefreshBatteryInfo(KeyguardUpdateMonitor.BatteryStatus status) { mShowingBatteryInfo = status.isPluggedIn() || status.isBatteryLow(); mPluggedIn = status.isPluggedIn(); mBatteryLevel = status.level; diff --git a/policy/src/com/android/internal/policy/impl/KeyguardUpdateMonitor.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardUpdateMonitor.java index 5c0cd4f..d990f5f 100644 --- a/policy/src/com/android/internal/policy/impl/KeyguardUpdateMonitor.java +++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardUpdateMonitor.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.internal.policy.impl; +package com.android.internal.policy.impl.keyguard_obsolete; import android.app.admin.DevicePolicyManager; import android.content.BroadcastReceiver; diff --git a/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardUpdateMonitorCallback.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardUpdateMonitorCallback.java new file mode 100644 index 0000000..79233e8 --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardUpdateMonitorCallback.java @@ -0,0 +1,96 @@ +/* + * 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_obsolete; + +import android.app.admin.DevicePolicyManager; +import android.media.AudioManager; + +import com.android.internal.telephony.IccCardConstants; + +/** + * Callback for general information relevant to lock screen. + */ +class KeyguardUpdateMonitorCallback { + /** + * Called when the battery status changes, e.g. when plugged in or unplugged, charge + * level, etc. changes. + * + * @param status current battery status + */ + void onRefreshBatteryInfo(KeyguardUpdateMonitor.BatteryStatus status) { } + + /** + * Called once per minute or when the time changes. + */ + void onTimeChanged() { } + + /** + * Called when the carrier PLMN or SPN changes. + * + * @param plmn The operator name of the registered network. May be null if it shouldn't + * be displayed. + * @param spn The service provider name. May be null if it shouldn't be displayed. + */ + void onRefreshCarrierInfo(CharSequence plmn, CharSequence spn) { } + + /** + * Called when the ringer mode changes. + * @param state the current ringer state, as defined in + * {@link AudioManager#RINGER_MODE_CHANGED_ACTION} + */ + void onRingerModeChanged(int state) { } + + /** + * Called when the phone state changes. String will be one of: + * {@link TelephonyManager#EXTRA_STATE_IDLE} + * {@link TelephonyManager@EXTRA_STATE_RINGING} + * {@link TelephonyManager#EXTRA_STATE_OFFHOOK + */ + void onPhoneStateChanged(int phoneState) { } + + /** + * Called when visibility of lockscreen clock changes, such as when + * obscured by a widget. + */ + void onClockVisibilityChanged() { } + + /** + * Called when the device becomes provisioned + */ + void onDeviceProvisioned() { } + + /** + * Called when the device policy changes. + * See {@link DevicePolicyManager#ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED} + */ + void onDevicePolicyManagerStateChanged() { } + + /** + * Called when the user changes. + */ + void onUserSwitched(int userId) { } + + /** + * Called when the SIM state changes. + * @param simState + */ + void onSimStateChanged(IccCardConstants.State simState) { } + + /** + * Called when a user is removed. + */ + void onUserRemoved(int userId) { } +} diff --git a/policy/src/com/android/internal/policy/impl/KeyguardViewBase.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardViewBase.java index 29a5573..f9fe797 100644 --- a/policy/src/com/android/internal/policy/impl/KeyguardViewBase.java +++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardViewBase.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.internal.policy.impl; +package com.android.internal.policy.impl.keyguard_obsolete; import android.content.Context; import android.content.Intent; diff --git a/policy/src/com/android/internal/policy/impl/KeyguardViewCallback.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardViewCallback.java index b376d65..4cc0f30 100644 --- a/policy/src/com/android/internal/policy/impl/KeyguardViewCallback.java +++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardViewCallback.java @@ -14,10 +14,10 @@ * limitations under the License. */ -package com.android.internal.policy.impl; +package com.android.internal.policy.impl.keyguard_obsolete; /** - * The callback used by the keyguard view to tell the {@link KeyguardViewMediator} + * The callback used by the keyguard view to tell the {@link KeyguardViewMediator} * various things. */ public interface KeyguardViewCallback { diff --git a/policy/src/com/android/internal/policy/impl/KeyguardViewManager.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardViewManager.java index d521c05..5dbef48 100644 --- a/policy/src/com/android/internal/policy/impl/KeyguardViewManager.java +++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardViewManager.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.internal.policy.impl; +package com.android.internal.policy.impl.keyguard_obsolete; import com.android.internal.R; @@ -231,7 +231,7 @@ public class KeyguardViewManager implements KeyguardWindowController { // Keyguard may be in the process of being shown, but not yet // updated with the window manager... give it a chance to do so. mKeyguardHost.post(new Runnable() { - @Override public void run() { + public void run() { if (mKeyguardHost.getVisibility() == View.VISIBLE) { showListener.onShown(mKeyguardHost.getWindowToken()); } else { diff --git a/policy/src/com/android/internal/policy/impl/KeyguardViewMediator.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardViewMediator.java index 236a4ea..641ee88 100644 --- a/policy/src/com/android/internal/policy/impl/KeyguardViewMediator.java +++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardViewMediator.java @@ -14,10 +14,11 @@ * limitations under the License. */ -package com.android.internal.policy.impl; +package com.android.internal.policy.impl.keyguard_obsolete; import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT; +import com.android.internal.policy.impl.PhoneWindowManager; import com.android.internal.telephony.IccCardConstants; import com.android.internal.widget.LockPatternUtils; diff --git a/policy/src/com/android/internal/policy/impl/KeyguardViewProperties.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardViewProperties.java index 51b7f1e..676574d 100644 --- a/policy/src/com/android/internal/policy/impl/KeyguardViewProperties.java +++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardViewProperties.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.internal.policy.impl; +package com.android.internal.policy.impl.keyguard_obsolete; import android.content.Context; diff --git a/policy/src/com/android/internal/policy/impl/KeyguardWindowController.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardWindowController.java index 4ad48fb..98e3209 100644 --- a/policy/src/com/android/internal/policy/impl/KeyguardWindowController.java +++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardWindowController.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.internal.policy.impl; +package com.android.internal.policy.impl.keyguard_obsolete; /** * Interface passed to the keyguard view, for it to call up to control diff --git a/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/LockPatternKeyguardView.java index 32aec10..0ce87b1 100644 --- a/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java +++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/LockPatternKeyguardView.java @@ -14,10 +14,9 @@ * limitations under the License. */ -package com.android.internal.policy.impl; +package com.android.internal.policy.impl.keyguard_obsolete; import com.android.internal.R; -import com.android.internal.policy.impl.KeyguardUpdateMonitor.BatteryStatus; import com.android.internal.telephony.IccCardConstants; import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockScreenWidgetCallback; @@ -689,7 +688,7 @@ public class LockPatternKeyguardView extends KeyguardViewBase { KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() { @Override - public void onRefreshBatteryInfo(BatteryStatus status) { + public void onRefreshBatteryInfo(KeyguardUpdateMonitor.BatteryStatus status) { // When someone plugs in or unplugs the device, we hide the biometric sensor area and // suppress its startup for the next onScreenTurnedOn(). Since plugging/unplugging // causes the screen to turn on, the biometric unlock would start if it wasn't diff --git a/policy/src/com/android/internal/policy/impl/LockPatternKeyguardViewProperties.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/LockPatternKeyguardViewProperties.java index 5066e3c..5d9cc8e 100644 --- a/policy/src/com/android/internal/policy/impl/LockPatternKeyguardViewProperties.java +++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/LockPatternKeyguardViewProperties.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.internal.policy.impl; +package com.android.internal.policy.impl.keyguard_obsolete; import com.android.internal.widget.LockPatternUtils; diff --git a/policy/src/com/android/internal/policy/impl/LockScreen.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/LockScreen.java index 91b5ca1..4e9a1f7 100644 --- a/policy/src/com/android/internal/policy/impl/LockScreen.java +++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/LockScreen.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.internal.policy.impl; +package com.android.internal.policy.impl.keyguard_obsolete; import com.android.internal.R; import com.android.internal.telephony.IccCardConstants; diff --git a/policy/src/com/android/internal/policy/impl/PasswordUnlockScreen.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/PasswordUnlockScreen.java index 203f9db..87a7371 100644 --- a/policy/src/com/android/internal/policy/impl/PasswordUnlockScreen.java +++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/PasswordUnlockScreen.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.internal.policy.impl; +package com.android.internal.policy.impl.keyguard_obsolete; import java.util.List; diff --git a/policy/src/com/android/internal/policy/impl/PatternUnlockScreen.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/PatternUnlockScreen.java index 9a6d2cc..6d5706b 100644 --- a/policy/src/com/android/internal/policy/impl/PatternUnlockScreen.java +++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/PatternUnlockScreen.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.internal.policy.impl; +package com.android.internal.policy.impl.keyguard_obsolete; import android.content.Context; import android.content.res.Configuration; diff --git a/policy/src/com/android/internal/policy/impl/SimPukUnlockScreen.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/SimPukUnlockScreen.java index 3b2a473..3c1703a 100644 --- a/policy/src/com/android/internal/policy/impl/SimPukUnlockScreen.java +++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/SimPukUnlockScreen.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.internal.policy.impl; +package com.android.internal.policy.impl.keyguard_obsolete; import android.app.Dialog; import android.app.ProgressDialog; diff --git a/policy/src/com/android/internal/policy/impl/SimUnlockScreen.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/SimUnlockScreen.java index 80407f5..13c040c 100644 --- a/policy/src/com/android/internal/policy/impl/SimUnlockScreen.java +++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/SimUnlockScreen.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.internal.policy.impl; +package com.android.internal.policy.impl.keyguard_obsolete; import android.app.Dialog; import android.app.ProgressDialog; |