diff --git a/policy/com/android/internal/policy/impl/AccountUnlockScreen.java b/policy/com/android/internal/policy/impl/AccountUnlockScreen.java index b5a144ec604dd..65102c67fc8b3 100644 --- a/policy/com/android/internal/policy/impl/AccountUnlockScreen.java +++ b/policy/com/android/internal/policy/impl/AccountUnlockScreen.java @@ -32,6 +32,7 @@ import android.text.Editable; import android.text.InputFilter; import android.text.LoginFilter; import android.text.TextWatcher; +import android.text.TextUtils; import android.util.Log; import android.view.KeyEvent; import android.view.LayoutInflater; @@ -50,8 +51,6 @@ import android.widget.TextView; */ public class AccountUnlockScreen extends RelativeLayout implements KeyguardScreen, View.OnClickListener, ServiceConnection, TextWatcher { - - private static final String LOCK_PATTERN_PACKAGE = "com.android.settings"; private static final String LOCK_PATTERN_CLASS = "com.android.settings.ChooseLockPattern"; @@ -135,7 +134,7 @@ public class AccountUnlockScreen extends RelativeLayout implements KeyguardScree public boolean needsInput() { return true; } - + /** {@inheritDoc} */ public void onPause() { @@ -192,11 +191,75 @@ public class AccountUnlockScreen extends RelativeLayout implements KeyguardScree 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 String findIntendedAccount(String username) { + String[] accounts = null; + try { + accounts = mAccountsService.getAccounts(); + } catch (RemoteException e) { + return null; + } + if (accounts == null) { + return null; + } + + // Try to figure out which account they meant if they + // typed only the username (and not the domain), or got + // the case wrong. + + String bestAccount = null; + int bestScore = 0; + for (String a: accounts) { + int score = 0; + if (username.equals(a)) { + score = 4; + } else if (username.equalsIgnoreCase(a)) { + score = 3; + } else if (username.indexOf('@') < 0) { + int i = a.indexOf('@'); + if (i >= 0) { + String aUsername = a.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 boolean checkPassword() { final String login = mLogin.getText().toString(); final String password = mPassword.getText().toString(); try { - return mAccountsService.shouldUnlock(login, password); + String account = findIntendedAccount(login); + if (account == null) { + return false; + } + return mAccountsService.shouldUnlock(account, password); } catch (RemoteException e) { return false; } diff --git a/policy/com/android/internal/policy/impl/GlobalActions.java b/policy/com/android/internal/policy/impl/GlobalActions.java index 7f8e57b137789..eeb875a2f4be5 100644 --- a/policy/com/android/internal/policy/impl/GlobalActions.java +++ b/policy/com/android/internal/policy/impl/GlobalActions.java @@ -16,29 +16,30 @@ package com.android.internal.policy.impl; -import com.android.internal.R; -import com.google.android.collect.Lists; - import android.app.AlertDialog; import android.app.StatusBarManager; -import android.content.Context; import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; -import android.content.DialogInterface; import android.media.AudioManager; -import android.os.LocalPowerManager; import android.os.Handler; import android.os.Message; -import android.os.SystemClock; +import android.provider.Settings; +import android.telephony.PhoneStateListener; +import android.telephony.ServiceState; +import android.telephony.TelephonyManager; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; import android.widget.BaseAdapter; import android.widget.ImageView; -import android.widget.LinearLayout; import android.widget.TextView; +import com.android.internal.R; +import com.google.android.collect.Lists; import java.util.ArrayList; @@ -49,34 +50,41 @@ import java.util.ArrayList; */ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterface.OnClickListener { + private static final String TAG = "GlobalActions"; + private StatusBarManager mStatusBar; private final Context mContext; - private final LocalPowerManager mPowerManager; private final AudioManager mAudioManager; + private ArrayList mItems; private AlertDialog mDialog; private ToggleAction mSilentModeToggle; + private ToggleAction mAirplaneModeOn; private MyAdapter mAdapter; private boolean mKeyguardShowing = false; private boolean mDeviceProvisioned = false; + private ToggleAction.State mAirplaneState = ToggleAction.State.Off; /** - * @param context everything needs a context :) - * @param powerManager used to turn the screen off (the lock action). + * @param context everything needs a context :( */ - public GlobalActions(Context context, LocalPowerManager powerManager) { + public GlobalActions(Context context) { mContext = context; - mPowerManager = powerManager; mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); // receive broadcasts IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); context.registerReceiver(mBroadcastReceiver, filter); + + // get notified of phone state changes + TelephonyManager telephonyManager = + (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE); } /** @@ -123,29 +131,48 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac } }; + mAirplaneModeOn = new ToggleAction( + R.drawable.ic_lock_airplane_mode, + R.drawable.ic_lock_airplane_mode_off, + R.string.global_actions_toggle_airplane_mode, + R.string.global_actions_airplane_mode_on_status, + R.string.global_actions_airplane_mode_off_status) { + + void onToggle(boolean on) { + // Change the system setting + Settings.System.putInt( + mContext.getContentResolver(), + Settings.System.AIRPLANE_MODE_ON, + on ? 1 : 0); + Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); + intent.putExtra("state", on); + mContext.sendBroadcast(intent); + } + + @Override + protected void changeStateFromPress(boolean buttonOn) { + mState = buttonOn ? State.TurningOn : State.TurningOff; + mAirplaneState = mState; + } + + public boolean showDuringKeyguard() { + return true; + } + + public boolean showBeforeProvisioning() { + return false; + } + }; + mItems = Lists.newArrayList( - /* Disabled pending bug 1304831 -- key or touch events wake up device before it - * can go to sleep. - // first: lock screen - new SinglePressAction(com.android.internal.R.drawable.ic_lock_lock, R.string.global_action_lock) { - - public void onPress() { - mPowerManager.goToSleep(SystemClock.uptimeMillis() + 1); - } - - public boolean showDuringKeyguard() { - return false; - } - - public boolean showBeforeProvisioning() { - return false; - } - }, - */ - // next: silent mode + // silent mode mSilentModeToggle, + // next: airplane mode + mAirplaneModeOn, // last: power off - new SinglePressAction(com.android.internal.R.drawable.ic_lock_power_off, R.string.global_action_power_off) { + new SinglePressAction( + com.android.internal.R.drawable.ic_lock_power_off, + R.string.global_action_power_off) { public void onPress() { // shutdown by making sure radio and power are handled accordingly. @@ -180,10 +207,11 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac } private void prepareDialog() { - // TODO: May need another 'Vibrate' toggle button, but for now treat them the same final boolean silentModeOn = mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL; - mSilentModeToggle.updateState(silentModeOn); + mSilentModeToggle.updateState( + silentModeOn ? ToggleAction.State.On : ToggleAction.State.Off); + mAirplaneModeOn.updateState(mAirplaneState); mAdapter.notifyDataSetChanged(); if (mKeyguardShowing) { mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); @@ -192,6 +220,7 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac } } + /** {@inheritDoc} */ public void onDismiss(DialogInterface dialog) { mStatusBar.disable(StatusBarManager.DISABLE_NONE); @@ -229,6 +258,16 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac return count; } + @Override + public boolean isEnabled(int position) { + return getItem(position).isEnabled(); + } + + @Override + public boolean areAllItemsEnabled() { + return false; + } + public Action getItem(int position) { int filteredPos = 0; @@ -259,7 +298,7 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac public View getView(int position, View convertView, ViewGroup parent) { Action action = getItem(position); - return action.create(mContext, (LinearLayout) convertView, LayoutInflater.from(mContext)); + return action.create(mContext, convertView, parent, LayoutInflater.from(mContext)); } } @@ -273,7 +312,7 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac * What each item in the global actions dialog must be able to support. */ private interface Action { - LinearLayout create(Context context, LinearLayout convertView, LayoutInflater inflater); + View create(Context context, View convertView, ViewGroup parent, LayoutInflater inflater); void onPress(); @@ -288,6 +327,8 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac * device is provisioned. */ boolean showBeforeProvisioning(); + + boolean isEnabled(); } /** @@ -303,12 +344,17 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac mMessageResId = messageResId; } + public boolean isEnabled() { + return true; + } + abstract public void onPress(); - public LinearLayout create(Context context, LinearLayout convertView, LayoutInflater inflater) { - LinearLayout v = (LinearLayout) ((convertView != null) ? + public View create( + Context context, View convertView, ViewGroup parent, LayoutInflater inflater) { + View v = (convertView != null) ? convertView : - inflater.inflate(R.layout.global_actions_item, null)); + inflater.inflate(R.layout.global_actions_item, parent, false); ImageView icon = (ImageView) v.findViewById(R.id.icon); TextView messageView = (TextView) v.findViewById(R.id.message); @@ -326,9 +372,26 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac * A toggle action knows whether it is on or off, and displays an icon * and status message accordingly. */ - static abstract class ToggleAction implements Action { + private static abstract class ToggleAction implements Action { - private boolean mOn = false; + enum State { + Off(false), + TurningOn(true), + TurningOff(true), + On(false); + + private final boolean inTransition; + + State(boolean intermediate) { + inTransition = intermediate; + } + + public boolean inTransition() { + return inTransition; + } + } + + protected State mState = State.Off; // prefs private final int mEnabledIconResId; @@ -356,12 +419,12 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac mDisabledStatusMessageResId = disabledStatusMessageResId; } - public LinearLayout create(Context context, LinearLayout convertView, + public View create(Context context, View convertView, ViewGroup parent, LayoutInflater inflater) { - LinearLayout v = (LinearLayout) ((convertView != null) ? + View v = (convertView != null) ? convertView : inflater.inflate(R - .layout.global_actions_item, null)); + .layout.global_actions_item, parent, false); ImageView icon = (ImageView) v.findViewById(R.id.icon); TextView messageView = (TextView) v.findViewById(R.id.message); @@ -369,23 +432,50 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac messageView.setText(mMessageResId); + boolean on = ((mState == State.On) || (mState == State.TurningOn)); icon.setImageDrawable(context.getResources().getDrawable( - (mOn ? mEnabledIconResId : mDisabledIconResid))); - statusView.setText(mOn ? mEnabledStatusMessageResId : mDisabledStatusMessageResId); + (on ? mEnabledIconResId : mDisabledIconResid))); + statusView.setText(on ? mEnabledStatusMessageResId : mDisabledStatusMessageResId); statusView.setVisibility(View.VISIBLE); + final boolean enabled = isEnabled(); + messageView.setEnabled(enabled); + statusView.setEnabled(enabled); + icon.setEnabled(enabled); + v.setEnabled(enabled); + return v; } - public void onPress() { - updateState(!mOn); - onToggle(mOn); + public final void onPress() { + if (mState.inTransition()) { + Log.w(TAG, "shouldn't be able to toggle when in transition"); + return; + } + + final boolean nowOn = !(mState == State.On); + onToggle(nowOn); + changeStateFromPress(nowOn); + } + + public boolean isEnabled() { + return !mState.inTransition(); + } + + /** + * Implementations may override this if their state can be in on of the intermediate + * states until some notification is received (e.g airplane mode is 'turning off' until + * we know the wireless connections are back online + * @param buttonOn Whether the button was turned on or off + */ + protected void changeStateFromPress(boolean buttonOn) { + mState = buttonOn ? State.On : State.Off; } abstract void onToggle(boolean on); - public void updateState(boolean on) { - mOn = on; + public void updateState(State state) { + mState = state; } } @@ -401,6 +491,16 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac } }; + PhoneStateListener mPhoneStateListener = new PhoneStateListener() { + @Override + public void onServiceStateChanged(ServiceState serviceState) { + final boolean inAirplaneMode = serviceState.getState() == ServiceState.STATE_POWER_OFF; + mAirplaneState = inAirplaneMode ? ToggleAction.State.On : ToggleAction.State.Off; + mAirplaneModeOn.updateState(mAirplaneState); + mAdapter.notifyDataSetChanged(); + } + }; + private static final int MESSAGE_DISMISS = 0; private Handler mHandler = new Handler() { public void handleMessage(Message msg) { diff --git a/policy/com/android/internal/policy/impl/LockPatternKeyguardView.java b/policy/com/android/internal/policy/impl/LockPatternKeyguardView.java index 1ec7e1507556d..cb3131d0e8b4e 100644 --- a/policy/com/android/internal/policy/impl/LockPatternKeyguardView.java +++ b/policy/com/android/internal/policy/impl/LockPatternKeyguardView.java @@ -32,6 +32,12 @@ import android.util.Log; import android.view.KeyEvent; import android.view.View; import android.view.WindowManager; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.PixelFormat; +import android.graphics.ColorFilter; import com.android.internal.R; import com.android.internal.widget.LockPatternUtils; @@ -233,7 +239,8 @@ public class LockPatternKeyguardView extends KeyguardViewBase { (LockPatternUtils.FAILED_ATTEMPTS_BEFORE_RESET - LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT)) { showAlmostAtAccountLoginDialog(); - } else if (mHasAccount && failedAttempts >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_RESET) { + } else if (mHasAccount + && failedAttempts >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_RESET) { mLockPatternUtils.setPermanentlyLocked(true); updateScreen(mMode); } else if ((failedAttempts % LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT) @@ -254,6 +261,11 @@ public class LockPatternKeyguardView extends KeyguardViewBase { setFocusableInTouchMode(true); setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); + // wall paper background + final BitmapDrawable drawable = (BitmapDrawable) context.getWallpaper(); + setBackgroundDrawable( + new FastBitmapDrawable(drawable.getBitmap())); + // create both the lock and unlock screen so they are quickly available // when the screen turns on mLockScreen = createLockScreen(); @@ -408,6 +420,10 @@ public class LockPatternKeyguardView extends KeyguardViewBase { final View visibleScreen = (mode == Mode.LockScreen) ? mLockScreen : getUnlockScreenForCurrentUnlockMode(); + // do this before changing visibility so focus isn't requested before the input + // flag is set + mWindowController.setNeedsInput(((KeyguardScreen)visibleScreen).needsInput()); + if (mScreenOn) { if (goneScreen.getVisibility() == View.VISIBLE) { @@ -421,8 +437,7 @@ public class LockPatternKeyguardView extends KeyguardViewBase { goneScreen.setVisibility(View.GONE); visibleScreen.setVisibility(View.VISIBLE); - mWindowController.setNeedsInput(((KeyguardScreen)visibleScreen).needsInput()); - + if (!visibleScreen.requestFocus()) { throw new IllegalStateException("keyguard screen must be able to take " + "focus when shown " + visibleScreen.getClass().getCanonicalName()); @@ -567,4 +582,58 @@ public class LockPatternKeyguardView extends KeyguardViewBase { WindowManager.LayoutParams.FLAG_BLUR_BEHIND); dialog.show(); } + + /** + * Used to put wallpaper on the background of the lock screen. Centers it Horizontally and + * vertically. + */ + static private class FastBitmapDrawable extends Drawable { + private Bitmap mBitmap; + + private FastBitmapDrawable(Bitmap bitmap) { + mBitmap = bitmap; + } + + @Override + public void draw(Canvas canvas) { + canvas.drawBitmap( + mBitmap, + (getBounds().width() - mBitmap.getWidth()) / 2, + (getBounds().height() - mBitmap.getHeight()) / 2, + null); + } + + @Override + public int getOpacity() { + return PixelFormat.TRANSLUCENT; + } + + @Override + public void setAlpha(int alpha) { + } + + @Override + public void setColorFilter(ColorFilter cf) { + } + + @Override + public int getIntrinsicWidth() { + return mBitmap.getWidth(); + } + + @Override + public int getIntrinsicHeight() { + return mBitmap.getHeight(); + } + + @Override + public int getMinimumWidth() { + return mBitmap.getWidth(); + } + + @Override + public int getMinimumHeight() { + return mBitmap.getHeight(); + } + } } diff --git a/policy/com/android/internal/policy/impl/PhoneWindow.java b/policy/com/android/internal/policy/impl/PhoneWindow.java index 9fa59e48f9a99..0d26bfbbfac38 100644 --- a/policy/com/android/internal/policy/impl/PhoneWindow.java +++ b/policy/com/android/internal/policy/impl/PhoneWindow.java @@ -155,14 +155,45 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { private boolean mKeycodeCameraTimeoutActive = false; static final int MSG_MENU_LONG_PRESS = 1; - static final int MSG_CALL_LONG_PRESS = 2; - static final int MSG_CAMERA_LONG_PRESS = 3; + static final int MSG_MENU_LONG_PRESS_COMPLETE = 2; + static final int MSG_CALL_LONG_PRESS = 3; + static final int MSG_CALL_LONG_PRESS_COMPLETE = 4; + static final int MSG_CAMERA_LONG_PRESS = 5; + static final int MSG_CAMERA_LONG_PRESS_COMPLETE = 6; private final Handler mKeycodeMenuTimeoutHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_MENU_LONG_PRESS: { + if (mPanelChordingKey == 0) return; + // Before actually doing the long press, enqueue another + // message and do the processing there. This helps if + // the app isn't being responsive, and finally woke up -- + // if the window manager wasn't told about it processing + // the down key for too long, it would enqueue the key up + // at a time after the timeout of this message. So we go + // through another message, to make sure we process an up + // before continuing. + mKeycodeMenuTimeoutHandler.sendEmptyMessage( + MSG_MENU_LONG_PRESS_COMPLETE); + break; + } + case MSG_CALL_LONG_PRESS: { + if (!mKeycodeCallTimeoutActive) return; + // See above. + mKeycodeMenuTimeoutHandler.sendEmptyMessage( + MSG_CALL_LONG_PRESS_COMPLETE); + break; + } + case MSG_CAMERA_LONG_PRESS: { + if (!mKeycodeCameraTimeoutActive) return; + // See above. + mKeycodeMenuTimeoutHandler.sendEmptyMessage( + MSG_CAMERA_LONG_PRESS_COMPLETE); + break; + } + case MSG_MENU_LONG_PRESS_COMPLETE: { if (mPanelChordingKey == 0) return; mPanelChordingKey = 0; mDecor.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); @@ -172,7 +203,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { imm.showSoftInputUnchecked(InputMethodManager.SHOW_FORCED); } } break; - case MSG_CALL_LONG_PRESS: { + case MSG_CALL_LONG_PRESS_COMPLETE: { if (!mKeycodeCallTimeoutActive) return; mKeycodeCallTimeoutActive = false; mDecor.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); @@ -186,7 +217,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { startCallActivity(); } } break; - case MSG_CAMERA_LONG_PRESS: { + case MSG_CAMERA_LONG_PRESS_COMPLETE: { if (!mKeycodeCameraTimeoutActive) return; mKeycodeCameraTimeoutActive = false; mDecor.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); @@ -194,6 +225,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { Intent intent = new Intent(Intent.ACTION_CAMERA_BUTTON, null); intent.putExtra(Intent.EXTRA_KEY_EVENT, (KeyEvent) msg.obj); getContext().sendOrderedBroadcast(intent, null); + sendCloseSystemWindows(); } break; } } diff --git a/policy/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/com/android/internal/policy/impl/PhoneWindowManager.java index 0fa8ff57f9d30..d1b757604465f 100644 --- a/policy/com/android/internal/policy/impl/PhoneWindowManager.java +++ b/policy/com/android/internal/policy/impl/PhoneWindowManager.java @@ -139,7 +139,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { static public final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps"; // Vibrator pattern for haptic feedback of a long press. - private static final long[] LONG_PRESS_VIBE_PATTERN = {0, 1, 40, 41}; + private static final long[] LONG_PRESS_VIBE_PATTERN = {0, 1, 20, 21}; // Vibrator pattern for haptic feedback of a zoom ring tick private static final long[] ZOOM_RING_TICK_VIBE_PATTERN = {0, 10}; @@ -388,7 +388,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { void showGlobalActionsDialog() { if (mGlobalActions == null) { - mGlobalActions = new GlobalActions(mContext, mPowerManager); + mGlobalActions = new GlobalActions(mContext); } final boolean keyguardShowing = mKeyguardMediator.isShowing(); mGlobalActions.showDialog(keyguardShowing, isDeviceProvisioned()); diff --git a/policy/com/android/internal/policy/impl/UnlockScreen.java b/policy/com/android/internal/policy/impl/UnlockScreen.java index 51e3621f368a7..9aedf901d232f 100644 --- a/policy/com/android/internal/policy/impl/UnlockScreen.java +++ b/policy/com/android/internal/policy/impl/UnlockScreen.java @@ -289,7 +289,7 @@ class UnlockScreen extends LinearLayoutWithDefaultTouchRecepient mLockPatternView .setDisplayMode(LockPatternView.DisplayMode.Correct); mUnlockIcon.setVisibility(View.GONE); - mUnlockHeader.setText(R.string.lockscreen_pattern_correct); + mUnlockHeader.setText(""); mCallback.keyguardDone(true); } else { mCallback.pokeWakelock(UNLOCK_PATTERN_WAKE_INTERVAL_MS);