diff options
author | Brett Chabot <brettchabot@android.com> | 2010-06-16 14:41:00 -0700 |
---|---|---|
committer | Android Git Automerger <android-git-automerger@android.com> | 2010-06-16 14:41:00 -0700 |
commit | 685fcf364b84d5ac911ae9cbbc4fec99f36cbd48 (patch) | |
tree | 9d6b4a4b491fdf2f558b5c690c0dba7839efa486 /policy/src | |
parent | 1af489205a3942630e6203237213e98ef53d4118 (diff) | |
parent | c95812e6cacaa14748c81323bac6561453991a24 (diff) | |
download | frameworks_base-685fcf364b84d5ac911ae9cbbc4fec99f36cbd48.zip frameworks_base-685fcf364b84d5ac911ae9cbbc4fec99f36cbd48.tar.gz frameworks_base-685fcf364b84d5ac911ae9cbbc4fec99f36cbd48.tar.bz2 |
am c95812e6: Merge "Move out all framework-tests classes." into gingerbread
Merge commit 'c95812e6cacaa14748c81323bac6561453991a24' into gingerbread-plus-aosp
* commit 'c95812e6cacaa14748c81323bac6561453991a24':
Move out all framework-tests classes.
Diffstat (limited to 'policy/src')
27 files changed, 12379 insertions, 0 deletions
diff --git a/policy/src/com/android/internal/policy/impl/AccountUnlockScreen.java b/policy/src/com/android/internal/policy/impl/AccountUnlockScreen.java new file mode 100644 index 0000000..840c5e1 --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/AccountUnlockScreen.java @@ -0,0 +1,347 @@ +/* + * 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; + +import com.android.internal.R; +import com.android.internal.widget.LockPatternUtils; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.accounts.OperationCanceledException; +import android.accounts.AccountManagerFuture; +import android.accounts.AuthenticatorException; +import android.accounts.AccountManagerCallback; +import android.content.Context; +import android.content.Intent; +import android.content.res.Configuration; +import android.graphics.Rect; +import android.text.Editable; +import android.text.InputFilter; +import android.text.LoginFilter; +import android.text.TextWatcher; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.WindowManager; +import android.widget.Button; +import android.widget.EditText; +import android.widget.RelativeLayout; +import android.widget.TextView; +import android.app.Dialog; +import android.app.ProgressDialog; +import android.os.Bundle; + +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 AccountUnlockScreen extends RelativeLayout implements KeyguardScreen, + KeyguardUpdateMonitor.InfoCallback,View.OnClickListener, TextWatcher { + private static final String LOCK_PATTERN_PACKAGE = "com.android.settings"; + private static final String LOCK_PATTERN_CLASS = + "com.android.settings.ChooseLockPattern"; + + /** + * The amount of millis to stay awake once this screen detects activity + */ + private static final int AWAKE_POKE_MILLIS = 30000; + + private final KeyguardScreenCallback mCallback; + private final LockPatternUtils mLockPatternUtils; + private KeyguardUpdateMonitor mUpdateMonitor; + + private TextView mTopHeader; + private TextView mInstructions; + private EditText mLogin; + private EditText mPassword; + private Button mOk; + private Button mEmergencyCall; + + /** + * Shown while making asynchronous check of password. + */ + private ProgressDialog mCheckingDialog; + + /** + * AccountUnlockScreen constructor. + * @param configuration + * @param updateMonitor + */ + public AccountUnlockScreen(Context context,Configuration configuration, + KeyguardUpdateMonitor updateMonitor, KeyguardScreenCallback callback, + LockPatternUtils lockPatternUtils) { + super(context); + mCallback = callback; + mLockPatternUtils = lockPatternUtils; + + LayoutInflater.from(context).inflate( + R.layout.keyguard_screen_glogin_unlock, this, true); + + mTopHeader = (TextView) findViewById(R.id.topHeader); + mTopHeader.setText(mLockPatternUtils.isPermanentlyLocked() ? + R.string.lockscreen_glogin_too_many_attempts : + R.string.lockscreen_glogin_forgot_pattern); + + mInstructions = (TextView) findViewById(R.id.instructions); + + 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); + + mEmergencyCall = (Button) findViewById(R.id.emergencyCall); + mEmergencyCall.setOnClickListener(this); + mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCall); + + mUpdateMonitor = updateMonitor; + mUpdateMonitor.registerInfoCallback(this); + } + + 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) { + mCallback.pokeWakelock(AWAKE_POKE_MILLIS); + } + + @Override + protected boolean onRequestFocusInDescendants(int direction, + Rect previouslyFocusedRect) { + // send focus to the login field + return mLogin.requestFocus(direction, previouslyFocusedRect); + } + + /** {@inheritDoc} */ + public boolean needsInput() { + return true; + } + + /** {@inheritDoc} */ + public void onPause() { + + } + + /** {@inheritDoc} */ + public void onResume() { + // start fresh + mLogin.setText(""); + mPassword.setText(""); + mLogin.requestFocus(); + mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCall); + } + + /** {@inheritDoc} */ + public void cleanUp() { + if (mCheckingDialog != null) { + mCheckingDialog.hide(); + } + mUpdateMonitor.removeCallback(this); + } + + /** {@inheritDoc} */ + public void onClick(View v) { + mCallback.pokeWakelock(); + if (v == mOk) { + asyncCheckPassword(); + } + + if (v == mEmergencyCall) { + mCallback.takeEmergencyCallAction(); + } + } + + 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(); + + // close the keyguard + mCallback.keyguardDone(true); + } else { + mInstructions.setText(R.string.lockscreen_glogin_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.goToLockScreen(); + } else { + 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.pokeWakelock(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.pokeWakelock(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.lockscreen_glogin_checking_password)); + mCheckingDialog.setIndeterminate(true); + mCheckingDialog.setCancelable(false); + mCheckingDialog.getWindow().setType( + WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + if (!mContext.getResources().getBoolean( + com.android.internal.R.bool.config_sf_slowBlur)) { + mCheckingDialog.getWindow().setFlags( + WindowManager.LayoutParams.FLAG_BLUR_BEHIND, + WindowManager.LayoutParams.FLAG_BLUR_BEHIND); + } + } + return mCheckingDialog; + } + + public void onPhoneStateChanged(String newState) { + mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCall); + } + + public void onRefreshBatteryInfo(boolean showBatteryInfo, boolean pluggedIn, int batteryLevel) { + + } + + public void onRefreshCarrierInfo(CharSequence plmn, CharSequence spn) { + + } + + public void onRingerModeChanged(int state) { + + } + + public void onTimeChanged() { + + } +} diff --git a/policy/src/com/android/internal/policy/impl/GlobalActions.java b/policy/src/com/android/internal/policy/impl/GlobalActions.java new file mode 100644 index 0000000..1f06dcc --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/GlobalActions.java @@ -0,0 +1,578 @@ +/* + * 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; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.StatusBarManager; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; +import android.media.AudioManager; +import android.os.Handler; +import android.os.Message; +import android.os.SystemProperties; +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.TextView; +import com.android.internal.R; +import com.android.internal.app.ShutdownThread; +import com.android.internal.telephony.TelephonyIntents; +import com.android.internal.telephony.TelephonyProperties; +import com.google.android.collect.Lists; + +import java.util.ArrayList; + +/** + * Helper to show the global actions dialog. Each item is an {@link Action} that + * may show depending on whether the keyguard is showing, and whether the device + * is provisioned. + */ +class GlobalActions implements DialogInterface.OnDismissListener, DialogInterface.OnClickListener { + + private static final String TAG = "GlobalActions"; + + private StatusBarManager mStatusBar; + + private final Context mContext; + private final AudioManager mAudioManager; + + private ArrayList<Action> 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; + private boolean mIsWaitingForEcmExit = false; + + /** + * @param context everything needs a context :( + */ + public GlobalActions(Context context) { + mContext = context; + mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); + + // receive broadcasts + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); + filter.addAction(Intent.ACTION_SCREEN_OFF); + filter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED); + 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); + } + + /** + * Show the global actions dialog (creating if necessary) + * @param keyguardShowing True if keyguard is showing + */ + public void showDialog(boolean keyguardShowing, boolean isDeviceProvisioned) { + mKeyguardShowing = keyguardShowing; + mDeviceProvisioned = isDeviceProvisioned; + if (mDialog == null) { + mStatusBar = (StatusBarManager)mContext.getSystemService(Context.STATUS_BAR_SERVICE); + mDialog = createDialog(); + } + prepareDialog(); + + mStatusBar.disable(StatusBarManager.DISABLE_EXPAND); + mDialog.show(); + } + + /** + * Create the global actions dialog. + * @return A new dialog. + */ + private AlertDialog createDialog() { + mSilentModeToggle = new ToggleAction( + R.drawable.ic_lock_silent_mode, + R.drawable.ic_lock_silent_mode_off, + R.string.global_action_toggle_silent_mode, + R.string.global_action_silent_mode_on_status, + R.string.global_action_silent_mode_off_status) { + + void willCreate() { + // XXX: FIXME: switch to ic_lock_vibrate_mode when available + mEnabledIconResId = (Settings.System.getInt(mContext.getContentResolver(), + Settings.System.VIBRATE_IN_SILENT, 1) == 1) + ? R.drawable.ic_lock_silent_mode_vibrate + : R.drawable.ic_lock_silent_mode; + } + + void onToggle(boolean on) { + if (on) { + mAudioManager.setRingerMode((Settings.System.getInt(mContext.getContentResolver(), + Settings.System.VIBRATE_IN_SILENT, 1) == 1) + ? AudioManager.RINGER_MODE_VIBRATE + : AudioManager.RINGER_MODE_SILENT); + } else { + mAudioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL); + } + } + + public boolean showDuringKeyguard() { + return true; + } + + public boolean showBeforeProvisioning() { + return false; + } + }; + + 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) { + if (Boolean.parseBoolean( + SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE))) { + mIsWaitingForEcmExit = true; + // Launch ECM exit dialog + Intent ecmDialogIntent = + new Intent(TelephonyIntents.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS, null); + ecmDialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + mContext.startActivity(ecmDialogIntent); + } else { + changeAirplaneModeSystemSetting(on); + } + } + + @Override + protected void changeStateFromPress(boolean buttonOn) { + // In ECM mode airplane state cannot be changed + if (!(Boolean.parseBoolean( + SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE)))) { + mState = buttonOn ? State.TurningOn : State.TurningOff; + mAirplaneState = mState; + } + } + + public boolean showDuringKeyguard() { + return true; + } + + public boolean showBeforeProvisioning() { + return false; + } + }; + + mItems = Lists.newArrayList( + // 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) { + + public void onPress() { + // shutdown by making sure radio and power are handled accordingly. + ShutdownThread.shutdown(mContext, true); + } + + public boolean showDuringKeyguard() { + return true; + } + + public boolean showBeforeProvisioning() { + return true; + } + }); + + mAdapter = new MyAdapter(); + + final AlertDialog.Builder ab = new AlertDialog.Builder(mContext); + + ab.setAdapter(mAdapter, this) + .setInverseBackgroundForced(true) + .setTitle(R.string.global_actions); + + final AlertDialog dialog = ab.create(); + dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG); + if (!mContext.getResources().getBoolean( + com.android.internal.R.bool.config_sf_slowBlur)) { + dialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND, + WindowManager.LayoutParams.FLAG_BLUR_BEHIND); + } + + dialog.setOnDismissListener(this); + + return dialog; + } + + private void prepareDialog() { + final boolean silentModeOn = + mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL; + mSilentModeToggle.updateState( + silentModeOn ? ToggleAction.State.On : ToggleAction.State.Off); + mAirplaneModeOn.updateState(mAirplaneState); + mAdapter.notifyDataSetChanged(); + if (mKeyguardShowing) { + mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + } else { + mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG); + } + } + + + /** {@inheritDoc} */ + public void onDismiss(DialogInterface dialog) { + mStatusBar.disable(StatusBarManager.DISABLE_NONE); + } + + /** {@inheritDoc} */ + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + mAdapter.getItem(which).onPress(); + } + + + /** + * The adapter used for the list within the global actions dialog, taking + * into account whether the keyguard is showing via + * {@link GlobalActions#mKeyguardShowing} and whether the device is provisioned + * via {@link GlobalActions#mDeviceProvisioned}. + */ + private class MyAdapter extends BaseAdapter { + + public int getCount() { + int count = 0; + + for (int i = 0; i < mItems.size(); i++) { + final Action action = mItems.get(i); + + if (mKeyguardShowing && !action.showDuringKeyguard()) { + continue; + } + if (!mDeviceProvisioned && !action.showBeforeProvisioning()) { + continue; + } + count++; + } + 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; + for (int i = 0; i < mItems.size(); i++) { + final Action action = mItems.get(i); + if (mKeyguardShowing && !action.showDuringKeyguard()) { + continue; + } + if (!mDeviceProvisioned && !action.showBeforeProvisioning()) { + continue; + } + if (filteredPos == position) { + return action; + } + filteredPos++; + } + + throw new IllegalArgumentException("position " + position + " out of " + + "range of showable actions, filtered count = " + + "= " + getCount() + ", keyguardshowing=" + mKeyguardShowing + + ", provisioned=" + mDeviceProvisioned); + } + + + public long getItemId(int position) { + return position; + } + + public View getView(int position, View convertView, ViewGroup parent) { + Action action = getItem(position); + return action.create(mContext, convertView, parent, LayoutInflater.from(mContext)); + } + } + + // note: the scheme below made more sense when we were planning on having + // 8 different things in the global actions dialog. seems overkill with + // only 3 items now, but may as well keep this flexible approach so it will + // be easy should someone decide at the last minute to include something + // else, such as 'enable wifi', or 'enable bluetooth' + + /** + * What each item in the global actions dialog must be able to support. + */ + private interface Action { + View create(Context context, View convertView, ViewGroup parent, LayoutInflater inflater); + + void onPress(); + + /** + * @return whether this action should appear in the dialog when the keygaurd + * is showing. + */ + boolean showDuringKeyguard(); + + /** + * @return whether this action should appear in the dialog before the + * device is provisioned. + */ + boolean showBeforeProvisioning(); + + boolean isEnabled(); + } + + /** + * A single press action maintains no state, just responds to a press + * and takes an action. + */ + private static abstract class SinglePressAction implements Action { + private final int mIconResId; + private final int mMessageResId; + + protected SinglePressAction(int iconResId, int messageResId) { + mIconResId = iconResId; + mMessageResId = messageResId; + } + + public boolean isEnabled() { + return true; + } + + abstract public void onPress(); + + public View create( + Context context, View convertView, ViewGroup parent, LayoutInflater inflater) { + View v = (convertView != null) ? + convertView : + 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); + + v.findViewById(R.id.status).setVisibility(View.GONE); + + icon.setImageDrawable(context.getResources().getDrawable(mIconResId)); + messageView.setText(mMessageResId); + + return v; + } + } + + /** + * A toggle action knows whether it is on or off, and displays an icon + * and status message accordingly. + */ + private static abstract class ToggleAction implements Action { + + 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 + protected int mEnabledIconResId; + protected int mDisabledIconResid; + protected int mMessageResId; + protected int mEnabledStatusMessageResId; + protected int mDisabledStatusMessageResId; + + /** + * @param enabledIconResId The icon for when this action is on. + * @param disabledIconResid The icon for when this action is off. + * @param essage The general information message, e.g 'Silent Mode' + * @param enabledStatusMessageResId The on status message, e.g 'sound disabled' + * @param disabledStatusMessageResId The off status message, e.g. 'sound enabled' + */ + public ToggleAction(int enabledIconResId, + int disabledIconResid, + int essage, + int enabledStatusMessageResId, + int disabledStatusMessageResId) { + mEnabledIconResId = enabledIconResId; + mDisabledIconResid = disabledIconResid; + mMessageResId = essage; + mEnabledStatusMessageResId = enabledStatusMessageResId; + mDisabledStatusMessageResId = disabledStatusMessageResId; + } + + /** + * Override to make changes to resource IDs just before creating the + * View. + */ + void willCreate() { + + } + + public View create(Context context, View convertView, ViewGroup parent, + LayoutInflater inflater) { + willCreate(); + + View v = (convertView != null) ? + convertView : + 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); + TextView statusView = (TextView) v.findViewById(R.id.status); + + messageView.setText(mMessageResId); + + boolean on = ((mState == State.On) || (mState == State.TurningOn)); + icon.setImageDrawable(context.getResources().getDrawable( + (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 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(State state) { + mState = state; + } + } + + private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action) + || Intent.ACTION_SCREEN_OFF.equals(action)) { + String reason = intent.getStringExtra(PhoneWindowManager.SYSTEM_DIALOG_REASON_KEY); + if (!PhoneWindowManager.SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS.equals(reason)) { + mHandler.sendEmptyMessage(MESSAGE_DISMISS); + } + } else if (TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED.equals(action)) { + // Airplane mode can be changed after ECM exits if airplane toggle button + // is pressed during ECM mode + if (!(intent.getBooleanExtra("PHONE_IN_ECM_STATE", false)) && + mIsWaitingForEcmExit) { + mIsWaitingForEcmExit = false; + changeAirplaneModeSystemSetting(true); + } + } + } + }; + + 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) { + if (msg.what == MESSAGE_DISMISS) { + if (mDialog != null) { + mDialog.dismiss(); + } + } + } + }; + + /** + * Change the airplane mode system setting + */ + private void changeAirplaneModeSystemSetting(boolean on) { + Settings.System.putInt( + mContext.getContentResolver(), + Settings.System.AIRPLANE_MODE_ON, + on ? 1 : 0); + Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); + intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); + intent.putExtra("state", on); + mContext.sendBroadcast(intent); + } +} diff --git a/policy/src/com/android/internal/policy/impl/IconUtilities.java b/policy/src/com/android/internal/policy/impl/IconUtilities.java new file mode 100644 index 0000000..99055cf --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/IconUtilities.java @@ -0,0 +1,192 @@ +/* + * 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; + +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.PaintDrawable; +import android.graphics.drawable.StateListDrawable; +import android.graphics.Bitmap; +import android.graphics.BlurMaskFilter; +import android.graphics.Canvas; +import android.graphics.ColorMatrix; +import android.graphics.ColorMatrixColorFilter; +import android.graphics.Paint; +import android.graphics.PaintFlagsDrawFilter; +import android.graphics.PixelFormat; +import android.graphics.PorterDuff; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.TableMaskFilter; +import android.graphics.Typeface; +import android.text.Layout.Alignment; +import android.text.StaticLayout; +import android.text.TextPaint; +import android.util.DisplayMetrics; +import android.util.Log; +import android.content.res.Resources; +import android.content.Context; + +/** + * Various utilities shared amongst the Launcher's classes. + */ +final class IconUtilities { + private static final String TAG = "IconUtilities"; + + private static final int sColors[] = { 0xffff0000, 0xff00ff00, 0xff0000ff }; + + private int mIconWidth = -1; + private int mIconHeight = -1; + private int mIconTextureWidth = -1; + private int mIconTextureHeight = -1; + + private final Paint mPaint = new Paint(); + private final Paint mBlurPaint = new Paint(); + private final Paint mGlowColorPressedPaint = new Paint(); + private final Paint mGlowColorFocusedPaint = new Paint(); + private final Rect mOldBounds = new Rect(); + private final Canvas mCanvas = new Canvas(); + private final DisplayMetrics mDisplayMetrics; + + private int mColorIndex = 0; + + public IconUtilities(Context context) { + final Resources resources = context.getResources(); + DisplayMetrics metrics = mDisplayMetrics = resources.getDisplayMetrics(); + final float density = metrics.density; + final float blurPx = 5 * density; + + mIconWidth = mIconHeight = (int) resources.getDimension(android.R.dimen.app_icon_size); + mIconTextureWidth = mIconTextureHeight = mIconWidth + (int)(blurPx*2); + + mBlurPaint.setMaskFilter(new BlurMaskFilter(blurPx, BlurMaskFilter.Blur.NORMAL)); + mGlowColorPressedPaint.setColor(0xffffc300); + mGlowColorPressedPaint.setMaskFilter(TableMaskFilter.CreateClipTable(0, 30)); + mGlowColorFocusedPaint.setColor(0xffff8e00); + mGlowColorFocusedPaint.setMaskFilter(TableMaskFilter.CreateClipTable(0, 30)); + + ColorMatrix cm = new ColorMatrix(); + cm.setSaturation(0.2f); + + mCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG, + Paint.FILTER_BITMAP_FLAG)); + } + + public Drawable createIconDrawable(Drawable src) { + Bitmap scaled = createIconBitmap(src); + + StateListDrawable result = new StateListDrawable(); + + result.addState(new int[] { android.R.attr.state_focused }, + new BitmapDrawable(createSelectedBitmap(scaled, false))); + result.addState(new int[] { android.R.attr.state_pressed }, + new BitmapDrawable(createSelectedBitmap(scaled, true))); + result.addState(new int[0], new BitmapDrawable(scaled)); + + result.setBounds(0, 0, mIconTextureWidth, mIconTextureHeight); + return result; + } + + /** + * Returns a bitmap suitable for the all apps view. The bitmap will be a power + * of two sized ARGB_8888 bitmap that can be used as a gl texture. + */ + private Bitmap createIconBitmap(Drawable icon) { + int width = mIconWidth; + int height = mIconHeight; + + if (icon instanceof PaintDrawable) { + PaintDrawable painter = (PaintDrawable) icon; + painter.setIntrinsicWidth(width); + painter.setIntrinsicHeight(height); + } else if (icon instanceof BitmapDrawable) { + // Ensure the bitmap has a density. + BitmapDrawable bitmapDrawable = (BitmapDrawable) icon; + Bitmap bitmap = bitmapDrawable.getBitmap(); + if (bitmap.getDensity() == Bitmap.DENSITY_NONE) { + bitmapDrawable.setTargetDensity(mDisplayMetrics); + } + } + int sourceWidth = icon.getIntrinsicWidth(); + int sourceHeight = icon.getIntrinsicHeight(); + + if (sourceWidth > 0 && sourceWidth > 0) { + // There are intrinsic sizes. + if (width < sourceWidth || height < sourceHeight) { + // It's too big, scale it down. + final float ratio = (float) sourceWidth / sourceHeight; + if (sourceWidth > sourceHeight) { + height = (int) (width / ratio); + } else if (sourceHeight > sourceWidth) { + width = (int) (height * ratio); + } + } else if (sourceWidth < width && sourceHeight < height) { + // It's small, use the size they gave us. + width = sourceWidth; + height = sourceHeight; + } + } + + // no intrinsic size --> use default size + int textureWidth = mIconTextureWidth; + int textureHeight = mIconTextureHeight; + + final Bitmap bitmap = Bitmap.createBitmap(textureWidth, textureHeight, + Bitmap.Config.ARGB_8888); + final Canvas canvas = mCanvas; + canvas.setBitmap(bitmap); + + final int left = (textureWidth-width) / 2; + final int top = (textureHeight-height) / 2; + + if (false) { + // draw a big box for the icon for debugging + canvas.drawColor(sColors[mColorIndex]); + if (++mColorIndex >= sColors.length) mColorIndex = 0; + Paint debugPaint = new Paint(); + debugPaint.setColor(0xffcccc00); + canvas.drawRect(left, top, left+width, top+height, debugPaint); + } + + mOldBounds.set(icon.getBounds()); + icon.setBounds(left, top, left+width, top+height); + icon.draw(canvas); + icon.setBounds(mOldBounds); + + return bitmap; + } + + private Bitmap createSelectedBitmap(Bitmap src, boolean pressed) { + final Bitmap result = Bitmap.createBitmap(mIconTextureWidth, mIconTextureHeight, + Bitmap.Config.ARGB_8888); + final Canvas dest = new Canvas(result); + + dest.drawColor(0, PorterDuff.Mode.CLEAR); + + int[] xy = new int[2]; + Bitmap mask = src.extractAlpha(mBlurPaint, xy); + + dest.drawBitmap(mask, xy[0], xy[1], + pressed ? mGlowColorPressedPaint : mGlowColorFocusedPaint); + + mask.recycle(); + + dest.drawBitmap(src, 0, 0, mPaint); + + return result; + } +} diff --git a/policy/src/com/android/internal/policy/impl/KeyguardScreen.java b/policy/src/com/android/internal/policy/impl/KeyguardScreen.java new file mode 100644 index 0000000..bbb6875 --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/KeyguardScreen.java @@ -0,0 +1,45 @@ +/* + * 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; + +/** + * Common interface of each {@link android.view.View} that is a screen of + * {@link LockPatternKeyguardView}. + */ +public interface KeyguardScreen { + + /** + * Return true if your view needs input, so should allow the soft + * keyboard to be displayed. + */ + boolean needsInput(); + + /** + * This screen is no longer in front of the user. + */ + void onPause(); + + /** + * This screen is going to be in front of the user. + */ + void onResume(); + + /** + * This view is going away; a hook to do cleanup. + */ + void cleanUp(); +} diff --git a/policy/src/com/android/internal/policy/impl/KeyguardScreenCallback.java b/policy/src/com/android/internal/policy/impl/KeyguardScreenCallback.java new file mode 100644 index 0000000..a843603 --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/KeyguardScreenCallback.java @@ -0,0 +1,82 @@ +/* + * 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; + +import android.content.res.Configuration; + +/** + * Within a keyguard, there may be several screens that need a callback + * to the host keyguard view. + */ +public interface KeyguardScreenCallback extends KeyguardViewCallback { + + /** + * Transition to the lock screen. + */ + void goToLockScreen(); + + /** + * Transition to the unlock screen. + */ + void goToUnlockScreen(); + + /** + * The user reported that they forgot their pattern (or not, when they want to back out of the + * forgot pattern screen). + * + * @param isForgotten True if the user hit the forgot pattern, false if they want to back out + * of the account screen. + */ + void forgotPattern(boolean isForgotten); + + /** + * @return Whether the keyguard requires some sort of PIN. + */ + boolean isSecure(); + + /** + * @return Whether we are in a mode where we only want to verify the + * user can get past the keyguard. + */ + boolean isVerifyUnlockOnly(); + + /** + * Stay on me, but recreate me (so I can use a different layout). + */ + void recreateMe(Configuration config); + + /** + * Take action to send an emergency call. + */ + void takeEmergencyCallAction(); + + /** + * Report that the user had a failed attempt to unlock with password or pattern. + */ + void reportFailedUnlockAttempt(); + + /** + * Report that the user successfully entered their password or pattern. + */ + void reportSuccessfulUnlockAttempt(); + + /** + * Report whether we there's another way to unlock the device. + * @return true + */ + boolean doesFallbackUnlockScreenExist(); +} diff --git a/policy/src/com/android/internal/policy/impl/KeyguardUpdateMonitor.java b/policy/src/com/android/internal/policy/impl/KeyguardUpdateMonitor.java new file mode 100644 index 0000000..b225e56 --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/KeyguardUpdateMonitor.java @@ -0,0 +1,526 @@ +/* + * 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; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.res.Configuration; +import android.database.ContentObserver; +import static android.os.BatteryManager.BATTERY_STATUS_CHARGING; +import static android.os.BatteryManager.BATTERY_STATUS_FULL; +import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN; +import android.media.AudioManager; +import android.os.Handler; +import android.os.Message; +import android.os.SystemClock; +import android.provider.Settings; +import android.provider.Telephony; +import static android.provider.Telephony.Intents.EXTRA_PLMN; +import static android.provider.Telephony.Intents.EXTRA_SHOW_PLMN; +import static android.provider.Telephony.Intents.EXTRA_SHOW_SPN; +import static android.provider.Telephony.Intents.EXTRA_SPN; +import static android.provider.Telephony.Intents.SPN_STRINGS_UPDATED_ACTION; + +import com.android.internal.telephony.IccCard; +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.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 { + + static private final String TAG = "KeyguardUpdateMonitor"; + static private final boolean DEBUG = false; + + private static final int LOW_BATTERY_THRESHOLD = 20; + + private final Context mContext; + + private IccCard.State mSimState = IccCard.State.READY; + + private boolean mKeyguardBypassEnabled; + + private boolean mDevicePluggedIn; + + private boolean mDeviceProvisioned; + + private int mBatteryLevel; + + private CharSequence mTelephonyPlmn; + private CharSequence mTelephonySpn; + + private int mFailedAttempts = 0; + + private Handler mHandler; + + private ArrayList<InfoCallback> mInfoCallbacks = Lists.newArrayList(); + private ArrayList<SimStateCallback> mSimStateCallbacks = Lists.newArrayList(); + private ContentObserver mContentObserver; + + // messages for the handler + 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; + + + /** + * 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 IccCard.State simState; + + private SimArgs(Intent intent) { + if (!TelephonyIntents.ACTION_SIM_STATE_CHANGED.equals(intent.getAction())) { + throw new IllegalArgumentException("only handles intent ACTION_SIM_STATE_CHANGED"); + } + String stateExtra = intent.getStringExtra(IccCard.INTENT_KEY_ICC_STATE); + if (IccCard.INTENT_VALUE_ICC_ABSENT.equals(stateExtra)) { + this.simState = IccCard.State.ABSENT; + } else if (IccCard.INTENT_VALUE_ICC_READY.equals(stateExtra)) { + this.simState = IccCard.State.READY; + } else if (IccCard.INTENT_VALUE_ICC_LOCKED.equals(stateExtra)) { + final String lockedReason = intent + .getStringExtra(IccCard.INTENT_KEY_LOCKED_REASON); + if (IccCard.INTENT_VALUE_LOCKED_ON_PIN.equals(lockedReason)) { + this.simState = IccCard.State.PIN_REQUIRED; + } else if (IccCard.INTENT_VALUE_LOCKED_ON_PUK.equals(lockedReason)) { + this.simState = IccCard.State.PUK_REQUIRED; + } else { + this.simState = IccCard.State.UNKNOWN; + } + } else if (IccCard.INTENT_VALUE_LOCKED_NETWORK.equals(stateExtra)) { + this.simState = IccCard.State.NETWORK_LOCKED; + } else { + this.simState = IccCard.State.UNKNOWN; + } + } + + public String toString() { + return simState.toString(); + } + } + + public KeyguardUpdateMonitor(Context context) { + mContext = context; + + mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_TIME_UPDATE: + handleTimeUpdate(); + break; + case MSG_BATTERY_UPDATE: + handleBatteryUpdate(msg.arg1, msg.arg2); + 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; + } + } + }; + + mKeyguardBypassEnabled = context.getResources().getBoolean( + com.android.internal.R.bool.config_bypass_keyguard_if_slider_open); + + 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) { + 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 && mContentObserver != null) { + // We don't need the observer anymore... + mContext.getContentResolver().unregisterContentObserver(mContentObserver); + mContentObserver = null; + } + 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... + mDeviceProvisioned = Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.DEVICE_PROVISIONED, 0) != 0; + } + + // take a guess to start + mSimState = IccCard.State.READY; + mDevicePluggedIn = true; + mBatteryLevel = 100; + + mTelephonyPlmn = getDefaultPlmn(); + + // setup receiver + 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(SPN_STRINGS_UPDATED_ACTION); + filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION); + context.registerReceiver(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 (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 pluggedInStatus = intent + .getIntExtra("status", BATTERY_STATUS_UNKNOWN); + int batteryLevel = intent.getIntExtra("level", 0); + final Message msg = mHandler.obtainMessage( + MSG_BATTERY_UPDATE, + pluggedInStatus, + batteryLevel); + mHandler.sendMessage(msg); + } else if (TelephonyIntents.ACTION_SIM_STATE_CHANGED.equals(action)) { + mHandler.sendMessage(mHandler.obtainMessage( + MSG_SIM_STATE_CHANGE, + new SimArgs(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)); + } + } + }, filter); + } + + protected void handlePhoneStateChanged(String newState) { + if (DEBUG) Log.d(TAG, "handlePhoneStateChanged(" + newState + ")"); + for (int i = 0; i < mInfoCallbacks.size(); i++) { + mInfoCallbacks.get(i).onPhoneStateChanged(newState); + } + } + + protected void handleRingerModeChange(int mode) { + if (DEBUG) Log.d(TAG, "handleRingerModeChange(" + mode + ")"); + for (int i = 0; i < mInfoCallbacks.size(); i++) { + mInfoCallbacks.get(i).onRingerModeChanged(mode); + } + } + + /** + * Handle {@link #MSG_TIME_UPDATE} + */ + private void handleTimeUpdate() { + if (DEBUG) Log.d(TAG, "handleTimeUpdate"); + for (int i = 0; i < mInfoCallbacks.size(); i++) { + mInfoCallbacks.get(i).onTimeChanged(); + } + } + + /** + * Handle {@link #MSG_BATTERY_UPDATE} + */ + private void handleBatteryUpdate(int pluggedInStatus, int batteryLevel) { + if (DEBUG) Log.d(TAG, "handleBatteryUpdate"); + final boolean pluggedIn = isPluggedIn(pluggedInStatus); + + if (isBatteryUpdateInteresting(pluggedIn, batteryLevel)) { + mBatteryLevel = batteryLevel; + mDevicePluggedIn = pluggedIn; + for (int i = 0; i < mInfoCallbacks.size(); i++) { + mInfoCallbacks.get(i).onRefreshBatteryInfo( + shouldShowBatteryInfo(), pluggedIn, batteryLevel); + } + } + } + + /** + * Handle {@link #MSG_CARRIER_INFO_UPDATE} + */ + private void handleCarrierInfoUpdate() { + if (DEBUG) Log.d(TAG, "handleCarrierInfoUpdate: plmn = " + mTelephonyPlmn + + ", spn = " + mTelephonySpn); + + for (int i = 0; i < mInfoCallbacks.size(); i++) { + mInfoCallbacks.get(i).onRefreshCarrierInfo(mTelephonyPlmn, mTelephonySpn); + } + } + + /** + * Handle {@link #MSG_SIM_STATE_CHANGE} + */ + private void handleSimStateChange(SimArgs simArgs) { + final IccCard.State state = simArgs.simState; + + if (DEBUG) { + Log.d(TAG, "handleSimStateChange: intentValue = " + simArgs + " " + + "state resolved to " + state.toString()); + } + + if (state != IccCard.State.UNKNOWN && state != mSimState) { + mSimState = state; + for (int i = 0; i < mSimStateCallbacks.size(); i++) { + mSimStateCallbacks.get(i).onSimStateChanged(state); + } + } + } + + /** + * @param status One of the statuses of {@link android.os.BatteryManager} + * @return Whether the status maps to a status for being plugged in. + */ + private boolean isPluggedIn(int status) { + return status == BATTERY_STATUS_CHARGING || status == BATTERY_STATUS_FULL; + } + + private boolean isBatteryUpdateInteresting(boolean pluggedIn, int batteryLevel) { + // change in plug is always interesting + if (mDevicePluggedIn != pluggedIn) { + return true; + } + + // change in battery level while plugged in + if (pluggedIn && mBatteryLevel != batteryLevel) { + return true; + } + + if (!pluggedIn) { + // not plugged in and below threshold + if (batteryLevel < LOW_BATTERY_THRESHOLD && batteryLevel != mBatteryLevel) { + return true; + } + } + return false; + } + + /** + * @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 getTelephonyPlmnFrom(Intent intent) { + if (intent.getBooleanExtra(EXTRA_SHOW_PLMN, false)) { + final String plmn = intent.getStringExtra(EXTRA_PLMN); + if (plmn != null) { + return plmn; + } else { + return 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(EXTRA_SHOW_SPN, false)) { + final String spn = intent.getStringExtra(EXTRA_SPN); + if (spn != null) { + return spn; + } + } + return null; + } + + /** + * Remove the given observer from being registered from any of the kinds + * of callbacks. + * @param observer The observer to remove (an instance of {@link ConfigurationChangeCallback}, + * {@link InfoCallback} or {@link SimStateCallback} + */ + public void removeCallback(Object observer) { + mInfoCallbacks.remove(observer); + mSimStateCallbacks.remove(observer); + } + + /** + * Callback for general information relevant to lock screen. + */ + interface InfoCallback { + void onRefreshBatteryInfo(boolean showBatteryInfo, boolean pluggedIn, int batteryLevel); + void onTimeChanged(); + + /** + * @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(String newState); + } + + /** + * Callback to notify of sim state change. + */ + interface SimStateCallback { + void onSimStateChanged(IccCard.State simState); + } + + /** + * Register to receive notifications about general keyguard information + * (see {@link InfoCallback}. + * @param callback The callback. + */ + public void registerInfoCallback(InfoCallback callback) { + if (!mInfoCallbacks.contains(callback)) { + mInfoCallbacks.add(callback); + } else { + Log.e(TAG, "Object tried to add another INFO callback", new Exception("Whoops")); + } + } + + /** + * Register to be notified of sim state changes. + * @param callback The callback. + */ + public void registerSimStateCallback(SimStateCallback callback) { + if (!mSimStateCallbacks.contains(callback)) { + mSimStateCallbacks.add(callback); + } else { + Log.e(TAG, "Object tried to add another SIM callback", new Exception("Whoops")); + } + } + + public IccCard.State getSimState() { + return mSimState; + } + + /** + * Report that the user succesfully entered the sim pin so we + * have the information earlier than waiting for the intent + * broadcast from the telephony code. + */ + public void reportSimPinUnlocked() { + mSimState = IccCard.State.READY; + } + + public boolean isKeyguardBypassEnabled() { + return mKeyguardBypassEnabled; + } + + public boolean isDevicePluggedIn() { + return mDevicePluggedIn; + } + + public int getBatteryLevel() { + return mBatteryLevel; + } + + public boolean shouldShowBatteryInfo() { + return mDevicePluggedIn || mBatteryLevel < LOW_BATTERY_THRESHOLD; + } + + 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; + } + + public void reportFailedAttempt() { + mFailedAttempts++; + } +} diff --git a/policy/src/com/android/internal/policy/impl/KeyguardViewBase.java b/policy/src/com/android/internal/policy/impl/KeyguardViewBase.java new file mode 100644 index 0000000..9dcbcb6 --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/KeyguardViewBase.java @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.policy.impl; + +import android.content.Context; +import android.content.Intent; +import android.media.AudioManager; +import android.telephony.TelephonyManager; +import android.view.KeyEvent; +import android.view.View; +import android.view.Gravity; +import android.widget.FrameLayout; +import android.util.AttributeSet; + +/** + * Base class for keyguard views. {@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 FrameLayout { + + private KeyguardViewCallback mCallback; + private AudioManager mAudioManager; + private TelephonyManager mTelephonyManager = null; + + public KeyguardViewBase(Context context) { + super(context); + + // drop shadow below status bar in keyguard too + mForegroundInPadding = false; + setForegroundGravity(Gravity.FILL_HORIZONTAL | Gravity.TOP); + setForeground( + context.getResources().getDrawable( + com.android.internal.R.drawable.title_bar_shadow)); + } + + // used to inject callback + void setCallback(KeyguardViewCallback callback) { + mCallback = callback; + } + + public KeyguardViewCallback getCallback() { + return mCallback; + } + + /** + * 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 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. + */ + 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 (shouldEventKeepScreenOnWhileKeyguardShowing(event)) { + mCallback.pokeWakelock(); + } + + if (interceptMediaKey(event)) { + return true; + } + return super.dispatchKeyEvent(event); + } + + private boolean shouldEventKeepScreenOnWhileKeyguardShowing(KeyEvent event) { + if (event.getAction() != KeyEvent.ACTION_DOWN) { + return false; + } + switch (event.getKeyCode()) { + case KeyEvent.KEYCODE_DPAD_DOWN: + case KeyEvent.KEYCODE_DPAD_LEFT: + case KeyEvent.KEYCODE_DPAD_RIGHT: + case KeyEvent.KEYCODE_DPAD_UP: + return false; + default: + return true; + } + } + + /** + * 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_PAUSE: + /* Suppress PLAYPAUSE 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_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_FAST_FORWARD: { + Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON, null); + intent.putExtra(Intent.EXTRA_KEY_EVENT, event); + getContext().sendOrderedBroadcast(intent, null); + return true; + } + + case KeyEvent.KEYCODE_VOLUME_UP: + case KeyEvent.KEYCODE_VOLUME_DOWN: { + synchronized (this) { + if (mAudioManager == null) { + mAudioManager = (AudioManager) getContext().getSystemService( + Context.AUDIO_SERVICE); + } + } + // Volume buttons should only function for music. + if (mAudioManager.isMusicActive()) { + mAudioManager.adjustStreamVolume( + AudioManager.STREAM_MUSIC, + keyCode == KeyEvent.KEYCODE_VOLUME_UP + ? AudioManager.ADJUST_RAISE + : AudioManager.ADJUST_LOWER, + 0); + } + // Don't execute default volume behavior + return true; + } + } + } else if (event.getAction() == KeyEvent.ACTION_UP) { + switch (keyCode) { + case KeyEvent.KEYCODE_MUTE: + case KeyEvent.KEYCODE_HEADSETHOOK: + 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_FAST_FORWARD: { + Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON, null); + intent.putExtra(Intent.EXTRA_KEY_EVENT, event); + getContext().sendOrderedBroadcast(intent, null); + return true; + } + } + } + return false; + } + +} diff --git a/policy/src/com/android/internal/policy/impl/KeyguardViewCallback.java b/policy/src/com/android/internal/policy/impl/KeyguardViewCallback.java new file mode 100644 index 0000000..b376d65 --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/KeyguardViewCallback.java @@ -0,0 +1,49 @@ +/* + * 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; + +/** + * The callback used by the keyguard view to tell the {@link KeyguardViewMediator} + * various things. + */ +public interface KeyguardViewCallback { + + /** + * 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(int 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(); +} diff --git a/policy/src/com/android/internal/policy/impl/KeyguardViewManager.java b/policy/src/com/android/internal/policy/impl/KeyguardViewManager.java new file mode 100644 index 0000000..ba1d7f5 --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/KeyguardViewManager.java @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.policy.impl; + +import com.android.internal.R; + +import android.content.Context; +import android.content.pm.ActivityInfo; +import android.graphics.PixelFormat; +import android.graphics.Canvas; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewManager; +import android.view.WindowManager; +import android.widget.FrameLayout; + +/** + * 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 implements KeyguardWindowController { + private final static boolean DEBUG = false; + private static String TAG = "KeyguardViewManager"; + + private final Context mContext; + private final ViewManager mViewManager; + private final KeyguardViewCallback mCallback; + private final KeyguardViewProperties mKeyguardViewProperties; + + private final KeyguardUpdateMonitor mUpdateMonitor; + + private WindowManager.LayoutParams mWindowLayoutParams; + private boolean mNeedsInput = false; + + private FrameLayout mKeyguardHost; + private KeyguardViewBase mKeyguardView; + + private boolean mScreenOn = false; + + /** + * @param context Used to create views. + * @param viewManager Keyguard will be attached to this. + * @param callback Used to notify of changes. + */ + public KeyguardViewManager(Context context, ViewManager viewManager, + KeyguardViewCallback callback, KeyguardViewProperties keyguardViewProperties, KeyguardUpdateMonitor updateMonitor) { + mContext = context; + mViewManager = viewManager; + mCallback = callback; + mKeyguardViewProperties = keyguardViewProperties; + + mUpdateMonitor = updateMonitor; + } + + /** + * Helper class to host the keyguard view. + */ + private static class KeyguardViewHost extends FrameLayout { + private final KeyguardViewCallback mCallback; + + private KeyguardViewHost(Context context, KeyguardViewCallback callback) { + super(context); + mCallback = callback; + } + + @Override + protected void dispatchDraw(Canvas canvas) { + super.dispatchDraw(canvas); + mCallback.keyguardDoneDrawing(); + } + } + + /** + * 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); + + if (mKeyguardHost == null) { + if (DEBUG) Log.d(TAG, "keyguard host is null, creating it..."); + + mKeyguardHost = new KeyguardViewHost(mContext, mCallback); + + final int stretch = ViewGroup.LayoutParams.MATCH_PARENT; + int flags = WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN + | WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER + | WindowManager.LayoutParams.FLAG_KEEP_SURFACE_WHILE_ANIMATING + /*| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN + | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR*/ ; + if (!mNeedsInput) { + flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; + } + WindowManager.LayoutParams lp = new WindowManager.LayoutParams( + stretch, stretch, WindowManager.LayoutParams.TYPE_KEYGUARD, + flags, PixelFormat.TRANSLUCENT); + lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN; + lp.windowAnimations = com.android.internal.R.style.Animation_LockScreen; + lp.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; + lp.setTitle("Keyguard"); + mWindowLayoutParams = lp; + + mViewManager.addView(mKeyguardHost, lp); + } + + if (mKeyguardView == null) { + if (DEBUG) Log.d(TAG, "keyguard view is null, creating it..."); + mKeyguardView = mKeyguardViewProperties.createKeyguardView(mContext, mUpdateMonitor, this); + mKeyguardView.setId(R.id.lock_screen); + mKeyguardView.setCallback(mCallback); + + final ViewGroup.LayoutParams lp = new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT); + + mKeyguardHost.addView(mKeyguardView, lp); + + if (mScreenOn) { + mKeyguardView.onScreenTurnedOn(); + } + } + + mKeyguardHost.setVisibility(View.VISIBLE); + mKeyguardView.requestFocus(); + } + + 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; + } + mViewManager.updateViewLayout(mKeyguardHost, mWindowLayoutParams); + } + } + + /** + * 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() { + if (DEBUG) Log.d(TAG, "onScreenTurnedOn()"); + mScreenOn = true; + if (mKeyguardView != null) { + mKeyguardView.onScreenTurnedOn(); + } + } + + 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. + */ + 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) { + mKeyguardHost.removeView(lastView); + lastView.cleanUp(); + } + } + }, 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/KeyguardViewMediator.java b/policy/src/com/android/internal/policy/impl/KeyguardViewMediator.java new file mode 100644 index 0000000..88203c3 --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/KeyguardViewMediator.java @@ -0,0 +1,1136 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.policy.impl; + +import com.android.internal.telephony.IccCard; +import com.android.internal.widget.LockPatternUtils; + +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.Ringtone; +import android.media.RingtoneManager; +import android.net.Uri; +import android.os.Handler; +import android.os.IBinder; +import android.os.LocalPowerManager; +import android.os.Message; +import android.os.PowerManager; +import android.os.RemoteException; +import android.os.SystemClock; +import android.os.SystemProperties; +import android.provider.Settings; +import android.telephony.TelephonyManager; +import android.util.Config; +import android.util.EventLog; +import android.util.Log; +import android.view.KeyEvent; +import android.view.WindowManagerImpl; +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.InputManager}'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 implements KeyguardViewCallback, + KeyguardUpdateMonitor.SimStateCallback { + private final static boolean DEBUG = false && Config.LOGD; + private final static boolean DBG_WAKE = DEBUG || true; + + 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 = 5000; + + + /** + * The default amount of time we stay awake (used for all key input) when + * the keyboard is open + */ + protected static final int AWAKE_INTERVAL_DEFAULT_KEYBOARD_OPEN_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_DELAY_MS = 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; + + private Context mContext; + private AlarmManager mAlarmManager; + 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; + + + /** Low level access to the power manager for enableUserActivity. Having this + * requires that we run in the system process. */ + LocalPowerManager mRealPowerManager; + + /** 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; + + private PhoneWindowManager mCallback; + + /** + * 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 KeyguardViewProperties mKeyguardViewProperties; + + private KeyguardUpdateMonitor mUpdateMonitor; + + private boolean mKeyboardOpen = false; + + private boolean mScreenOn = false; + + // 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; + + public KeyguardViewMediator(Context context, PhoneWindowManager callback, + LocalPowerManager powerManager) { + mContext = context; + + mRealPowerManager = powerManager; + 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); + + IntentFilter filter = new IntentFilter(); + filter.addAction(DELAYED_KEYGUARD_ACTION); + filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED); + context.registerReceiver(mBroadCastReceiver, filter); + mAlarmManager = (AlarmManager) context + .getSystemService(Context.ALARM_SERVICE); + mCallback = callback; + + mUpdateMonitor = new KeyguardUpdateMonitor(context); + + mUpdateMonitor.registerSimStateCallback(this); + + mKeyguardViewProperties = new LockPatternKeyguardViewProperties( + new LockPatternUtils(mContext), mUpdateMonitor); + + mKeyguardViewManager = new KeyguardViewManager( + context, WindowManagerImpl.getDefault(), this, + mKeyguardViewProperties, mUpdateMonitor); + + mUserPresentIntent = new Intent(Intent.ACTION_USER_PRESENT); + mUserPresentIntent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); + + final ContentResolver cr = mContext.getContentResolver(); + mShowLockIcon = (Settings.System.getInt(cr, "show_status_bar_lock", 0) == 1); + } + + /** + * Let us know that the system is ready after startup. + */ + public void onSystemReady() { + synchronized (this) { + if (DEBUG) Log.d(TAG, "onSystemReady"); + mSystemReady = true; + doKeyguard(); + } + } + + /** + * 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 + ")"); + + 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) { + // if the screen turned off because of timeout, 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) + long when = SystemClock.elapsedRealtime() + KEYGUARD_DELAY_MS; + 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 { + doKeyguard(); + } + } + } + + /** + * Let's us know the screen was turned on. + */ + public void onScreenTurnedOn() { + synchronized (this) { + mScreenOn = true; + mDelayedShowingSequence++; + if (DEBUG) Log.d(TAG, "onScreenTurnedOn, seq = " + mDelayedShowingSequence); + notifyScreenOnLocked(); + } + } + + /** + * 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; + 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(); + } + + /** + * Returns true if the change is resulting in the keyguard beign dismissed, + * meaning the screen can turn on immediately. Otherwise returns false. + */ + public boolean doLidChangeTq(boolean isLidOpen) { + mKeyboardOpen = isLidOpen; + + if (mUpdateMonitor.isKeyguardBypassEnabled() && mKeyboardOpen + && !mKeyguardViewProperties.isSecure() && mKeyguardViewManager.isShowing()) { + if (DEBUG) Log.d(TAG, "bypassing keyguard on sliding open of keyboard with non-secure keyguard"); + mHandler.sendEmptyMessage(KEYGUARD_DONE_AUTHENTICATING); + return true; + } + return false; + } + + /** + * Enable the keyguard if the settings are appropriate. + */ + private void doKeyguard() { + synchronized (this) { + // 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 IccCard.State state = mUpdateMonitor.getSimState(); + final boolean lockedOrMissing = state.isPinLocked() + || ((state == IccCard.State.ABSENT) && 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 (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() { + if (DEBUG) Log.d(TAG, "notifyScreenOnLocked"); + mHandler.sendEmptyMessage(NOTIFY_SCREEN_ON); + } + + /** + * 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); + } + + /** {@inheritDoc} */ + public void onSimStateChanged(IccCard.State simState) { + if (DEBUG) Log.d(TAG, "onSimStateChanged: " + simState); + + switch (simState) { + case ABSENT: + // only force lock screen in case of missing sim if user hasn't + // gone through setup wizard + if (!mUpdateMonitor.isDeviceProvisioned()) { + if (!isShowing()) { + if (DEBUG) Log.d(TAG, "INTENT_VALUE_ICC_ABSENT and keygaurd isn't showing, we need " + + "to show the keyguard since the device isn't provisioned yet."); + doKeyguard(); + } else { + resetStateLocked(); + } + } + break; + case PIN_REQUIRED: + case PUK_REQUIRED: + if (!isShowing()) { + if (DEBUG) Log.d(TAG, "INTENT_VALUE_ICC_LOCKED and keygaurd isn't showing, we need " + + "to show the keyguard so the user can enter their sim pin"); + doKeyguard(); + } else { + resetStateLocked(); + } + + break; + case READY: + if (isShowing()) { + resetStateLocked(); + } + break; + } + } + + public boolean isSecure() { + return mKeyguardViewProperties.isSecure(); + } + + private BroadcastReceiver mBroadCastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + if (action.equals(DELAYED_KEYGUARD_ACTION)) { + + int sequence = intent.getIntExtra("seq", 0); + + if (false) Log.d(TAG, "received DELAYED_KEYGUARD_ACTION with seq = " + + sequence + ", mDelayedShowingSequence = " + mDelayedShowingSequence); + + if (mDelayedShowingSequence == sequence) { + // Don't play lockscreen SFX if the screen went off due to + // timeout. + mSuppressNextLockSound = true; + + doKeyguard(); + } + } else if (TelephonyManager.ACTION_PHONE_STATE_CHANGED.equals(action)) { + mPhoneState = intent.getStringExtra(TelephonyManager.EXTRA_STATE); + + if (TelephonyManager.EXTRA_STATE_IDLE.equals(mPhoneState) // 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"); + doKeyguard(); + } + } + } + }; + + + /** + * 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 + * @return Whether we poked the wake lock (and turned the screen on) + */ + public boolean onWakeKeyWhenKeyguardShowingTq(int keyCode) { + if (DEBUG) Log.d(TAG, "onWakeKeyWhenKeyguardShowing(" + keyCode + ")"); + + if (isWakeKeyWhenKeyguardShowing(keyCode)) { + // 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; + } + } + + private boolean isWakeKeyWhenKeyguardShowing(int keyCode) { + switch (keyCode) { + case KeyEvent.KEYCODE_VOLUME_UP: + case KeyEvent.KEYCODE_VOLUME_DOWN: + case KeyEvent.KEYCODE_MUTE: + case KeyEvent.KEYCODE_HEADSETHOOK: + 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_FAST_FORWARD: + case KeyEvent.KEYCODE_CAMERA: + return false; + } + return true; + } + + /** + * Callbacks from {@link KeyguardViewManager}. + */ + + /** {@inheritDoc} */ + public void pokeWakelock() { + pokeWakelock(mKeyboardOpen ? + AWAKE_INTERVAL_DEFAULT_KEYBOARD_OPEN_MS : AWAKE_INTERVAL_DEFAULT_MS); + } + + /** {@inheritDoc} */ + public void pokeWakelock(int 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); + } + } + + /** + * {@inheritDoc} + * + * @see #handleKeyguardDone + */ + public void keyguardDone(boolean authenticated) { + keyguardDone(authenticated, 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; + } + } + } + } + + /** + * {@inheritDoc} + * + * @see #handleKeyguardDoneDrawing + */ + public void keyguardDoneDrawing() { + mHandler.sendEmptyMessage(KEYGUARD_DONE_DRAWING); + } + + /** + * 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() { + @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(); + 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); + return; + case SET_HIDDEN: + handleSetHidden(msg.arg1 != 0); + break; + case KEYGUARD_TIMEOUT: + doKeyguard(); + break; + } + } + }; + + /** + * @see #keyguardDone + * @see #KEYGUARD_DONE + */ + private void handleKeyguardDone(boolean wakeup) { + if (DEBUG) Log.d(TAG, "handleKeyguardDone"); + handleHide(); + if (wakeup) { + mPM.userActivity(SystemClock.uptimeMillis(), true); + } + mWakeLock.release(); + mContext.sendBroadcast(mUserPresentIntent); + } + + /** + * @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 String whichSound = locked + ? Settings.System.LOCK_SOUND + : Settings.System.UNLOCK_SOUND; + final String soundPath = Settings.System.getString(cr, whichSound); + if (soundPath != null) { + final Uri soundUri = Uri.parse("file://" + soundPath); + if (soundUri != null) { + final Ringtone sfx = RingtoneManager.getRingtone(mContext, soundUri); + if (sfx != null) { + sfx.setStreamType(AudioManager.STREAM_SYSTEM); + sfx.play(); + } else { + Log.d(TAG, "playSounds: failed to load ringtone from uri: " + soundUri); + } + } else { + Log.d(TAG, "playSounds: could not parse Uri: " + soundPath); + } + } else { + Log.d(TAG, "playSounds: whichSound = " + whichSound + "; soundPath was null"); + } + } + } + + /** + * Handle message sent by {@link #showLocked}. + * @see #SHOW + */ + private void handleShow() { + synchronized (KeyguardViewMediator.this) { + if (DEBUG) Log.d(TAG, "handleShow"); + if (!mSystemReady) return; + + playSounds(true); + + mKeyguardViewManager.show(); + mShowing = true; + adjustUserActivityLocked(); + adjustStatusBarLocked(); + try { + ActivityManagerNative.getDefault().closeSystemDialogs("lock"); + } catch (RemoteException e) { + } + 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; + 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; + 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) { + mStatusBarManager.setIcon("secure", + com.android.internal.R.drawable.stat_sys_secure, 0); + mShowingLockIcon = true; + } + } else { + if (mShowingLockIcon) { + mStatusBarManager.removeIcon("secure"); + mShowingLockIcon = false; + } + } + } + + // if the keyguard is shown, allow the status bar to open + // only if the keyguard is insecure and is covered by another window + boolean enable = !mShowing || (mHidden && !isSecure()); + mStatusBarManager.disable(enable ? + StatusBarManager.DISABLE_NONE : + StatusBarManager.DISABLE_EXPAND); + } + } + + /** + * 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; + } + } + + /** + * 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() { + synchronized (KeyguardViewMediator.this) { + if (DEBUG) Log.d(TAG, "handleNotifyScreenOn"); + mKeyguardViewManager.onScreenTurnedOn(); + } + } +} + + diff --git a/policy/src/com/android/internal/policy/impl/KeyguardViewProperties.java b/policy/src/com/android/internal/policy/impl/KeyguardViewProperties.java new file mode 100644 index 0000000..bda08eb --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/KeyguardViewProperties.java @@ -0,0 +1,46 @@ +/* + * 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; + +import android.content.Context; + +/** + * Defines operations necessary for showing a keyguard, including how to create + * it, and various properties that are useful to be able to query independant + * of whether the keyguard instance is around or not. + */ +public interface KeyguardViewProperties { + + /** + * Create a keyguard view. + * @param context the context to use when creating the view. + * @param updateMonitor configuration may be based on this. + * @param controller for talking back with the containing window. + * @return the view. + */ + KeyguardViewBase createKeyguardView(Context context, + KeyguardUpdateMonitor updateMonitor, + KeyguardWindowController controller); + + /** + * Would the keyguard be secure right now? + * @return Whether the keyguard is currently secure, meaning it will block + * the user from getting past it until the user enters some sort of PIN. + */ + boolean isSecure(); + +} diff --git a/policy/src/com/android/internal/policy/impl/KeyguardWindowController.java b/policy/src/com/android/internal/policy/impl/KeyguardWindowController.java new file mode 100644 index 0000000..4ad48fb --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/KeyguardWindowController.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2009 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; + +/** + * Interface passed to the keyguard view, for it to call up to control + * its containing window. + */ +public interface KeyguardWindowController { + /** + * Control whether the window needs input -- that is if it has + * text fields and thus should allow input method interaction. + */ + void setNeedsInput(boolean needsInput); +} diff --git a/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java b/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java new file mode 100644 index 0000000..27706ef --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java @@ -0,0 +1,790 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.policy.impl; + +import com.android.internal.R; +import com.android.internal.telephony.IccCard; +import com.android.internal.widget.LockPatternUtils; + +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.AlertDialog; +import android.app.admin.DevicePolicyManager; +import android.content.Context; +import android.content.Intent; +import android.content.res.Configuration; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.PixelFormat; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.os.SystemClock; +import android.os.SystemProperties; +import android.telephony.TelephonyManager; +import android.text.TextUtils; +import android.util.Log; +import android.view.KeyEvent; +import android.view.View; +import android.view.WindowManager; + +import java.io.IOException; + +/** + * The host view for all of the screens of the pattern unlock screen. There are + * two {@link Mode}s of operation, lock and unlock. This will show the appropriate + * screen, and listen for callbacks via + * {@link com.android.internal.policy.impl.KeyguardScreenCallback} + * from the current screen. + * + * This view, in turn, communicates back to + * {@link com.android.internal.policy.impl.KeyguardViewManager} + * via its {@link com.android.internal.policy.impl.KeyguardViewCallback}, as appropriate. + */ +public class LockPatternKeyguardView extends KeyguardViewBase { + + static final boolean DEBUG_CONFIGURATION = false; + + // 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 boolean DEBUG = false; + private static final String TAG = "LockPatternKeyguardView"; + + private final KeyguardUpdateMonitor mUpdateMonitor; + private final KeyguardWindowController mWindowController; + + private View mLockScreen; + private View mUnlockScreen; + + private boolean mScreenOn = false; + private boolean mEnableFallback = false; // assume no fallback UI until we know better + + /** + * The current {@link KeyguardScreen} will use this to communicate back to us. + */ + KeyguardScreenCallback mKeyguardScreenCallback; + + + private boolean mRequiresSim; + + + /** + * Either a lock screen (an informational keyguard screen), or an unlock + * screen (a means for unlocking the device) is shown at any given time. + */ + enum Mode { + LockScreen, + UnlockScreen + } + + /** + * The different types screens available for {@link Mode#UnlockScreen}. + * @see com.android.internal.policy.impl.LockPatternKeyguardView#getUnlockMode() + */ + enum UnlockMode { + + /** + * Unlock by drawing a pattern. + */ + Pattern, + + /** + * Unlock by entering a sim pin. + */ + SimPin, + + /** + * Unlock by entering an account's login and password. + */ + Account, + + /** + * Unlock by entering a password or PIN + */ + Password, + + /** + * Unknown (uninitialized) value + */ + Unknown + } + + /** + * The current mode. + */ + private Mode mMode = Mode.LockScreen; + + /** + * Keeps track of what mode the current unlock screen is (cached from most recent computation in + * {@link #getUnlockMode}). + */ + private UnlockMode mUnlockScreenMode; + + private boolean mForgotPattern; + + /** + * If true, it means we are in the process of verifying that the user + * can get past the lock screen per {@link #verifyUnlock()} + */ + private boolean mIsVerifyUnlockOnly = false; + + + /** + * Used to lookup the state of the lock pattern + */ + private final LockPatternUtils mLockPatternUtils; + + private UnlockMode mCurrentUnlockMode = UnlockMode.Unknown; + + /** + * The current configuration. + */ + private Configuration mConfiguration; + + /** + * @return Whether we are stuck on the lock screen because the sim is + * missing. + */ + private boolean stuckOnLockScreenBecauseSimMissing() { + return mRequiresSim + && (!mUpdateMonitor.isDeviceProvisioned()) + && (mUpdateMonitor.getSimState() == IccCard.State.ABSENT); + } + + /** + * @param context Used to inflate, and create views. + * @param updateMonitor Knows the state of the world, and passed along to each + * screen so they can use the knowledge, and also register for callbacks + * on dynamic information. + * @param lockPatternUtils Used to look up state of lock pattern. + */ + public LockPatternKeyguardView( + Context context, + KeyguardUpdateMonitor updateMonitor, + LockPatternUtils lockPatternUtils, + KeyguardWindowController controller) { + super(context); + + mConfiguration = context.getResources().getConfiguration(); + mEnableFallback = false; + + mRequiresSim = + TextUtils.isEmpty(SystemProperties.get("keyguard.no_require_sim")); + + mUpdateMonitor = updateMonitor; + mLockPatternUtils = lockPatternUtils; + mWindowController = controller; + + mMode = getInitialMode(); + + mKeyguardScreenCallback = new KeyguardScreenCallback() { + + public void goToLockScreen() { + mForgotPattern = false; + if (mIsVerifyUnlockOnly) { + // navigating away from unlock screen during verify mode means + // we are done and the user failed to authenticate. + mIsVerifyUnlockOnly = false; + getCallback().keyguardDone(false); + } else { + updateScreen(Mode.LockScreen); + } + } + + public void goToUnlockScreen() { + final IccCard.State simState = mUpdateMonitor.getSimState(); + if (stuckOnLockScreenBecauseSimMissing() + || (simState == IccCard.State.PUK_REQUIRED)){ + // stuck on lock screen when sim missing or puk'd + return; + } + if (!isSecure()) { + getCallback().keyguardDone(true); + } else { + updateScreen(Mode.UnlockScreen); + } + } + + public void forgotPattern(boolean isForgotten) { + if (mEnableFallback) { + mForgotPattern = isForgotten; + updateScreen(Mode.UnlockScreen); + } + } + + public boolean isSecure() { + return LockPatternKeyguardView.this.isSecure(); + } + + public boolean isVerifyUnlockOnly() { + return mIsVerifyUnlockOnly; + } + + public void recreateMe(Configuration config) { + mConfiguration = config; + recreateScreens(); + } + + public void takeEmergencyCallAction() { + pokeWakelock(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); + } + } + + public void pokeWakelock() { + getCallback().pokeWakelock(); + } + + public void pokeWakelock(int millis) { + getCallback().pokeWakelock(millis); + } + + public void keyguardDone(boolean authenticated) { + getCallback().keyguardDone(authenticated); + } + + public void keyguardDoneDrawing() { + // irrelevant to keyguard screen, they shouldn't be calling this + } + + public void reportFailedUnlockAttempt() { + mUpdateMonitor.reportFailedAttempt(); + final int failedAttempts = mUpdateMonitor.getFailedAttempts(); + if (DEBUG) Log.d(TAG, + "reportFailedPatternAttempt: #" + failedAttempts + + " (enableFallback=" + mEnableFallback + ")"); + final boolean usingLockPattern = mLockPatternUtils.getKeyguardStoredPasswordQuality() + == DevicePolicyManager.PASSWORD_QUALITY_SOMETHING; + if (usingLockPattern && mEnableFallback && failedAttempts == + (LockPatternUtils.FAILED_ATTEMPTS_BEFORE_RESET + - LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT)) { + showAlmostAtAccountLoginDialog(); + } else if (usingLockPattern && mEnableFallback + && failedAttempts >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_RESET) { + mLockPatternUtils.setPermanentlyLocked(true); + updateScreen(mMode); + } else if ((failedAttempts % LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT) + == 0) { + showTimeoutDialog(); + } + mLockPatternUtils.reportFailedPasswordAttempt(); + } + + public boolean doesFallbackUnlockScreenExist() { + return mEnableFallback; + } + + public void reportSuccessfulUnlockAttempt() { + mLockPatternUtils.reportSuccessfulPasswordAttempt(); + } + }; + + /** + * We'll get key events the current screen doesn't use. see + * {@link KeyguardViewBase#onKeyDown(int, android.view.KeyEvent)} + */ + setFocusableInTouchMode(true); + setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); + + // create both the lock and unlock screen so they are quickly available + // when the screen turns on + mLockScreen = createLockScreen(); + addView(mLockScreen); + final UnlockMode unlockMode = getUnlockMode(); + if (DEBUG) Log.d(TAG, + "LockPatternKeyguardView ctor: about to createUnlockScreenFor; mEnableFallback=" + + mEnableFallback); + mUnlockScreen = createUnlockScreenFor(unlockMode); + mUnlockScreenMode = unlockMode; + + maybeEnableFallback(context); + + addView(mUnlockScreen); + updateScreen(mMode); + } + + 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 (mEnableFallback || mAccountIndex >= mAccounts.length) { + if (mUnlockScreen == null) { + Log.w(TAG, "no unlock screen when trying to enable fallback"); + } else if (mUnlockScreen instanceof PatternUnlockScreen) { + ((PatternUnlockScreen)mUnlockScreen).setEnableFallback(mEnableFallback); + } + 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 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(); + } + + + // TODO: + // This overloaded method was added to workaround a race condition in the framework between + // notification for orientation changed, layout() and switching resources. This code attempts + // to avoid drawing the incorrect layout while things are in transition. The method can just + // be removed once the race condition is fixed. See bugs 2262578 and 2292713. + @Override + protected void dispatchDraw(Canvas canvas) { + if (DEBUG) Log.v(TAG, "*** dispatchDraw() time: " + SystemClock.elapsedRealtime()); + super.dispatchDraw(canvas); + } + + @Override + public void reset() { + mIsVerifyUnlockOnly = false; + mForgotPattern = false; + updateScreen(getInitialMode()); + } + + @Override + public void onScreenTurnedOff() { + mScreenOn = false; + mForgotPattern = false; + if (mMode == Mode.LockScreen) { + ((KeyguardScreen) mLockScreen).onPause(); + } else { + ((KeyguardScreen) mUnlockScreen).onPause(); + } + } + + @Override + public void onScreenTurnedOn() { + mScreenOn = true; + if (mMode == Mode.LockScreen) { + ((KeyguardScreen) mLockScreen).onResume(); + } else { + ((KeyguardScreen) mUnlockScreen).onResume(); + } + } + + private void recreateLockScreen() { + if (mLockScreen.getVisibility() == View.VISIBLE) { + ((KeyguardScreen) mLockScreen).onPause(); + } + ((KeyguardScreen) mLockScreen).cleanUp(); + removeView(mLockScreen); + + mLockScreen = createLockScreen(); + mLockScreen.setVisibility(View.INVISIBLE); + addView(mLockScreen); + } + + private void recreateUnlockScreen() { + if (mUnlockScreen.getVisibility() == View.VISIBLE) { + ((KeyguardScreen) mUnlockScreen).onPause(); + } + ((KeyguardScreen) mUnlockScreen).cleanUp(); + removeView(mUnlockScreen); + + final UnlockMode unlockMode = getUnlockMode(); + mUnlockScreen = createUnlockScreenFor(unlockMode); + mUnlockScreen.setVisibility(View.INVISIBLE); + mUnlockScreenMode = unlockMode; + addView(mUnlockScreen); + } + + private void recreateScreens() { + recreateLockScreen(); + recreateUnlockScreen(); + updateScreen(mMode); + } + + @Override + public void wakeWhenReadyTq(int keyCode) { + if (DEBUG) Log.d(TAG, "onWakeKey"); + if (keyCode == KeyEvent.KEYCODE_MENU && isSecure() && (mMode == Mode.LockScreen) + && (mUpdateMonitor.getSimState() != IccCard.State.PUK_REQUIRED)) { + if (DEBUG) Log.d(TAG, "switching screens to unlock screen because wake key was MENU"); + updateScreen(Mode.UnlockScreen); + getCallback().pokeWakelock(); + } else { + if (DEBUG) Log.d(TAG, "poking wake lock immediately"); + getCallback().pokeWakelock(); + } + } + + @Override + public void verifyUnlock() { + if (!isSecure()) { + // non-secure keyguard screens are successfull by default + getCallback().keyguardDone(true); + } else if (mUnlockScreenMode != UnlockMode.Pattern) { + // can only verify unlock when in pattern mode + getCallback().keyguardDone(false); + } else { + // otherwise, go to the unlock screen, see if they can verify it + mIsVerifyUnlockOnly = true; + updateScreen(Mode.UnlockScreen); + } + } + + @Override + public void cleanUp() { + ((KeyguardScreen) mLockScreen).onPause(); + ((KeyguardScreen) mLockScreen).cleanUp(); + ((KeyguardScreen) mUnlockScreen).onPause(); + ((KeyguardScreen) mUnlockScreen).cleanUp(); + } + + private boolean isSecure() { + UnlockMode unlockMode = getUnlockMode(); + boolean secure = false; + switch (unlockMode) { + case Pattern: + secure = mLockPatternUtils.isLockPatternEnabled(); + break; + case SimPin: + secure = mUpdateMonitor.getSimState() == IccCard.State.PIN_REQUIRED + || mUpdateMonitor.getSimState() == IccCard.State.PUK_REQUIRED; + break; + case Account: + secure = true; + break; + case Password: + secure = mLockPatternUtils.isLockPasswordEnabled(); + break; + default: + throw new IllegalStateException("unknown unlock mode " + unlockMode); + } + return secure; + } + + private void updateScreen(final Mode mode) { + + if (DEBUG_CONFIGURATION) Log.v(TAG, "**** UPDATE SCREEN: mode=" + mode + + " last mode=" + mMode, new RuntimeException()); + + mMode = mode; + + // Re-create the unlock screen if necessary. This is primarily required to properly handle + // SIM state changes. This typically happens when this method is called by reset() + if (mode == Mode.UnlockScreen && mCurrentUnlockMode != getUnlockMode()) { + recreateUnlockScreen(); + } + + final View goneScreen = (mode == Mode.LockScreen) ? mUnlockScreen : mLockScreen; + final View visibleScreen = (mode == Mode.LockScreen) ? mLockScreen : mUnlockScreen; + + // do this before changing visibility so focus isn't requested before the input + // flag is set + mWindowController.setNeedsInput(((KeyguardScreen)visibleScreen).needsInput()); + + if (DEBUG_CONFIGURATION) { + Log.v(TAG, "Gone=" + goneScreen); + Log.v(TAG, "Visible=" + visibleScreen); + } + + if (mScreenOn) { + if (goneScreen.getVisibility() == View.VISIBLE) { + ((KeyguardScreen) goneScreen).onPause(); + } + if (visibleScreen.getVisibility() != View.VISIBLE) { + ((KeyguardScreen) visibleScreen).onResume(); + } + } + + goneScreen.setVisibility(View.GONE); + visibleScreen.setVisibility(View.VISIBLE); + requestLayout(); + + if (!visibleScreen.requestFocus()) { + throw new IllegalStateException("keyguard screen must be able to take " + + "focus when shown " + visibleScreen.getClass().getCanonicalName()); + } + } + + View createLockScreen() { + return new LockScreen( + mContext, + mConfiguration, + mLockPatternUtils, + mUpdateMonitor, + mKeyguardScreenCallback); + } + + View createUnlockScreenFor(UnlockMode unlockMode) { + View unlockView = null; + if (unlockMode == UnlockMode.Pattern) { + PatternUnlockScreen view = new PatternUnlockScreen( + mContext, + mConfiguration, + mLockPatternUtils, + mUpdateMonitor, + mKeyguardScreenCallback, + mUpdateMonitor.getFailedAttempts()); + if (DEBUG) Log.d(TAG, + "createUnlockScreenFor(" + unlockMode + "): mEnableFallback=" + mEnableFallback); + view.setEnableFallback(mEnableFallback); + unlockView = view; + } else if (unlockMode == UnlockMode.SimPin) { + unlockView = new SimUnlockScreen( + mContext, + mConfiguration, + mUpdateMonitor, + mKeyguardScreenCallback, + mLockPatternUtils); + } else if (unlockMode == UnlockMode.Account) { + try { + unlockView = new AccountUnlockScreen( + mContext, + mConfiguration, + mUpdateMonitor, + mKeyguardScreenCallback, + mLockPatternUtils); + } catch (IllegalStateException e) { + Log.i(TAG, "Couldn't instantiate AccountUnlockScreen" + + " (IAccountsService isn't available)"); + // TODO: Need a more general way to provide a + // platform-specific fallback UI here. + // For now, if we can't display the account login + // unlock UI, just bring back the regular "Pattern" unlock mode. + + // (We do this by simply returning a regular UnlockScreen + // here. This means that the user will still see the + // regular pattern unlock UI, regardless of the value of + // mUnlockScreenMode or whether or not we're in the + // "permanently locked" state.) + unlockView = createUnlockScreenFor(UnlockMode.Pattern); + } + } else if (unlockMode == UnlockMode.Password) { + unlockView = new PasswordUnlockScreen( + mContext, + mConfiguration, + mLockPatternUtils, + mUpdateMonitor, + mKeyguardScreenCallback); + } else { + throw new IllegalArgumentException("unknown unlock mode " + unlockMode); + } + mCurrentUnlockMode = unlockMode; + return unlockView; + } + + /** + * Given the current state of things, what should be the initial mode of + * the lock screen (lock or unlock). + */ + private Mode getInitialMode() { + final IccCard.State simState = mUpdateMonitor.getSimState(); + if (stuckOnLockScreenBecauseSimMissing() || (simState == IccCard.State.PUK_REQUIRED)) { + return Mode.LockScreen; + } else { + // Show LockScreen first for any screen other than Pattern unlock. + final boolean usingLockPattern = mLockPatternUtils.getKeyguardStoredPasswordQuality() + == DevicePolicyManager.PASSWORD_QUALITY_SOMETHING; + if (isSecure() && usingLockPattern) { + return Mode.UnlockScreen; + } else { + return Mode.LockScreen; + } + } + } + + /** + * Given the current state of things, what should the unlock screen be? + */ + private UnlockMode getUnlockMode() { + final IccCard.State simState = mUpdateMonitor.getSimState(); + UnlockMode currentMode; + if (simState == IccCard.State.PIN_REQUIRED || simState == IccCard.State.PUK_REQUIRED) { + currentMode = UnlockMode.SimPin; + } else { + final int mode = mLockPatternUtils.getKeyguardStoredPasswordQuality(); + switch (mode) { + case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC: + case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC: + case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC: + currentMode = UnlockMode.Password; + break; + case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING: + case DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED: + // "forgot pattern" button is only available in the pattern mode... + if (mForgotPattern || mLockPatternUtils.isPermanentlyLocked()) { + currentMode = UnlockMode.Account; + } else { + currentMode = UnlockMode.Pattern; + } + break; + default: + throw new IllegalStateException("Unknown unlock mode:" + mode); + } + } + return currentMode; + } + + private void showTimeoutDialog() { + int timeoutInSeconds = (int) LockPatternUtils.FAILED_ATTEMPT_TIMEOUT_MS / 1000; + String message = mContext.getString( + R.string.lockscreen_too_many_failed_attempts_dialog_message, + mUpdateMonitor.getFailedAttempts(), + timeoutInSeconds); + final AlertDialog dialog = new AlertDialog.Builder(mContext) + .setTitle(null) + .setMessage(message) + .setNeutralButton(R.string.ok, null) + .create(); + dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + if (!mContext.getResources().getBoolean( + com.android.internal.R.bool.config_sf_slowBlur)) { + dialog.getWindow().setFlags( + WindowManager.LayoutParams.FLAG_BLUR_BEHIND, + WindowManager.LayoutParams.FLAG_BLUR_BEHIND); + } + dialog.show(); + } + + private void showAlmostAtAccountLoginDialog() { + int timeoutInSeconds = (int) LockPatternUtils.FAILED_ATTEMPT_TIMEOUT_MS / 1000; + String message = mContext.getString( + R.string.lockscreen_failed_attempts_almost_glogin, + LockPatternUtils.FAILED_ATTEMPTS_BEFORE_RESET + - LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT, + LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT, + timeoutInSeconds); + final AlertDialog dialog = new AlertDialog.Builder(mContext) + .setTitle(null) + .setMessage(message) + .setNeutralButton(R.string.ok, null) + .create(); + dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + if (!mContext.getResources().getBoolean( + com.android.internal.R.bool.config_sf_slowBlur)) { + dialog.getWindow().setFlags( + WindowManager.LayoutParams.FLAG_BLUR_BEHIND, + WindowManager.LayoutParams.FLAG_BLUR_BEHIND); + } + dialog.show(); + } + + /** + * Used to put wallpaper on the background of the lock screen. Centers it + * Horizontally and pins the bottom (assuming that the lock screen is aligned + * with the bottom, so the wallpaper should extend above the top into the + * status bar). + */ + static private class FastBitmapDrawable extends Drawable { + private Bitmap mBitmap; + private int mOpacity; + + private FastBitmapDrawable(Bitmap bitmap) { + mBitmap = bitmap; + mOpacity = mBitmap.hasAlpha() ? PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE; + } + + @Override + public void draw(Canvas canvas) { + canvas.drawBitmap( + mBitmap, + (getBounds().width() - mBitmap.getWidth()) / 2, + (getBounds().height() - mBitmap.getHeight()), + null); + } + + @Override + public int getOpacity() { + return mOpacity; + } + + @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/src/com/android/internal/policy/impl/LockPatternKeyguardViewProperties.java b/policy/src/com/android/internal/policy/impl/LockPatternKeyguardViewProperties.java new file mode 100644 index 0000000..ed5a058 --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/LockPatternKeyguardViewProperties.java @@ -0,0 +1,62 @@ +/* + * 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; + +import com.android.internal.widget.LockPatternUtils; + +import android.content.Context; +import com.android.internal.telephony.IccCard; + +/** + * Knows how to create a lock pattern keyguard view, and answer questions about + * it (even if it hasn't been created, per the interface specs). + */ +public class LockPatternKeyguardViewProperties implements KeyguardViewProperties { + + private final LockPatternUtils mLockPatternUtils; + private final KeyguardUpdateMonitor mUpdateMonitor; + + /** + * @param lockPatternUtils Used to know whether the pattern enabled, and passed + * onto the keygaurd view when it is created. + * @param updateMonitor Used to know whether the sim pin is enabled, and passed + * onto the keyguard view when it is created. + */ + public LockPatternKeyguardViewProperties(LockPatternUtils lockPatternUtils, + KeyguardUpdateMonitor updateMonitor) { + mLockPatternUtils = lockPatternUtils; + mUpdateMonitor = updateMonitor; + } + + public KeyguardViewBase createKeyguardView(Context context, + KeyguardUpdateMonitor updateMonitor, + KeyguardWindowController controller) { + return new LockPatternKeyguardView(context, updateMonitor, + mLockPatternUtils, controller); + } + + public boolean isSecure() { + return mLockPatternUtils.isSecure() || isSimPinSecure(); + } + + private boolean isSimPinSecure() { + final IccCard.State simState = mUpdateMonitor.getSimState(); + return (simState == IccCard.State.PIN_REQUIRED || simState == IccCard.State.PUK_REQUIRED + || simState == IccCard.State.ABSENT); + } + +} diff --git a/policy/src/com/android/internal/policy/impl/LockScreen.java b/policy/src/com/android/internal/policy/impl/LockScreen.java new file mode 100644 index 0000000..a5ef1fa --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/LockScreen.java @@ -0,0 +1,681 @@ +/* + * 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; + +import com.android.internal.R; +import com.android.internal.telephony.IccCard; +import com.android.internal.widget.LockPatternUtils; +import com.android.internal.widget.SlidingTab; + +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.content.res.ColorStateList; +import android.text.format.DateFormat; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.*; +import android.graphics.drawable.Drawable; +import android.util.Log; +import android.media.AudioManager; +import android.os.SystemClock; +import android.os.SystemProperties; +import android.provider.Settings; + +import java.util.Date; +import java.io.File; + +/** + * The screen within {@link LockPatternKeyguardView} that shows general + * information about the device depending on its state, and how to get + * past it, as applicable. + */ +class LockScreen extends LinearLayout implements KeyguardScreen, KeyguardUpdateMonitor.InfoCallback, + KeyguardUpdateMonitor.SimStateCallback, SlidingTab.OnTriggerListener { + + private static final boolean DBG = false; + private static final String TAG = "LockScreen"; + private static final String ENABLE_MENU_KEY_FILE = "/data/local/enable_menu_key"; + + private Status mStatus = Status.Normal; + + private final LockPatternUtils mLockPatternUtils; + private final KeyguardUpdateMonitor mUpdateMonitor; + private final KeyguardScreenCallback mCallback; + + private TextView mCarrier; + private SlidingTab mSelector; + private TextView mTime; + private TextView mDate; + private TextView mStatus1; + private TextView mStatus2; + private TextView mScreenLocked; + private TextView mEmergencyCallText; + private Button mEmergencyCallButton; + + // current configuration state of keyboard and display + private int mKeyboardHidden; + private int mCreationOrientation; + + // 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; + + private String mNextAlarm = null; + private Drawable mAlarmIcon = null; + private String mCharging = null; + private Drawable mChargingIcon = null; + + private boolean mSilentMode; + private AudioManager mAudioManager; + private String mDateFormatString; + private java.text.DateFormat mTimeFormat; + private boolean mEnableMenuKeyInLockScreen; + + /** + * The status of this lock screen. + */ + enum Status { + /** + * 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); + + private final boolean mShowStatusLines; + + Status(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 showStatusLines() { + return mShowStatusLines; + } + } + + /** + * In general, we enable unlocking the insecure key guard with the menu key. However, there are + * some cases where we wish to disable it, notably when the menu button placement or technology + * is prone to false positives. + * + * @return true if the menu key should be enabled + */ + private boolean shouldEnableMenuKey() { + final Resources res = getResources(); + final boolean configDisabled = res.getBoolean(R.bool.config_disableMenuKeyInLockScreen); + final boolean isMonkey = SystemProperties.getBoolean("ro.monkey", false); + final boolean fileOverride = (new File(ENABLE_MENU_KEY_FILE)).exists(); + return !configDisabled || isMonkey || fileOverride; + } + + /** + * @param context Used to setup the view. + * @param configuration The current configuration. Used to use when selecting layout, etc. + * @param lockPatternUtils Used to know the state of the lock pattern settings. + * @param updateMonitor Used to register for updates on various keyguard related + * state, and query the initial state at setup. + * @param callback Used to communicate back to the host keyguard view. + */ + LockScreen(Context context, Configuration configuration, LockPatternUtils lockPatternUtils, + KeyguardUpdateMonitor updateMonitor, + KeyguardScreenCallback callback) { + super(context); + mLockPatternUtils = lockPatternUtils; + mUpdateMonitor = updateMonitor; + mCallback = callback; + + mEnableMenuKeyInLockScreen = shouldEnableMenuKey(); + + mCreationOrientation = configuration.orientation; + + mKeyboardHidden = configuration.hardKeyboardHidden; + + if (LockPatternKeyguardView.DEBUG_CONFIGURATION) { + Log.v(TAG, "***** CREATING LOCK SCREEN", new RuntimeException()); + Log.v(TAG, "Cur orient=" + mCreationOrientation + + " res orient=" + context.getResources().getConfiguration().orientation); + } + + final LayoutInflater inflater = LayoutInflater.from(context); + if (DBG) Log.v(TAG, "Creation orientation = " + mCreationOrientation); + if (mCreationOrientation != Configuration.ORIENTATION_LANDSCAPE) { + inflater.inflate(R.layout.keyguard_screen_tab_unlock, this, true); + } else { + inflater.inflate(R.layout.keyguard_screen_tab_unlock_land, this, true); + } + + mCarrier = (TextView) findViewById(R.id.carrier); + // Required for Marquee to work + mCarrier.setSelected(true); + mCarrier.setTextColor(0xffffffff); + + mDate = (TextView) findViewById(R.id.date); + mStatus1 = (TextView) findViewById(R.id.status1); + mStatus2 = (TextView) findViewById(R.id.status2); + + mScreenLocked = (TextView) findViewById(R.id.screenLocked); + mSelector = (SlidingTab) findViewById(R.id.tab_selector); + mSelector.setHoldAfterTrigger(true, false); + mSelector.setLeftHintText(R.string.lockscreen_unlock_label); + + mEmergencyCallText = (TextView) findViewById(R.id.emergencyCallText); + mEmergencyCallButton = (Button) findViewById(R.id.emergencyCallButton); + mEmergencyCallButton.setText(R.string.lockscreen_emergency_call); + + mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCallButton); + mEmergencyCallButton.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + mCallback.takeEmergencyCallAction(); + } + }); + + + setFocusable(true); + setFocusableInTouchMode(true); + setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); + + updateMonitor.registerInfoCallback(this); + updateMonitor.registerSimStateCallback(this); + + mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE); + mSilentMode = isSilentMode(); + + mSelector.setLeftTabResources( + R.drawable.ic_jog_dial_unlock, + R.drawable.jog_tab_target_green, + R.drawable.jog_tab_bar_left_unlock, + R.drawable.jog_tab_left_unlock); + + updateRightTabResources(); + + mSelector.setOnTriggerListener(this); + + resetStatusInfo(updateMonitor); + } + + private boolean isSilentMode() { + return mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL; + } + + private void updateRightTabResources() { + boolean vibe = mSilentMode + && (mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE); + + mSelector.setRightTabResources( + mSilentMode ? ( vibe ? R.drawable.ic_jog_dial_vibrate_on + : R.drawable.ic_jog_dial_sound_off ) + : R.drawable.ic_jog_dial_sound_on, + mSilentMode ? R.drawable.jog_tab_target_yellow + : R.drawable.jog_tab_target_gray, + mSilentMode ? R.drawable.jog_tab_bar_right_sound_on + : R.drawable.jog_tab_bar_right_sound_off, + mSilentMode ? R.drawable.jog_tab_right_sound_on + : R.drawable.jog_tab_right_sound_off); + } + + private void resetStatusInfo(KeyguardUpdateMonitor updateMonitor) { + mShowingBatteryInfo = updateMonitor.shouldShowBatteryInfo(); + mPluggedIn = updateMonitor.isDevicePluggedIn(); + mBatteryLevel = updateMonitor.getBatteryLevel(); + + mStatus = getCurrentStatus(updateMonitor.getSimState()); + updateLayout(mStatus); + + refreshBatteryStringAndIcon(); + refreshAlarmDisplay(); + + mTimeFormat = DateFormat.getTimeFormat(getContext()); + mDateFormatString = getContext().getString(R.string.full_wday_month_day_no_year); + refreshTimeAndDateDisplay(); + updateStatusLines(); + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_MENU && mEnableMenuKeyInLockScreen) { + mCallback.goToUnlockScreen(); + } + return false; + } + + /** {@inheritDoc} */ + public void onTrigger(View v, int whichHandle) { + if (whichHandle == SlidingTab.OnTriggerListener.LEFT_HANDLE) { + mCallback.goToUnlockScreen(); + } else if (whichHandle == SlidingTab.OnTriggerListener.RIGHT_HANDLE) { + // toggle silent mode + mSilentMode = !mSilentMode; + if (mSilentMode) { + final boolean vibe = (Settings.System.getInt( + getContext().getContentResolver(), + Settings.System.VIBRATE_IN_SILENT, 1) == 1); + + mAudioManager.setRingerMode(vibe + ? AudioManager.RINGER_MODE_VIBRATE + : AudioManager.RINGER_MODE_SILENT); + } else { + mAudioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL); + } + + updateRightTabResources(); + + String message = mSilentMode ? + getContext().getString(R.string.global_action_silent_mode_on_status) : + getContext().getString(R.string.global_action_silent_mode_off_status); + + final int toastIcon = mSilentMode + ? R.drawable.ic_lock_ringer_off + : R.drawable.ic_lock_ringer_on; + + final int toastColor = mSilentMode + ? getContext().getResources().getColor(R.color.keyguard_text_color_soundoff) + : getContext().getResources().getColor(R.color.keyguard_text_color_soundon); + toastMessage(mScreenLocked, message, toastColor, toastIcon); + mCallback.pokeWakelock(); + } + } + + /** {@inheritDoc} */ + public void onGrabbedStateChange(View v, int grabbedState) { + if (grabbedState == SlidingTab.OnTriggerListener.RIGHT_HANDLE) { + mSilentMode = isSilentMode(); + mSelector.setRightHintText(mSilentMode ? R.string.lockscreen_sound_on_label + : R.string.lockscreen_sound_off_label); + } + mCallback.pokeWakelock(); + } + + /** + * Displays a message in a text view and then restores the previous text. + * @param textView The text view. + * @param text The text. + * @param color The color to apply to the text, or 0 if the existing color should be used. + * @param iconResourceId The left hand icon. + */ + private void toastMessage(final TextView textView, final String text, final int color, final int iconResourceId) { + if (mPendingR1 != null) { + textView.removeCallbacks(mPendingR1); + mPendingR1 = null; + } + if (mPendingR2 != null) { + mPendingR2.run(); // fire immediately, restoring non-toasted appearance + textView.removeCallbacks(mPendingR2); + mPendingR2 = null; + } + + final String oldText = textView.getText().toString(); + final ColorStateList oldColors = textView.getTextColors(); + + mPendingR1 = new Runnable() { + public void run() { + textView.setText(text); + if (color != 0) { + textView.setTextColor(color); + } + textView.setCompoundDrawablesWithIntrinsicBounds(iconResourceId, 0, 0, 0); + } + }; + + textView.postDelayed(mPendingR1, 0); + mPendingR2 = new Runnable() { + public void run() { + textView.setText(oldText); + textView.setTextColor(oldColors); + textView.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); + } + }; + textView.postDelayed(mPendingR2, 3500); + } + private Runnable mPendingR1; + private Runnable mPendingR2; + + private void refreshAlarmDisplay() { + mNextAlarm = mLockPatternUtils.getNextAlarm(); + if (mNextAlarm != null) { + mAlarmIcon = getContext().getResources().getDrawable(R.drawable.ic_lock_idle_alarm); + } + updateStatusLines(); + } + + /** {@inheritDoc} */ + public void onRefreshBatteryInfo(boolean showBatteryInfo, boolean pluggedIn, + int batteryLevel) { + if (DBG) Log.d(TAG, "onRefreshBatteryInfo(" + showBatteryInfo + ", " + pluggedIn + ")"); + mShowingBatteryInfo = showBatteryInfo; + mPluggedIn = pluggedIn; + mBatteryLevel = batteryLevel; + + refreshBatteryStringAndIcon(); + updateStatusLines(); + } + + private void refreshBatteryStringAndIcon() { + if (!mShowingBatteryInfo) { + mCharging = null; + return; + } + + if (mChargingIcon == null) { + mChargingIcon = + getContext().getResources().getDrawable(R.drawable.ic_lock_idle_charging); + } + + if (mPluggedIn) { + if (mBatteryLevel >= 100) { + mCharging = getContext().getString(R.string.lockscreen_charged); + } else { + mCharging = getContext().getString(R.string.lockscreen_plugged_in, mBatteryLevel); + } + } else { + mCharging = getContext().getString(R.string.lockscreen_low_battery); + } + } + + /** {@inheritDoc} */ + public void onTimeChanged() { + refreshTimeAndDateDisplay(); + } + + private void refreshTimeAndDateDisplay() { + mDate.setText(DateFormat.format(mDateFormatString, new Date())); + } + + private void updateStatusLines() { + if (!mStatus.showStatusLines() + || (mCharging == null && mNextAlarm == null)) { + mStatus1.setVisibility(View.INVISIBLE); + mStatus2.setVisibility(View.INVISIBLE); + } else if (mCharging != null && mNextAlarm == null) { + // charging only + mStatus1.setVisibility(View.VISIBLE); + mStatus2.setVisibility(View.INVISIBLE); + + mStatus1.setText(mCharging); + mStatus1.setCompoundDrawablesWithIntrinsicBounds(mChargingIcon, null, null, null); + } else if (mNextAlarm != null && mCharging == null) { + // next alarm only + mStatus1.setVisibility(View.VISIBLE); + mStatus2.setVisibility(View.INVISIBLE); + + mStatus1.setText(mNextAlarm); + mStatus1.setCompoundDrawablesWithIntrinsicBounds(mAlarmIcon, null, null, null); + } else if (mCharging != null && mNextAlarm != null) { + // both charging and next alarm + mStatus1.setVisibility(View.VISIBLE); + mStatus2.setVisibility(View.VISIBLE); + + mStatus1.setText(mCharging); + mStatus1.setCompoundDrawablesWithIntrinsicBounds(mChargingIcon, null, null, null); + mStatus2.setText(mNextAlarm); + mStatus2.setCompoundDrawablesWithIntrinsicBounds(mAlarmIcon, null, null, null); + } + } + + /** {@inheritDoc} */ + public void onRefreshCarrierInfo(CharSequence plmn, CharSequence spn) { + if (DBG) Log.d(TAG, "onRefreshCarrierInfo(" + plmn + ", " + spn + ")"); + updateLayout(mStatus); + } + + /** + * Determine the current status of the lock screen given the sim state and other stuff. + */ + private Status getCurrentStatus(IccCard.State simState) { + boolean missingAndNotProvisioned = (!mUpdateMonitor.isDeviceProvisioned() + && simState == IccCard.State.ABSENT); + if (missingAndNotProvisioned) { + return Status.SimMissingLocked; + } + + switch (simState) { + case ABSENT: + return Status.SimMissing; + case NETWORK_LOCKED: + return Status.SimMissingLocked; + case NOT_READY: + return Status.SimMissing; + case PIN_REQUIRED: + return Status.SimLocked; + case PUK_REQUIRED: + return Status.SimPukLocked; + case READY: + return Status.Normal; + case UNKNOWN: + return Status.SimMissing; + } + return Status.SimMissing; + } + + /** + * Update the layout to match the current status. + */ + private void updateLayout(Status status) { + // The emergency call button no longer appears on this screen. + if (DBG) Log.d(TAG, "updateLayout: status=" + status); + + mEmergencyCallButton.setVisibility(View.GONE); // in almost all cases + + switch (status) { + case Normal: + // text + mCarrier.setText( + getCarrierString( + mUpdateMonitor.getTelephonyPlmn(), + mUpdateMonitor.getTelephonySpn())); + + // Empty now, but used for sliding tab feedback + mScreenLocked.setText(""); + + // layout + mScreenLocked.setVisibility(View.VISIBLE); + mSelector.setVisibility(View.VISIBLE); + mEmergencyCallText.setVisibility(View.GONE); + break; + case NetworkLocked: + // The carrier string shows both sim card status (i.e. No Sim Card) and + // carrier's name and/or "Emergency Calls Only" status + mCarrier.setText( + getCarrierString( + mUpdateMonitor.getTelephonyPlmn(), + getContext().getText(R.string.lockscreen_network_locked_message))); + mScreenLocked.setText(R.string.lockscreen_instructions_when_pattern_disabled); + + // layout + mScreenLocked.setVisibility(View.VISIBLE); + mSelector.setVisibility(View.VISIBLE); + mEmergencyCallText.setVisibility(View.GONE); + break; + case SimMissing: + // text + mCarrier.setText(R.string.lockscreen_missing_sim_message_short); + mScreenLocked.setText(R.string.lockscreen_missing_sim_instructions); + + // layout + mScreenLocked.setVisibility(View.VISIBLE); + mSelector.setVisibility(View.VISIBLE); + mEmergencyCallText.setVisibility(View.VISIBLE); + // do not need to show the e-call button; user may unlock + break; + case SimMissingLocked: + // text + mCarrier.setText( + getCarrierString( + mUpdateMonitor.getTelephonyPlmn(), + getContext().getText(R.string.lockscreen_missing_sim_message_short))); + mScreenLocked.setText(R.string.lockscreen_missing_sim_instructions); + + // layout + mScreenLocked.setVisibility(View.VISIBLE); + mSelector.setVisibility(View.GONE); // cannot unlock + mEmergencyCallText.setVisibility(View.VISIBLE); + mEmergencyCallButton.setVisibility(View.VISIBLE); + break; + case SimLocked: + // text + mCarrier.setText( + getCarrierString( + mUpdateMonitor.getTelephonyPlmn(), + getContext().getText(R.string.lockscreen_sim_locked_message))); + + // layout + mScreenLocked.setVisibility(View.INVISIBLE); + mSelector.setVisibility(View.VISIBLE); + mEmergencyCallText.setVisibility(View.GONE); + break; + case SimPukLocked: + // text + mCarrier.setText( + getCarrierString( + mUpdateMonitor.getTelephonyPlmn(), + getContext().getText(R.string.lockscreen_sim_puk_locked_message))); + mScreenLocked.setText(R.string.lockscreen_sim_puk_locked_instructions); + + // layout + mScreenLocked.setVisibility(View.VISIBLE); + mSelector.setVisibility(View.GONE); // cannot unlock + mEmergencyCallText.setVisibility(View.VISIBLE); + mEmergencyCallButton.setVisibility(View.VISIBLE); + break; + } + } + + static CharSequence getCarrierString(CharSequence telephonyPlmn, CharSequence telephonySpn) { + if (telephonyPlmn != null && telephonySpn == null) { + return telephonyPlmn; + } else if (telephonyPlmn != null && telephonySpn != null) { + return telephonyPlmn + "|" + telephonySpn; + } else if (telephonyPlmn == null && telephonySpn != null) { + return telephonySpn; + } else { + return ""; + } + } + + public void onSimStateChanged(IccCard.State simState) { + if (DBG) Log.d(TAG, "onSimStateChanged(" + simState + ")"); + mStatus = getCurrentStatus(simState); + updateLayout(mStatus); + updateStatusLines(); + } + + void updateConfiguration() { + Configuration newConfig = getResources().getConfiguration(); + if (newConfig.orientation != mCreationOrientation) { + mCallback.recreateMe(newConfig); + } else if (newConfig.hardKeyboardHidden != mKeyboardHidden) { + mKeyboardHidden = newConfig.hardKeyboardHidden; + final boolean isKeyboardOpen = mKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO; + if (mUpdateMonitor.isKeyguardBypassEnabled() && isKeyboardOpen) { + mCallback.goToUnlockScreen(); + } + } + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + if (LockPatternKeyguardView.DEBUG_CONFIGURATION) { + Log.v(TAG, "***** LOCK ATTACHED TO WINDOW"); + Log.v(TAG, "Cur orient=" + mCreationOrientation + + ", new config=" + getResources().getConfiguration()); + } + updateConfiguration(); + } + + /** {@inheritDoc} */ + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + if (LockPatternKeyguardView.DEBUG_CONFIGURATION) { + Log.w(TAG, "***** LOCK CONFIG CHANGING", new RuntimeException()); + Log.v(TAG, "Cur orient=" + mCreationOrientation + + ", new config=" + newConfig); + } + updateConfiguration(); + } + + /** {@inheritDoc} */ + public boolean needsInput() { + return false; + } + + /** {@inheritDoc} */ + public void onPause() { + + } + + /** {@inheritDoc} */ + public void onResume() { + resetStatusInfo(mUpdateMonitor); + mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCallButton); + } + + /** {@inheritDoc} */ + public void cleanUp() { + mUpdateMonitor.removeCallback(this); + } + + /** {@inheritDoc} */ + public void onRingerModeChanged(int state) { + boolean silent = AudioManager.RINGER_MODE_NORMAL != state; + if (silent != mSilentMode) { + mSilentMode = silent; + updateRightTabResources(); + } + } + + public void onPhoneStateChanged(String newState) { + mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCallButton); + } +} diff --git a/policy/src/com/android/internal/policy/impl/PasswordUnlockScreen.java b/policy/src/com/android/internal/policy/impl/PasswordUnlockScreen.java new file mode 100644 index 0000000..39f2917 --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/PasswordUnlockScreen.java @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.policy.impl; + +import android.app.admin.DevicePolicyManager; +import android.content.Context; +import android.content.res.Configuration; +import android.graphics.Rect; + +import com.android.internal.policy.impl.PatternUnlockScreen.FooterMode; +import com.android.internal.widget.LockPatternUtils; +import com.android.internal.widget.PasswordEntryKeyboardView; + +import android.os.CountDownTimer; +import android.os.SystemClock; +import android.telephony.TelephonyManager; +import android.text.method.DigitsKeyListener; +import android.text.method.TextKeyListener; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.inputmethod.EditorInfo; +import android.widget.Button; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.widget.TextView.OnEditorActionListener; + +import com.android.internal.R; +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 PasswordUnlockScreen extends LinearLayout implements KeyguardScreen, + View.OnClickListener, KeyguardUpdateMonitor.InfoCallback, OnEditorActionListener { + + private final KeyguardUpdateMonitor mUpdateMonitor; + private final KeyguardScreenCallback mCallback; + + private EditText mPasswordEntry; + private Button mEmergencyCallButton; + private LockPatternUtils mLockPatternUtils; + private PasswordEntryKeyboardView mKeyboardView; + private PasswordEntryKeyboardHelper mKeyboardHelper; + + private int mCreationOrientation; + private int mCreationHardKeyboardHidden; + private CountDownTimer mCountdownTimer; + private TextView mTitle; + + // 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 PasswordUnlockScreen(Context context, Configuration configuration, + LockPatternUtils lockPatternUtils, KeyguardUpdateMonitor updateMonitor, + KeyguardScreenCallback callback) { + super(context); + + mCreationHardKeyboardHidden = configuration.hardKeyboardHidden; + mCreationOrientation = configuration.orientation; + mUpdateMonitor = updateMonitor; + mCallback = callback; + mLockPatternUtils = lockPatternUtils; + + LayoutInflater layoutInflater = LayoutInflater.from(context); + if (mCreationOrientation != Configuration.ORIENTATION_LANDSCAPE) { + layoutInflater.inflate(R.layout.keyguard_screen_password_portrait, this, true); + } else { + layoutInflater.inflate(R.layout.keyguard_screen_password_landscape, this, true); + } + + final int quality = lockPatternUtils.getKeyguardStoredPasswordQuality(); + final boolean isAlpha = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC == quality + || DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC == quality; + + mKeyboardView = (PasswordEntryKeyboardView) findViewById(R.id.keyboard); + mPasswordEntry = (EditText) findViewById(R.id.passwordEntry); + mPasswordEntry.setOnEditorActionListener(this); + mEmergencyCallButton = (Button) findViewById(R.id.emergencyCall); + mEmergencyCallButton.setOnClickListener(this); + mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCallButton); + mTitle = (TextView) findViewById(R.id.enter_password_label); + + mKeyboardHelper = new PasswordEntryKeyboardHelper(context, mKeyboardView, this); + mKeyboardHelper.setKeyboardMode(isAlpha ? PasswordEntryKeyboardHelper.KEYBOARD_MODE_ALPHA + : PasswordEntryKeyboardHelper.KEYBOARD_MODE_NUMERIC); + + mKeyboardView.setVisibility(mCreationHardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO + ? View.INVISIBLE : View.VISIBLE); + mPasswordEntry.requestFocus(); + + // This allows keyboards with overlapping qwerty/numeric keys to choose just the + // numeric keys. + if (isAlpha) { + mPasswordEntry.setKeyListener(TextKeyListener.getInstance()); + } else { + mPasswordEntry.setKeyListener(DigitsKeyListener.getInstance()); + } + + mKeyboardHelper.setVibratePattern(mLockPatternUtils.isTactileFeedbackEnabled() ? + com.android.internal.R.array.config_virtualKeyVibePattern : 0); + } + + @Override + protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { + // send focus to the password field + return mPasswordEntry.requestFocus(direction, previouslyFocusedRect); + } + + /** {@inheritDoc} */ + public boolean needsInput() { + return false; + } + + /** {@inheritDoc} */ + public void onPause() { + + } + + /** {@inheritDoc} */ + public void onResume() { + // start fresh + mPasswordEntry.setText(""); + mPasswordEntry.requestFocus(); + mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCallButton); + + // if the user is currently locked out, enforce it. + long deadline = mLockPatternUtils.getLockoutAttemptDeadline(); + if (deadline != 0) { + handleAttemptLockout(deadline); + } + } + + /** {@inheritDoc} */ + public void cleanUp() { + mUpdateMonitor.removeCallback(this); + } + + public void onClick(View v) { + if (v == mEmergencyCallButton) { + mCallback.takeEmergencyCallAction(); + } + mCallback.pokeWakelock(); + } + + private void verifyPasswordAndUnlock() { + String entry = mPasswordEntry.getText().toString(); + if (mLockPatternUtils.checkPassword(entry)) { + mCallback.keyguardDone(true); + mCallback.reportSuccessfulUnlockAttempt(); + } 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 == (mUpdateMonitor.getFailedAttempts() + % LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT)) { + long deadline = mLockPatternUtils.setLockoutAttemptDeadline(); + handleAttemptLockout(deadline); + } + } + 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(); + mCountdownTimer = new CountDownTimer(elapsedRealtimeDeadline - elapsedRealtime, 1000) { + + @Override + public void onTick(long millisUntilFinished) { + int secondsRemaining = (int) (millisUntilFinished / 1000); + String instructions = getContext().getString( + R.string.lockscreen_too_many_failed_attempts_countdown, + secondsRemaining); + mTitle.setText(instructions); + } + + @Override + public void onFinish() { + mPasswordEntry.setEnabled(true); + mTitle.setText(R.string.keyguard_password_enter_password_code); + mKeyboardView.setEnabled(true); + } + }.start(); + } + + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + mCallback.pokeWakelock(); + return false; + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + Configuration config = getResources().getConfiguration(); + if (config.orientation != mCreationOrientation + || config.hardKeyboardHidden != mCreationHardKeyboardHidden) { + mCallback.recreateMe(config); + } + } + + /** {@inheritDoc} */ + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + if (newConfig.orientation != mCreationOrientation + || newConfig.hardKeyboardHidden != mCreationHardKeyboardHidden) { + mCallback.recreateMe(newConfig); + } + } + + public void onKeyboardChange(boolean isKeyboardOpen) { + // Don't show the soft keyboard when the real keyboard is open + mKeyboardView.setVisibility(isKeyboardOpen ? View.INVISIBLE : View.VISIBLE); + } + + 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) { + verifyPasswordAndUnlock(); + return true; + } + return false; + } + + public void onPhoneStateChanged(String newState) { + mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCallButton); + } + + public void onRefreshBatteryInfo(boolean showBatteryInfo, boolean pluggedIn, int batteryLevel) { + + } + + public void onRefreshCarrierInfo(CharSequence plmn, CharSequence spn) { + + } + + public void onRingerModeChanged(int state) { + + } + + public void onTimeChanged() { + + } + +} diff --git a/policy/src/com/android/internal/policy/impl/PatternUnlockScreen.java b/policy/src/com/android/internal/policy/impl/PatternUnlockScreen.java new file mode 100644 index 0000000..418e243 --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/PatternUnlockScreen.java @@ -0,0 +1,580 @@ +/* + * 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; + +import android.content.Context; +import android.content.res.Configuration; +import android.os.CountDownTimer; +import android.os.SystemClock; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.MotionEvent; +import android.widget.Button; +import android.widget.TextView; +import android.text.format.DateFormat; +import android.text.TextUtils; +import android.util.Log; +import com.android.internal.R; +import com.android.internal.telephony.IccCard; +import com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient; +import com.android.internal.widget.LockPatternUtils; +import com.android.internal.widget.LockPatternView; +import com.android.internal.widget.LockPatternView.Cell; + +import java.util.List; +import java.util.Date; + +/** + * This is the screen that shows the 9 circle unlock widget and instructs + * the user how to unlock their device, or make an emergency call. + */ +class PatternUnlockScreen extends LinearLayoutWithDefaultTouchRecepient + implements KeyguardScreen, KeyguardUpdateMonitor.InfoCallback, + KeyguardUpdateMonitor.SimStateCallback { + + private static final boolean DEBUG = false; + private static final String TAG = "UnlockScreen"; + + // 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 final LockPatternUtils mLockPatternUtils; + private final KeyguardUpdateMonitor mUpdateMonitor; + private final KeyguardScreenCallback mCallback; + + /** + * whether there is a fallback option available when the pattern is forgotten. + */ + private boolean mEnableFallback; + + private String mDateFormatString; + + private TextView mCarrier; + private TextView mDate; + + // 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; + + private String mNextAlarm = null; + + private String mInstructions = null; + private TextView mStatus1; + private TextView mStatusSep; + private TextView mStatus2; + + + private LockPatternView mLockPatternView; + + private ViewGroup mFooterNormal; + private ViewGroup mFooterForgotPattern; + + /** + * Keeps track of the last time we poked the wake lock during dispatching + * of the touch event, initalized to something gauranteed to make us + * poke it 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(); + } + }; + + private Button mForgotPatternButton; + private Button mEmergencyAlone; + private Button mEmergencyTogether; + private int mCreationOrientation; + + enum FooterMode { + Normal, + ForgotLockPattern, + VerifyUnlocked + } + + private void updateFooter(FooterMode mode) { + switch (mode) { + case Normal: + mFooterNormal.setVisibility(View.VISIBLE); + mFooterForgotPattern.setVisibility(View.GONE); + break; + case ForgotLockPattern: + mFooterNormal.setVisibility(View.GONE); + mFooterForgotPattern.setVisibility(View.VISIBLE); + mForgotPatternButton.setVisibility(View.VISIBLE); + break; + case VerifyUnlocked: + mFooterNormal.setVisibility(View.GONE); + mFooterForgotPattern.setVisibility(View.GONE); + } + } + + /** + * @param context The context. + * @param configuration + * @param lockPatternUtils Used to lookup lock pattern settings. + * @param updateMonitor Used to lookup state affecting keyguard. + * @param callback Used to notify the manager when we're done, etc. + * @param totalFailedAttempts The current number of failed attempts. + * @param enableFallback True if a backup unlock option is available when the user has forgotten + * their pattern (e.g they have a google account so we can show them the account based + * backup option). + */ + PatternUnlockScreen(Context context, + Configuration configuration, LockPatternUtils lockPatternUtils, + KeyguardUpdateMonitor updateMonitor, + KeyguardScreenCallback callback, + int totalFailedAttempts) { + super(context); + mLockPatternUtils = lockPatternUtils; + mUpdateMonitor = updateMonitor; + mCallback = callback; + mTotalFailedPatternAttempts = totalFailedAttempts; + mFailedPatternAttemptsSinceLastTimeout = + totalFailedAttempts % LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT; + + if (DEBUG) Log.d(TAG, + "UnlockScreen() ctor: totalFailedAttempts=" + + totalFailedAttempts + ", mFailedPat...=" + + mFailedPatternAttemptsSinceLastTimeout + ); + + mCreationOrientation = configuration.orientation; + + LayoutInflater inflater = LayoutInflater.from(context); + if (mCreationOrientation != Configuration.ORIENTATION_LANDSCAPE) { + inflater.inflate(R.layout.keyguard_screen_unlock_portrait, this, true); + } else { + inflater.inflate(R.layout.keyguard_screen_unlock_landscape, this, true); + } + + mCarrier = (TextView) findViewById(R.id.carrier); + mDate = (TextView) findViewById(R.id.date); + + mDateFormatString = getContext().getString(R.string.full_wday_month_day_no_year); + refreshTimeAndDateDisplay(); + + mStatus1 = (TextView) findViewById(R.id.status1); + mStatusSep = (TextView) findViewById(R.id.statusSep); + mStatus2 = (TextView) findViewById(R.id.status2); + + resetStatusInfo(); + + + mLockPatternView = (LockPatternView) findViewById(R.id.lockPattern); + + mFooterNormal = (ViewGroup) findViewById(R.id.footerNormal); + mFooterForgotPattern = (ViewGroup) findViewById(R.id.footerForgotPattern); + + // emergency call buttons + final OnClickListener emergencyClick = new OnClickListener() { + public void onClick(View v) { + mCallback.takeEmergencyCallAction(); + } + }; + + mEmergencyAlone = (Button) findViewById(R.id.emergencyCallAlone); + mEmergencyAlone.setFocusable(false); // touch only! + mEmergencyAlone.setOnClickListener(emergencyClick); + mEmergencyTogether = (Button) findViewById(R.id.emergencyCallTogether); + mEmergencyTogether.setFocusable(false); + mEmergencyTogether.setOnClickListener(emergencyClick); + refreshEmergencyButtonText(); + + mForgotPatternButton = (Button) findViewById(R.id.forgotPattern); + mForgotPatternButton.setText(R.string.lockscreen_forgot_pattern_button_text); + mForgotPatternButton.setOnClickListener(new OnClickListener() { + + public void onClick(View v) { + mCallback.forgotPattern(true); + } + }); + + // make it so unhandled touch events within the unlock screen go to the + // lock pattern view. + setDefaultTouchRecepient(mLockPatternView); + + 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()); + + // assume normal footer mode for now + updateFooter(FooterMode.Normal); + + updateMonitor.registerInfoCallback(this); + updateMonitor.registerSimStateCallback(this); + setFocusableInTouchMode(true); + + // Required to get Marquee to work. + mCarrier.setSelected(true); + mCarrier.setTextColor(0xffffffff); + + // until we get an update... + mCarrier.setText( + LockScreen.getCarrierString( + mUpdateMonitor.getTelephonyPlmn(), + mUpdateMonitor.getTelephonySpn())); + } + + private void refreshEmergencyButtonText() { + mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyAlone); + mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyTogether); + } + + public void setEnableFallback(boolean state) { + if (DEBUG) Log.d(TAG, "setEnableFallback(" + state + ")"); + mEnableFallback = state; + } + + private void resetStatusInfo() { + mInstructions = null; + mShowingBatteryInfo = mUpdateMonitor.shouldShowBatteryInfo(); + mPluggedIn = mUpdateMonitor.isDevicePluggedIn(); + mBatteryLevel = mUpdateMonitor.getBatteryLevel(); + mNextAlarm = mLockPatternUtils.getNextAlarm(); + updateStatusLines(); + } + + private void updateStatusLines() { + if (mInstructions != null) { + // instructions only + mStatus1.setText(mInstructions); + if (TextUtils.isEmpty(mInstructions)) { + mStatus1.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); + } else { + mStatus1.setCompoundDrawablesWithIntrinsicBounds( + R.drawable.ic_lock_idle_lock, 0, 0, 0); + } + + mStatus1.setVisibility(View.VISIBLE); + mStatusSep.setVisibility(View.GONE); + mStatus2.setVisibility(View.GONE); + } else if (mShowingBatteryInfo && mNextAlarm == null) { + // battery only + if (mPluggedIn) { + if (mBatteryLevel >= 100) { + mStatus1.setText(getContext().getString(R.string.lockscreen_charged)); + } else { + mStatus1.setText(getContext().getString(R.string.lockscreen_plugged_in, mBatteryLevel)); + } + } else { + mStatus1.setText(getContext().getString(R.string.lockscreen_low_battery)); + } + mStatus1.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_lock_idle_charging, 0, 0, 0); + + mStatus1.setVisibility(View.VISIBLE); + mStatusSep.setVisibility(View.GONE); + mStatus2.setVisibility(View.GONE); + + } else if (mNextAlarm != null && !mShowingBatteryInfo) { + // alarm only + mStatus1.setText(mNextAlarm); + mStatus1.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_lock_idle_alarm, 0, 0, 0); + + mStatus1.setVisibility(View.VISIBLE); + mStatusSep.setVisibility(View.GONE); + mStatus2.setVisibility(View.GONE); + } else if (mNextAlarm != null && mShowingBatteryInfo) { + // both battery and next alarm + mStatus1.setText(mNextAlarm); + mStatusSep.setText("|"); + mStatus2.setText(getContext().getString( + R.string.lockscreen_battery_short, + Math.min(100, mBatteryLevel))); + mStatus1.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_lock_idle_alarm, 0, 0, 0); + if (mPluggedIn) { + mStatus2.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_lock_idle_charging, 0, 0, 0); + } else { + mStatus2.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); + } + + mStatus1.setVisibility(View.VISIBLE); + mStatusSep.setVisibility(View.VISIBLE); + mStatus2.setVisibility(View.VISIBLE); + } else { + // nothing specific to show; show general instructions + mStatus1.setText(R.string.lockscreen_pattern_instructions); + mStatus1.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_lock_idle_lock, 0, 0, 0); + + mStatus1.setVisibility(View.VISIBLE); + mStatusSep.setVisibility(View.GONE); + mStatus2.setVisibility(View.GONE); + } + } + + + private void refreshTimeAndDateDisplay() { + mDate.setText(DateFormat.format(mDateFormatString, new Date())); + } + + + @Override + public boolean dispatchTouchEvent(MotionEvent 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 boolean result = super.dispatchTouchEvent(ev); + if (result && + ((SystemClock.elapsedRealtime() - mLastPokeTime) + > (UNLOCK_PATTERN_WAKE_INTERVAL_MS - 100))) { + mLastPokeTime = SystemClock.elapsedRealtime(); + } + return result; + } + + + // ---------- InfoCallback + + /** {@inheritDoc} */ + public void onRefreshBatteryInfo(boolean showBatteryInfo, boolean pluggedIn, int batteryLevel) { + mShowingBatteryInfo = showBatteryInfo; + mPluggedIn = pluggedIn; + mBatteryLevel = batteryLevel; + updateStatusLines(); + } + + /** {@inheritDoc} */ + public void onTimeChanged() { + refreshTimeAndDateDisplay(); + } + + /** {@inheritDoc} */ + public void onRefreshCarrierInfo(CharSequence plmn, CharSequence spn) { + mCarrier.setText(LockScreen.getCarrierString(plmn, spn)); + } + + /** {@inheritDoc} */ + public void onRingerModeChanged(int state) { + // not currently used + } + + // ---------- SimStateCallback + + /** {@inheritDoc} */ + public void onSimStateChanged(IccCard.State simState) { + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + if (LockPatternKeyguardView.DEBUG_CONFIGURATION) { + Log.v(TAG, "***** PATTERN ATTACHED TO WINDOW"); + Log.v(TAG, "Cur orient=" + mCreationOrientation + + ", new config=" + getResources().getConfiguration()); + } + if (getResources().getConfiguration().orientation != mCreationOrientation) { + mCallback.recreateMe(getResources().getConfiguration()); + } + } + + + /** {@inheritDoc} */ + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + if (LockPatternKeyguardView.DEBUG_CONFIGURATION) { + Log.v(TAG, "***** PATTERN CONFIGURATION CHANGED"); + Log.v(TAG, "Cur orient=" + mCreationOrientation + + ", new config=" + getResources().getConfiguration()); + } + if (newConfig.orientation != mCreationOrientation) { + mCallback.recreateMe(newConfig); + } + } + + /** {@inheritDoc} */ + public void onKeyboardChange(boolean isKeyboardOpen) {} + + /** {@inheritDoc} */ + public boolean needsInput() { + return false; + } + + /** {@inheritDoc} */ + public void onPause() { + if (mCountdownTimer != null) { + mCountdownTimer.cancel(); + mCountdownTimer = null; + } + } + + /** {@inheritDoc} */ + public void onResume() { + // reset header + resetStatusInfo(); + + // reset lock pattern + mLockPatternView.enableInput(); + mLockPatternView.setEnabled(true); + mLockPatternView.clearPattern(); + + // show "forgot pattern?" button if we have an alternate authentication method + mForgotPatternButton.setVisibility(mCallback.doesFallbackUnlockScreenExist() + ? View.VISIBLE : View.INVISIBLE); + + // if the user is currently locked out, enforce it. + long deadline = mLockPatternUtils.getLockoutAttemptDeadline(); + if (deadline != 0) { + handleAttemptLockout(deadline); + } + + // 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); + } + + refreshEmergencyButtonText(); + } + + /** {@inheritDoc} */ + public void cleanUp() { + mUpdateMonitor.removeCallback(this); + } + + @Override + public void onWindowFocusChanged(boolean hasWindowFocus) { + super.onWindowFocusChanged(hasWindowFocus); + if (hasWindowFocus) { + // when timeout dialog closes we want to update our state + onResume(); + } + } + + private class UnlockPatternListener + implements LockPatternView.OnPatternListener { + + public void onPatternStart() { + mLockPatternView.removeCallbacks(mCancelPatternRunnable); + } + + public void onPatternCleared() { + } + + public void onPatternCellAdded(List<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.pokeWakelock(UNLOCK_PATTERN_WAKE_INTERVAL_MS); + } else { + // Give just a little extra time if they hit one of the first few dots + mCallback.pokeWakelock(UNLOCK_PATTERN_WAKE_INTERVAL_FIRST_DOTS_MS); + } + } + + public void onPatternDetected(List<LockPatternView.Cell> pattern) { + if (mLockPatternUtils.checkPattern(pattern)) { + mLockPatternView + .setDisplayMode(LockPatternView.DisplayMode.Correct); + mInstructions = ""; + updateStatusLines(); + mCallback.keyguardDone(true); + mCallback.reportSuccessfulUnlockAttempt(); + } else { + if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) { + mCallback.pokeWakelock(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 { + // TODO mUnlockIcon.setVisibility(View.VISIBLE); + mInstructions = getContext().getString(R.string.lockscreen_pattern_wrong); + updateStatusLines(); + mLockPatternView.postDelayed( + mCancelPatternRunnable, + PATTERN_CLEAR_TIMEOUT_MS); + } + } + } + } + + private void handleAttemptLockout(long elapsedRealtimeDeadline) { + mLockPatternView.clearPattern(); + mLockPatternView.setEnabled(false); + long elapsedRealtime = SystemClock.elapsedRealtime(); + mCountdownTimer = new CountDownTimer(elapsedRealtimeDeadline - elapsedRealtime, 1000) { + + @Override + public void onTick(long millisUntilFinished) { + int secondsRemaining = (int) (millisUntilFinished / 1000); + mInstructions = getContext().getString( + R.string.lockscreen_too_many_failed_attempts_countdown, + secondsRemaining); + updateStatusLines(); + } + + @Override + public void onFinish() { + mLockPatternView.setEnabled(true); + mInstructions = getContext().getString(R.string.lockscreen_pattern_instructions); + updateStatusLines(); + // TODO mUnlockIcon.setVisibility(View.VISIBLE); + mFailedPatternAttemptsSinceLastTimeout = 0; + if (mEnableFallback) { + updateFooter(FooterMode.ForgotLockPattern); + } else { + updateFooter(FooterMode.Normal); + } + } + }.start(); + } + + public void onPhoneStateChanged(String newState) { + refreshEmergencyButtonText(); + } +} diff --git a/policy/src/com/android/internal/policy/impl/PhoneLayoutInflater.java b/policy/src/com/android/internal/policy/impl/PhoneLayoutInflater.java new file mode 100644 index 0000000..6bf4beb --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/PhoneLayoutInflater.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.policy.impl; + +import java.util.Map; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.view.LayoutInflater; + +public class PhoneLayoutInflater extends LayoutInflater { + private static final String[] sClassPrefixList = { + "android.widget.", + "android.webkit." + }; + + /** + * Instead of instantiating directly, you should retrieve an instance + * through {@link Context#getSystemService} + * + * @param context The Context in which in which to find resources and other + * application-specific things. + * + * @see Context#getSystemService + */ + public PhoneLayoutInflater(Context context) { + super(context); + } + + protected PhoneLayoutInflater(LayoutInflater original, Context newContext) { + super(original, newContext); + } + + /** Override onCreateView to instantiate names that correspond to the + widgets known to the Widget factory. If we don't find a match, + call through to our super class. + */ + @Override protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException { + for (String prefix : sClassPrefixList) { + try { + View view = createView(name, prefix, attrs); + if (view != null) { + return view; + } + } catch (ClassNotFoundException e) { + // In this case we want to let the base class take a crack + // at it. + } + } + + return super.onCreateView(name, attrs); + } + + public LayoutInflater cloneInContext(Context newContext) { + return new PhoneLayoutInflater(this, newContext); + } +} + diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java new file mode 100644 index 0000000..0cb0efc --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java @@ -0,0 +1,2799 @@ +/* + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.policy.impl; + +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; +import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; +import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN; +import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR; +import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; +import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; + +import com.android.internal.view.BaseSurfaceHolder; +import com.android.internal.view.RootViewSurfaceTaker; +import com.android.internal.view.menu.ContextMenuBuilder; +import com.android.internal.view.menu.MenuBuilder; +import com.android.internal.view.menu.MenuDialogHelper; +import com.android.internal.view.menu.MenuView; +import com.android.internal.view.menu.SubMenuBuilder; + +import android.app.KeyguardManager; +import android.app.SearchManager; +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.Intent; +import android.content.res.Configuration; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.media.AudioManager; +import android.net.Uri; +import android.os.Bundle; +import android.os.Message; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.SystemClock; +import android.telephony.TelephonyManager; +import android.util.AndroidRuntimeException; +import android.util.Config; +import android.util.EventLog; +import android.util.Log; +import android.util.SparseArray; +import android.view.Gravity; +import android.view.HapticFeedbackConstants; +import android.view.KeyCharacterMap; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.Surface; +import android.view.SurfaceHolder; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewManager; +import android.view.VolumePanel; +import android.view.Window; +import android.view.WindowManager; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityManager; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; +import android.view.inputmethod.InputMethodManager; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.ProgressBar; +import android.widget.TextView; + +/** + * Android-specific Window. + * <p> + * todo: need to pull the generic functionality out into a base class + * in android.widget. + */ +public class PhoneWindow extends Window implements MenuBuilder.Callback { + + private final static String TAG = "PhoneWindow"; + + private final static boolean SWEEP_OPEN_MENU = false; + + /** + * Simple callback used by the context menu and its submenus. The options + * menu submenus do not use this (their behavior is more complex). + */ + ContextMenuCallback mContextMenuCallback = new ContextMenuCallback(FEATURE_CONTEXT_MENU); + + // This is the top-level view of the window, containing the window decor. + private DecorView mDecor; + + // This is the view in which the window contents are placed. It is either + // mDecor itself, or a child of mDecor where the contents go. + private ViewGroup mContentParent; + + SurfaceHolder.Callback mTakeSurfaceCallback; + BaseSurfaceHolder mSurfaceHolder; + + private boolean mIsFloating; + + private LayoutInflater mLayoutInflater; + + private TextView mTitleView; + + private DrawableFeatureState[] mDrawables; + + private PanelFeatureState[] mPanels; + + /** + * The panel that is prepared or opened (the most recent one if there are + * multiple panels). Shortcuts will go to this panel. It gets set in + * {@link #preparePanel} and cleared in {@link #closePanel}. + */ + private PanelFeatureState mPreparedPanel; + + /** + * The keycode that is currently held down (as a modifier) for chording. If + * this is 0, there is no key held down. + */ + private int mPanelChordingKey; + private boolean mPanelMayLongPress; + + private ImageView mLeftIconView; + + private ImageView mRightIconView; + + private ProgressBar mCircularProgressBar; + + private ProgressBar mHorizontalProgressBar; + + private int mBackgroundResource = 0; + + private Drawable mBackgroundDrawable; + + private int mFrameResource = 0; + + private int mTextColor = 0; + + private CharSequence mTitle = null; + + private int mTitleColor = 0; + + private ContextMenuBuilder mContextMenu; + private MenuDialogHelper mContextMenuHelper; + + private int mVolumeControlStreamType = AudioManager.USE_DEFAULT_STREAM_TYPE; + private long mVolumeKeyUpTime; + + private KeyguardManager mKeyguardManager = null; + + private SearchManager mSearchManager = null; + + private TelephonyManager mTelephonyManager = null; + + public PhoneWindow(Context context) { + super(context); + mLayoutInflater = LayoutInflater.from(context); + } + + @Override + public final void setContainer(Window container) { + super.setContainer(container); + } + + @Override + public boolean requestFeature(int featureId) { + if (mContentParent != null) { + throw new AndroidRuntimeException("requestFeature() must be called before adding content"); + } + final int features = getFeatures(); + if ((features != DEFAULT_FEATURES) && (featureId == FEATURE_CUSTOM_TITLE)) { + + /* Another feature is enabled and the user is trying to enable the custom title feature */ + throw new AndroidRuntimeException("You cannot combine custom titles with other title features"); + } + if (((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) && (featureId != FEATURE_CUSTOM_TITLE)) { + + /* Custom title feature is enabled and the user is trying to enable another feature */ + throw new AndroidRuntimeException("You cannot combine custom titles with other title features"); + } + if (featureId == FEATURE_OPENGL) { + getAttributes().memoryType = WindowManager.LayoutParams.MEMORY_TYPE_GPU; + } + return super.requestFeature(featureId); + } + + @Override + public void setContentView(int layoutResID) { + if (mContentParent == null) { + installDecor(); + } else { + mContentParent.removeAllViews(); + } + mLayoutInflater.inflate(layoutResID, mContentParent); + final Callback cb = getCallback(); + if (cb != null) { + cb.onContentChanged(); + } + } + + @Override + public void setContentView(View view) { + setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); + } + + @Override + public void setContentView(View view, ViewGroup.LayoutParams params) { + if (mContentParent == null) { + installDecor(); + } else { + mContentParent.removeAllViews(); + } + mContentParent.addView(view, params); + final Callback cb = getCallback(); + if (cb != null) { + cb.onContentChanged(); + } + } + + @Override + public void addContentView(View view, ViewGroup.LayoutParams params) { + if (mContentParent == null) { + installDecor(); + } + mContentParent.addView(view, params); + final Callback cb = getCallback(); + if (cb != null) { + cb.onContentChanged(); + } + } + + @Override + public View getCurrentFocus() { + return mDecor != null ? mDecor.findFocus() : null; + } + + @Override + public void takeSurface(SurfaceHolder.Callback callback) { + mTakeSurfaceCallback = callback; + } + + @Override + public boolean isFloating() { + return mIsFloating; + } + + /** + * Return a LayoutInflater instance that can be used to inflate XML view layout + * resources for use in this Window. + * + * @return LayoutInflater The shared LayoutInflater. + */ + @Override + public LayoutInflater getLayoutInflater() { + return mLayoutInflater; + } + + @Override + public void setTitle(CharSequence title) { + if (mTitleView != null) { + mTitleView.setText(title); + } + mTitle = title; + } + + @Override + public void setTitleColor(int textColor) { + if (mTitleView != null) { + mTitleView.setTextColor(textColor); + } + mTitleColor = textColor; + } + + /** + * Prepares the panel to either be opened or chorded. This creates the Menu + * instance for the panel and populates it via the Activity callbacks. + * + * @param st The panel state to prepare. + * @param event The event that triggered the preparing of the panel. + * @return Whether the panel was prepared. If the panel should not be shown, + * returns false. + */ + public final boolean preparePanel(PanelFeatureState st, KeyEvent event) { + // Already prepared (isPrepared will be reset to false later) + if (st.isPrepared) + return true; + + if ((mPreparedPanel != null) && (mPreparedPanel != st)) { + // Another Panel is prepared and possibly open, so close it + closePanel(mPreparedPanel, false); + } + + final Callback cb = getCallback(); + + if (cb != null) { + st.createdPanelView = cb.onCreatePanelView(st.featureId); + } + + if (st.createdPanelView == null) { + // Init the panel state's menu--return false if init failed + if (st.menu == null) { + if (!initializePanelMenu(st) || (st.menu == null)) { + return false; + } + // Call callback, and return if it doesn't want to display menu + if ((cb == null) || !cb.onCreatePanelMenu(st.featureId, st.menu)) { + // Ditch the menu created above + st.menu = null; + + return false; + } + } + + // Callback and return if the callback does not want to show the menu + if (!cb.onPreparePanel(st.featureId, st.createdPanelView, st.menu)) { + return false; + } + + // Set the proper keymap + KeyCharacterMap kmap = KeyCharacterMap.load(event != null ? event.getDeviceId() : 0); + st.qwertyMode = kmap.getKeyboardType() != KeyCharacterMap.NUMERIC; + st.menu.setQwertyMode(st.qwertyMode); + } + + // Set other state + st.isPrepared = true; + st.isHandled = false; + mPreparedPanel = st; + + return true; + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false); + if ((st != null) && (st.menu != null)) { + final MenuBuilder menuBuilder = (MenuBuilder) st.menu; + + if (st.isOpen) { + // Freeze state + final Bundle state = new Bundle(); + menuBuilder.saveHierarchyState(state); + + // Remove the menu views since they need to be recreated + // according to the new configuration + clearMenuViews(st); + + // Re-open the same menu + reopenMenu(false); + + // Restore state + menuBuilder.restoreHierarchyState(state); + + } else { + // Clear menu views so on next menu opening, it will use + // the proper layout + clearMenuViews(st); + } + } + + } + + private static void clearMenuViews(PanelFeatureState st) { + + // This can be called on config changes, so we should make sure + // the views will be reconstructed based on the new orientation, etc. + + // Allow the callback to create a new panel view + st.createdPanelView = null; + + // Causes the decor view to be recreated + st.refreshDecorView = true; + + ((MenuBuilder) st.menu).clearMenuViews(); + } + + @Override + public final void openPanel(int featureId, KeyEvent event) { + openPanel(getPanelState(featureId, true), event); + } + + private void openPanel(PanelFeatureState st, KeyEvent event) { + // System.out.println("Open panel: isOpen=" + st.isOpen); + + // Already open, return + if (st.isOpen) { + return; + } + + Callback cb = getCallback(); + if ((cb != null) && (!cb.onMenuOpened(st.featureId, st.menu))) { + // Callback doesn't want the menu to open, reset any state + closePanel(st, true); + return; + } + + final WindowManager wm = getWindowManager(); + if (wm == null) { + return; + } + + // Prepare panel (should have been done before, but just in case) + if (!preparePanel(st, event)) { + return; + } + + if (st.decorView == null || st.refreshDecorView) { + if (st.decorView == null) { + // Initialize the panel decor, this will populate st.decorView + if (!initializePanelDecor(st) || (st.decorView == null)) + return; + } else if (st.refreshDecorView && (st.decorView.getChildCount() > 0)) { + // Decor needs refreshing, so remove its views + st.decorView.removeAllViews(); + } + + // This will populate st.shownPanelView + if (!initializePanelContent(st) || (st.shownPanelView == null)) { + return; + } + + ViewGroup.LayoutParams lp = st.shownPanelView.getLayoutParams(); + if (lp == null) { + lp = new ViewGroup.LayoutParams(WRAP_CONTENT, WRAP_CONTENT); + } + + int backgroundResId; + if (lp.width == ViewGroup.LayoutParams.MATCH_PARENT) { + // If the contents is fill parent for the width, set the + // corresponding background + backgroundResId = st.fullBackground; + } else { + // Otherwise, set the normal panel background + backgroundResId = st.background; + } + st.decorView.setWindowBackground(getContext().getResources().getDrawable( + backgroundResId)); + + + st.decorView.addView(st.shownPanelView, lp); + + /* + * Give focus to the view, if it or one of its children does not + * already have it. + */ + if (!st.shownPanelView.hasFocus()) { + st.shownPanelView.requestFocus(); + } + } + + st.isOpen = true; + st.isHandled = false; + + WindowManager.LayoutParams lp = new WindowManager.LayoutParams( + WRAP_CONTENT, WRAP_CONTENT, + st.x, st.y, WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG, + WindowManager.LayoutParams.FLAG_DITHER + | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, + st.decorView.mDefaultOpacity); + + lp.gravity = st.gravity; + lp.windowAnimations = st.windowAnimations; + + wm.addView(st.decorView, lp); + // Log.v(TAG, "Adding main menu to window manager."); + } + + @Override + public final void closePanel(int featureId) { + if (featureId == FEATURE_CONTEXT_MENU) { + closeContextMenu(); + } else { + closePanel(getPanelState(featureId, true), true); + } + } + + /** + * Closes the given panel. + * + * @param st The panel to be closed. + * @param doCallback Whether to notify the callback that the panel was + * closed. If the panel is in the process of re-opening or + * opening another panel (e.g., menu opening a sub menu), the + * callback should not happen and this variable should be false. + * In addition, this method internally will only perform the + * callback if the panel is open. + */ + public final void closePanel(PanelFeatureState st, boolean doCallback) { + // System.out.println("Close panel: isOpen=" + st.isOpen); + final ViewManager wm = getWindowManager(); + if ((wm != null) && st.isOpen) { + if (st.decorView != null) { + wm.removeView(st.decorView); + // Log.v(TAG, "Removing main menu from window manager."); + } + + if (doCallback) { + callOnPanelClosed(st.featureId, st, null); + } + } + st.isPrepared = false; + st.isHandled = false; + st.isOpen = false; + + // This view is no longer shown, so null it out + st.shownPanelView = null; + + if (st.isInExpandedMode) { + // Next time the menu opens, it should not be in expanded mode, so + // force a refresh of the decor + st.refreshDecorView = true; + st.isInExpandedMode = false; + } + + if (mPreparedPanel == st) { + mPreparedPanel = null; + mPanelChordingKey = 0; + } + } + + @Override + public final void togglePanel(int featureId, KeyEvent event) { + PanelFeatureState st = getPanelState(featureId, true); + if (st.isOpen) { + closePanel(st, true); + } else { + openPanel(st, event); + } + } + + /** + * Called when the panel key is pushed down. + * @param featureId The feature ID of the relevant panel (defaults to FEATURE_OPTIONS_PANEL}. + * @param event The key event. + * @return Whether the key was handled. + */ + public final boolean onKeyDownPanel(int featureId, KeyEvent event) { + final int keyCode = event.getKeyCode(); + + if (event.getRepeatCount() == 0) { + // The panel key was pushed, so set the chording key + mPanelChordingKey = keyCode; + mPanelMayLongPress = false; + + PanelFeatureState st = getPanelState(featureId, true); + if (!st.isOpen) { + if (getContext().getResources().getConfiguration().keyboard + == Configuration.KEYBOARD_NOKEYS) { + mPanelMayLongPress = true; + } + return preparePanel(st, event); + } + + } else if (mPanelMayLongPress && mPanelChordingKey == keyCode + && (event.getFlags()&KeyEvent.FLAG_LONG_PRESS) != 0) { + // We have had a long press while in a state where this + // should be executed... do it! + mPanelChordingKey = 0; + mPanelMayLongPress = false; + InputMethodManager imm = (InputMethodManager) + getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + if (imm != null) { + mDecor.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); + imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0); + } + + } + + return false; + } + + /** + * Called when the panel key is released. + * @param featureId The feature ID of the relevant panel (defaults to FEATURE_OPTIONS_PANEL}. + * @param event The key event. + */ + public final void onKeyUpPanel(int featureId, KeyEvent event) { + // The panel key was released, so clear the chording key + if (mPanelChordingKey != 0) { + mPanelChordingKey = 0; + mPanelMayLongPress = false; + + if (event.isCanceled()) { + return; + } + + boolean playSoundEffect = false; + PanelFeatureState st = getPanelState(featureId, true); + if (st.isOpen || st.isHandled) { + + // Play the sound effect if the user closed an open menu (and not if + // they just released a menu shortcut) + playSoundEffect = st.isOpen; + + // Close menu + closePanel(st, true); + + } else if (st.isPrepared) { + + // Write 'menu opened' to event log + EventLog.writeEvent(50001, 0); + + // Show menu + openPanel(st, event); + + playSoundEffect = true; + } + + if (playSoundEffect) { + AudioManager audioManager = (AudioManager) getContext().getSystemService( + Context.AUDIO_SERVICE); + if (audioManager != null) { + audioManager.playSoundEffect(AudioManager.FX_KEY_CLICK); + } else { + Log.w(TAG, "Couldn't get audio manager"); + } + } + } + } + + @Override + public final void closeAllPanels() { + final ViewManager wm = getWindowManager(); + if (wm == null) { + return; + } + + final PanelFeatureState[] panels = mPanels; + final int N = panels != null ? panels.length : 0; + for (int i = 0; i < N; i++) { + final PanelFeatureState panel = panels[i]; + if (panel != null) { + closePanel(panel, true); + } + } + + closeContextMenu(); + } + + /** + * Closes the context menu. This notifies the menu logic of the close, along + * with dismissing it from the UI. + */ + private synchronized void closeContextMenu() { + if (mContextMenu != null) { + mContextMenu.close(); + dismissContextMenu(); + } + } + + /** + * Dismisses just the context menu UI. To close the context menu, use + * {@link #closeContextMenu()}. + */ + private synchronized void dismissContextMenu() { + mContextMenu = null; + + if (mContextMenuHelper != null) { + mContextMenuHelper.dismiss(); + mContextMenuHelper = null; + } + } + + @Override + public boolean performPanelShortcut(int featureId, int keyCode, KeyEvent event, int flags) { + return performPanelShortcut(getPanelState(featureId, true), keyCode, event, flags); + } + + private boolean performPanelShortcut(PanelFeatureState st, int keyCode, KeyEvent event, + int flags) { + if (event.isSystem() || (st == null)) { + return false; + } + + boolean handled = false; + + // Only try to perform menu shortcuts if preparePanel returned true (possible false + // return value from application not wanting to show the menu). + if ((st.isPrepared || preparePanel(st, event)) && st.menu != null) { + // The menu is prepared now, perform the shortcut on it + handled = st.menu.performShortcut(keyCode, event, flags); + } + + if (handled) { + // Mark as handled + st.isHandled = true; + + if ((flags & Menu.FLAG_PERFORM_NO_CLOSE) == 0) { + closePanel(st, true); + } + } + + return handled; + } + + @Override + public boolean performPanelIdentifierAction(int featureId, int id, int flags) { + + PanelFeatureState st = getPanelState(featureId, true); + if (!preparePanel(st, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MENU))) { + return false; + } + if (st.menu == null) { + return false; + } + + boolean res = st.menu.performIdentifierAction(id, flags); + + closePanel(st, true); + + return res; + } + + public PanelFeatureState findMenuPanel(Menu menu) { + final PanelFeatureState[] panels = mPanels; + final int N = panels != null ? panels.length : 0; + for (int i = 0; i < N; i++) { + final PanelFeatureState panel = panels[i]; + if (panel != null && panel.menu == menu) { + return panel; + } + } + return null; + } + + public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) { + final Callback cb = getCallback(); + if (cb != null) { + final PanelFeatureState panel = findMenuPanel(menu.getRootMenu()); + if (panel != null) { + return cb.onMenuItemSelected(panel.featureId, item); + } + } + return false; + } + + public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { + final PanelFeatureState panel = findMenuPanel(menu); + if (panel != null) { + // Close the panel and only do the callback if the menu is being + // closed + // completely, not if opening a sub menu + closePanel(panel, allMenusAreClosing); + } + } + + public void onCloseSubMenu(SubMenuBuilder subMenu) { + final Menu parentMenu = subMenu.getRootMenu(); + final PanelFeatureState panel = findMenuPanel(parentMenu); + + // Callback + if (panel != null) { + callOnPanelClosed(panel.featureId, panel, parentMenu); + closePanel(panel, true); + } + } + + public boolean onSubMenuSelected(final SubMenuBuilder subMenu) { + if (!subMenu.hasVisibleItems()) { + return true; + } + + // The window manager will give us a valid window token + new MenuDialogHelper(subMenu).show(null); + + return true; + } + + public void onMenuModeChange(MenuBuilder menu) { + reopenMenu(true); + } + + private void reopenMenu(boolean toggleMenuMode) { + PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, true); + + // Save the future expanded mode state since closePanel will reset it + boolean newExpandedMode = toggleMenuMode ? !st.isInExpandedMode : st.isInExpandedMode; + + st.refreshDecorView = true; + closePanel(st, false); + + // Set the expanded mode state + st.isInExpandedMode = newExpandedMode; + + openPanel(st, null); + } + + /** + * Initializes the menu associated with the given panel feature state. You + * must at the very least set PanelFeatureState.menu to the Menu to be + * associated with the given panel state. The default implementation creates + * a new menu for the panel state. + * + * @param st The panel whose menu is being initialized. + * @return Whether the initialization was successful. + */ + protected boolean initializePanelMenu(final PanelFeatureState st) { + final MenuBuilder menu = new MenuBuilder(getContext()); + + menu.setCallback(this); + st.setMenu(menu); + + return true; + } + + /** + * Perform initial setup of a panel. This should at the very least set the + * style information in the PanelFeatureState and must set + * PanelFeatureState.decor to the panel's window decor view. + * + * @param st The panel being initialized. + */ + protected boolean initializePanelDecor(PanelFeatureState st) { + st.decorView = new DecorView(getContext(), st.featureId); + st.gravity = Gravity.CENTER | Gravity.BOTTOM; + st.setStyle(getContext()); + + return true; + } + + /** + * Initializes the panel associated with the panel feature state. You must + * at the very least set PanelFeatureState.panel to the View implementing + * its contents. The default implementation gets the panel from the menu. + * + * @param st The panel state being initialized. + * @return Whether the initialization was successful. + */ + protected boolean initializePanelContent(PanelFeatureState st) { + + if (st.createdPanelView != null) { + st.shownPanelView = st.createdPanelView; + return true; + } + + final MenuBuilder menu = (MenuBuilder)st.menu; + if (menu == null) { + return false; + } + + st.shownPanelView = menu.getMenuView((st.isInExpandedMode) ? MenuBuilder.TYPE_EXPANDED + : MenuBuilder.TYPE_ICON, st.decorView); + + if (st.shownPanelView != null) { + // Use the menu View's default animations if it has any + final int defaultAnimations = ((MenuView) st.shownPanelView).getWindowAnimations(); + if (defaultAnimations != 0) { + st.windowAnimations = defaultAnimations; + } + return true; + } else { + return false; + } + } + + @Override + public boolean performContextMenuIdentifierAction(int id, int flags) { + return (mContextMenu != null) ? mContextMenu.performIdentifierAction(id, flags) : false; + } + + @Override + public final void setBackgroundDrawable(Drawable drawable) { + if (drawable != mBackgroundDrawable || mBackgroundResource != 0) { + mBackgroundResource = 0; + mBackgroundDrawable = drawable; + if (mDecor != null) { + mDecor.setWindowBackground(drawable); + } + } + } + + @Override + public final void setFeatureDrawableResource(int featureId, int resId) { + if (resId != 0) { + DrawableFeatureState st = getDrawableState(featureId, true); + if (st.resid != resId) { + st.resid = resId; + st.uri = null; + st.local = getContext().getResources().getDrawable(resId); + updateDrawable(featureId, st, false); + } + } else { + setFeatureDrawable(featureId, null); + } + } + + @Override + public final void setFeatureDrawableUri(int featureId, Uri uri) { + if (uri != null) { + DrawableFeatureState st = getDrawableState(featureId, true); + if (st.uri == null || !st.uri.equals(uri)) { + st.resid = 0; + st.uri = uri; + st.local = loadImageURI(uri); + updateDrawable(featureId, st, false); + } + } else { + setFeatureDrawable(featureId, null); + } + } + + @Override + public final void setFeatureDrawable(int featureId, Drawable drawable) { + DrawableFeatureState st = getDrawableState(featureId, true); + st.resid = 0; + st.uri = null; + if (st.local != drawable) { + st.local = drawable; + updateDrawable(featureId, st, false); + } + } + + @Override + public void setFeatureDrawableAlpha(int featureId, int alpha) { + DrawableFeatureState st = getDrawableState(featureId, true); + if (st.alpha != alpha) { + st.alpha = alpha; + updateDrawable(featureId, st, false); + } + } + + protected final void setFeatureDefaultDrawable(int featureId, Drawable drawable) { + DrawableFeatureState st = getDrawableState(featureId, true); + if (st.def != drawable) { + st.def = drawable; + updateDrawable(featureId, st, false); + } + } + + @Override + public final void setFeatureInt(int featureId, int value) { + // XXX Should do more management (as with drawable features) to + // deal with interactions between multiple window policies. + updateInt(featureId, value, false); + } + + /** + * Update the state of a drawable feature. This should be called, for every + * drawable feature supported, as part of onActive(), to make sure that the + * contents of a containing window is properly updated. + * + * @see #onActive + * @param featureId The desired drawable feature to change. + * @param fromActive Always true when called from onActive(). + */ + protected final void updateDrawable(int featureId, boolean fromActive) { + final DrawableFeatureState st = getDrawableState(featureId, false); + if (st != null) { + updateDrawable(featureId, st, fromActive); + } + } + + /** + * Called when a Drawable feature changes, for the window to update its + * graphics. + * + * @param featureId The feature being changed. + * @param drawable The new Drawable to show, or null if none. + * @param alpha The new alpha blending of the Drawable. + */ + protected void onDrawableChanged(int featureId, Drawable drawable, int alpha) { + ImageView view; + if (featureId == FEATURE_LEFT_ICON) { + view = getLeftIconView(); + } else if (featureId == FEATURE_RIGHT_ICON) { + view = getRightIconView(); + } else { + return; + } + + if (drawable != null) { + drawable.setAlpha(alpha); + view.setImageDrawable(drawable); + view.setVisibility(View.VISIBLE); + } else { + view.setVisibility(View.GONE); + } + } + + /** + * Called when an int feature changes, for the window to update its + * graphics. + * + * @param featureId The feature being changed. + * @param value The new integer value. + */ + protected void onIntChanged(int featureId, int value) { + if (featureId == FEATURE_PROGRESS || featureId == FEATURE_INDETERMINATE_PROGRESS) { + updateProgressBars(value); + } else if (featureId == FEATURE_CUSTOM_TITLE) { + FrameLayout titleContainer = (FrameLayout) findViewById(com.android.internal.R.id.title_container); + if (titleContainer != null) { + mLayoutInflater.inflate(value, titleContainer); + } + } + } + + /** + * Updates the progress bars that are shown in the title bar. + * + * @param value Can be one of {@link Window#PROGRESS_VISIBILITY_ON}, + * {@link Window#PROGRESS_VISIBILITY_OFF}, + * {@link Window#PROGRESS_INDETERMINATE_ON}, + * {@link Window#PROGRESS_INDETERMINATE_OFF}, or a value + * starting at {@link Window#PROGRESS_START} through + * {@link Window#PROGRESS_END} for setting the default + * progress (if {@link Window#PROGRESS_END} is given, + * the progress bar widgets in the title will be hidden after an + * animation), a value between + * {@link Window#PROGRESS_SECONDARY_START} - + * {@link Window#PROGRESS_SECONDARY_END} for the + * secondary progress (if + * {@link Window#PROGRESS_SECONDARY_END} is given, the + * progress bar widgets will still be shown with the secondary + * progress bar will be completely filled in.) + */ + private void updateProgressBars(int value) { + ProgressBar circularProgressBar = getCircularProgressBar(true); + ProgressBar horizontalProgressBar = getHorizontalProgressBar(true); + + final int features = getLocalFeatures(); + if (value == PROGRESS_VISIBILITY_ON) { + if ((features & (1 << FEATURE_PROGRESS)) != 0) { + int level = horizontalProgressBar.getProgress(); + int visibility = (horizontalProgressBar.isIndeterminate() || level < 10000) ? + View.VISIBLE : View.INVISIBLE; + horizontalProgressBar.setVisibility(visibility); + } + if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) { + circularProgressBar.setVisibility(View.VISIBLE); + } + } else if (value == PROGRESS_VISIBILITY_OFF) { + if ((features & (1 << FEATURE_PROGRESS)) != 0) { + horizontalProgressBar.setVisibility(View.GONE); + } + if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) { + circularProgressBar.setVisibility(View.GONE); + } + } else if (value == PROGRESS_INDETERMINATE_ON) { + horizontalProgressBar.setIndeterminate(true); + } else if (value == PROGRESS_INDETERMINATE_OFF) { + horizontalProgressBar.setIndeterminate(false); + } else if (PROGRESS_START <= value && value <= PROGRESS_END) { + // We want to set the progress value before testing for visibility + // so that when the progress bar becomes visible again, it has the + // correct level. + horizontalProgressBar.setProgress(value - PROGRESS_START); + + if (value < PROGRESS_END) { + showProgressBars(horizontalProgressBar, circularProgressBar); + } else { + hideProgressBars(horizontalProgressBar, circularProgressBar); + } + } else if (PROGRESS_SECONDARY_START <= value && value <= PROGRESS_SECONDARY_END) { + horizontalProgressBar.setSecondaryProgress(value - PROGRESS_SECONDARY_START); + + showProgressBars(horizontalProgressBar, circularProgressBar); + } + + } + + private void showProgressBars(ProgressBar horizontalProgressBar, ProgressBar spinnyProgressBar) { + final int features = getLocalFeatures(); + if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0 && + spinnyProgressBar.getVisibility() == View.INVISIBLE) { + spinnyProgressBar.setVisibility(View.VISIBLE); + } + // Only show the progress bars if the primary progress is not complete + if ((features & (1 << FEATURE_PROGRESS)) != 0 && + horizontalProgressBar.getProgress() < 10000) { + horizontalProgressBar.setVisibility(View.VISIBLE); + } + } + + private void hideProgressBars(ProgressBar horizontalProgressBar, ProgressBar spinnyProgressBar) { + final int features = getLocalFeatures(); + Animation anim = AnimationUtils.loadAnimation(getContext(), com.android.internal.R.anim.fade_out); + anim.setDuration(1000); + if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0 && + spinnyProgressBar.getVisibility() == View.VISIBLE) { + spinnyProgressBar.startAnimation(anim); + spinnyProgressBar.setVisibility(View.INVISIBLE); + } + if ((features & (1 << FEATURE_PROGRESS)) != 0 && + horizontalProgressBar.getVisibility() == View.VISIBLE) { + horizontalProgressBar.startAnimation(anim); + horizontalProgressBar.setVisibility(View.INVISIBLE); + } + } + + /** + * Request that key events come to this activity. Use this if your activity + * has no views with focus, but the activity still wants a chance to process + * key events. + */ + @Override + public void takeKeyEvents(boolean get) { + mDecor.setFocusable(get); + } + + @Override + public boolean superDispatchKeyEvent(KeyEvent event) { + return mDecor.superDispatchKeyEvent(event); + } + + @Override + public boolean superDispatchTouchEvent(MotionEvent event) { + return mDecor.superDispatchTouchEvent(event); + } + + @Override + public boolean superDispatchTrackballEvent(MotionEvent event) { + return mDecor.superDispatchTrackballEvent(event); + } + + /** + * A key was pressed down and not handled by anything else in the window. + * + * @see #onKeyUp + * @see android.view.KeyEvent + */ + protected boolean onKeyDown(int featureId, int keyCode, KeyEvent event) { + final KeyEvent.DispatcherState dispatcher = + mDecor != null ? mDecor.getKeyDispatcherState() : null; + //Log.i(TAG, "Key down: repeat=" + event.getRepeatCount() + // + " flags=0x" + Integer.toHexString(event.getFlags())); + + switch (keyCode) { + case KeyEvent.KEYCODE_VOLUME_UP: + case KeyEvent.KEYCODE_VOLUME_DOWN: { + AudioManager audioManager = (AudioManager) getContext().getSystemService( + Context.AUDIO_SERVICE); + if (audioManager != null) { + /* + * Adjust the volume in on key down since it is more + * responsive to the user. + */ + audioManager.adjustSuggestedStreamVolume( + keyCode == KeyEvent.KEYCODE_VOLUME_UP + ? AudioManager.ADJUST_RAISE + : AudioManager.ADJUST_LOWER, + mVolumeControlStreamType, + AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE); + } + return true; + } + + + case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: + /* Suppress PLAYPAUSE 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_FAST_FORWARD: { + Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON, null); + intent.putExtra(Intent.EXTRA_KEY_EVENT, event); + getContext().sendOrderedBroadcast(intent, null); + return true; + } + + case KeyEvent.KEYCODE_CAMERA: { + if (getKeyguardManager().inKeyguardRestrictedInputMode() + || dispatcher == null) { + break; + } + if (event.getRepeatCount() == 0) { + dispatcher.startTracking(event, this); + } else if (event.isLongPress() && dispatcher.isTracking(event)) { + dispatcher.performedLongPress(event); + mDecor.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); + sendCloseSystemWindows(); + // Broadcast an intent that the Camera button was longpressed + Intent intent = new Intent(Intent.ACTION_CAMERA_BUTTON, null); + intent.putExtra(Intent.EXTRA_KEY_EVENT, event); + getContext().sendOrderedBroadcast(intent, null); + } + return true; + } + + case KeyEvent.KEYCODE_MENU: { + onKeyDownPanel((featureId < 0) ? FEATURE_OPTIONS_PANEL : featureId, event); + return true; + } + + case KeyEvent.KEYCODE_BACK: { + if (event.getRepeatCount() > 0) break; + if (featureId < 0) break; + // Currently don't do anything with long press. + dispatcher.startTracking(event, this); + return true; + } + + case KeyEvent.KEYCODE_CALL: { + if (getKeyguardManager().inKeyguardRestrictedInputMode() + || dispatcher == null) { + break; + } + if (event.getRepeatCount() == 0) { + dispatcher.startTracking(event, this); + } else if (event.isLongPress() && dispatcher.isTracking(event)) { + dispatcher.performedLongPress(event); + mDecor.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); + // launch the VoiceDialer + Intent intent = new Intent(Intent.ACTION_VOICE_COMMAND); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + try { + sendCloseSystemWindows(); + getContext().startActivity(intent); + } catch (ActivityNotFoundException e) { + startCallActivity(); + } + } + return true; + } + + case KeyEvent.KEYCODE_SEARCH: { + if (getKeyguardManager().inKeyguardRestrictedInputMode() + || dispatcher == null) { + break; + } + if (event.getRepeatCount() == 0) { + dispatcher.startTracking(event, this); + } else if (event.isLongPress() && dispatcher.isTracking(event)) { + Configuration config = getContext().getResources().getConfiguration(); + if (config.keyboard == Configuration.KEYBOARD_NOKEYS + || config.hardKeyboardHidden + == Configuration.HARDKEYBOARDHIDDEN_YES) { + // launch the search activity + Intent intent = new Intent(Intent.ACTION_SEARCH_LONG_PRESS); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + try { + mDecor.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); + sendCloseSystemWindows(); + getSearchManager().stopSearch(); + getContext().startActivity(intent); + // Only clear this if we successfully start the + // activity; otherwise we will allow the normal short + // press action to be performed. + dispatcher.performedLongPress(event); + return true; + } catch (ActivityNotFoundException e) { + // Ignore + } + } + } + break; + } + } + + return false; + } + + /** + * @return A handle to the keyguard manager. + */ + private KeyguardManager getKeyguardManager() { + if (mKeyguardManager == null) { + mKeyguardManager = (KeyguardManager) getContext().getSystemService(Context.KEYGUARD_SERVICE); + } + return mKeyguardManager; + } + + /** + * @return A handle to the search manager. + */ + private SearchManager getSearchManager() { + if (mSearchManager == null) { + mSearchManager = (SearchManager) getContext().getSystemService(Context.SEARCH_SERVICE); + } + return mSearchManager; + } + + /** + * A key was released and not handled by anything else in the window. + * + * @see #onKeyDown + * @see android.view.KeyEvent + */ + protected boolean onKeyUp(int featureId, int keyCode, KeyEvent event) { + final KeyEvent.DispatcherState dispatcher = + mDecor != null ? mDecor.getKeyDispatcherState() : null; + if (dispatcher != null) { + dispatcher.handleUpEvent(event); + } + //Log.i(TAG, "Key up: repeat=" + event.getRepeatCount() + // + " flags=0x" + Integer.toHexString(event.getFlags())); + + switch (keyCode) { + case KeyEvent.KEYCODE_VOLUME_UP: + case KeyEvent.KEYCODE_VOLUME_DOWN: { + AudioManager audioManager = (AudioManager) getContext().getSystemService( + Context.AUDIO_SERVICE); + if (audioManager != null) { + /* + * Play a sound. This is done on key up since we don't want the + * sound to play when a user holds down volume down to mute. + */ + audioManager.adjustSuggestedStreamVolume( + AudioManager.ADJUST_SAME, + mVolumeControlStreamType, + AudioManager.FLAG_PLAY_SOUND); + mVolumeKeyUpTime = SystemClock.uptimeMillis(); + } + return true; + } + + case KeyEvent.KEYCODE_MENU: { + onKeyUpPanel(featureId < 0 ? FEATURE_OPTIONS_PANEL : featureId, + event); + return true; + } + + case KeyEvent.KEYCODE_BACK: { + if (featureId < 0) break; + if (event.isTracking() && !event.isCanceled()) { + if (featureId == FEATURE_OPTIONS_PANEL) { + PanelFeatureState st = getPanelState(featureId, false); + if (st != null && st.isInExpandedMode) { + // If the user is in an expanded menu and hits back, it + // should go back to the icon menu + reopenMenu(true); + return true; + } + } + closePanel(featureId); + return true; + } + break; + } + + case KeyEvent.KEYCODE_HEADSETHOOK: + 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_FAST_FORWARD: { + Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON, null); + intent.putExtra(Intent.EXTRA_KEY_EVENT, event); + getContext().sendOrderedBroadcast(intent, null); + return true; + } + + case KeyEvent.KEYCODE_CAMERA: { + if (getKeyguardManager().inKeyguardRestrictedInputMode()) { + break; + } + if (event.isTracking() && !event.isCanceled()) { + // Add short press behavior here if desired + } + return true; + } + + case KeyEvent.KEYCODE_CALL: { + if (getKeyguardManager().inKeyguardRestrictedInputMode()) { + break; + } + if (event.isTracking() && !event.isCanceled()) { + startCallActivity(); + } + return true; + } + + case KeyEvent.KEYCODE_SEARCH: { + /* + * Do this in onKeyUp since the Search key is also used for + * chording quick launch shortcuts. + */ + if (getKeyguardManager().inKeyguardRestrictedInputMode()) { + break; + } + if (event.isTracking() && !event.isCanceled()) { + launchDefaultSearch(); + } + return true; + } + } + + return false; + } + + private void startCallActivity() { + sendCloseSystemWindows(); + Intent intent = new Intent(Intent.ACTION_CALL_BUTTON); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + getContext().startActivity(intent); + } + + @Override + protected void onActive() { + } + + @Override + public final View getDecorView() { + if (mDecor == null) { + installDecor(); + } + return mDecor; + } + + @Override + public final View peekDecorView() { + return mDecor; + } + + static private final String FOCUSED_ID_TAG = "android:focusedViewId"; + static private final String VIEWS_TAG = "android:views"; + static private final String PANELS_TAG = "android:Panels"; + + /** {@inheritDoc} */ + @Override + public Bundle saveHierarchyState() { + Bundle outState = new Bundle(); + if (mContentParent == null) { + return outState; + } + + SparseArray<Parcelable> states = new SparseArray<Parcelable>(); + mContentParent.saveHierarchyState(states); + outState.putSparseParcelableArray(VIEWS_TAG, states); + + // save the focused view id + View focusedView = mContentParent.findFocus(); + if (focusedView != null) { + if (focusedView.getId() != View.NO_ID) { + outState.putInt(FOCUSED_ID_TAG, focusedView.getId()); + } else { + if (Config.LOGD) { + Log.d(TAG, "couldn't save which view has focus because the focused view " + + focusedView + " has no id."); + } + } + } + + // save the panels + SparseArray<Parcelable> panelStates = new SparseArray<Parcelable>(); + savePanelState(panelStates); + if (panelStates.size() > 0) { + outState.putSparseParcelableArray(PANELS_TAG, panelStates); + } + + return outState; + } + + /** {@inheritDoc} */ + @Override + public void restoreHierarchyState(Bundle savedInstanceState) { + if (mContentParent == null) { + return; + } + + SparseArray<Parcelable> savedStates + = savedInstanceState.getSparseParcelableArray(VIEWS_TAG); + if (savedStates != null) { + mContentParent.restoreHierarchyState(savedStates); + } + + // restore the focused view + int focusedViewId = savedInstanceState.getInt(FOCUSED_ID_TAG, View.NO_ID); + if (focusedViewId != View.NO_ID) { + View needsFocus = mContentParent.findViewById(focusedViewId); + if (needsFocus != null) { + needsFocus.requestFocus(); + } else { + Log.w(TAG, + "Previously focused view reported id " + focusedViewId + + " during save, but can't be found during restore."); + } + } + + // restore the panels + SparseArray<Parcelable> panelStates = savedInstanceState.getSparseParcelableArray(PANELS_TAG); + if (panelStates != null) { + restorePanelState(panelStates); + } + } + + /** + * Invoked when the panels should freeze their state. + * + * @param icicles Save state into this. This is usually indexed by the + * featureId. This will be given to {@link #restorePanelState} in the + * future. + */ + private void savePanelState(SparseArray<Parcelable> icicles) { + PanelFeatureState[] panels = mPanels; + if (panels == null) { + return; + } + + for (int curFeatureId = panels.length - 1; curFeatureId >= 0; curFeatureId--) { + if (panels[curFeatureId] != null) { + icicles.put(curFeatureId, panels[curFeatureId].onSaveInstanceState()); + } + } + } + + /** + * Invoked when the panels should thaw their state from a previously frozen state. + * + * @param icicles The state saved by {@link #savePanelState} that needs to be thawed. + */ + private void restorePanelState(SparseArray<Parcelable> icicles) { + PanelFeatureState st; + for (int curFeatureId = icicles.size() - 1; curFeatureId >= 0; curFeatureId--) { + st = getPanelState(curFeatureId, false /* required */); + if (st == null) { + // The panel must not have been required, and is currently not around, skip it + continue; + } + + st.onRestoreInstanceState(icicles.get(curFeatureId)); + } + + /* + * Implementation note: call openPanelsAfterRestore later to actually open the + * restored panels. + */ + } + + /** + * Opens the panels that have had their state restored. This should be + * called sometime after {@link #restorePanelState} when it is safe to add + * to the window manager. + */ + private void openPanelsAfterRestore() { + PanelFeatureState[] panels = mPanels; + + if (panels == null) { + return; + } + + PanelFeatureState st; + for (int i = panels.length - 1; i >= 0; i--) { + st = panels[i]; + // We restore the panel if it was last open; we skip it if it + // now is open, to avoid a race condition if the user immediately + // opens it when we are resuming. + if ((st != null) && !st.isOpen && st.wasLastOpen) { + st.isInExpandedMode = st.wasLastExpanded; + openPanel(st, null); + } + } + } + + private final class DecorView extends FrameLayout implements RootViewSurfaceTaker { + /* package */int mDefaultOpacity = PixelFormat.OPAQUE; + + /** The feature ID of the panel, or -1 if this is the application's DecorView */ + private final int mFeatureId; + + private final Rect mDrawingBounds = new Rect(); + + private final Rect mBackgroundPadding = new Rect(); + + private final Rect mFramePadding = new Rect(); + + private final Rect mFrameOffsets = new Rect(); + + private boolean mChanging; + + private Drawable mMenuBackground; + private boolean mWatchingForMenu; + private int mDownY; + + public DecorView(Context context, int featureId) { + super(context); + mFeatureId = featureId; + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + final int keyCode = event.getKeyCode(); + final boolean isDown = event.getAction() == KeyEvent.ACTION_DOWN; + + /* + * If the user hits another key within the play sound delay, then + * cancel the sound + */ + if (keyCode != KeyEvent.KEYCODE_VOLUME_DOWN && keyCode != KeyEvent.KEYCODE_VOLUME_UP + && mVolumeKeyUpTime + VolumePanel.PLAY_SOUND_DELAY + > SystemClock.uptimeMillis()) { + /* + * The user has hit another key during the delay (e.g., 300ms) + * since the last volume key up, so cancel any sounds. + */ + AudioManager audioManager = (AudioManager) getContext().getSystemService( + Context.AUDIO_SERVICE); + if (audioManager != null) { + audioManager.adjustSuggestedStreamVolume(AudioManager.ADJUST_SAME, + mVolumeControlStreamType, AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE); + } + } + + if (isDown && (event.getRepeatCount() == 0)) { + // First handle chording of panel key: if a panel key is held + // but not released, try to execute a shortcut in it. + if ((mPanelChordingKey > 0) && (mPanelChordingKey != keyCode)) { + // Perform the shortcut (mPreparedPanel can be null since + // global shortcuts (such as search) don't rely on a + // prepared panel or menu). + boolean handled = performPanelShortcut(mPreparedPanel, keyCode, event, + Menu.FLAG_PERFORM_NO_CLOSE); + + if (!handled) { + /* + * If not handled, then pass it to the view hierarchy + * and anyone else that may be interested. + */ + handled = dispatchKeyShortcutEvent(event); + + if (handled && mPreparedPanel != null) { + mPreparedPanel.isHandled = true; + } + } + + if (handled) { + return true; + } + } + + // If a panel is open, perform a shortcut on it without the + // chorded panel key + if ((mPreparedPanel != null) && mPreparedPanel.isOpen) { + if (performPanelShortcut(mPreparedPanel, keyCode, event, 0)) { + return true; + } + } + } + + final Callback cb = getCallback(); + final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event) + : super.dispatchKeyEvent(event); + if (handled) { + return true; + } + return isDown ? PhoneWindow.this.onKeyDown(mFeatureId, event.getKeyCode(), event) + : PhoneWindow.this.onKeyUp(mFeatureId, event.getKeyCode(), event); + } + + @Override + public boolean dispatchTouchEvent(MotionEvent ev) { + final Callback cb = getCallback(); + return cb != null && mFeatureId < 0 ? cb.dispatchTouchEvent(ev) : super + .dispatchTouchEvent(ev); + } + + @Override + public boolean dispatchTrackballEvent(MotionEvent ev) { + final Callback cb = getCallback(); + return cb != null && mFeatureId < 0 ? cb.dispatchTrackballEvent(ev) : super + .dispatchTrackballEvent(ev); + } + + public boolean superDispatchKeyEvent(KeyEvent event) { + return super.dispatchKeyEvent(event); + } + + public boolean superDispatchTouchEvent(MotionEvent event) { + return super.dispatchTouchEvent(event); + } + + public boolean superDispatchTrackballEvent(MotionEvent event) { + return super.dispatchTrackballEvent(event); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + return onInterceptTouchEvent(event); + } + + private boolean isOutOfBounds(int x, int y) { + return x < -5 || y < -5 || x > (getWidth() + 5) + || y > (getHeight() + 5); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent event) { + int action = event.getAction(); + if (mFeatureId >= 0) { + if (action == MotionEvent.ACTION_DOWN) { + int x = (int)event.getX(); + int y = (int)event.getY(); + if (isOutOfBounds(x, y)) { + closePanel(mFeatureId); + return true; + } + } + } + + if (!SWEEP_OPEN_MENU) { + return false; + } + + if (mFeatureId >= 0) { + if (action == MotionEvent.ACTION_DOWN) { + Log.i(TAG, "Watchiing!"); + mWatchingForMenu = true; + mDownY = (int) event.getY(); + return false; + } + + if (!mWatchingForMenu) { + return false; + } + + int y = (int)event.getY(); + if (action == MotionEvent.ACTION_MOVE) { + if (y > (mDownY+30)) { + Log.i(TAG, "Closing!"); + closePanel(mFeatureId); + mWatchingForMenu = false; + return true; + } + } else if (action == MotionEvent.ACTION_UP) { + mWatchingForMenu = false; + } + + return false; + } + + //Log.i(TAG, "Intercept: action=" + action + " y=" + event.getY() + // + " (in " + getHeight() + ")"); + + if (action == MotionEvent.ACTION_DOWN) { + int y = (int)event.getY(); + if (y >= (getHeight()-5) && !hasChildren()) { + Log.i(TAG, "Watchiing!"); + mWatchingForMenu = true; + } + return false; + } + + if (!mWatchingForMenu) { + return false; + } + + int y = (int)event.getY(); + if (action == MotionEvent.ACTION_MOVE) { + if (y < (getHeight()-30)) { + Log.i(TAG, "Opening!"); + openPanel(FEATURE_OPTIONS_PANEL, new KeyEvent( + KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MENU)); + mWatchingForMenu = false; + return true; + } + } else if (action == MotionEvent.ACTION_UP) { + mWatchingForMenu = false; + } + + return false; + } + + @Override + public void sendAccessibilityEvent(int eventType) { + if (!AccessibilityManager.getInstance(mContext).isEnabled()) { + return; + } + + // if we are showing a feature that should be announced and one child + // make this child the event source since this is the feature itself + // otherwise the callback will take over and announce its client + if ((mFeatureId == FEATURE_OPTIONS_PANEL || + mFeatureId == FEATURE_CONTEXT_MENU || + mFeatureId == FEATURE_PROGRESS || + mFeatureId == FEATURE_INDETERMINATE_PROGRESS) + && getChildCount() == 1) { + getChildAt(0).sendAccessibilityEvent(eventType); + } else { + super.sendAccessibilityEvent(eventType); + } + } + + @Override + public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { + final Callback cb = getCallback(); + if (cb != null) { + if (cb.dispatchPopulateAccessibilityEvent(event)) { + return true; + } + } + return super.dispatchPopulateAccessibilityEvent(event); + } + + @Override + protected boolean setFrame(int l, int t, int r, int b) { + boolean changed = super.setFrame(l, t, r, b); + if (changed) { + final Rect drawingBounds = mDrawingBounds; + getDrawingRect(drawingBounds); + + Drawable fg = getForeground(); + if (fg != null) { + final Rect frameOffsets = mFrameOffsets; + drawingBounds.left += frameOffsets.left; + drawingBounds.top += frameOffsets.top; + drawingBounds.right -= frameOffsets.right; + drawingBounds.bottom -= frameOffsets.bottom; + fg.setBounds(drawingBounds); + final Rect framePadding = mFramePadding; + drawingBounds.left += framePadding.left - frameOffsets.left; + drawingBounds.top += framePadding.top - frameOffsets.top; + drawingBounds.right -= framePadding.right - frameOffsets.right; + drawingBounds.bottom -= framePadding.bottom - frameOffsets.bottom; + } + + Drawable bg = getBackground(); + if (bg != null) { + bg.setBounds(drawingBounds); + } + + if (SWEEP_OPEN_MENU) { + if (mMenuBackground == null && mFeatureId < 0 + && getAttributes().height + == WindowManager.LayoutParams.MATCH_PARENT) { + mMenuBackground = getContext().getResources().getDrawable( + com.android.internal.R.drawable.menu_background); + } + if (mMenuBackground != null) { + mMenuBackground.setBounds(drawingBounds.left, + drawingBounds.bottom-6, drawingBounds.right, + drawingBounds.bottom+20); + } + } + } + return changed; + } + + @Override + public void draw(Canvas canvas) { + super.draw(canvas); + + if (mMenuBackground != null) { + mMenuBackground.draw(canvas); + } + } + + + @Override + public boolean showContextMenuForChild(View originalView) { + // Reuse the context menu builder + if (mContextMenu == null) { + mContextMenu = new ContextMenuBuilder(getContext()); + mContextMenu.setCallback(mContextMenuCallback); + } else { + mContextMenu.clearAll(); + } + + mContextMenuHelper = mContextMenu.show(originalView, originalView.getWindowToken()); + return mContextMenuHelper != null; + } + + public void startChanging() { + mChanging = true; + } + + public void finishChanging() { + mChanging = false; + drawableChanged(); + } + + public void setWindowBackground(Drawable drawable) { + if (getBackground() != drawable) { + setBackgroundDrawable(drawable); + if (drawable != null) { + drawable.getPadding(mBackgroundPadding); + } else { + mBackgroundPadding.setEmpty(); + } + drawableChanged(); + } + } + + public void setWindowFrame(Drawable drawable) { + if (getForeground() != drawable) { + setForeground(drawable); + if (drawable != null) { + drawable.getPadding(mFramePadding); + } else { + mFramePadding.setEmpty(); + } + drawableChanged(); + } + } + + @Override + protected boolean fitSystemWindows(Rect insets) { + mFrameOffsets.set(insets); + if (getForeground() != null) { + drawableChanged(); + } + return super.fitSystemWindows(insets); + } + + private void drawableChanged() { + if (mChanging) { + return; + } + + setPadding(mFramePadding.left + mBackgroundPadding.left, mFramePadding.top + + mBackgroundPadding.top, mFramePadding.right + mBackgroundPadding.right, + mFramePadding.bottom + mBackgroundPadding.bottom); + requestLayout(); + invalidate(); + + int opacity = PixelFormat.OPAQUE; + + // Note: if there is no background, we will assume opaque. The + // common case seems to be that an application sets there to be + // no background so it can draw everything itself. For that, + // we would like to assume OPAQUE and let the app force it to + // the slower TRANSLUCENT mode if that is really what it wants. + Drawable bg = getBackground(); + Drawable fg = getForeground(); + if (bg != null) { + if (fg == null) { + opacity = bg.getOpacity(); + } else if (mFramePadding.left <= 0 && mFramePadding.top <= 0 + && mFramePadding.right <= 0 && mFramePadding.bottom <= 0) { + // If the frame padding is zero, then we can be opaque + // if either the frame -or- the background is opaque. + int fop = fg.getOpacity(); + int bop = bg.getOpacity(); + if (Config.LOGV) + Log.v(TAG, "Background opacity: " + bop + ", Frame opacity: " + fop); + if (fop == PixelFormat.OPAQUE || bop == PixelFormat.OPAQUE) { + opacity = PixelFormat.OPAQUE; + } else if (fop == PixelFormat.UNKNOWN) { + opacity = bop; + } else if (bop == PixelFormat.UNKNOWN) { + opacity = fop; + } else { + opacity = Drawable.resolveOpacity(fop, bop); + } + } else { + // For now we have to assume translucent if there is a + // frame with padding... there is no way to tell if the + // frame and background together will draw all pixels. + if (Config.LOGV) + Log.v(TAG, "Padding: " + mFramePadding); + opacity = PixelFormat.TRANSLUCENT; + } + } + + if (Config.LOGV) + Log.v(TAG, "Background: " + bg + ", Frame: " + fg); + if (Config.LOGV) + Log.v(TAG, "Selected default opacity: " + opacity); + + mDefaultOpacity = opacity; + if (mFeatureId < 0) { + setDefaultWindowFormat(opacity); + } + } + + @Override + public void onWindowFocusChanged(boolean hasWindowFocus) { + super.onWindowFocusChanged(hasWindowFocus); + + mPanelMayLongPress = false; + + // If the user is chording a menu shortcut, release the chord since + // this window lost focus + if (!hasWindowFocus && mPanelChordingKey != 0) { + closePanel(FEATURE_OPTIONS_PANEL); + } + + final Callback cb = getCallback(); + if (cb != null && mFeatureId < 0) { + cb.onWindowFocusChanged(hasWindowFocus); + } + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + + final Callback cb = getCallback(); + if (cb != null && mFeatureId < 0) { + cb.onAttachedToWindow(); + } + + if (mFeatureId == -1) { + /* + * The main window has been attached, try to restore any panels + * that may have been open before. This is called in cases where + * an activity is being killed for configuration change and the + * menu was open. When the activity is recreated, the menu + * should be shown again. + */ + openPanelsAfterRestore(); + } + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + + final Callback cb = getCallback(); + if (cb != null && mFeatureId < 0) { + cb.onDetachedFromWindow(); + } + } + + @Override + public void onCloseSystemDialogs(String reason) { + if (mFeatureId >= 0) { + closeAllPanels(); + } + } + + public android.view.SurfaceHolder.Callback willYouTakeTheSurface() { + return mFeatureId < 0 ? mTakeSurfaceCallback : null; + } + + public void setSurfaceType(int type) { + PhoneWindow.this.setType(type); + } + + public void setSurfaceFormat(int format) { + PhoneWindow.this.setFormat(format); + } + + public void setSurfaceKeepScreenOn(boolean keepOn) { + if (keepOn) PhoneWindow.this.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + else PhoneWindow.this.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } + } + + protected DecorView generateDecor() { + return new DecorView(getContext(), -1); + } + + protected void setFeatureFromAttrs(int featureId, TypedArray attrs, + int drawableAttr, int alphaAttr) { + Drawable d = attrs.getDrawable(drawableAttr); + if (d != null) { + requestFeature(featureId); + setFeatureDefaultDrawable(featureId, d); + } + if ((getFeatures() & (1 << featureId)) != 0) { + int alpha = attrs.getInt(alphaAttr, -1); + if (alpha >= 0) { + setFeatureDrawableAlpha(featureId, alpha); + } + } + } + + protected ViewGroup generateLayout(DecorView decor) { + // Apply data from current theme. + + TypedArray a = getWindowStyle(); + + if (false) { + System.out.println("From style:"); + String s = "Attrs:"; + for (int i = 0; i < com.android.internal.R.styleable.Window.length; i++) { + s = s + " " + Integer.toHexString(com.android.internal.R.styleable.Window[i]) + "=" + + a.getString(i); + } + System.out.println(s); + } + + mIsFloating = a.getBoolean(com.android.internal.R.styleable.Window_windowIsFloating, false); + int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR) + & (~getForcedWindowFlags()); + if (mIsFloating) { + setLayout(WRAP_CONTENT, WRAP_CONTENT); + setFlags(0, flagsToUpdate); + } else { + setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate); + } + + if (a.getBoolean(com.android.internal.R.styleable.Window_windowNoTitle, false)) { + requestFeature(FEATURE_NO_TITLE); + } + + if (a.getBoolean(com.android.internal.R.styleable.Window_windowFullscreen, false)) { + setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN&(~getForcedWindowFlags())); + } + + if (a.getBoolean(com.android.internal.R.styleable.Window_windowShowWallpaper, false)) { + setFlags(FLAG_SHOW_WALLPAPER, FLAG_SHOW_WALLPAPER&(~getForcedWindowFlags())); + } + + WindowManager.LayoutParams params = getAttributes(); + + if (!hasSoftInputMode()) { + params.softInputMode = a.getInt( + com.android.internal.R.styleable.Window_windowSoftInputMode, + params.softInputMode); + } + + if (a.getBoolean(com.android.internal.R.styleable.Window_backgroundDimEnabled, + mIsFloating)) { + /* All dialogs should have the window dimmed */ + if ((getForcedWindowFlags()&WindowManager.LayoutParams.FLAG_DIM_BEHIND) == 0) { + params.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND; + } + params.dimAmount = a.getFloat( + android.R.styleable.Window_backgroundDimAmount, 0.5f); + } + + if (params.windowAnimations == 0) { + params.windowAnimations = a.getResourceId( + com.android.internal.R.styleable.Window_windowAnimationStyle, 0); + } + + // The rest are only done if this window is not embedded; otherwise, + // the values are inherited from our container. + if (getContainer() == null) { + if (mBackgroundDrawable == null) { + if (mBackgroundResource == 0) { + mBackgroundResource = a.getResourceId( + com.android.internal.R.styleable.Window_windowBackground, 0); + } + if (mFrameResource == 0) { + mFrameResource = a.getResourceId(com.android.internal.R.styleable.Window_windowFrame, 0); + } + if (false) { + System.out.println("Background: " + + Integer.toHexString(mBackgroundResource) + " Frame: " + + Integer.toHexString(mFrameResource)); + } + } + mTextColor = a.getColor(com.android.internal.R.styleable.Window_textColor, 0xFF000000); + } + + // Inflate the window decor. + + int layoutResource; + int features = getLocalFeatures(); + // System.out.println("Features: 0x" + Integer.toHexString(features)); + if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) { + if (mIsFloating) { + layoutResource = com.android.internal.R.layout.dialog_title_icons; + } else { + layoutResource = com.android.internal.R.layout.screen_title_icons; + } + // System.out.println("Title Icons!"); + } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0) { + // Special case for a window with only a progress bar (and title). + // XXX Need to have a no-title version of embedded windows. + layoutResource = com.android.internal.R.layout.screen_progress; + // System.out.println("Progress!"); + } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) { + // Special case for a window with a custom title. + // If the window is floating, we need a dialog layout + if (mIsFloating) { + layoutResource = com.android.internal.R.layout.dialog_custom_title; + } else { + layoutResource = com.android.internal.R.layout.screen_custom_title; + } + } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) { + // If no other features and not embedded, only need a title. + // If the window is floating, we need a dialog layout + if (mIsFloating) { + layoutResource = com.android.internal.R.layout.dialog_title; + } else { + layoutResource = com.android.internal.R.layout.screen_title; + } + // System.out.println("Title!"); + } else { + // Embedded, so no decoration is needed. + layoutResource = com.android.internal.R.layout.screen_simple; + // System.out.println("Simple!"); + } + + mDecor.startChanging(); + + View in = mLayoutInflater.inflate(layoutResource, null); + decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); + + ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); + if (contentParent == null) { + throw new RuntimeException("Window couldn't find content container view"); + } + + if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) { + ProgressBar progress = getCircularProgressBar(false); + if (progress != null) { + progress.setIndeterminate(true); + } + } + + // Remaining setup -- of background and title -- that only applies + // to top-level windows. + if (getContainer() == null) { + Drawable drawable = mBackgroundDrawable; + if (mBackgroundResource != 0) { + drawable = getContext().getResources().getDrawable(mBackgroundResource); + } + mDecor.setWindowBackground(drawable); + drawable = null; + if (mFrameResource != 0) { + drawable = getContext().getResources().getDrawable(mFrameResource); + } + mDecor.setWindowFrame(drawable); + + // System.out.println("Text=" + Integer.toHexString(mTextColor) + + // " Sel=" + Integer.toHexString(mTextSelectedColor) + + // " Title=" + Integer.toHexString(mTitleColor)); + + if (mTitleColor == 0) { + mTitleColor = mTextColor; + } + + if (mTitle != null) { + setTitle(mTitle); + } + setTitleColor(mTitleColor); + } + + mDecor.finishChanging(); + + return contentParent; + } + + private void installDecor() { + if (mDecor == null) { + mDecor = generateDecor(); + mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); + mDecor.setIsRootNamespace(true); + } + if (mContentParent == null) { + mContentParent = generateLayout(mDecor); + + mTitleView = (TextView)findViewById(com.android.internal.R.id.title); + if (mTitleView != null) { + if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) { + View titleContainer = findViewById(com.android.internal.R.id.title_container); + if (titleContainer != null) { + titleContainer.setVisibility(View.GONE); + } else { + mTitleView.setVisibility(View.GONE); + } + if (mContentParent instanceof FrameLayout) { + ((FrameLayout)mContentParent).setForeground(null); + } + } else { + mTitleView.setText(mTitle); + } + } + } + } + + private Drawable loadImageURI(Uri uri) { + try { + return Drawable.createFromStream( + getContext().getContentResolver().openInputStream(uri), null); + } catch (Exception e) { + Log.w(TAG, "Unable to open content: " + uri); + } + return null; + } + + private DrawableFeatureState getDrawableState(int featureId, boolean required) { + if ((getFeatures() & (1 << featureId)) == 0) { + if (!required) { + return null; + } + throw new RuntimeException("The feature has not been requested"); + } + + DrawableFeatureState[] ar; + if ((ar = mDrawables) == null || ar.length <= featureId) { + DrawableFeatureState[] nar = new DrawableFeatureState[featureId + 1]; + if (ar != null) { + System.arraycopy(ar, 0, nar, 0, ar.length); + } + mDrawables = ar = nar; + } + + DrawableFeatureState st = ar[featureId]; + if (st == null) { + ar[featureId] = st = new DrawableFeatureState(featureId); + } + return st; + } + + /** + * Gets a panel's state based on its feature ID. + * + * @param featureId The feature ID of the panel. + * @param required Whether the panel is required (if it is required and it + * isn't in our features, this throws an exception). + * @return The panel state. + */ + private PanelFeatureState getPanelState(int featureId, boolean required) { + return getPanelState(featureId, required, null); + } + + /** + * Gets a panel's state based on its feature ID. + * + * @param featureId The feature ID of the panel. + * @param required Whether the panel is required (if it is required and it + * isn't in our features, this throws an exception). + * @param convertPanelState Optional: If the panel state does not exist, use + * this as the panel state. + * @return The panel state. + */ + private PanelFeatureState getPanelState(int featureId, boolean required, + PanelFeatureState convertPanelState) { + if ((getFeatures() & (1 << featureId)) == 0) { + if (!required) { + return null; + } + throw new RuntimeException("The feature has not been requested"); + } + + PanelFeatureState[] ar; + if ((ar = mPanels) == null || ar.length <= featureId) { + PanelFeatureState[] nar = new PanelFeatureState[featureId + 1]; + if (ar != null) { + System.arraycopy(ar, 0, nar, 0, ar.length); + } + mPanels = ar = nar; + } + + PanelFeatureState st = ar[featureId]; + if (st == null) { + ar[featureId] = st = (convertPanelState != null) + ? convertPanelState + : new PanelFeatureState(featureId); + } + return st; + } + + @Override + public final void setChildDrawable(int featureId, Drawable drawable) { + DrawableFeatureState st = getDrawableState(featureId, true); + st.child = drawable; + updateDrawable(featureId, st, false); + } + + @Override + public final void setChildInt(int featureId, int value) { + updateInt(featureId, value, false); + } + + @Override + public boolean isShortcutKey(int keyCode, KeyEvent event) { + PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, true); + return st.menu != null && st.menu.isShortcutKey(keyCode, event); + } + + private void updateDrawable(int featureId, DrawableFeatureState st, boolean fromResume) { + // Do nothing if the decor is not yet installed... an update will + // need to be forced when we eventually become active. + if (mContentParent == null) { + return; + } + + final int featureMask = 1 << featureId; + + if ((getFeatures() & featureMask) == 0 && !fromResume) { + return; + } + + Drawable drawable = null; + if (st != null) { + drawable = st.child; + if (drawable == null) + drawable = st.local; + if (drawable == null) + drawable = st.def; + } + if ((getLocalFeatures() & featureMask) == 0) { + if (getContainer() != null) { + if (isActive() || fromResume) { + getContainer().setChildDrawable(featureId, drawable); + } + } + } else if (st != null && (st.cur != drawable || st.curAlpha != st.alpha)) { + // System.out.println("Drawable changed: old=" + st.cur + // + ", new=" + drawable); + st.cur = drawable; + st.curAlpha = st.alpha; + onDrawableChanged(featureId, drawable, st.alpha); + } + } + + private void updateInt(int featureId, int value, boolean fromResume) { + + // Do nothing if the decor is not yet installed... an update will + // need to be forced when we eventually become active. + if (mContentParent == null) { + return; + } + + final int featureMask = 1 << featureId; + + if ((getFeatures() & featureMask) == 0 && !fromResume) { + return; + } + + if ((getLocalFeatures() & featureMask) == 0) { + if (getContainer() != null) { + getContainer().setChildInt(featureId, value); + } + } else { + onIntChanged(featureId, value); + } + } + + private ImageView getLeftIconView() { + if (mLeftIconView != null) { + return mLeftIconView; + } + if (mContentParent == null) { + installDecor(); + } + return (mLeftIconView = (ImageView)findViewById(com.android.internal.R.id.left_icon)); + } + + private ProgressBar getCircularProgressBar(boolean shouldInstallDecor) { + if (mCircularProgressBar != null) { + return mCircularProgressBar; + } + if (mContentParent == null && shouldInstallDecor) { + installDecor(); + } + mCircularProgressBar = (ProgressBar)findViewById(com.android.internal.R.id.progress_circular); + mCircularProgressBar.setVisibility(View.INVISIBLE); + return mCircularProgressBar; + } + + private ProgressBar getHorizontalProgressBar(boolean shouldInstallDecor) { + if (mHorizontalProgressBar != null) { + return mHorizontalProgressBar; + } + if (mContentParent == null && shouldInstallDecor) { + installDecor(); + } + mHorizontalProgressBar = (ProgressBar)findViewById(com.android.internal.R.id.progress_horizontal); + mHorizontalProgressBar.setVisibility(View.INVISIBLE); + return mHorizontalProgressBar; + } + + private ImageView getRightIconView() { + if (mRightIconView != null) { + return mRightIconView; + } + if (mContentParent == null) { + installDecor(); + } + return (mRightIconView = (ImageView)findViewById(com.android.internal.R.id.right_icon)); + } + + /** + * Helper method for calling the {@link Callback#onPanelClosed(int, Menu)} + * callback. This method will grab whatever extra state is needed for the + * callback that isn't given in the parameters. If the panel is not open, + * this will not perform the callback. + * + * @param featureId Feature ID of the panel that was closed. Must be given. + * @param panel Panel that was closed. Optional but useful if there is no + * menu given. + * @param menu The menu that was closed. Optional, but give if you have. + */ + private void callOnPanelClosed(int featureId, PanelFeatureState panel, Menu menu) { + final Callback cb = getCallback(); + if (cb == null) + return; + + // Try to get a menu + if (menu == null) { + // Need a panel to grab the menu, so try to get that + if (panel == null) { + if ((featureId >= 0) && (featureId < mPanels.length)) { + panel = mPanels[featureId]; + } + } + + if (panel != null) { + // menu still may be null, which is okay--we tried our best + menu = panel.menu; + } + } + + // If the panel is not open, do not callback + if ((panel != null) && (!panel.isOpen)) + return; + + cb.onPanelClosed(featureId, menu); + } + + /** + * Helper method for adding launch-search to most applications. Opens the + * search window using default settings. + * + * @return true if search window opened + */ + private boolean launchDefaultSearch() { + final Callback cb = getCallback(); + if (cb == null) { + return false; + } else { + sendCloseSystemWindows("search"); + return cb.onSearchRequested(); + } + } + + @Override + public void setVolumeControlStream(int streamType) { + mVolumeControlStreamType = streamType; + } + + @Override + public int getVolumeControlStream() { + return mVolumeControlStreamType; + } + + private static final class DrawableFeatureState { + DrawableFeatureState(int _featureId) { + featureId = _featureId; + } + + final int featureId; + + int resid; + + Uri uri; + + Drawable local; + + Drawable child; + + Drawable def; + + Drawable cur; + + int alpha = 255; + + int curAlpha = 255; + } + + private static final class PanelFeatureState { + + /** Feature ID for this panel. */ + int featureId; + + // Information pulled from the style for this panel. + + int background; + + /** The background when the panel spans the entire available width. */ + int fullBackground; + + int gravity; + + int x; + + int y; + + int windowAnimations; + + /** Dynamic state of the panel. */ + DecorView decorView; + + /** The panel that was returned by onCreatePanelView(). */ + View createdPanelView; + + /** The panel that we are actually showing. */ + View shownPanelView; + + /** Use {@link #setMenu} to set this. */ + Menu menu; + + /** + * Whether the panel has been prepared (see + * {@link PhoneWindow#preparePanel}). + */ + boolean isPrepared; + + /** + * Whether an item's action has been performed. This happens in obvious + * scenarios (user clicks on menu item), but can also happen with + * chording menu+(shortcut key). + */ + boolean isHandled; + + boolean isOpen; + + /** + * True if the menu is in expanded mode, false if the menu is in icon + * mode + */ + boolean isInExpandedMode; + + public boolean qwertyMode; + + boolean refreshDecorView; + + boolean wasLastOpen; + + boolean wasLastExpanded; + + /** + * Contains the state of the menu when told to freeze. + */ + Bundle frozenMenuState; + + PanelFeatureState(int featureId) { + this.featureId = featureId; + + refreshDecorView = false; + } + + void setStyle(Context context) { + TypedArray a = context.obtainStyledAttributes(com.android.internal.R.styleable.Theme); + background = a.getResourceId( + com.android.internal.R.styleable.Theme_panelBackground, 0); + fullBackground = a.getResourceId( + com.android.internal.R.styleable.Theme_panelFullBackground, 0); + windowAnimations = a.getResourceId( + com.android.internal.R.styleable.Theme_windowAnimationStyle, 0); + a.recycle(); + } + + void setMenu(Menu menu) { + this.menu = menu; + + if (frozenMenuState != null) { + ((MenuBuilder) menu).restoreHierarchyState(frozenMenuState); + frozenMenuState = null; + } + } + + Parcelable onSaveInstanceState() { + SavedState savedState = new SavedState(); + savedState.featureId = featureId; + savedState.isOpen = isOpen; + savedState.isInExpandedMode = isInExpandedMode; + + if (menu != null) { + savedState.menuState = new Bundle(); + ((MenuBuilder) menu).saveHierarchyState(savedState.menuState); + } + + return savedState; + } + + void onRestoreInstanceState(Parcelable state) { + SavedState savedState = (SavedState) state; + featureId = savedState.featureId; + wasLastOpen = savedState.isOpen; + wasLastExpanded = savedState.isInExpandedMode; + frozenMenuState = savedState.menuState; + + /* + * A LocalActivityManager keeps the same instance of this class around. + * The first time the menu is being shown after restoring, the + * Activity.onCreateOptionsMenu should be called. But, if it is the + * same instance then menu != null and we won't call that method. + * So, clear this. Also clear any cached views. + */ + menu = null; + createdPanelView = null; + shownPanelView = null; + decorView = null; + } + + private static class SavedState implements Parcelable { + int featureId; + boolean isOpen; + boolean isInExpandedMode; + Bundle menuState; + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(featureId); + dest.writeInt(isOpen ? 1 : 0); + dest.writeInt(isInExpandedMode ? 1 : 0); + + if (isOpen) { + dest.writeBundle(menuState); + } + } + + private static SavedState readFromParcel(Parcel source) { + SavedState savedState = new SavedState(); + savedState.featureId = source.readInt(); + savedState.isOpen = source.readInt() == 1; + savedState.isInExpandedMode = source.readInt() == 1; + + if (savedState.isOpen) { + savedState.menuState = source.readBundle(); + } + + return savedState; + } + + public static final Parcelable.Creator<SavedState> CREATOR + = new Parcelable.Creator<SavedState>() { + public SavedState createFromParcel(Parcel in) { + return readFromParcel(in); + } + + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + } + + } + + /** + * Simple implementation of MenuBuilder.Callback that: + * <li> Opens a submenu when selected. + * <li> Calls back to the callback's onMenuItemSelected when an item is + * selected. + */ + private final class ContextMenuCallback implements MenuBuilder.Callback { + private int mFeatureId; + private MenuDialogHelper mSubMenuHelper; + + public ContextMenuCallback(int featureId) { + mFeatureId = featureId; + } + + public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { + if (allMenusAreClosing) { + Callback callback = getCallback(); + if (callback != null) callback.onPanelClosed(mFeatureId, menu); + + if (menu == mContextMenu) { + dismissContextMenu(); + } + + // Dismiss the submenu, if it is showing + if (mSubMenuHelper != null) { + mSubMenuHelper.dismiss(); + mSubMenuHelper = null; + } + } + } + + public void onCloseSubMenu(SubMenuBuilder menu) { + Callback callback = getCallback(); + if (callback != null) callback.onPanelClosed(mFeatureId, menu.getRootMenu()); + } + + public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) { + Callback callback = getCallback(); + return (callback != null) && callback.onMenuItemSelected(mFeatureId, item); + } + + public void onMenuModeChange(MenuBuilder menu) { + } + + public boolean onSubMenuSelected(SubMenuBuilder subMenu) { + // Set a simple callback for the submenu + subMenu.setCallback(this); + + // The window manager will give us a valid window token + mSubMenuHelper = new MenuDialogHelper(subMenu); + mSubMenuHelper.show(null); + + return true; + } + } + + void sendCloseSystemWindows() { + PhoneWindowManager.sendCloseSystemWindows(getContext(), null); + } + + void sendCloseSystemWindows(String reason) { + PhoneWindowManager.sendCloseSystemWindows(getContext(), reason); + } +} diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java new file mode 100755 index 0000000..a01e25b --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java @@ -0,0 +1,2434 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.policy.impl; + +import android.app.Activity; +import android.app.ActivityManagerNative; +import android.app.IActivityManager; +import android.app.IUiModeManager; +import android.app.UiModeManager; +import android.content.ActivityNotFoundException; +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.database.ContentObserver; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.os.Handler; +import android.os.IBinder; +import android.os.LocalPowerManager; +import android.os.PowerManager; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemClock; +import android.os.SystemProperties; +import android.os.Vibrator; +import android.provider.Settings; + +import com.android.internal.policy.PolicyManager; +import com.android.internal.statusbar.IStatusBarService; +import com.android.internal.telephony.ITelephony; +import com.android.internal.widget.PointerLocationView; + +import android.util.Config; +import android.util.EventLog; +import android.util.Log; +import android.view.Display; +import android.view.Gravity; +import android.view.HapticFeedbackConstants; +import android.view.IWindowManager; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.WindowOrientationListener; +import android.view.RawInputEvent; +import android.view.Surface; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.Window; +import android.view.WindowManager; +import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW; +import static android.view.WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN; +import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN; +import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; +import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR; +import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; +import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; +import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD; +import static android.view.WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON; +import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST; +import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; +import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; +import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD; +import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG; +import static android.view.WindowManager.LayoutParams.TYPE_PHONE; +import static android.view.WindowManager.LayoutParams.TYPE_PRIORITY_PHONE; +import static android.view.WindowManager.LayoutParams.TYPE_SEARCH_BAR; +import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR; +import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL; +import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG; +import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT; +import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR; +import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; +import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG; +import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY; +import static android.view.WindowManager.LayoutParams.TYPE_TOAST; +import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; +import android.view.WindowManagerImpl; +import android.view.WindowManagerPolicy; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; +import android.media.IAudioService; +import android.media.AudioManager; + +import java.util.ArrayList; + +/** + * WindowManagerPolicy implementation for the Android phone UI. This + * introduces a new method suffix, Lp, for an internal lock of the + * PhoneWindowManager. This is used to protect some internal state, and + * can be acquired with either thw Lw and Li lock held, so has the restrictions + * of both of those when held. + */ +public class PhoneWindowManager implements WindowManagerPolicy { + static final String TAG = "WindowManager"; + static final boolean DEBUG = false; + static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV; + static final boolean DEBUG_LAYOUT = false; + static final boolean SHOW_STARTING_ANIMATIONS = true; + static final boolean SHOW_PROCESSES_ON_ALT_MENU = false; + + // wallpaper is at the bottom, though the window manager may move it. + static final int WALLPAPER_LAYER = 2; + static final int APPLICATION_LAYER = 2; + static final int PHONE_LAYER = 3; + static final int SEARCH_BAR_LAYER = 4; + static final int STATUS_BAR_PANEL_LAYER = 5; + static final int SYSTEM_DIALOG_LAYER = 6; + // toasts and the plugged-in battery thing + static final int TOAST_LAYER = 7; + static final int STATUS_BAR_LAYER = 8; + // SIM errors and unlock. Not sure if this really should be in a high layer. + static final int PRIORITY_PHONE_LAYER = 9; + // like the ANR / app crashed dialogs + static final int SYSTEM_ALERT_LAYER = 10; + // system-level error dialogs + static final int SYSTEM_ERROR_LAYER = 11; + // on-screen keyboards and other such input method user interfaces go here. + static final int INPUT_METHOD_LAYER = 12; + // on-screen keyboards and other such input method user interfaces go here. + static final int INPUT_METHOD_DIALOG_LAYER = 13; + // the keyguard; nothing on top of these can take focus, since they are + // responsible for power management when displayed. + static final int KEYGUARD_LAYER = 14; + static final int KEYGUARD_DIALOG_LAYER = 15; + // things in here CAN NOT take focus, but are shown on top of everything else. + static final int SYSTEM_OVERLAY_LAYER = 16; + + static final int APPLICATION_MEDIA_SUBLAYER = -2; + static final int APPLICATION_MEDIA_OVERLAY_SUBLAYER = -1; + static final int APPLICATION_PANEL_SUBLAYER = 1; + static final int APPLICATION_SUB_PANEL_SUBLAYER = 2; + + static final float SLIDE_TOUCH_EVENT_SIZE_LIMIT = 0.6f; + + // Debugging: set this to have the system act like there is no hard keyboard. + static final boolean KEYBOARD_ALWAYS_HIDDEN = false; + + static public final String SYSTEM_DIALOG_REASON_KEY = "reason"; + static public final String SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS = "globalactions"; + static public final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps"; + static public final String SYSTEM_DIALOG_REASON_HOME_KEY = "homekey"; + + final Object mLock = new Object(); + + Context mContext; + IWindowManager mWindowManager; + LocalPowerManager mPowerManager; + Vibrator mVibrator; // Vibrator for giving feedback of orientation changes + + // Vibrator pattern for haptic feedback of a long press. + long[] mLongPressVibePattern; + + // Vibrator pattern for haptic feedback of virtual key press. + long[] mVirtualKeyVibePattern; + + // Vibrator pattern for a short vibration. + long[] mKeyboardTapVibePattern; + + // Vibrator pattern for haptic feedback during boot when safe mode is disabled. + long[] mSafeModeDisabledVibePattern; + + // Vibrator pattern for haptic feedback during boot when safe mode is enabled. + long[] mSafeModeEnabledVibePattern; + + /** If true, hitting shift & menu will broadcast Intent.ACTION_BUG_REPORT */ + boolean mEnableShiftMenuBugReports = false; + + boolean mSafeMode; + WindowState mStatusBar = null; + final ArrayList<WindowState> mStatusBarPanels = new ArrayList<WindowState>(); + WindowState mKeyguard = null; + KeyguardViewMediator mKeyguardMediator; + GlobalActions mGlobalActions; + boolean mShouldTurnOffOnKeyUp; + RecentApplicationsDialog mRecentAppsDialog; + Handler mHandler; + + boolean mSystemReady; + boolean mLidOpen; + int mUiMode = Configuration.UI_MODE_TYPE_NORMAL; + int mDockMode = Intent.EXTRA_DOCK_STATE_UNDOCKED; + int mLidOpenRotation; + int mCarDockRotation; + int mDeskDockRotation; + boolean mCarDockEnablesAccelerometer; + boolean mDeskDockEnablesAccelerometer; + int mLidKeyboardAccessibility; + int mLidNavigationAccessibility; + boolean mScreenOn = false; + boolean mOrientationSensorEnabled = false; + int mCurrentAppOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; + static final int DEFAULT_ACCELEROMETER_ROTATION = 0; + int mAccelerometerDefault = DEFAULT_ACCELEROMETER_ROTATION; + boolean mHasSoftInput = false; + + int mPointerLocationMode = 0; + PointerLocationView mPointerLocationView = null; + + // The current size of the screen. + int mW, mH; + // During layout, the current screen borders with all outer decoration + // (status bar, input method dock) accounted for. + int mCurLeft, mCurTop, mCurRight, mCurBottom; + // During layout, the frame in which content should be displayed + // to the user, accounting for all screen decoration except for any + // space they deem as available for other content. This is usually + // the same as mCur*, but may be larger if the screen decor has supplied + // content insets. + int mContentLeft, mContentTop, mContentRight, mContentBottom; + // During layout, the current screen borders along with input method + // windows are placed. + int mDockLeft, mDockTop, mDockRight, mDockBottom; + // During layout, the layer at which the doc window is placed. + int mDockLayer; + + static final Rect mTmpParentFrame = new Rect(); + static final Rect mTmpDisplayFrame = new Rect(); + static final Rect mTmpContentFrame = new Rect(); + static final Rect mTmpVisibleFrame = new Rect(); + + WindowState mTopFullscreenOpaqueWindowState; + boolean mForceStatusBar; + boolean mHideLockScreen; + boolean mDismissKeyguard; + boolean mHomePressed; + Intent mHomeIntent; + Intent mCarDockIntent; + Intent mDeskDockIntent; + boolean mSearchKeyPressed; + boolean mConsumeSearchKeyUp; + + // support for activating the lock screen while the screen is on + boolean mAllowLockscreenWhenOn; + int mLockScreenTimeout; + boolean mLockScreenTimerActive; + + // Behavior of ENDCALL Button. (See Settings.System.END_BUTTON_BEHAVIOR.) + int mEndcallBehavior; + + // Behavior of POWER button while in-call and screen on. + // (See Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR.) + int mIncallPowerBehavior; + + int mLandscapeRotation = -1; + int mPortraitRotation = -1; + + // Nothing to see here, move along... + int mFancyRotationAnimation; + + ShortcutManager mShortcutManager; + PowerManager.WakeLock mBroadcastWakeLock; + + class SettingsObserver extends ContentObserver { + SettingsObserver(Handler handler) { + super(handler); + } + + void observe() { + ContentResolver resolver = mContext.getContentResolver(); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.END_BUTTON_BEHAVIOR), false, this); + resolver.registerContentObserver(Settings.Secure.getUriFor( + Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR), false, this); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.ACCELEROMETER_ROTATION), false, this); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.SCREEN_OFF_TIMEOUT), false, this); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.POINTER_LOCATION), false, this); + resolver.registerContentObserver(Settings.Secure.getUriFor( + Settings.Secure.DEFAULT_INPUT_METHOD), false, this); + resolver.registerContentObserver(Settings.System.getUriFor( + "fancy_rotation_anim"), false, this); + updateSettings(); + } + + @Override public void onChange(boolean selfChange) { + updateSettings(); + try { + mWindowManager.setRotation(USE_LAST_ROTATION, false, + mFancyRotationAnimation); + } catch (RemoteException e) { + // Ignore + } + } + } + + class MyOrientationListener extends WindowOrientationListener { + MyOrientationListener(Context context) { + super(context); + } + + @Override + public void onOrientationChanged(int rotation) { + // Send updates based on orientation value + if (localLOGV) Log.v(TAG, "onOrientationChanged, rotation changed to " +rotation); + try { + mWindowManager.setRotation(rotation, false, + mFancyRotationAnimation); + } catch (RemoteException e) { + // Ignore + + } + } + } + MyOrientationListener mOrientationListener; + + boolean useSensorForOrientationLp(int appOrientation) { + // The app says use the sensor. + if (appOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR) { + return true; + } + // The user preference says we can rotate, and the app is willing to rotate. + if (mAccelerometerDefault != 0 && + (appOrientation == ActivityInfo.SCREEN_ORIENTATION_USER + || appOrientation == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED)) { + return true; + } + // We're in a dock that has a rotation affinity, an the app is willing to rotate. + if ((mCarDockEnablesAccelerometer && mDockMode == Intent.EXTRA_DOCK_STATE_CAR) + || (mDeskDockEnablesAccelerometer && mDockMode == Intent.EXTRA_DOCK_STATE_DESK)) { + // Note we override the nosensor flag here. + if (appOrientation == ActivityInfo.SCREEN_ORIENTATION_USER + || appOrientation == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED + || appOrientation == ActivityInfo.SCREEN_ORIENTATION_NOSENSOR) { + return true; + } + } + // Else, don't use the sensor. + return false; + } + + /* + * We always let the sensor be switched on by default except when + * the user has explicitly disabled sensor based rotation or when the + * screen is switched off. + */ + boolean needSensorRunningLp() { + if (mCurrentAppOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR) { + // If the application has explicitly requested to follow the + // orientation, then we need to turn the sensor or. + return true; + } + if ((mCarDockEnablesAccelerometer && mDockMode == Intent.EXTRA_DOCK_STATE_CAR) || + (mDeskDockEnablesAccelerometer && mDockMode == Intent.EXTRA_DOCK_STATE_DESK)) { + // enable accelerometer if we are docked in a dock that enables accelerometer + // orientation management, + return true; + } + if (mAccelerometerDefault == 0) { + // If the setting for using the sensor by default is enabled, then + // we will always leave it on. Note that the user could go to + // a window that forces an orientation that does not use the + // sensor and in theory we could turn it off... however, when next + // turning it on we won't have a good value for the current + // orientation for a little bit, which can cause orientation + // changes to lag, so we'd like to keep it always on. (It will + // still be turned off when the screen is off.) + return false; + } + return true; + } + + /* + * Various use cases for invoking this function + * screen turning off, should always disable listeners if already enabled + * screen turned on and current app has sensor based orientation, enable listeners + * if not already enabled + * screen turned on and current app does not have sensor orientation, disable listeners if + * already enabled + * screen turning on and current app has sensor based orientation, enable listeners if needed + * screen turning on and current app has nosensor based orientation, do nothing + */ + void updateOrientationListenerLp() { + if (!mOrientationListener.canDetectOrientation()) { + // If sensor is turned off or nonexistent for some reason + return; + } + //Could have been invoked due to screen turning on or off or + //change of the currently visible window's orientation + if (localLOGV) Log.v(TAG, "Screen status="+mScreenOn+ + ", current orientation="+mCurrentAppOrientation+ + ", SensorEnabled="+mOrientationSensorEnabled); + boolean disable = true; + if (mScreenOn) { + if (needSensorRunningLp()) { + disable = false; + //enable listener if not already enabled + if (!mOrientationSensorEnabled) { + mOrientationListener.enable(); + if(localLOGV) Log.v(TAG, "Enabling listeners"); + mOrientationSensorEnabled = true; + } + } + } + //check if sensors need to be disabled + if (disable && mOrientationSensorEnabled) { + mOrientationListener.disable(); + if(localLOGV) Log.v(TAG, "Disabling listeners"); + mOrientationSensorEnabled = false; + } + } + + Runnable mPowerLongPress = new Runnable() { + public void run() { + mShouldTurnOffOnKeyUp = false; + performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false); + sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS); + showGlobalActionsDialog(); + } + }; + + void showGlobalActionsDialog() { + if (mGlobalActions == null) { + mGlobalActions = new GlobalActions(mContext); + } + final boolean keyguardShowing = mKeyguardMediator.isShowingAndNotHidden(); + mGlobalActions.showDialog(keyguardShowing, isDeviceProvisioned()); + if (keyguardShowing) { + // since it took two seconds of long press to bring this up, + // poke the wake lock so they have some time to see the dialog. + mKeyguardMediator.pokeWakelock(); + } + } + + boolean isDeviceProvisioned() { + return Settings.Secure.getInt( + mContext.getContentResolver(), Settings.Secure.DEVICE_PROVISIONED, 0) != 0; + } + + /** + * When a home-key longpress expires, close other system windows and launch the recent apps + */ + Runnable mHomeLongPress = new Runnable() { + public void run() { + /* + * Eat the longpress so it won't dismiss the recent apps dialog when + * the user lets go of the home key + */ + mHomePressed = false; + performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false); + sendCloseSystemWindows(SYSTEM_DIALOG_REASON_RECENT_APPS); + showRecentAppsDialog(); + } + }; + + /** + * Create (if necessary) and launch the recent apps dialog + */ + void showRecentAppsDialog() { + if (mRecentAppsDialog == null) { + mRecentAppsDialog = new RecentApplicationsDialog(mContext); + } + mRecentAppsDialog.show(); + } + + /** {@inheritDoc} */ + public void init(Context context, IWindowManager windowManager, + LocalPowerManager powerManager) { + mContext = context; + mWindowManager = windowManager; + mPowerManager = powerManager; + mKeyguardMediator = new KeyguardViewMediator(context, this, powerManager); + mHandler = new Handler(); + mOrientationListener = new MyOrientationListener(mContext); + SettingsObserver settingsObserver = new SettingsObserver(mHandler); + settingsObserver.observe(); + mShortcutManager = new ShortcutManager(context, mHandler); + mShortcutManager.observe(); + mHomeIntent = new Intent(Intent.ACTION_MAIN, null); + mHomeIntent.addCategory(Intent.CATEGORY_HOME); + mHomeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); + mCarDockIntent = new Intent(Intent.ACTION_MAIN, null); + mCarDockIntent.addCategory(Intent.CATEGORY_CAR_DOCK); + mCarDockIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); + mDeskDockIntent = new Intent(Intent.ACTION_MAIN, null); + mDeskDockIntent.addCategory(Intent.CATEGORY_DESK_DOCK); + mDeskDockIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); + PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE); + mBroadcastWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, + "PhoneWindowManager.mBroadcastWakeLock"); + mEnableShiftMenuBugReports = "1".equals(SystemProperties.get("ro.debuggable")); + mLidOpenRotation = readRotation( + com.android.internal.R.integer.config_lidOpenRotation); + mCarDockRotation = readRotation( + com.android.internal.R.integer.config_carDockRotation); + mDeskDockRotation = readRotation( + com.android.internal.R.integer.config_deskDockRotation); + mCarDockEnablesAccelerometer = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_carDockEnablesAccelerometer); + mDeskDockEnablesAccelerometer = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_deskDockEnablesAccelerometer); + mLidKeyboardAccessibility = mContext.getResources().getInteger( + com.android.internal.R.integer.config_lidKeyboardAccessibility); + mLidNavigationAccessibility = mContext.getResources().getInteger( + com.android.internal.R.integer.config_lidNavigationAccessibility); + // register for dock events + IntentFilter filter = new IntentFilter(); + filter.addAction(UiModeManager.ACTION_ENTER_CAR_MODE); + filter.addAction(UiModeManager.ACTION_EXIT_CAR_MODE); + filter.addAction(UiModeManager.ACTION_ENTER_DESK_MODE); + filter.addAction(UiModeManager.ACTION_EXIT_DESK_MODE); + filter.addAction(Intent.ACTION_DOCK_EVENT); + Intent intent = context.registerReceiver(mDockReceiver, filter); + if (intent != null) { + // Retrieve current sticky dock event broadcast. + mDockMode = intent.getIntExtra(Intent.EXTRA_DOCK_STATE, + Intent.EXTRA_DOCK_STATE_UNDOCKED); + } + mVibrator = new Vibrator(); + mLongPressVibePattern = getLongIntArray(mContext.getResources(), + com.android.internal.R.array.config_longPressVibePattern); + mVirtualKeyVibePattern = getLongIntArray(mContext.getResources(), + com.android.internal.R.array.config_virtualKeyVibePattern); + mKeyboardTapVibePattern = getLongIntArray(mContext.getResources(), + com.android.internal.R.array.config_keyboardTapVibePattern); + mSafeModeDisabledVibePattern = getLongIntArray(mContext.getResources(), + com.android.internal.R.array.config_safeModeDisabledVibePattern); + mSafeModeEnabledVibePattern = getLongIntArray(mContext.getResources(), + com.android.internal.R.array.config_safeModeEnabledVibePattern); + } + + public void updateSettings() { + ContentResolver resolver = mContext.getContentResolver(); + boolean updateRotation = false; + View addView = null; + View removeView = null; + synchronized (mLock) { + mEndcallBehavior = Settings.System.getInt(resolver, + Settings.System.END_BUTTON_BEHAVIOR, + Settings.System.END_BUTTON_BEHAVIOR_DEFAULT); + mIncallPowerBehavior = Settings.Secure.getInt(resolver, + Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR, + Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_DEFAULT); + mFancyRotationAnimation = Settings.System.getInt(resolver, + "fancy_rotation_anim", 0) != 0 ? 0x80 : 0; + int accelerometerDefault = Settings.System.getInt(resolver, + Settings.System.ACCELEROMETER_ROTATION, DEFAULT_ACCELEROMETER_ROTATION); + if (mAccelerometerDefault != accelerometerDefault) { + mAccelerometerDefault = accelerometerDefault; + updateOrientationListenerLp(); + } + if (mSystemReady) { + int pointerLocation = Settings.System.getInt(resolver, + Settings.System.POINTER_LOCATION, 0); + if (mPointerLocationMode != pointerLocation) { + mPointerLocationMode = pointerLocation; + if (pointerLocation != 0) { + if (mPointerLocationView == null) { + mPointerLocationView = new PointerLocationView(mContext); + mPointerLocationView.setPrintCoords(false); + addView = mPointerLocationView; + } + } else { + removeView = mPointerLocationView; + mPointerLocationView = null; + } + } + } + // use screen off timeout setting as the timeout for the lockscreen + mLockScreenTimeout = Settings.System.getInt(resolver, + Settings.System.SCREEN_OFF_TIMEOUT, 0); + String imId = Settings.Secure.getString(resolver, + Settings.Secure.DEFAULT_INPUT_METHOD); + boolean hasSoftInput = imId != null && imId.length() > 0; + if (mHasSoftInput != hasSoftInput) { + mHasSoftInput = hasSoftInput; + updateRotation = true; + } + } + if (updateRotation) { + updateRotation(0); + } + if (addView != null) { + WindowManager.LayoutParams lp = new WindowManager.LayoutParams( + WindowManager.LayoutParams.MATCH_PARENT, + WindowManager.LayoutParams.MATCH_PARENT); + lp.type = WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY; + lp.flags = + WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE| + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE| + WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; + lp.format = PixelFormat.TRANSLUCENT; + lp.setTitle("PointerLocation"); + WindowManagerImpl wm = (WindowManagerImpl) + mContext.getSystemService(Context.WINDOW_SERVICE); + wm.addView(addView, lp); + } + if (removeView != null) { + WindowManagerImpl wm = (WindowManagerImpl) + mContext.getSystemService(Context.WINDOW_SERVICE); + wm.removeView(removeView); + } + } + + private int readRotation(int resID) { + try { + int rotation = mContext.getResources().getInteger(resID); + switch (rotation) { + case 0: + return Surface.ROTATION_0; + case 90: + return Surface.ROTATION_90; + case 180: + return Surface.ROTATION_180; + case 270: + return Surface.ROTATION_270; + } + } catch (Resources.NotFoundException e) { + // fall through + } + return -1; + } + + /** {@inheritDoc} */ + public int checkAddPermission(WindowManager.LayoutParams attrs) { + int type = attrs.type; + + if (type < WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW + || type > WindowManager.LayoutParams.LAST_SYSTEM_WINDOW) { + return WindowManagerImpl.ADD_OKAY; + } + String permission = null; + switch (type) { + case TYPE_TOAST: + // XXX right now the app process has complete control over + // this... should introduce a token to let the system + // monitor/control what they are doing. + break; + case TYPE_INPUT_METHOD: + case TYPE_WALLPAPER: + // The window manager will check these. + break; + case TYPE_PHONE: + case TYPE_PRIORITY_PHONE: + case TYPE_SYSTEM_ALERT: + case TYPE_SYSTEM_ERROR: + case TYPE_SYSTEM_OVERLAY: + permission = android.Manifest.permission.SYSTEM_ALERT_WINDOW; + break; + default: + permission = android.Manifest.permission.INTERNAL_SYSTEM_WINDOW; + } + if (permission != null) { + if (mContext.checkCallingOrSelfPermission(permission) + != PackageManager.PERMISSION_GRANTED) { + return WindowManagerImpl.ADD_PERMISSION_DENIED; + } + } + return WindowManagerImpl.ADD_OKAY; + } + + public void adjustWindowParamsLw(WindowManager.LayoutParams attrs) { + switch (attrs.type) { + case TYPE_SYSTEM_OVERLAY: + case TYPE_TOAST: + // These types of windows can't receive input events. + attrs.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; + break; + } + } + + void readLidState() { + try { + int sw = mWindowManager.getSwitchState(RawInputEvent.SW_LID); + if (sw >= 0) { + mLidOpen = sw == 0; + } + } catch (RemoteException e) { + // Ignore + } + } + + private int determineHiddenState(boolean lidOpen, + int mode, int hiddenValue, int visibleValue) { + switch (mode) { + case 1: + return lidOpen ? visibleValue : hiddenValue; + case 2: + return lidOpen ? hiddenValue : visibleValue; + } + return visibleValue; + } + + /** {@inheritDoc} */ + public void adjustConfigurationLw(Configuration config) { + readLidState(); + final boolean lidOpen = !KEYBOARD_ALWAYS_HIDDEN && mLidOpen; + mPowerManager.setKeyboardVisibility(lidOpen); + config.hardKeyboardHidden = determineHiddenState(lidOpen, + mLidKeyboardAccessibility, Configuration.HARDKEYBOARDHIDDEN_YES, + Configuration.HARDKEYBOARDHIDDEN_NO); + config.navigationHidden = determineHiddenState(lidOpen, + mLidNavigationAccessibility, Configuration.NAVIGATIONHIDDEN_YES, + Configuration.NAVIGATIONHIDDEN_NO); + config.keyboardHidden = (config.hardKeyboardHidden + == Configuration.HARDKEYBOARDHIDDEN_NO || mHasSoftInput) + ? Configuration.KEYBOARDHIDDEN_NO + : Configuration.KEYBOARDHIDDEN_YES; + } + + public boolean isCheekPressedAgainstScreen(MotionEvent ev) { + if(ev.getSize() > SLIDE_TOUCH_EVENT_SIZE_LIMIT) { + return true; + } + int size = ev.getHistorySize(); + for(int i = 0; i < size; i++) { + if(ev.getHistoricalSize(i) > SLIDE_TOUCH_EVENT_SIZE_LIMIT) { + return true; + } + } + return false; + } + + public void dispatchedPointerEventLw(MotionEvent ev, int targetX, int targetY) { + if (mPointerLocationView == null) { + return; + } + synchronized (mLock) { + if (mPointerLocationView == null) { + return; + } + ev.offsetLocation(targetX, targetY); + mPointerLocationView.addTouchEvent(ev); + ev.offsetLocation(-targetX, -targetY); + } + } + + /** {@inheritDoc} */ + public int windowTypeToLayerLw(int type) { + if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) { + return APPLICATION_LAYER; + } + switch (type) { + case TYPE_STATUS_BAR: + return STATUS_BAR_LAYER; + case TYPE_STATUS_BAR_PANEL: + return STATUS_BAR_PANEL_LAYER; + case TYPE_SYSTEM_DIALOG: + return SYSTEM_DIALOG_LAYER; + case TYPE_SEARCH_BAR: + return SEARCH_BAR_LAYER; + case TYPE_PHONE: + return PHONE_LAYER; + case TYPE_KEYGUARD: + return KEYGUARD_LAYER; + case TYPE_KEYGUARD_DIALOG: + return KEYGUARD_DIALOG_LAYER; + case TYPE_SYSTEM_ALERT: + return SYSTEM_ALERT_LAYER; + case TYPE_SYSTEM_ERROR: + return SYSTEM_ERROR_LAYER; + case TYPE_INPUT_METHOD: + return INPUT_METHOD_LAYER; + case TYPE_INPUT_METHOD_DIALOG: + return INPUT_METHOD_DIALOG_LAYER; + case TYPE_SYSTEM_OVERLAY: + return SYSTEM_OVERLAY_LAYER; + case TYPE_PRIORITY_PHONE: + return PRIORITY_PHONE_LAYER; + case TYPE_TOAST: + return TOAST_LAYER; + case TYPE_WALLPAPER: + return WALLPAPER_LAYER; + } + Log.e(TAG, "Unknown window type: " + type); + return APPLICATION_LAYER; + } + + /** {@inheritDoc} */ + public int subWindowTypeToLayerLw(int type) { + switch (type) { + case TYPE_APPLICATION_PANEL: + case TYPE_APPLICATION_ATTACHED_DIALOG: + return APPLICATION_PANEL_SUBLAYER; + case TYPE_APPLICATION_MEDIA: + return APPLICATION_MEDIA_SUBLAYER; + case TYPE_APPLICATION_MEDIA_OVERLAY: + return APPLICATION_MEDIA_OVERLAY_SUBLAYER; + case TYPE_APPLICATION_SUB_PANEL: + return APPLICATION_SUB_PANEL_SUBLAYER; + } + Log.e(TAG, "Unknown sub-window type: " + type); + return 0; + } + + public int getMaxWallpaperLayer() { + return STATUS_BAR_LAYER; + } + + public boolean doesForceHide(WindowState win, WindowManager.LayoutParams attrs) { + return attrs.type == WindowManager.LayoutParams.TYPE_KEYGUARD; + } + + public boolean canBeForceHidden(WindowState win, WindowManager.LayoutParams attrs) { + return attrs.type != WindowManager.LayoutParams.TYPE_STATUS_BAR + && attrs.type != WindowManager.LayoutParams.TYPE_WALLPAPER; + } + + /** {@inheritDoc} */ + public View addStartingWindow(IBinder appToken, String packageName, + int theme, CharSequence nonLocalizedLabel, + int labelRes, int icon) { + if (!SHOW_STARTING_ANIMATIONS) { + return null; + } + if (packageName == null) { + return null; + } + + try { + Context context = mContext; + boolean setTheme = false; + //Log.i(TAG, "addStartingWindow " + packageName + ": nonLocalizedLabel=" + // + nonLocalizedLabel + " theme=" + Integer.toHexString(theme)); + if (theme != 0 || labelRes != 0) { + try { + context = context.createPackageContext(packageName, 0); + if (theme != 0) { + context.setTheme(theme); + setTheme = true; + } + } catch (PackageManager.NameNotFoundException e) { + // Ignore + } + } + if (!setTheme) { + context.setTheme(com.android.internal.R.style.Theme); + } + + Window win = PolicyManager.makeNewWindow(context); + if (win.getWindowStyle().getBoolean( + com.android.internal.R.styleable.Window_windowDisablePreview, false)) { + return null; + } + + Resources r = context.getResources(); + win.setTitle(r.getText(labelRes, nonLocalizedLabel)); + + win.setType( + WindowManager.LayoutParams.TYPE_APPLICATION_STARTING); + // Force the window flags: this is a fake window, so it is not really + // touchable or focusable by the user. We also add in the ALT_FOCUSABLE_IM + // flag because we do know that the next window will take input + // focus, so we want to get the IME window up on top of us right away. + win.setFlags( + WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE| + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE| + WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, + WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE| + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE| + WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); + + win.setLayout(WindowManager.LayoutParams.MATCH_PARENT, + WindowManager.LayoutParams.MATCH_PARENT); + + final WindowManager.LayoutParams params = win.getAttributes(); + params.token = appToken; + params.packageName = packageName; + params.windowAnimations = win.getWindowStyle().getResourceId( + com.android.internal.R.styleable.Window_windowAnimationStyle, 0); + params.setTitle("Starting " + packageName); + + WindowManagerImpl wm = (WindowManagerImpl) + context.getSystemService(Context.WINDOW_SERVICE); + View view = win.getDecorView(); + + if (win.isFloating()) { + // Whoops, there is no way to display an animation/preview + // of such a thing! After all that work... let's skip it. + // (Note that we must do this here because it is in + // getDecorView() where the theme is evaluated... maybe + // we should peek the floating attribute from the theme + // earlier.) + return null; + } + + if (localLOGV) Log.v( + TAG, "Adding starting window for " + packageName + + " / " + appToken + ": " + + (view.getParent() != null ? view : null)); + + wm.addView(view, params); + + // Only return the view if it was successfully added to the + // window manager... which we can tell by it having a parent. + return view.getParent() != null ? view : null; + } catch (WindowManagerImpl.BadTokenException e) { + // ignore + Log.w(TAG, appToken + " already running, starting window not displayed"); + } catch (RuntimeException e) { + // don't crash if something else bad happens, for example a + // failure loading resources because we are loading from an app + // on external storage that has been unmounted. + Log.w(TAG, appToken + " failed creating starting window", e); + } + + return null; + } + + /** {@inheritDoc} */ + public void removeStartingWindow(IBinder appToken, View window) { + // RuntimeException e = new RuntimeException(); + // Log.i(TAG, "remove " + appToken + " " + window, e); + + if (localLOGV) Log.v( + TAG, "Removing starting window for " + appToken + ": " + window); + + if (window != null) { + WindowManagerImpl wm = (WindowManagerImpl) mContext.getSystemService(Context.WINDOW_SERVICE); + wm.removeView(window); + } + } + + /** + * Preflight adding a window to the system. + * + * Currently enforces that three window types are singletons: + * <ul> + * <li>STATUS_BAR_TYPE</li> + * <li>KEYGUARD_TYPE</li> + * </ul> + * + * @param win The window to be added + * @param attrs Information about the window to be added + * + * @return If ok, WindowManagerImpl.ADD_OKAY. If too many singletons, WindowManagerImpl.ADD_MULTIPLE_SINGLETON + */ + public int prepareAddWindowLw(WindowState win, WindowManager.LayoutParams attrs) { + switch (attrs.type) { + case TYPE_STATUS_BAR: + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.STATUS_BAR_SERVICE, + "PhoneWindowManager"); + // TODO: Need to handle the race condition of the status bar proc + // dying and coming back before the removeWindowLw cleanup has happened. + if (mStatusBar != null) { + return WindowManagerImpl.ADD_MULTIPLE_SINGLETON; + } + mStatusBar = win; + break; + case TYPE_STATUS_BAR_PANEL: + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.STATUS_BAR_SERVICE, + "PhoneWindowManager"); + mStatusBarPanels.add(win); + break; + case TYPE_KEYGUARD: + if (mKeyguard != null) { + return WindowManagerImpl.ADD_MULTIPLE_SINGLETON; + } + mKeyguard = win; + break; + } + return WindowManagerImpl.ADD_OKAY; + } + + /** {@inheritDoc} */ + public void removeWindowLw(WindowState win) { + if (mStatusBar == win) { + mStatusBar = null; + } + else if (mKeyguard == win) { + mKeyguard = null; + } else { + mStatusBarPanels.remove(win); + } + } + + static final boolean PRINT_ANIM = false; + + /** {@inheritDoc} */ + public int selectAnimationLw(WindowState win, int transit) { + if (PRINT_ANIM) Log.i(TAG, "selectAnimation in " + win + + ": transit=" + transit); + if (transit == TRANSIT_PREVIEW_DONE) { + if (win.hasAppShownWindows()) { + if (PRINT_ANIM) Log.i(TAG, "**** STARTING EXIT"); + return com.android.internal.R.anim.app_starting_exit; + } + } + + return 0; + } + + public Animation createForceHideEnterAnimation() { + return AnimationUtils.loadAnimation(mContext, + com.android.internal.R.anim.lock_screen_behind_enter); + } + + static ITelephony getPhoneInterface() { + return ITelephony.Stub.asInterface(ServiceManager.checkService(Context.TELEPHONY_SERVICE)); + } + + static IAudioService getAudioInterface() { + return IAudioService.Stub.asInterface(ServiceManager.checkService(Context.AUDIO_SERVICE)); + } + + boolean keyguardOn() { + return keyguardIsShowingTq() || inKeyguardRestrictedKeyInputMode(); + } + + private static final int[] WINDOW_TYPES_WHERE_HOME_DOESNT_WORK = { + WindowManager.LayoutParams.TYPE_SYSTEM_ALERT, + WindowManager.LayoutParams.TYPE_SYSTEM_ERROR, + }; + + /** {@inheritDoc} */ + public boolean interceptKeyTi(WindowState win, int code, int metaKeys, boolean down, + int repeatCount, int flags) { + boolean keyguardOn = keyguardOn(); + + if (false) { + Log.d(TAG, "interceptKeyTi code=" + code + " down=" + down + " repeatCount=" + + repeatCount + " keyguardOn=" + keyguardOn + " mHomePressed=" + mHomePressed); + } + + // Clear a pending HOME longpress if the user releases Home + // TODO: This could probably be inside the next bit of logic, but that code + // turned out to be a bit fragile so I'm doing it here explicitly, for now. + if ((code == KeyEvent.KEYCODE_HOME) && !down) { + mHandler.removeCallbacks(mHomeLongPress); + } + + // If the HOME button is currently being held, then we do special + // chording with it. + if (mHomePressed) { + + // If we have released the home key, and didn't do anything else + // while it was pressed, then it is time to go home! + if (code == KeyEvent.KEYCODE_HOME) { + if (!down) { + mHomePressed = false; + + if ((flags&KeyEvent.FLAG_CANCELED) == 0) { + // If an incoming call is ringing, HOME is totally disabled. + // (The user is already on the InCallScreen at this point, + // and his ONLY options are to answer or reject the call.) + boolean incomingRinging = false; + try { + ITelephony phoneServ = getPhoneInterface(); + if (phoneServ != null) { + incomingRinging = phoneServ.isRinging(); + } else { + Log.w(TAG, "Unable to find ITelephony interface"); + } + } catch (RemoteException ex) { + Log.w(TAG, "RemoteException from getPhoneInterface()", ex); + } + + if (incomingRinging) { + Log.i(TAG, "Ignoring HOME; there's a ringing incoming call."); + } else { + launchHomeFromHotKey(); + } + } else { + Log.i(TAG, "Ignoring HOME; event canceled."); + } + } + } + + return true; + } + + // First we always handle the home key here, so applications + // can never break it, although if keyguard is on, we do let + // it handle it, because that gives us the correct 5 second + // timeout. + if (code == KeyEvent.KEYCODE_HOME) { + + // If a system window has focus, then it doesn't make sense + // right now to interact with applications. + WindowManager.LayoutParams attrs = win != null ? win.getAttrs() : null; + if (attrs != null) { + final int type = attrs.type; + if (type == WindowManager.LayoutParams.TYPE_KEYGUARD + || type == WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG) { + // the "app" is keyguard, so give it the key + return false; + } + final int typeCount = WINDOW_TYPES_WHERE_HOME_DOESNT_WORK.length; + for (int i=0; i<typeCount; i++) { + if (type == WINDOW_TYPES_WHERE_HOME_DOESNT_WORK[i]) { + // don't do anything, but also don't pass it to the app + return true; + } + } + } + + if (down && repeatCount == 0) { + if (!keyguardOn) { + mHandler.postDelayed(mHomeLongPress, ViewConfiguration.getGlobalActionKeyTimeout()); + } + mHomePressed = true; + } + return true; + } else if (code == KeyEvent.KEYCODE_MENU) { + // Hijack modified menu keys for debugging features + final int chordBug = KeyEvent.META_SHIFT_ON; + + if (down && repeatCount == 0) { + if (mEnableShiftMenuBugReports && (metaKeys & chordBug) == chordBug) { + Intent intent = new Intent(Intent.ACTION_BUG_REPORT); + mContext.sendOrderedBroadcast(intent, null); + return true; + } else if (SHOW_PROCESSES_ON_ALT_MENU && + (metaKeys & KeyEvent.META_ALT_ON) == KeyEvent.META_ALT_ON) { + Intent service = new Intent(); + service.setClassName(mContext, "com.android.server.LoadAverageService"); + ContentResolver res = mContext.getContentResolver(); + boolean shown = Settings.System.getInt( + res, Settings.System.SHOW_PROCESSES, 0) != 0; + if (!shown) { + mContext.startService(service); + } else { + mContext.stopService(service); + } + Settings.System.putInt( + res, Settings.System.SHOW_PROCESSES, shown ? 0 : 1); + return true; + } + } + } else if (code == KeyEvent.KEYCODE_SEARCH) { + if (down) { + if (repeatCount == 0) { + mSearchKeyPressed = true; + } + } else { + mSearchKeyPressed = false; + + if (mConsumeSearchKeyUp) { + // Consume the up-event + mConsumeSearchKeyUp = false; + return true; + } + } + } + + // Shortcuts are invoked through Search+key, so intercept those here + if (mSearchKeyPressed) { + if (down && repeatCount == 0 && !keyguardOn) { + Intent shortcutIntent = mShortcutManager.getIntent(code, metaKeys); + if (shortcutIntent != null) { + shortcutIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + mContext.startActivity(shortcutIntent); + + /* + * We launched an app, so the up-event of the search key + * should be consumed + */ + mConsumeSearchKeyUp = true; + return true; + } + } + } + + return false; + } + + /** + * A home key -> launch home action was detected. Take the appropriate action + * given the situation with the keyguard. + */ + void launchHomeFromHotKey() { + if (mKeyguardMediator.isShowingAndNotHidden()) { + // don't launch home if keyguard showing + } else if (!mHideLockScreen && mKeyguardMediator.isInputRestricted()) { + // when in keyguard restricted mode, must first verify unlock + // before launching home + mKeyguardMediator.verifyUnlock(new OnKeyguardExitResult() { + public void onKeyguardExitResult(boolean success) { + if (success) { + try { + ActivityManagerNative.getDefault().stopAppSwitches(); + } catch (RemoteException e) { + } + sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY); + startDockOrHome(); + } + } + }); + } else { + // no keyguard stuff to worry about, just launch home! + try { + ActivityManagerNative.getDefault().stopAppSwitches(); + } catch (RemoteException e) { + } + sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY); + startDockOrHome(); + } + } + + public void getContentInsetHintLw(WindowManager.LayoutParams attrs, Rect contentInset) { + final int fl = attrs.flags; + + if ((fl & + (FLAG_LAYOUT_IN_SCREEN | FLAG_FULLSCREEN | FLAG_LAYOUT_INSET_DECOR)) + == (FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR)) { + contentInset.set(mCurLeft, mCurTop, mW - mCurRight, mH - mCurBottom); + } else { + contentInset.setEmpty(); + } + } + + /** {@inheritDoc} */ + public void beginLayoutLw(int displayWidth, int displayHeight) { + mW = displayWidth; + mH = displayHeight; + mDockLeft = mContentLeft = mCurLeft = 0; + mDockTop = mContentTop = mCurTop = 0; + mDockRight = mContentRight = mCurRight = displayWidth; + mDockBottom = mContentBottom = mCurBottom = displayHeight; + mDockLayer = 0x10000000; + + // decide where the status bar goes ahead of time + if (mStatusBar != null) { + final Rect pf = mTmpParentFrame; + final Rect df = mTmpDisplayFrame; + final Rect vf = mTmpVisibleFrame; + pf.left = df.left = vf.left = 0; + pf.top = df.top = vf.top = 0; + pf.right = df.right = vf.right = displayWidth; + pf.bottom = df.bottom = vf.bottom = displayHeight; + + mStatusBar.computeFrameLw(pf, df, vf, vf); + if (mStatusBar.isVisibleLw()) { + // If the status bar is hidden, we don't want to cause + // windows behind it to scroll. + mDockTop = mContentTop = mCurTop = mStatusBar.getFrameLw().bottom; + if (DEBUG_LAYOUT) Log.v(TAG, "Status bar: mDockBottom=" + + mDockBottom + " mContentBottom=" + + mContentBottom + " mCurBottom=" + mCurBottom); + } + } + } + + void setAttachedWindowFrames(WindowState win, int fl, int sim, + WindowState attached, boolean insetDecors, Rect pf, Rect df, Rect cf, Rect vf) { + if (win.getSurfaceLayer() > mDockLayer && attached.getSurfaceLayer() < mDockLayer) { + // Here's a special case: if this attached window is a panel that is + // above the dock window, and the window it is attached to is below + // the dock window, then the frames we computed for the window it is + // attached to can not be used because the dock is effectively part + // of the underlying window and the attached window is floating on top + // of the whole thing. So, we ignore the attached window and explicitly + // compute the frames that would be appropriate without the dock. + df.left = cf.left = vf.left = mDockLeft; + df.top = cf.top = vf.top = mDockTop; + df.right = cf.right = vf.right = mDockRight; + df.bottom = cf.bottom = vf.bottom = mDockBottom; + } else { + // The effective display frame of the attached window depends on + // whether it is taking care of insetting its content. If not, + // we need to use the parent's content frame so that the entire + // window is positioned within that content. Otherwise we can use + // the display frame and let the attached window take care of + // positioning its content appropriately. + if ((sim & SOFT_INPUT_MASK_ADJUST) != SOFT_INPUT_ADJUST_RESIZE) { + cf.set(attached.getDisplayFrameLw()); + } else { + // If the window is resizing, then we want to base the content + // frame on our attached content frame to resize... however, + // things can be tricky if the attached window is NOT in resize + // mode, in which case its content frame will be larger. + // Ungh. So to deal with that, make sure the content frame + // we end up using is not covering the IM dock. + cf.set(attached.getContentFrameLw()); + if (attached.getSurfaceLayer() < mDockLayer) { + if (cf.left < mContentLeft) cf.left = mContentLeft; + if (cf.top < mContentTop) cf.top = mContentTop; + if (cf.right > mContentRight) cf.right = mContentRight; + if (cf.bottom > mContentBottom) cf.bottom = mContentBottom; + } + } + df.set(insetDecors ? attached.getDisplayFrameLw() : cf); + vf.set(attached.getVisibleFrameLw()); + } + // The LAYOUT_IN_SCREEN flag is used to determine whether the attached + // window should be positioned relative to its parent or the entire + // screen. + pf.set((fl & FLAG_LAYOUT_IN_SCREEN) == 0 + ? attached.getFrameLw() : df); + } + + /** {@inheritDoc} */ + public void layoutWindowLw(WindowState win, WindowManager.LayoutParams attrs, + WindowState attached) { + // we've already done the status bar + if (win == mStatusBar) { + return; + } + + if (false) { + if ("com.google.android.youtube".equals(attrs.packageName) + && attrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) { + Log.i(TAG, "GOTCHA!"); + } + } + + final int fl = attrs.flags; + final int sim = attrs.softInputMode; + + final Rect pf = mTmpParentFrame; + final Rect df = mTmpDisplayFrame; + final Rect cf = mTmpContentFrame; + final Rect vf = mTmpVisibleFrame; + + if (attrs.type == TYPE_INPUT_METHOD) { + pf.left = df.left = cf.left = vf.left = mDockLeft; + pf.top = df.top = cf.top = vf.top = mDockTop; + pf.right = df.right = cf.right = vf.right = mDockRight; + pf.bottom = df.bottom = cf.bottom = vf.bottom = mDockBottom; + // IM dock windows always go to the bottom of the screen. + attrs.gravity = Gravity.BOTTOM; + mDockLayer = win.getSurfaceLayer(); + } else { + if ((fl & + (FLAG_LAYOUT_IN_SCREEN | FLAG_FULLSCREEN | FLAG_LAYOUT_INSET_DECOR)) + == (FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR)) { + // This is the case for a normal activity window: we want it + // to cover all of the screen space, and it can take care of + // moving its contents to account for screen decorations that + // intrude into that space. + if (attached != null) { + // If this window is attached to another, our display + // frame is the same as the one we are attached to. + setAttachedWindowFrames(win, fl, sim, attached, true, pf, df, cf, vf); + } else { + pf.left = df.left = 0; + pf.top = df.top = 0; + pf.right = df.right = mW; + pf.bottom = df.bottom = mH; + if ((sim & SOFT_INPUT_MASK_ADJUST) != SOFT_INPUT_ADJUST_RESIZE) { + cf.left = mDockLeft; + cf.top = mDockTop; + cf.right = mDockRight; + cf.bottom = mDockBottom; + } else { + cf.left = mContentLeft; + cf.top = mContentTop; + cf.right = mContentRight; + cf.bottom = mContentBottom; + } + vf.left = mCurLeft; + vf.top = mCurTop; + vf.right = mCurRight; + vf.bottom = mCurBottom; + } + } else if ((fl & FLAG_LAYOUT_IN_SCREEN) != 0) { + // A window that has requested to fill the entire screen just + // gets everything, period. + pf.left = df.left = cf.left = 0; + pf.top = df.top = cf.top = 0; + pf.right = df.right = cf.right = mW; + pf.bottom = df.bottom = cf.bottom = mH; + vf.left = mCurLeft; + vf.top = mCurTop; + vf.right = mCurRight; + vf.bottom = mCurBottom; + } else if (attached != null) { + // A child window should be placed inside of the same visible + // frame that its parent had. + setAttachedWindowFrames(win, fl, sim, attached, false, pf, df, cf, vf); + } else { + // Otherwise, a normal window must be placed inside the content + // of all screen decorations. + pf.left = mContentLeft; + pf.top = mContentTop; + pf.right = mContentRight; + pf.bottom = mContentBottom; + if ((sim & SOFT_INPUT_MASK_ADJUST) != SOFT_INPUT_ADJUST_RESIZE) { + df.left = cf.left = mDockLeft; + df.top = cf.top = mDockTop; + df.right = cf.right = mDockRight; + df.bottom = cf.bottom = mDockBottom; + } else { + df.left = cf.left = mContentLeft; + df.top = cf.top = mContentTop; + df.right = cf.right = mContentRight; + df.bottom = cf.bottom = mContentBottom; + } + vf.left = mCurLeft; + vf.top = mCurTop; + vf.right = mCurRight; + vf.bottom = mCurBottom; + } + } + + if ((fl & FLAG_LAYOUT_NO_LIMITS) != 0) { + df.left = df.top = cf.left = cf.top = vf.left = vf.top = -10000; + df.right = df.bottom = cf.right = cf.bottom = vf.right = vf.bottom = 10000; + } + + if (DEBUG_LAYOUT) Log.v(TAG, "Compute frame " + attrs.getTitle() + + ": sim=#" + Integer.toHexString(sim) + + " pf=" + pf.toShortString() + " df=" + df.toShortString() + + " cf=" + cf.toShortString() + " vf=" + vf.toShortString()); + + if (false) { + if ("com.google.android.youtube".equals(attrs.packageName) + && attrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) { + if (true || localLOGV) Log.v(TAG, "Computing frame of " + win + + ": sim=#" + Integer.toHexString(sim) + + " pf=" + pf.toShortString() + " df=" + df.toShortString() + + " cf=" + cf.toShortString() + " vf=" + vf.toShortString()); + } + } + + win.computeFrameLw(pf, df, cf, vf); + + // Dock windows carve out the bottom of the screen, so normal windows + // can't appear underneath them. + if (attrs.type == TYPE_INPUT_METHOD && !win.getGivenInsetsPendingLw()) { + int top = win.getContentFrameLw().top; + top += win.getGivenContentInsetsLw().top; + if (mContentBottom > top) { + mContentBottom = top; + } + top = win.getVisibleFrameLw().top; + top += win.getGivenVisibleInsetsLw().top; + if (mCurBottom > top) { + mCurBottom = top; + } + if (DEBUG_LAYOUT) Log.v(TAG, "Input method: mDockBottom=" + + mDockBottom + " mContentBottom=" + + mContentBottom + " mCurBottom=" + mCurBottom); + } + } + + /** {@inheritDoc} */ + public int finishLayoutLw() { + return 0; + } + + /** {@inheritDoc} */ + public void beginAnimationLw(int displayWidth, int displayHeight) { + mTopFullscreenOpaqueWindowState = null; + mForceStatusBar = false; + + mHideLockScreen = false; + mAllowLockscreenWhenOn = false; + mDismissKeyguard = false; + } + + /** {@inheritDoc} */ + public void animatingWindowLw(WindowState win, + WindowManager.LayoutParams attrs) { + if (mTopFullscreenOpaqueWindowState == null && + win.isVisibleOrBehindKeyguardLw()) { + if ((attrs.flags & FLAG_FORCE_NOT_FULLSCREEN) != 0) { + mForceStatusBar = true; + } + if (attrs.type >= FIRST_APPLICATION_WINDOW + && attrs.type <= LAST_APPLICATION_WINDOW + && win.fillsScreenLw(mW, mH, false, false)) { + if (DEBUG_LAYOUT) Log.v(TAG, "Fullscreen window: " + win); + mTopFullscreenOpaqueWindowState = win; + if ((attrs.flags & FLAG_SHOW_WHEN_LOCKED) != 0) { + if (localLOGV) Log.v(TAG, "Setting mHideLockScreen to true by win " + win); + mHideLockScreen = true; + } + if ((attrs.flags & FLAG_DISMISS_KEYGUARD) != 0) { + if (localLOGV) Log.v(TAG, "Setting mDismissKeyguard to true by win " + win); + mDismissKeyguard = true; + } + if ((attrs.flags & FLAG_ALLOW_LOCK_WHILE_SCREEN_ON) != 0) { + mAllowLockscreenWhenOn = true; + } + } + } + } + + /** {@inheritDoc} */ + public int finishAnimationLw() { + int changes = 0; + + boolean hiding = false; + if (mStatusBar != null) { + if (localLOGV) Log.i(TAG, "force=" + mForceStatusBar + + " top=" + mTopFullscreenOpaqueWindowState); + if (mForceStatusBar) { + if (DEBUG_LAYOUT) Log.v(TAG, "Showing status bar"); + if (mStatusBar.showLw(true)) changes |= FINISH_LAYOUT_REDO_LAYOUT; + } else if (mTopFullscreenOpaqueWindowState != null) { + //Log.i(TAG, "frame: " + mTopFullscreenOpaqueWindowState.getFrameLw() + // + " shown frame: " + mTopFullscreenOpaqueWindowState.getShownFrameLw()); + //Log.i(TAG, "attr: " + mTopFullscreenOpaqueWindowState.getAttrs()); + WindowManager.LayoutParams lp = + mTopFullscreenOpaqueWindowState.getAttrs(); + boolean hideStatusBar = + (lp.flags & WindowManager.LayoutParams.FLAG_FULLSCREEN) != 0; + if (hideStatusBar) { + if (DEBUG_LAYOUT) Log.v(TAG, "Hiding status bar"); + if (mStatusBar.hideLw(true)) changes |= FINISH_LAYOUT_REDO_LAYOUT; + hiding = true; + } else { + if (DEBUG_LAYOUT) Log.v(TAG, "Showing status bar"); + if (mStatusBar.showLw(true)) changes |= FINISH_LAYOUT_REDO_LAYOUT; + } + } + } + + if (changes != 0 && hiding) { + IStatusBarService sbs = IStatusBarService.Stub.asInterface(ServiceManager.getService("statusbar")); + if (sbs != null) { + try { + // Make sure the window shade is hidden. + sbs.collapse(); + } catch (RemoteException e) { + } + } + } + + // Hide the key guard if a visible window explicitly specifies that it wants to be displayed + // when the screen is locked + if (mKeyguard != null) { + if (localLOGV) Log.v(TAG, "finishLayoutLw::mHideKeyguard="+mHideLockScreen); + if (mDismissKeyguard && !mKeyguardMediator.isSecure()) { + if (mKeyguard.hideLw(true)) { + changes |= FINISH_LAYOUT_REDO_LAYOUT + | FINISH_LAYOUT_REDO_CONFIG + | FINISH_LAYOUT_REDO_WALLPAPER; + } + if (mKeyguardMediator.isShowing()) { + mHandler.post(new Runnable() { + public void run() { + mKeyguardMediator.keyguardDone(false, false); + } + }); + } + } else if (mHideLockScreen) { + if (mKeyguard.hideLw(true)) { + changes |= FINISH_LAYOUT_REDO_LAYOUT + | FINISH_LAYOUT_REDO_CONFIG + | FINISH_LAYOUT_REDO_WALLPAPER; + } + mKeyguardMediator.setHidden(true); + } else { + if (mKeyguard.showLw(true)) { + changes |= FINISH_LAYOUT_REDO_LAYOUT + | FINISH_LAYOUT_REDO_CONFIG + | FINISH_LAYOUT_REDO_WALLPAPER; + } + mKeyguardMediator.setHidden(false); + } + } + + // update since mAllowLockscreenWhenOn might have changed + updateLockScreenTimeout(); + return changes; + } + + public boolean allowAppAnimationsLw() { + if (mKeyguard != null && mKeyguard.isVisibleLw()) { + // If keyguard is currently visible, no reason to animate + // behind it. + return false; + } + if (mStatusBar != null && mStatusBar.isVisibleLw()) { + Rect rect = new Rect(mStatusBar.getShownFrameLw()); + for (int i=mStatusBarPanels.size()-1; i>=0; i--) { + WindowState w = mStatusBarPanels.get(i); + if (w.isVisibleLw()) { + rect.union(w.getShownFrameLw()); + } + } + final int insetw = mW/10; + final int inseth = mH/10; + if (rect.contains(insetw, inseth, mW-insetw, mH-inseth)) { + // All of the status bar windows put together cover the + // screen, so the app can't be seen. (Note this test doesn't + // work if the rects of these windows are at off offsets or + // sizes, causing gaps in the rect union we have computed.) + return false; + } + } + return true; + } + + /** {@inheritDoc} */ + public boolean preprocessInputEventTq(RawInputEvent event) { + switch (event.type) { + case RawInputEvent.EV_SW: + if (event.keycode == RawInputEvent.SW_LID) { + // lid changed state + mLidOpen = event.value == 0; + boolean awakeNow = mKeyguardMediator.doLidChangeTq(mLidOpen); + updateRotation(Surface.FLAGS_ORIENTATION_ANIMATION_DISABLE); + if (awakeNow) { + // If the lid opening and we don't have to keep the + // keyguard up, then we can turn on the screen + // immediately. + mKeyguardMediator.pokeWakelock(); + } else if (keyguardIsShowingTq()) { + if (mLidOpen) { + // If we are opening the lid and not hiding the + // keyguard, then we need to have it turn on the + // screen once it is shown. + mKeyguardMediator.onWakeKeyWhenKeyguardShowingTq( + KeyEvent.KEYCODE_POWER); + } + } else { + // Light up the keyboard if we are sliding up. + if (mLidOpen) { + mPowerManager.userActivity(SystemClock.uptimeMillis(), false, + LocalPowerManager.BUTTON_EVENT); + } else { + mPowerManager.userActivity(SystemClock.uptimeMillis(), false, + LocalPowerManager.OTHER_EVENT); + } + } + } + } + return false; + } + + public void notifyLidSwitchChanged(long whenNanos, boolean lidOpen) { + // lid changed state + mLidOpen = lidOpen; + boolean awakeNow = mKeyguardMediator.doLidChangeTq(mLidOpen); + updateRotation(Surface.FLAGS_ORIENTATION_ANIMATION_DISABLE); + if (awakeNow) { + // If the lid opening and we don't have to keep the + // keyguard up, then we can turn on the screen + // immediately. + mKeyguardMediator.pokeWakelock(); + } else if (keyguardIsShowingTq()) { + if (mLidOpen) { + // If we are opening the lid and not hiding the + // keyguard, then we need to have it turn on the + // screen once it is shown. + mKeyguardMediator.onWakeKeyWhenKeyguardShowingTq( + KeyEvent.KEYCODE_POWER); + } + } else { + // Light up the keyboard if we are sliding up. + if (mLidOpen) { + mPowerManager.userActivity(SystemClock.uptimeMillis(), false, + LocalPowerManager.BUTTON_EVENT); + } else { + mPowerManager.userActivity(SystemClock.uptimeMillis(), false, + LocalPowerManager.OTHER_EVENT); + } + } + } + + + /** {@inheritDoc} */ + public boolean isAppSwitchKeyTqTiLwLi(int keycode) { + return keycode == KeyEvent.KEYCODE_HOME + || keycode == KeyEvent.KEYCODE_ENDCALL; + } + + /** {@inheritDoc} */ + public boolean isMovementKeyTi(int keycode) { + switch (keycode) { + case KeyEvent.KEYCODE_DPAD_UP: + case KeyEvent.KEYCODE_DPAD_DOWN: + case KeyEvent.KEYCODE_DPAD_LEFT: + case KeyEvent.KEYCODE_DPAD_RIGHT: + return true; + } + return false; + } + + + /** + * @return Whether a telephone call is in progress right now. + */ + boolean isInCall() { + final ITelephony phone = getPhoneInterface(); + if (phone == null) { + Log.w(TAG, "couldn't get ITelephony reference"); + return false; + } + try { + return phone.isOffhook(); + } catch (RemoteException e) { + Log.w(TAG, "ITelephony.isOffhhook threw RemoteException " + e); + return false; + } + } + + /** + * @return Whether music is being played right now. + */ + boolean isMusicActive() { + final AudioManager am = (AudioManager)mContext.getSystemService(Context.AUDIO_SERVICE); + if (am == null) { + Log.w(TAG, "isMusicActive: couldn't get AudioManager reference"); + return false; + } + return am.isMusicActive(); + } + + /** + * Tell the audio service to adjust the volume appropriate to the event. + * @param keycode + */ + void handleVolumeKey(int stream, int keycode) { + final IAudioService audio = getAudioInterface(); + if (audio == null) { + Log.w(TAG, "handleVolumeKey: couldn't get IAudioService reference"); + return; + } + try { + // since audio is playing, we shouldn't have to hold a wake lock + // during the call, but we do it as a precaution for the rare possibility + // that the music stops right before we call this + mBroadcastWakeLock.acquire(); + audio.adjustStreamVolume(stream, + keycode == KeyEvent.KEYCODE_VOLUME_UP + ? AudioManager.ADJUST_RAISE + : AudioManager.ADJUST_LOWER, + 0); + } catch (RemoteException e) { + Log.w(TAG, "IAudioService.adjustStreamVolume() threw RemoteException " + e); + } finally { + mBroadcastWakeLock.release(); + } + } + + static boolean isMediaKey(int code) { + if (code == KeyEvent.KEYCODE_HEADSETHOOK || + code == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE || + code == KeyEvent.KEYCODE_MEDIA_STOP || + code == KeyEvent.KEYCODE_MEDIA_NEXT || + code == KeyEvent.KEYCODE_MEDIA_PREVIOUS || + code == KeyEvent.KEYCODE_MEDIA_REWIND || + code == KeyEvent.KEYCODE_MEDIA_FAST_FORWARD) { + return true; + } + return false; + } + + /** {@inheritDoc} */ + public int interceptKeyTq(RawInputEvent event, boolean screenIsOn) { + int result = ACTION_PASS_TO_USER; + final boolean isWakeKey = isWakeKeyTq(event); + // If screen is off then we treat the case where the keyguard is open but hidden + // the same as if it were open and in front. + // This will prevent any keys other than the power button from waking the screen + // when the keyguard is hidden by another activity. + final boolean keyguardActive = (screenIsOn ? + mKeyguardMediator.isShowingAndNotHidden() : + mKeyguardMediator.isShowing()); + + if (false) { + Log.d(TAG, "interceptKeyTq event=" + event + " keycode=" + event.keycode + + " screenIsOn=" + screenIsOn + " keyguardActive=" + keyguardActive); + } + + if (keyguardActive) { + if (screenIsOn) { + // when the screen is on, always give the event to the keyguard + result |= ACTION_PASS_TO_USER; + } else { + // otherwise, don't pass it to the user + result &= ~ACTION_PASS_TO_USER; + + final boolean isKeyDown = + (event.type == RawInputEvent.EV_KEY) && (event.value != 0); + if (isWakeKey && isKeyDown) { + + // tell the mediator about a wake key, it may decide to + // turn on the screen depending on whether the key is + // appropriate. + if (!mKeyguardMediator.onWakeKeyWhenKeyguardShowingTq(event.keycode) + && (event.keycode == KeyEvent.KEYCODE_VOLUME_DOWN + || event.keycode == KeyEvent.KEYCODE_VOLUME_UP)) { + // when keyguard is showing and screen off, we need + // to handle the volume key for calls and music here + if (isInCall()) { + handleVolumeKey(AudioManager.STREAM_VOICE_CALL, event.keycode); + } else if (isMusicActive()) { + handleVolumeKey(AudioManager.STREAM_MUSIC, event.keycode); + } + } + } + } + } else if (!screenIsOn) { + // If we are in-call with screen off and keyguard is not showing, + // then handle the volume key ourselves. + // This is necessary because the phone app will disable the keyguard + // when the proximity sensor is in use. + if (isInCall() && event.type == RawInputEvent.EV_KEY && + (event.keycode == KeyEvent.KEYCODE_VOLUME_DOWN + || event.keycode == KeyEvent.KEYCODE_VOLUME_UP)) { + result &= ~ACTION_PASS_TO_USER; + handleVolumeKey(AudioManager.STREAM_VOICE_CALL, event.keycode); + } + if (isWakeKey) { + // a wake key has a sole purpose of waking the device; don't pass + // it to the user + result |= ACTION_POKE_USER_ACTIVITY; + result &= ~ACTION_PASS_TO_USER; + } + } + + int type = event.type; + int code = event.keycode; + boolean down = event.value != 0; + + if (type == RawInputEvent.EV_KEY) { + if (code == KeyEvent.KEYCODE_ENDCALL + || code == KeyEvent.KEYCODE_POWER) { + if (down) { + boolean handled = false; + boolean hungUp = false; + // key repeats are generated by the window manager, and we don't see them + // here, so unless the driver is doing something it shouldn't be, we know + // this is the real press event. + ITelephony phoneServ = getPhoneInterface(); + if (phoneServ != null) { + try { + if (code == KeyEvent.KEYCODE_ENDCALL) { + handled = hungUp = phoneServ.endCall(); + } else if (code == KeyEvent.KEYCODE_POWER) { + if (phoneServ.isRinging()) { + // Pressing Power while there's a ringing incoming + // call should silence the ringer. + phoneServ.silenceRinger(); + handled = true; + } else if (phoneServ.isOffhook() && + ((mIncallPowerBehavior + & Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP) + != 0)) { + // Otherwise, if "Power button ends call" is enabled, + // the Power button will hang up any current active call. + handled = hungUp = phoneServ.endCall(); + } + } + } catch (RemoteException ex) { + Log.w(TAG, "ITelephony threw RemoteException" + ex); + } + } else { + Log.w(TAG, "!!! Unable to find ITelephony interface !!!"); + } + + if (!screenIsOn + || (handled && code != KeyEvent.KEYCODE_POWER) + || (handled && hungUp && code == KeyEvent.KEYCODE_POWER)) { + mShouldTurnOffOnKeyUp = false; + } else { + // only try to turn off the screen if we didn't already hang up + mShouldTurnOffOnKeyUp = true; + mHandler.postDelayed(mPowerLongPress, + ViewConfiguration.getGlobalActionKeyTimeout()); + result &= ~ACTION_PASS_TO_USER; + } + } else { + mHandler.removeCallbacks(mPowerLongPress); + if (mShouldTurnOffOnKeyUp) { + mShouldTurnOffOnKeyUp = false; + boolean gohome, sleeps; + if (code == KeyEvent.KEYCODE_ENDCALL) { + gohome = (mEndcallBehavior + & Settings.System.END_BUTTON_BEHAVIOR_HOME) != 0; + sleeps = (mEndcallBehavior + & Settings.System.END_BUTTON_BEHAVIOR_SLEEP) != 0; + } else { + gohome = false; + sleeps = true; + } + if (keyguardActive + || (sleeps && !gohome) + || (gohome && !goHome() && sleeps)) { + // they must already be on the keyguad or home screen, + // go to sleep instead + Log.d(TAG, "I'm tired mEndcallBehavior=0x" + + Integer.toHexString(mEndcallBehavior)); + result &= ~ACTION_POKE_USER_ACTIVITY; + result |= ACTION_GO_TO_SLEEP; + } + result &= ~ACTION_PASS_TO_USER; + } + } + } else if (isMediaKey(code)) { + // This key needs to be handled even if the screen is off. + // If others need to be handled while it's off, this is a reasonable + // pattern to follow. + if ((result & ACTION_PASS_TO_USER) == 0) { + // Only do this if we would otherwise not pass it to the user. In that + // case, the PhoneWindow class will do the same thing, except it will + // only do it if the showing app doesn't process the key on its own. + KeyEvent keyEvent = new KeyEvent(event.when, event.when, + down ? KeyEvent.ACTION_DOWN : KeyEvent.ACTION_UP, + code, 0); + mBroadcastWakeLock.acquire(); + mHandler.post(new PassHeadsetKey(keyEvent)); + } + } else if (code == KeyEvent.KEYCODE_CALL) { + // If an incoming call is ringing, answer it! + // (We handle this key here, rather than in the InCallScreen, to make + // sure we'll respond to the key even if the InCallScreen hasn't come to + // the foreground yet.) + + // We answer the call on the DOWN event, to agree with + // the "fallback" behavior in the InCallScreen. + if (down) { + try { + ITelephony phoneServ = getPhoneInterface(); + if (phoneServ != null) { + if (phoneServ.isRinging()) { + Log.i(TAG, "interceptKeyTq:" + + " CALL key-down while ringing: Answer the call!"); + phoneServ.answerRingingCall(); + + // And *don't* pass this key thru to the current activity + // (which is presumably the InCallScreen.) + result &= ~ACTION_PASS_TO_USER; + } + } else { + Log.w(TAG, "CALL button: Unable to find ITelephony interface"); + } + } catch (RemoteException ex) { + Log.w(TAG, "CALL button: RemoteException from getPhoneInterface()", ex); + } + } + } else if ((code == KeyEvent.KEYCODE_VOLUME_UP) + || (code == KeyEvent.KEYCODE_VOLUME_DOWN)) { + // If an incoming call is ringing, either VOLUME key means + // "silence ringer". We handle these keys here, rather than + // in the InCallScreen, to make sure we'll respond to them + // even if the InCallScreen hasn't come to the foreground yet. + + // Look for the DOWN event here, to agree with the "fallback" + // behavior in the InCallScreen. + if (down) { + try { + ITelephony phoneServ = getPhoneInterface(); + if (phoneServ != null) { + if (phoneServ.isRinging()) { + Log.i(TAG, "interceptKeyTq:" + + " VOLUME key-down while ringing: Silence ringer!"); + // Silence the ringer. (It's safe to call this + // even if the ringer has already been silenced.) + phoneServ.silenceRinger(); + + // And *don't* pass this key thru to the current activity + // (which is probably the InCallScreen.) + result &= ~ACTION_PASS_TO_USER; + } + } else { + Log.w(TAG, "VOLUME button: Unable to find ITelephony interface"); + } + } catch (RemoteException ex) { + Log.w(TAG, "VOLUME button: RemoteException from getPhoneInterface()", ex); + } + } + } + } + + return result; + } + + class PassHeadsetKey implements Runnable { + KeyEvent mKeyEvent; + + PassHeadsetKey(KeyEvent keyEvent) { + mKeyEvent = keyEvent; + } + + public void run() { + if (ActivityManagerNative.isSystemReady()) { + Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON, null); + intent.putExtra(Intent.EXTRA_KEY_EVENT, mKeyEvent); + mContext.sendOrderedBroadcast(intent, null, mBroadcastDone, + mHandler, Activity.RESULT_OK, null, null); + } + } + } + + BroadcastReceiver mBroadcastDone = new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + mBroadcastWakeLock.release(); + } + }; + + BroadcastReceiver mDockReceiver = new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + if (Intent.ACTION_DOCK_EVENT.equals(intent.getAction())) { + mDockMode = intent.getIntExtra(Intent.EXTRA_DOCK_STATE, + Intent.EXTRA_DOCK_STATE_UNDOCKED); + } else { + try { + IUiModeManager uiModeService = IUiModeManager.Stub.asInterface( + ServiceManager.getService(Context.UI_MODE_SERVICE)); + mUiMode = uiModeService.getCurrentModeType(); + } catch (RemoteException e) { + } + } + updateRotation(Surface.FLAGS_ORIENTATION_ANIMATION_DISABLE); + updateOrientationListenerLp(); + } + }; + + /** {@inheritDoc} */ + public boolean isWakeRelMovementTq(int device, int classes, + RawInputEvent event) { + // if it's tagged with one of the wake bits, it wakes up the device + return ((event.flags & (FLAG_WAKE | FLAG_WAKE_DROPPED)) != 0); + } + + /** {@inheritDoc} */ + public boolean isWakeAbsMovementTq(int device, int classes, + RawInputEvent event) { + // if it's tagged with one of the wake bits, it wakes up the device + return ((event.flags & (FLAG_WAKE | FLAG_WAKE_DROPPED)) != 0); + } + + /** + * Given the current state of the world, should this key wake up the device? + */ + protected boolean isWakeKeyTq(RawInputEvent event) { + // There are not key maps for trackball devices, but we'd still + // like to have pressing it wake the device up, so force it here. + int keycode = event.keycode; + int flags = event.flags; + if (keycode == RawInputEvent.BTN_MOUSE) { + flags |= WindowManagerPolicy.FLAG_WAKE; + } + return (flags + & (WindowManagerPolicy.FLAG_WAKE | WindowManagerPolicy.FLAG_WAKE_DROPPED)) != 0; + } + + /** {@inheritDoc} */ + public void screenTurnedOff(int why) { + EventLog.writeEvent(70000, 0); + mKeyguardMediator.onScreenTurnedOff(why); + synchronized (mLock) { + mScreenOn = false; + updateOrientationListenerLp(); + updateLockScreenTimeout(); + } + } + + /** {@inheritDoc} */ + public void screenTurnedOn() { + EventLog.writeEvent(70000, 1); + mKeyguardMediator.onScreenTurnedOn(); + synchronized (mLock) { + mScreenOn = true; + updateOrientationListenerLp(); + updateLockScreenTimeout(); + } + } + + /** {@inheritDoc} */ + public boolean isScreenOn() { + return mScreenOn; + } + + /** {@inheritDoc} */ + public void enableKeyguard(boolean enabled) { + mKeyguardMediator.setKeyguardEnabled(enabled); + } + + /** {@inheritDoc} */ + public void exitKeyguardSecurely(OnKeyguardExitResult callback) { + mKeyguardMediator.verifyUnlock(callback); + } + + private boolean keyguardIsShowingTq() { + return mKeyguardMediator.isShowingAndNotHidden(); + } + + /** {@inheritDoc} */ + public boolean inKeyguardRestrictedKeyInputMode() { + return mKeyguardMediator.isInputRestricted(); + } + + void sendCloseSystemWindows() { + sendCloseSystemWindows(mContext, null); + } + + void sendCloseSystemWindows(String reason) { + sendCloseSystemWindows(mContext, reason); + } + + static void sendCloseSystemWindows(Context context, String reason) { + if (ActivityManagerNative.isSystemReady()) { + try { + ActivityManagerNative.getDefault().closeSystemDialogs(reason); + } catch (RemoteException e) { + } + } + } + + public int rotationForOrientationLw(int orientation, int lastRotation, + boolean displayEnabled) { + + if (mPortraitRotation < 0) { + // Initialize the rotation angles for each orientation once. + Display d = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE)) + .getDefaultDisplay(); + if (d.getWidth() > d.getHeight()) { + mPortraitRotation = Surface.ROTATION_90; + mLandscapeRotation = Surface.ROTATION_0; + } else { + mPortraitRotation = Surface.ROTATION_0; + mLandscapeRotation = Surface.ROTATION_90; + } + } + + synchronized (mLock) { + switch (orientation) { + case ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE: + //always return landscape if orientation set to landscape + return mLandscapeRotation; + case ActivityInfo.SCREEN_ORIENTATION_PORTRAIT: + //always return portrait if orientation set to portrait + return mPortraitRotation; + } + // case for nosensor meaning ignore sensor and consider only lid + // or orientation sensor disabled + //or case.unspecified + if (mLidOpen) { + return mLidOpenRotation; + } else if (mDockMode == Intent.EXTRA_DOCK_STATE_CAR && mCarDockRotation >= 0) { + return mCarDockRotation; + } else if (mDockMode == Intent.EXTRA_DOCK_STATE_DESK && mDeskDockRotation >= 0) { + return mDeskDockRotation; + } else { + if (useSensorForOrientationLp(orientation)) { + // If the user has enabled auto rotation by default, do it. + int curRotation = mOrientationListener.getCurrentRotation(); + return curRotation >= 0 ? curRotation : lastRotation; + } + return Surface.ROTATION_0; + } + } + } + + public boolean detectSafeMode() { + try { + int menuState = mWindowManager.getKeycodeState(KeyEvent.KEYCODE_MENU); + int sState = mWindowManager.getKeycodeState(KeyEvent.KEYCODE_S); + int dpadState = mWindowManager.getDPadKeycodeState(KeyEvent.KEYCODE_DPAD_CENTER); + int trackballState = mWindowManager.getTrackballScancodeState(RawInputEvent.BTN_MOUSE); + mSafeMode = menuState > 0 || sState > 0 || dpadState > 0 || trackballState > 0; + performHapticFeedbackLw(null, mSafeMode + ? HapticFeedbackConstants.SAFE_MODE_ENABLED + : HapticFeedbackConstants.SAFE_MODE_DISABLED, true); + if (mSafeMode) { + Log.i(TAG, "SAFE MODE ENABLED (menu=" + menuState + " s=" + sState + + " dpad=" + dpadState + " trackball=" + trackballState + ")"); + } else { + Log.i(TAG, "SAFE MODE not enabled"); + } + return mSafeMode; + } catch (RemoteException e) { + // Doom! (it's also local) + throw new RuntimeException("window manager dead"); + } + } + + static long[] getLongIntArray(Resources r, int resid) { + int[] ar = r.getIntArray(resid); + if (ar == null) { + return null; + } + long[] out = new long[ar.length]; + for (int i=0; i<ar.length; i++) { + out[i] = ar[i]; + } + return out; + } + + /** {@inheritDoc} */ + public void systemReady() { + // tell the keyguard + mKeyguardMediator.onSystemReady(); + android.os.SystemProperties.set("dev.bootcomplete", "1"); + synchronized (mLock) { + updateOrientationListenerLp(); + mSystemReady = true; + mHandler.post(new Runnable() { + public void run() { + updateSettings(); + } + }); + } + } + + /** {@inheritDoc} */ + public void userActivity() { + synchronized (mScreenLockTimeout) { + if (mLockScreenTimerActive) { + // reset the timer + mHandler.removeCallbacks(mScreenLockTimeout); + mHandler.postDelayed(mScreenLockTimeout, mLockScreenTimeout); + } + } + } + + Runnable mScreenLockTimeout = new Runnable() { + public void run() { + synchronized (this) { + if (localLOGV) Log.v(TAG, "mScreenLockTimeout activating keyguard"); + mKeyguardMediator.doKeyguardTimeout(); + mLockScreenTimerActive = false; + } + } + }; + + private void updateLockScreenTimeout() { + synchronized (mScreenLockTimeout) { + boolean enable = (mAllowLockscreenWhenOn && mScreenOn && mKeyguardMediator.isSecure()); + if (mLockScreenTimerActive != enable) { + if (enable) { + if (localLOGV) Log.v(TAG, "setting lockscreen timer"); + mHandler.postDelayed(mScreenLockTimeout, mLockScreenTimeout); + } else { + if (localLOGV) Log.v(TAG, "clearing lockscreen timer"); + mHandler.removeCallbacks(mScreenLockTimeout); + } + mLockScreenTimerActive = enable; + } + } + } + + /** {@inheritDoc} */ + public void enableScreenAfterBoot() { + readLidState(); + updateRotation(Surface.FLAGS_ORIENTATION_ANIMATION_DISABLE); + } + + void updateRotation(int animFlags) { + mPowerManager.setKeyboardVisibility(mLidOpen); + int rotation = Surface.ROTATION_0; + if (mLidOpen) { + rotation = mLidOpenRotation; + } else if (mDockMode == Intent.EXTRA_DOCK_STATE_CAR && mCarDockRotation >= 0) { + rotation = mCarDockRotation; + } else if (mDockMode == Intent.EXTRA_DOCK_STATE_DESK && mDeskDockRotation >= 0) { + rotation = mDeskDockRotation; + } + //if lid is closed orientation will be portrait + try { + //set orientation on WindowManager + mWindowManager.setRotation(rotation, true, + mFancyRotationAnimation | animFlags); + } catch (RemoteException e) { + // Ignore + } + } + + /** + * Return an Intent to launch the currently active dock as home. Returns + * null if the standard home should be launched. + * @return + */ + Intent createHomeDockIntent() { + Intent intent; + + // What home does is based on the mode, not the dock state. That + // is, when in car mode you should be taken to car home regardless + // of whether we are actually in a car dock. + if (mUiMode == Configuration.UI_MODE_TYPE_CAR) { + intent = mCarDockIntent; + } else if (mUiMode == Configuration.UI_MODE_TYPE_DESK) { + intent = mDeskDockIntent; + } else { + return null; + } + + ActivityInfo ai = intent.resolveActivityInfo( + mContext.getPackageManager(), PackageManager.GET_META_DATA); + if (ai == null) { + return null; + } + + if (ai.metaData != null && ai.metaData.getBoolean(Intent.METADATA_DOCK_HOME)) { + intent = new Intent(intent); + intent.setClassName(ai.packageName, ai.name); + return intent; + } + + return null; + } + + void startDockOrHome() { + Intent dock = createHomeDockIntent(); + if (dock != null) { + try { + mContext.startActivity(dock); + return; + } catch (ActivityNotFoundException e) { + } + } + mContext.startActivity(mHomeIntent); + } + + /** + * goes to the home screen + * @return whether it did anything + */ + boolean goHome() { + if (false) { + // This code always brings home to the front. + try { + ActivityManagerNative.getDefault().stopAppSwitches(); + } catch (RemoteException e) { + } + sendCloseSystemWindows(); + startDockOrHome(); + } else { + // This code brings home to the front or, if it is already + // at the front, puts the device to sleep. + try { + if (SystemProperties.getInt("persist.sys.uts-test-mode", 0) == 1) { + /// Roll back EndcallBehavior as the cupcake design to pass P1 lab entry. + Log.d(TAG, "UTS-TEST-MODE"); + } else { + ActivityManagerNative.getDefault().stopAppSwitches(); + sendCloseSystemWindows(); + Intent dock = createHomeDockIntent(); + if (dock != null) { + int result = ActivityManagerNative.getDefault() + .startActivity(null, dock, + dock.resolveTypeIfNeeded(mContext.getContentResolver()), + null, 0, null, null, 0, true /* onlyIfNeeded*/, false); + if (result == IActivityManager.START_RETURN_INTENT_TO_CALLER) { + return false; + } + } + } + int result = ActivityManagerNative.getDefault() + .startActivity(null, mHomeIntent, + mHomeIntent.resolveTypeIfNeeded(mContext.getContentResolver()), + null, 0, null, null, 0, true /* onlyIfNeeded*/, false); + if (result == IActivityManager.START_RETURN_INTENT_TO_CALLER) { + return false; + } + } catch (RemoteException ex) { + // bummer, the activity manager, which is in this process, is dead + } + } + return true; + } + + public void setCurrentOrientationLw(int newOrientation) { + synchronized (mLock) { + if (newOrientation != mCurrentAppOrientation) { + mCurrentAppOrientation = newOrientation; + updateOrientationListenerLp(); + } + } + } + + public boolean performHapticFeedbackLw(WindowState win, int effectId, boolean always) { + final boolean hapticsDisabled = Settings.System.getInt(mContext.getContentResolver(), + Settings.System.HAPTIC_FEEDBACK_ENABLED, 0) == 0; + if (!always && (hapticsDisabled || mKeyguardMediator.isShowingAndNotHidden())) { + return false; + } + long[] pattern = null; + switch (effectId) { + case HapticFeedbackConstants.LONG_PRESS: + pattern = mLongPressVibePattern; + break; + case HapticFeedbackConstants.VIRTUAL_KEY: + pattern = mVirtualKeyVibePattern; + break; + case HapticFeedbackConstants.KEYBOARD_TAP: + pattern = mKeyboardTapVibePattern; + break; + case HapticFeedbackConstants.SAFE_MODE_DISABLED: + pattern = mSafeModeDisabledVibePattern; + break; + case HapticFeedbackConstants.SAFE_MODE_ENABLED: + pattern = mSafeModeEnabledVibePattern; + break; + default: + return false; + } + if (pattern.length == 1) { + // One-shot vibration + mVibrator.vibrate(pattern[0]); + } else { + // Pattern vibration + mVibrator.vibrate(pattern, -1); + } + return true; + } + + public void keyFeedbackFromInput(KeyEvent event) { + if (event.getAction() == KeyEvent.ACTION_DOWN + && (event.getFlags()&KeyEvent.FLAG_VIRTUAL_HARD_KEY) != 0) { + performHapticFeedbackLw(null, HapticFeedbackConstants.VIRTUAL_KEY, false); + } + } + + public void screenOnStoppedLw() { + if (!mKeyguardMediator.isShowingAndNotHidden() && mPowerManager.isScreenOn()) { + long curTime = SystemClock.uptimeMillis(); + mPowerManager.userActivity(curTime, false, LocalPowerManager.OTHER_EVENT); + } + } + + public boolean allowKeyRepeat() { + // disable key repeat when screen is off + return mScreenOn; + } +} diff --git a/policy/src/com/android/internal/policy/impl/Policy.java b/policy/src/com/android/internal/policy/impl/Policy.java new file mode 100644 index 0000000..17f3e91 --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/Policy.java @@ -0,0 +1,69 @@ +/* + * 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; + +import android.content.Context; +import android.util.Log; + +import com.android.internal.policy.IPolicy; +import com.android.internal.policy.impl.PhoneLayoutInflater; +import com.android.internal.policy.impl.PhoneWindow; +import com.android.internal.policy.impl.PhoneWindowManager; + +/** + * {@hide} + */ + +// Simple implementation of the policy interface that spawns the right +// set of objects +public class Policy implements IPolicy { + private static final String TAG = "PhonePolicy"; + + private static final String[] preload_classes = { + "com.android.internal.policy.impl.PhoneLayoutInflater", + "com.android.internal.policy.impl.PhoneWindow", + "com.android.internal.policy.impl.PhoneWindow$1", + "com.android.internal.policy.impl.PhoneWindow$ContextMenuCallback", + "com.android.internal.policy.impl.PhoneWindow$DecorView", + "com.android.internal.policy.impl.PhoneWindow$PanelFeatureState", + "com.android.internal.policy.impl.PhoneWindow$PanelFeatureState$SavedState", + }; + + static { + // For performance reasons, preload some policy specific classes when + // the policy gets loaded. + for (String s : preload_classes) { + try { + Class.forName(s); + } catch (ClassNotFoundException ex) { + Log.e(TAG, "Could not preload class for phone policy: " + s); + } + } + } + + public PhoneWindow makeNewWindow(Context context) { + return new PhoneWindow(context); + } + + public PhoneLayoutInflater makeNewLayoutInflater(Context context) { + return new PhoneLayoutInflater(context); + } + + public PhoneWindowManager makeNewWindowManager() { + return new PhoneWindowManager(); + } +} diff --git a/policy/src/com/android/internal/policy/impl/PowerDialog.java b/policy/src/com/android/internal/policy/impl/PowerDialog.java new file mode 100644 index 0000000..de35bd7 --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/PowerDialog.java @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.policy.impl; + +import com.android.internal.R; + +import android.app.Dialog; +import android.app.StatusBarManager; +import android.content.Context; +import android.os.Bundle; +import android.os.RemoteException; +import android.os.LocalPowerManager; +import android.os.ServiceManager; +import android.os.SystemClock; + +import com.android.internal.app.ShutdownThread; +import com.android.internal.telephony.ITelephony; +import android.view.KeyEvent; +import android.util.Log; +import android.view.View; +import android.view.WindowManager; +import android.view.View.OnClickListener; +import android.view.View.OnKeyListener; +import android.widget.Button; + +/** + * @deprecated use {@link GlobalActions} instead. + */ +public class PowerDialog extends Dialog implements OnClickListener, + OnKeyListener { + private static final String TAG = "PowerDialog"; + + static private StatusBarManager sStatusBar; + private Button mKeyguard; + private Button mPower; + private Button mRadioPower; + private Button mSilent; + + private LocalPowerManager mPowerManager; + + public PowerDialog(Context context, LocalPowerManager powerManager) { + super(context); + mPowerManager = powerManager; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Context context = getContext(); + + if (sStatusBar == null) { + sStatusBar = (StatusBarManager)context.getSystemService(Context.STATUS_BAR_SERVICE); + } + + setContentView(com.android.internal.R.layout.power_dialog); + + getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG); + if (!getContext().getResources().getBoolean( + com.android.internal.R.bool.config_sf_slowBlur)) { + getWindow().setFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND, + WindowManager.LayoutParams.FLAG_BLUR_BEHIND); + } + getWindow().setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, + WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); + + setTitle(context.getText(R.string.power_dialog)); + + mKeyguard = (Button) findViewById(R.id.keyguard); + mPower = (Button) findViewById(R.id.off); + mRadioPower = (Button) findViewById(R.id.radio_power); + mSilent = (Button) findViewById(R.id.silent); + + if (mKeyguard != null) { + mKeyguard.setOnKeyListener(this); + mKeyguard.setOnClickListener(this); + } + if (mPower != null) { + mPower.setOnClickListener(this); + } + if (mRadioPower != null) { + mRadioPower.setOnClickListener(this); + } + if (mSilent != null) { + mSilent.setOnClickListener(this); + // XXX: HACK for now hide the silent until we get mute support + mSilent.setVisibility(View.GONE); + } + + CharSequence text; + + // set the keyguard button's text + text = context.getText(R.string.screen_lock); + mKeyguard.setText(text); + mKeyguard.requestFocus(); + + try { + ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone")); + if (phone != null) { + text = phone.isRadioOn() ? context + .getText(R.string.turn_off_radio) : context + .getText(R.string.turn_on_radio); + } + } catch (RemoteException ex) { + // ignore it + } + + mRadioPower.setText(text); + } + + public void onClick(View v) { + this.dismiss(); + if (v == mPower) { + // shutdown by making sure radio and power are handled accordingly. + ShutdownThread.shutdown(getContext(), true); + } else if (v == mRadioPower) { + try { + ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone")); + if (phone != null) { + phone.toggleRadioOnOff(); + } + } catch (RemoteException ex) { + // ignore it + } + } else if (v == mSilent) { + // do something + } else if (v == mKeyguard) { + if (v.isInTouchMode()) { + // only in touch mode for the reasons explained in onKey. + this.dismiss(); + mPowerManager.goToSleep(SystemClock.uptimeMillis() + 1); + } + } + } + + public boolean onKey(View v, int keyCode, KeyEvent event) { + // The activate keyguard button needs to put the device to sleep on the + // key up event. If we try to put it to sleep on the click or down + // action + // the the up action will cause the device to wake back up. + + // Log.i(TAG, "keyCode: " + keyCode + " action: " + event.getAction()); + if (keyCode != KeyEvent.KEYCODE_DPAD_CENTER + || event.getAction() != KeyEvent.ACTION_UP) { + // Log.i(TAG, "getting out of dodge..."); + return false; + } + + // Log.i(TAG, "Clicked mKeyguard! dimissing dialog"); + this.dismiss(); + // Log.i(TAG, "onKey: turning off the screen..."); + // XXX: This is a hack for now + mPowerManager.goToSleep(event.getEventTime() + 1); + return true; + } + + public void show() { + super.show(); + Log.d(TAG, "show... disabling expand"); + sStatusBar.disable(StatusBarManager.DISABLE_EXPAND); + } + + public void dismiss() { + super.dismiss(); + Log.d(TAG, "dismiss... reenabling expand"); + sStatusBar.disable(StatusBarManager.DISABLE_NONE); + } +} diff --git a/policy/src/com/android/internal/policy/impl/RecentApplicationsBackground.java b/policy/src/com/android/internal/policy/impl/RecentApplicationsBackground.java new file mode 100644 index 0000000..7c99e87 --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/RecentApplicationsBackground.java @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.policy.impl; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.util.Log; +import android.view.Gravity; +import android.view.View; +import android.widget.LinearLayout; + +/** + * A vertical linear layout. However, instead of drawing the background + * behnd the items, it draws the background outside the items based on the + * padding. If there isn't enough room to draw both, it clips the background + * instead of the contents. + */ +public class RecentApplicationsBackground extends LinearLayout { + private static final String TAG = "RecentApplicationsBackground"; + + private boolean mBackgroundSizeChanged; + private Drawable mBackground; + private Rect mTmp0 = new Rect(); + private Rect mTmp1 = new Rect(); + + public RecentApplicationsBackground(Context context) { + this(context, null); + init(); + } + + public RecentApplicationsBackground(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + private void init() { + mBackground = getBackground(); + setBackgroundDrawable(null); + setPadding(0, 0, 0, 0); + setGravity(Gravity.CENTER); + } + + @Override + protected boolean setFrame(int left, int top, int right, int bottom) { + setWillNotDraw(false); + if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) { + mBackgroundSizeChanged = true; + } + return super.setFrame(left, top, right, bottom); + } + + @Override + protected boolean verifyDrawable(Drawable who) { + return who == mBackground || super.verifyDrawable(who); + } + + @Override + protected void drawableStateChanged() { + Drawable d = mBackground; + if (d != null && d.isStateful()) { + d.setState(getDrawableState()); + } + super.drawableStateChanged(); + } + + @Override + public void draw(Canvas canvas) { + final Drawable background = mBackground; + if (background != null) { + if (mBackgroundSizeChanged) { + mBackgroundSizeChanged = false; + Rect chld = mTmp0; + Rect bkg = mTmp1; + mBackground.getPadding(bkg); + getChildBounds(chld); + // This doesn't clamp to this view's bounds, which is what we want, + // so that the drawing is clipped. + final int top = chld.top - bkg.top; + final int bottom = chld.bottom + bkg.bottom; + // The background here is a gradient that wants to + // extend the full width of the screen (whatever that + // may be). + int left, right; + if (false) { + // This limits the width of the drawable. + left = chld.left - bkg.left; + right = chld.right + bkg.right; + } else { + // This expands it to full width. + left = 0; + right = getRight(); + } + background.setBounds(left, top, right, bottom); + } + } + mBackground.draw(canvas); + + if (false) { + android.graphics.Paint p = new android.graphics.Paint(); + p.setColor(0x88ffff00); + canvas.drawRect(background.getBounds(), p); + } + canvas.drawARGB((int)(0.75*0xff), 0, 0, 0); + + super.draw(canvas); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + mBackground.setCallback(this); + setWillNotDraw(false); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + mBackground.setCallback(null); + } + + private void getChildBounds(Rect r) { + r.left = r.top = Integer.MAX_VALUE; + r.bottom = r.right = Integer.MIN_VALUE; + final int N = getChildCount(); + for (int i=0; i<N; i++) { + View v = getChildAt(i); + if (v.getVisibility() == View.VISIBLE) { + r.left = Math.min(r.left, v.getLeft()); + r.top = Math.min(r.top, v.getTop()); + r.right = Math.max(r.right, v.getRight()); + r.bottom = Math.max(r.bottom, v.getBottom()); + } + } + } +} diff --git a/policy/src/com/android/internal/policy/impl/RecentApplicationsDialog.java b/policy/src/com/android/internal/policy/impl/RecentApplicationsDialog.java new file mode 100644 index 0000000..9608b9a --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/RecentApplicationsDialog.java @@ -0,0 +1,262 @@ +/* + * 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; + +import android.app.ActivityManager; +import android.app.Dialog; +import android.app.StatusBarManager; +import android.content.ActivityNotFoundException; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.res.Resources; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; +import android.view.View.OnClickListener; +import android.widget.TextView; + +import java.util.List; + +public class RecentApplicationsDialog extends Dialog implements OnClickListener { + // Elements for debugging support +// private static final String LOG_TAG = "RecentApplicationsDialog"; + private static final boolean DBG_FORCE_EMPTY_LIST = false; + + static private StatusBarManager sStatusBar; + + private static final int NUM_BUTTONS = 8; + private static final int MAX_RECENT_TASKS = NUM_BUTTONS * 2; // allow for some discards + + final TextView[] mIcons = new TextView[NUM_BUTTONS]; + View mNoAppsText; + IntentFilter mBroadcastIntentFilter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); + + private int mIconSize; + + public RecentApplicationsDialog(Context context) { + super(context, com.android.internal.R.style.Theme_Dialog_RecentApplications); + + final Resources resources = context.getResources(); + mIconSize = (int) resources.getDimension(android.R.dimen.app_icon_size); + } + + /** + * We create the recent applications dialog just once, and it stays around (hidden) + * until activated by the user. + * + * @see PhoneWindowManager#showRecentAppsDialog + */ + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Context context = getContext(); + + if (sStatusBar == null) { + sStatusBar = (StatusBarManager)context.getSystemService(Context.STATUS_BAR_SERVICE); + } + + Window window = getWindow(); + window.requestFeature(Window.FEATURE_NO_TITLE); + window.setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG); + window.setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, + WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); + window.setTitle("Recents"); + + setContentView(com.android.internal.R.layout.recent_apps_dialog); + + final WindowManager.LayoutParams params = window.getAttributes(); + params.width = WindowManager.LayoutParams.MATCH_PARENT; + params.height = WindowManager.LayoutParams.MATCH_PARENT; + window.setAttributes(params); + window.setFlags(0, WindowManager.LayoutParams.FLAG_DIM_BEHIND); + + mIcons[0] = (TextView)findViewById(com.android.internal.R.id.button0); + mIcons[1] = (TextView)findViewById(com.android.internal.R.id.button1); + mIcons[2] = (TextView)findViewById(com.android.internal.R.id.button2); + mIcons[3] = (TextView)findViewById(com.android.internal.R.id.button3); + mIcons[4] = (TextView)findViewById(com.android.internal.R.id.button4); + mIcons[5] = (TextView)findViewById(com.android.internal.R.id.button5); + mIcons[6] = (TextView)findViewById(com.android.internal.R.id.button6); + mIcons[7] = (TextView)findViewById(com.android.internal.R.id.button7); + mNoAppsText = findViewById(com.android.internal.R.id.no_applications_message); + + for (TextView b: mIcons) { + b.setOnClickListener(this); + } + } + + /** + * Handler for user clicks. If a button was clicked, launch the corresponding activity. + */ + public void onClick(View v) { + + for (TextView b: mIcons) { + if (b == v) { + // prepare a launch intent and send it + Intent intent = (Intent)b.getTag(); + if (intent != null) { + intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY); + try { + getContext().startActivity(intent); + } catch (ActivityNotFoundException e) { + Log.w("Recent", "Unable to launch recent task", e); + } + } + break; + } + } + dismiss(); + } + + /** + * Set up and show the recent activities dialog. + */ + @Override + public void onStart() { + super.onStart(); + reloadButtons(); + if (sStatusBar != null) { + sStatusBar.disable(StatusBarManager.DISABLE_EXPAND); + } + + // receive broadcasts + getContext().registerReceiver(mBroadcastReceiver, mBroadcastIntentFilter); + } + + /** + * Dismiss the recent activities dialog. + */ + @Override + public void onStop() { + super.onStop(); + + // dump extra memory we're hanging on to + for (TextView icon: mIcons) { + icon.setCompoundDrawables(null, null, null, null); + icon.setTag(null); + } + + if (sStatusBar != null) { + sStatusBar.disable(StatusBarManager.DISABLE_NONE); + } + + // stop receiving broadcasts + getContext().unregisterReceiver(mBroadcastReceiver); + } + + /** + * Reload the 6 buttons with recent activities + */ + private void reloadButtons() { + + final Context context = getContext(); + final PackageManager pm = context.getPackageManager(); + final ActivityManager am = (ActivityManager) + context.getSystemService(Context.ACTIVITY_SERVICE); + final List<ActivityManager.RecentTaskInfo> recentTasks = + am.getRecentTasks(MAX_RECENT_TASKS, ActivityManager.RECENT_IGNORE_UNAVAILABLE); + + ActivityInfo homeInfo = + new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME) + .resolveActivityInfo(pm, 0); + + IconUtilities iconUtilities = new IconUtilities(getContext()); + + // Performance note: Our android performance guide says to prefer Iterator when + // using a List class, but because we know that getRecentTasks() always returns + // an ArrayList<>, we'll use a simple index instead. + int index = 0; + int numTasks = recentTasks.size(); + for (int i = 0; i < numTasks && (index < NUM_BUTTONS); ++i) { + final ActivityManager.RecentTaskInfo info = recentTasks.get(i); + + // for debug purposes only, disallow first result to create empty lists + if (DBG_FORCE_EMPTY_LIST && (i == 0)) continue; + + Intent intent = new Intent(info.baseIntent); + if (info.origActivity != null) { + intent.setComponent(info.origActivity); + } + + // Skip the current home activity. + if (homeInfo != null) { + if (homeInfo.packageName.equals( + intent.getComponent().getPackageName()) + && homeInfo.name.equals( + intent.getComponent().getClassName())) { + continue; + } + } + + intent.setFlags((intent.getFlags()&~Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) + | Intent.FLAG_ACTIVITY_NEW_TASK); + final ResolveInfo resolveInfo = pm.resolveActivity(intent, 0); + if (resolveInfo != null) { + final ActivityInfo activityInfo = resolveInfo.activityInfo; + final String title = activityInfo.loadLabel(pm).toString(); + Drawable icon = activityInfo.loadIcon(pm); + + if (title != null && title.length() > 0 && icon != null) { + final TextView tv = mIcons[index]; + tv.setText(title); + icon = iconUtilities.createIconDrawable(icon); + tv.setCompoundDrawables(null, icon, null, null); + tv.setTag(intent); + tv.setVisibility(View.VISIBLE); + tv.setPressed(false); + tv.clearFocus(); + ++index; + } + } + } + + // handle the case of "no icons to show" + mNoAppsText.setVisibility((index == 0) ? View.VISIBLE : View.GONE); + + // hide the rest + for (; index < NUM_BUTTONS; ++index) { + mIcons[index].setVisibility(View.GONE); + } + } + + /** + * This is the listener for the ACTION_CLOSE_SYSTEM_DIALOGS intent. It's an indication that + * we should close ourselves immediately, in order to allow a higher-priority UI to take over + * (e.g. phone call received). + */ + private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) { + String reason = intent.getStringExtra(PhoneWindowManager.SYSTEM_DIALOG_REASON_KEY); + if (! PhoneWindowManager.SYSTEM_DIALOG_REASON_RECENT_APPS.equals(reason)) { + dismiss(); + } + } + } + }; +} diff --git a/policy/src/com/android/internal/policy/impl/ShortcutManager.java b/policy/src/com/android/internal/policy/impl/ShortcutManager.java new file mode 100644 index 0000000..d86ac44 --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/ShortcutManager.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.policy.impl; + +import android.content.Context; +import android.content.Intent; +import android.database.ContentObserver; +import android.database.Cursor; +import android.os.Handler; +import android.provider.Settings; +import android.util.Log; +import android.util.SparseArray; +import android.view.KeyCharacterMap; + +import java.net.URISyntaxException; + +/** + * Manages quick launch shortcuts by: + * <li> Keeping the local copy in sync with the database (this is an observer) + * <li> Returning a shortcut-matching intent to clients + */ +class ShortcutManager extends ContentObserver { + + private static final String TAG = "ShortcutManager"; + + private static final int COLUMN_SHORTCUT = 0; + private static final int COLUMN_INTENT = 1; + private static final String[] sProjection = new String[] { + Settings.Bookmarks.SHORTCUT, Settings.Bookmarks.INTENT + }; + + private Context mContext; + private Cursor mCursor; + /** Map of a shortcut to its intent. */ + private SparseArray<Intent> mShortcutIntents; + + public ShortcutManager(Context context, Handler handler) { + super(handler); + + mContext = context; + mShortcutIntents = new SparseArray<Intent>(); + } + + /** Observes the provider of shortcut+intents */ + public void observe() { + mCursor = mContext.getContentResolver().query( + Settings.Bookmarks.CONTENT_URI, sProjection, null, null, null); + mCursor.registerContentObserver(this); + updateShortcuts(); + } + + @Override + public void onChange(boolean selfChange) { + updateShortcuts(); + } + + private void updateShortcuts() { + Cursor c = mCursor; + if (!c.requery()) { + Log.e(TAG, "ShortcutObserver could not re-query shortcuts."); + return; + } + + mShortcutIntents.clear(); + while (c.moveToNext()) { + int shortcut = c.getInt(COLUMN_SHORTCUT); + if (shortcut == 0) continue; + String intentURI = c.getString(COLUMN_INTENT); + Intent intent = null; + try { + intent = Intent.getIntent(intentURI); + } catch (URISyntaxException e) { + Log.w(TAG, "Intent URI for shortcut invalid.", e); + } + if (intent == null) continue; + mShortcutIntents.put(shortcut, intent); + } + } + + /** + * Gets the shortcut intent for a given keycode+modifier. Make sure you + * strip whatever modifier is used for invoking shortcuts (for example, + * if 'Sym+A' should invoke a shortcut on 'A', you should strip the + * 'Sym' bit from the modifiers before calling this method. + * <p> + * This will first try an exact match (with modifiers), and then try a + * match without modifiers (primary character on a key). + * + * @param keyCode The keycode of the key pushed. + * @param modifiers The modifiers without any that are used for chording + * to invoke a shortcut. + * @return The intent that matches the shortcut, or null if not found. + */ + public Intent getIntent(int keyCode, int modifiers) { + KeyCharacterMap kcm = KeyCharacterMap.load(KeyCharacterMap.BUILT_IN_KEYBOARD); + // First try the exact keycode (with modifiers) + int shortcut = kcm.get(keyCode, modifiers); + Intent intent = shortcut != 0 ? mShortcutIntents.get(shortcut) : null; + if (intent != null) return intent; + + // Next try the keycode without modifiers (the primary character on that key) + shortcut = Character.toLowerCase(kcm.get(keyCode, 0)); + return shortcut != 0 ? mShortcutIntents.get(shortcut) : null; + } + +} diff --git a/policy/src/com/android/internal/policy/impl/SimUnlockScreen.java b/policy/src/com/android/internal/policy/impl/SimUnlockScreen.java new file mode 100644 index 0000000..5518e11 --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/SimUnlockScreen.java @@ -0,0 +1,423 @@ +/* + * 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; + +import android.app.Dialog; +import android.app.ProgressDialog; +import android.content.Context; +import android.content.res.Configuration; +import android.os.RemoteException; +import android.os.ServiceManager; + +import com.android.internal.telephony.ITelephony; +import com.android.internal.widget.LockPatternUtils; + +import android.text.Editable; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.WindowManager; +import android.widget.Button; +import android.widget.LinearLayout; +import android.widget.TextView; +import com.android.internal.R; + +/** + * Displays a dialer like interface to unlock the SIM PIN. + */ +public class SimUnlockScreen extends LinearLayout implements KeyguardScreen, View.OnClickListener, + KeyguardUpdateMonitor.InfoCallback { + + private static final int DIGIT_PRESS_WAKE_MILLIS = 5000; + + private final KeyguardUpdateMonitor mUpdateMonitor; + private final KeyguardScreenCallback mCallback; + + private TextView mHeaderText; + private TextView mPinText; + + private TextView mOkButton; + private Button mEmergencyCallButton; + + private View mBackSpaceButton; + + private final int[] mEnteredPin = {0, 0, 0, 0, 0, 0, 0, 0}; + private int mEnteredDigits = 0; + + private ProgressDialog mSimUnlockProgressDialog = null; + + private LockPatternUtils mLockPatternUtils; + + private int mCreationOrientation; + + private int mKeyboardHidden; + + private static final char[] DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}; + + public SimUnlockScreen(Context context, Configuration configuration, + KeyguardUpdateMonitor updateMonitor, KeyguardScreenCallback callback, + LockPatternUtils lockpatternutils) { + super(context); + mUpdateMonitor = updateMonitor; + mCallback = callback; + + mCreationOrientation = configuration.orientation; + mKeyboardHidden = configuration.hardKeyboardHidden; + mLockPatternUtils = lockpatternutils; + + LayoutInflater inflater = LayoutInflater.from(context); + if (mKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO) { + inflater.inflate(R.layout.keyguard_screen_sim_pin_landscape, this, true); + } else { + inflater.inflate(R.layout.keyguard_screen_sim_pin_portrait, this, true); + new TouchInput(); + } + + mHeaderText = (TextView) findViewById(R.id.headerText); + mPinText = (TextView) findViewById(R.id.pinDisplay); + mBackSpaceButton = findViewById(R.id.backspace); + mBackSpaceButton.setOnClickListener(this); + + mEmergencyCallButton = (Button) findViewById(R.id.emergencyCall); + mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCallButton); + mOkButton = (TextView) findViewById(R.id.ok); + + mHeaderText.setText(R.string.keyguard_password_enter_pin_code); + mPinText.setFocusable(false); + + mEmergencyCallButton.setOnClickListener(this); + mOkButton.setOnClickListener(this); + + setFocusableInTouchMode(true); + } + + /** {@inheritDoc} */ + public boolean needsInput() { + return true; + } + + /** {@inheritDoc} */ + public void onPause() { + + } + + /** {@inheritDoc} */ + public void onResume() { + // start fresh + mHeaderText.setText(R.string.keyguard_password_enter_pin_code); + + // make sure that the number of entered digits is consistent when we + // erase the SIM unlock code, including orientation changes. + mPinText.setText(""); + mEnteredDigits = 0; + + mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCallButton); + } + + /** {@inheritDoc} */ + public void cleanUp() { + // hide the dialog. + if (mSimUnlockProgressDialog != null) { + mSimUnlockProgressDialog.hide(); + } + mUpdateMonitor.removeCallback(this); + } + + + /** + * 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 void onClick(View v) { + if (v == mBackSpaceButton) { + final Editable digits = mPinText.getEditableText(); + final int len = digits.length(); + if (len > 0) { + digits.delete(len-1, len); + mEnteredDigits--; + } + mCallback.pokeWakelock(); + } else if (v == mEmergencyCallButton) { + mCallback.takeEmergencyCallAction(); + } else if (v == mOkButton) { + checkPin(); + } + } + + private Dialog getSimUnlockProgressDialog() { + if (mSimUnlockProgressDialog == null) { + mSimUnlockProgressDialog = new ProgressDialog(mContext); + mSimUnlockProgressDialog.setMessage( + mContext.getString(R.string.lockscreen_sim_unlock_progress_dialog_message)); + mSimUnlockProgressDialog.setIndeterminate(true); + mSimUnlockProgressDialog.setCancelable(false); + mSimUnlockProgressDialog.getWindow().setType( + WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + if (!mContext.getResources().getBoolean( + com.android.internal.R.bool.config_sf_slowBlur)) { + mSimUnlockProgressDialog.getWindow().setFlags( + WindowManager.LayoutParams.FLAG_BLUR_BEHIND, + WindowManager.LayoutParams.FLAG_BLUR_BEHIND); + } + } + return mSimUnlockProgressDialog; + } + + private void checkPin() { + + // make sure that the pin is at least 4 digits long. + if (mEnteredDigits < 4) { + // otherwise, display a message to the user, and don't submit. + mHeaderText.setText(R.string.invalidPin); + mPinText.setText(""); + mEnteredDigits = 0; + mCallback.pokeWakelock(); + return; + } + getSimUnlockProgressDialog().show(); + + new CheckSimPin(mPinText.getText().toString()) { + void onSimLockChangedResponse(boolean success) { + if (mSimUnlockProgressDialog != null) { + mSimUnlockProgressDialog.hide(); + } + if (success) { + // before closing the keyguard, report back that + // the sim is unlocked so it knows right away + mUpdateMonitor.reportSimPinUnlocked(); + mCallback.goToUnlockScreen(); + } else { + mHeaderText.setText(R.string.keyguard_password_wrong_pin_code); + mPinText.setText(""); + mEnteredDigits = 0; + } + mCallback.pokeWakelock(); + } + }.start(); + } + + + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_BACK) { + mCallback.goToLockScreen(); + return true; + } + + final char match = event.getMatch(DIGITS); + if (match != 0) { + reportDigit(match - '0'); + return true; + } + if (keyCode == KeyEvent.KEYCODE_DEL) { + if (mEnteredDigits > 0) { + mPinText.onKeyDown(keyCode, event); + mEnteredDigits--; + } + return true; + } + + if (keyCode == KeyEvent.KEYCODE_ENTER) { + checkPin(); + return true; + } + + return false; + } + + private void reportDigit(int digit) { + if (mEnteredDigits == 0) { + mPinText.setText(""); + } + if (mEnteredDigits == 8) { + return; + } + mPinText.append(Integer.toString(digit)); + mEnteredPin[mEnteredDigits++] = digit; + } + + void updateConfiguration() { + Configuration newConfig = getResources().getConfiguration(); + if (newConfig.orientation != mCreationOrientation) { + mCallback.recreateMe(newConfig); + } else if (newConfig.hardKeyboardHidden != mKeyboardHidden) { + mKeyboardHidden = newConfig.hardKeyboardHidden; + final boolean isKeyboardOpen = mKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO; + if (mUpdateMonitor.isKeyguardBypassEnabled() && isKeyboardOpen) { + mCallback.goToUnlockScreen(); + } + } + + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + updateConfiguration(); + } + + /** {@inheritDoc} */ + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + updateConfiguration(); + } + + /** + * Helper class to handle input from touch dialer. Only relevant when + * the keyboard is shut. + */ + private class TouchInput implements View.OnClickListener { + private TextView mZero; + private TextView mOne; + private TextView mTwo; + private TextView mThree; + private TextView mFour; + private TextView mFive; + private TextView mSix; + private TextView mSeven; + private TextView mEight; + private TextView mNine; + private TextView mCancelButton; + + private TouchInput() { + mZero = (TextView) findViewById(R.id.zero); + mOne = (TextView) findViewById(R.id.one); + mTwo = (TextView) findViewById(R.id.two); + mThree = (TextView) findViewById(R.id.three); + mFour = (TextView) findViewById(R.id.four); + mFive = (TextView) findViewById(R.id.five); + mSix = (TextView) findViewById(R.id.six); + mSeven = (TextView) findViewById(R.id.seven); + mEight = (TextView) findViewById(R.id.eight); + mNine = (TextView) findViewById(R.id.nine); + mCancelButton = (TextView) findViewById(R.id.cancel); + + mZero.setText("0"); + mOne.setText("1"); + mTwo.setText("2"); + mThree.setText("3"); + mFour.setText("4"); + mFive.setText("5"); + mSix.setText("6"); + mSeven.setText("7"); + mEight.setText("8"); + mNine.setText("9"); + + mZero.setOnClickListener(this); + mOne.setOnClickListener(this); + mTwo.setOnClickListener(this); + mThree.setOnClickListener(this); + mFour.setOnClickListener(this); + mFive.setOnClickListener(this); + mSix.setOnClickListener(this); + mSeven.setOnClickListener(this); + mEight.setOnClickListener(this); + mNine.setOnClickListener(this); + mCancelButton.setOnClickListener(this); + } + + + public void onClick(View v) { + if (v == mCancelButton) { + mCallback.goToLockScreen(); + return; + } + + final int digit = checkDigit(v); + if (digit >= 0) { + mCallback.pokeWakelock(DIGIT_PRESS_WAKE_MILLIS); + reportDigit(digit); + } + } + + private int checkDigit(View v) { + int digit = -1; + if (v == mZero) { + digit = 0; + } else if (v == mOne) { + digit = 1; + } else if (v == mTwo) { + digit = 2; + } else if (v == mThree) { + digit = 3; + } else if (v == mFour) { + digit = 4; + } else if (v == mFive) { + digit = 5; + } else if (v == mSix) { + digit = 6; + } else if (v == mSeven) { + digit = 7; + } else if (v == mEight) { + digit = 8; + } else if (v == mNine) { + digit = 9; + } + return digit; + } + } + + public void onPhoneStateChanged(String newState) { + mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCallButton); + } + + public void onRefreshBatteryInfo(boolean showBatteryInfo, boolean pluggedIn, int batteryLevel) { + + } + + public void onRefreshCarrierInfo(CharSequence plmn, CharSequence spn) { + + } + + public void onRingerModeChanged(int state) { + + } + + public void onTimeChanged() { + + } +} diff --git a/policy/src/com/android/internal/policy/impl/package.html b/policy/src/com/android/internal/policy/impl/package.html new file mode 100644 index 0000000..c9f96a6 --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/package.html @@ -0,0 +1,5 @@ +<body> + +{@hide} + +</body> |