summaryrefslogtreecommitdiffstats
path: root/policy/src
diff options
context:
space:
mode:
authorBrett Chabot <brettchabot@android.com>2010-06-16 14:41:00 -0700
committerAndroid Git Automerger <android-git-automerger@android.com>2010-06-16 14:41:00 -0700
commit685fcf364b84d5ac911ae9cbbc4fec99f36cbd48 (patch)
tree9d6b4a4b491fdf2f558b5c690c0dba7839efa486 /policy/src
parent1af489205a3942630e6203237213e98ef53d4118 (diff)
parentc95812e6cacaa14748c81323bac6561453991a24 (diff)
downloadframeworks_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')
-rw-r--r--policy/src/com/android/internal/policy/impl/AccountUnlockScreen.java347
-rw-r--r--policy/src/com/android/internal/policy/impl/GlobalActions.java578
-rw-r--r--policy/src/com/android/internal/policy/impl/IconUtilities.java192
-rw-r--r--policy/src/com/android/internal/policy/impl/KeyguardScreen.java45
-rw-r--r--policy/src/com/android/internal/policy/impl/KeyguardScreenCallback.java82
-rw-r--r--policy/src/com/android/internal/policy/impl/KeyguardUpdateMonitor.java526
-rw-r--r--policy/src/com/android/internal/policy/impl/KeyguardViewBase.java209
-rw-r--r--policy/src/com/android/internal/policy/impl/KeyguardViewCallback.java49
-rw-r--r--policy/src/com/android/internal/policy/impl/KeyguardViewManager.java241
-rw-r--r--policy/src/com/android/internal/policy/impl/KeyguardViewMediator.java1136
-rw-r--r--policy/src/com/android/internal/policy/impl/KeyguardViewProperties.java46
-rw-r--r--policy/src/com/android/internal/policy/impl/KeyguardWindowController.java29
-rw-r--r--policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java790
-rw-r--r--policy/src/com/android/internal/policy/impl/LockPatternKeyguardViewProperties.java62
-rw-r--r--policy/src/com/android/internal/policy/impl/LockScreen.java681
-rw-r--r--policy/src/com/android/internal/policy/impl/PasswordUnlockScreen.java267
-rw-r--r--policy/src/com/android/internal/policy/impl/PatternUnlockScreen.java580
-rw-r--r--policy/src/com/android/internal/policy/impl/PhoneLayoutInflater.java73
-rw-r--r--policy/src/com/android/internal/policy/impl/PhoneWindow.java2799
-rwxr-xr-xpolicy/src/com/android/internal/policy/impl/PhoneWindowManager.java2434
-rw-r--r--policy/src/com/android/internal/policy/impl/Policy.java69
-rw-r--r--policy/src/com/android/internal/policy/impl/PowerDialog.java182
-rw-r--r--policy/src/com/android/internal/policy/impl/RecentApplicationsBackground.java152
-rw-r--r--policy/src/com/android/internal/policy/impl/RecentApplicationsDialog.java262
-rw-r--r--policy/src/com/android/internal/policy/impl/ShortcutManager.java120
-rw-r--r--policy/src/com/android/internal/policy/impl/SimUnlockScreen.java423
-rw-r--r--policy/src/com/android/internal/policy/impl/package.html5
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>