From 983983871f46d55634858287c25352cc95523790 Mon Sep 17 00:00:00 2001 From: The Android Open Source Project Date: Wed, 17 Dec 2008 18:05:50 -0800 Subject: [PATCH] Code drop from //branches/cupcake/...@124589 --- PolicyConfig.mk | 85 + mid/Android.mk | 13 + .../internal/policy/impl/GlobalActions.java | 411 ++++ .../internal/policy/impl/MidLayoutInflater.java | 73 + .../android/internal/policy/impl/MidWindow.java | 2596 ++++++++++++++++++++ .../internal/policy/impl/MidWindowManager.java | 1043 ++++++++ mid/com/android/internal/policy/impl/Policy.java | 45 + .../android/internal/policy/impl/PowerDialog.java | 177 ++ .../policy/impl/RecentApplicationsDialog.java | 252 ++ .../internal/policy/impl/ShortcutManager.java | 120 + .../internal/policy/impl/ShutdownThread.java | 137 ++ mid/com/android/internal/policy/impl/package.html | 5 + phone/Android.mk | 3 +- .../policy/impl/KeyguardScreenCallback.java | 5 + .../policy/impl/KeyguardUpdateMonitor.java | 14 +- .../internal/policy/impl/KeyguardViewBase.java | 16 +- .../internal/policy/impl/KeyguardViewMediator.java | 14 +- .../policy/impl/LockPatternKeyguardView.java | 58 +- .../android/internal/policy/impl/LockScreen.java | 4 +- .../android/internal/policy/impl/PhoneWindow.java | 100 +- .../internal/policy/impl/PhoneWindowManager.java | 568 +++-- phone/com/android/internal/policy/impl/Policy.java | 24 + .../internal/policy/impl/SimUnlockScreen.java | 14 +- .../android/internal/policy/impl/UnlockScreen.java | 14 + 24 files changed, 5570 insertions(+), 221 deletions(-) create mode 100644 PolicyConfig.mk create mode 100644 mid/Android.mk create mode 100644 mid/com/android/internal/policy/impl/GlobalActions.java create mode 100644 mid/com/android/internal/policy/impl/MidLayoutInflater.java create mode 100644 mid/com/android/internal/policy/impl/MidWindow.java create mode 100644 mid/com/android/internal/policy/impl/MidWindowManager.java create mode 100644 mid/com/android/internal/policy/impl/Policy.java create mode 100644 mid/com/android/internal/policy/impl/PowerDialog.java create mode 100644 mid/com/android/internal/policy/impl/RecentApplicationsDialog.java create mode 100644 mid/com/android/internal/policy/impl/ShortcutManager.java create mode 100644 mid/com/android/internal/policy/impl/ShutdownThread.java create mode 100644 mid/com/android/internal/policy/impl/package.html diff --git a/PolicyConfig.mk b/PolicyConfig.mk new file mode 100644 index 0000000..1b8cb49 --- /dev/null +++ b/PolicyConfig.mk @@ -0,0 +1,85 @@ +# +# 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. +# + +# Make sure our needed product policy is present. +ifeq ($(PRODUCT_POLICY),) +$(error PRODUCT_POLICY MUST be defined.) +else + ifeq ($(filter $(PRODUCT_POLICY),$(ALL_MODULES)),) + $(error No module defined for the given PRODUCT_POLICY ($(PRODUCT_POLICY))) + else + # The policy MUST specify a module which builds a single jar + ifneq ($(words $(call module-built-files,$(PRODUCT_POLICY))),1) + $(error Policy module $(PRODUCT_POLICY) must build a single library jar) + endif + endif +endif + + +# We will always build all the policies. Then, we will copy the jars from +# the product specified policy into the locations where the current module +# is expected to have its jars. Then, the normal module install code will +# take care of things. We make each policy me non-installable, so that only +# the right one gets taken and installed. Having all the policies built +# also allows us to have unittests that refer to specific policies, but not +# necessarily the one used for the current product being built. +# TODO: Is this the best way to do this? + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE := android.policy +LOCAL_MODULE_CLASS := JAVA_LIBRARIES +LOCAL_MODULE_SUFFIX := $(COMMON_JAVA_PACKAGE_SUFFIX) + +LOCAL_BUILT_MODULE_STEM := javalib.jar + +# base_rules.mk may use this in unintended ways if a stale value is +# left lying around, so make sure to clear it. +all_res_assets := + +####################################### +include $(BUILD_SYSTEM)/base_rules.mk +####################################### + +src_classes_jar := $(call _java-lib-full-classes.jar,$(PRODUCT_POLICY)) +tgt_classes_jar := $(call _java-lib-full-classes.jar,$(LOCAL_MODULE)) + +src_javalib_jar := $(call module-built-files,$(PRODUCT_POLICY)) +tgt_javalib_jar := $(LOCAL_BUILT_MODULE) + +# make sure that the classes file gets there first since some rules in other +# places assume that the classes.jar for a module exists if the javalib.jar +# file exists. +$(tgt_javalib_jar): $(tgt_classes_jar) + +$(tgt_javalib_jar): $(src_javalib_jar) | $(ACP) + @echo "Copying policy javalib.jar: $@" + $(copy-file-to-target) + +$(tgt_classes_jar): $(src_classes_jar) | $(ACP) + @echo "Copying policy classes.jar: $@" + $(copy-file-to-target) + +# +# Clean up after ourselves when switching build types +# +.PHONY: policy_installclean +policy_installclean: PRIVATE_CLEAN_DIR := $(call local-intermediates-dir,COMMON) +policy_installclean: + $(hide) rm -rf $(PRIVATE_CLEAN_DIR) + +installclean: policy_installclean diff --git a/mid/Android.mk b/mid/Android.mk new file mode 100644 index 0000000..78de0a6 --- /dev/null +++ b/mid/Android.mk @@ -0,0 +1,13 @@ +LOCAL_PATH:= $(call my-dir) + +# the library +# ============================================================ +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + $(call all-subdir-java-files) + +LOCAL_MODULE := android.policy_mid +LOCAL_UNINSTALLABLE_MODULE := true + +include $(BUILD_JAVA_LIBRARY) diff --git a/mid/com/android/internal/policy/impl/GlobalActions.java b/mid/com/android/internal/policy/impl/GlobalActions.java new file mode 100644 index 0000000..c62a6fd --- /dev/null +++ b/mid/com/android/internal/policy/impl/GlobalActions.java @@ -0,0 +1,411 @@ +/* + * 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.google.android.collect.Lists; + +import android.app.AlertDialog; +import android.app.StatusBarManager; +import android.content.Context; +import android.content.BroadcastReceiver; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.DialogInterface; +import android.media.AudioManager; +import android.os.LocalPowerManager; +import android.os.Handler; +import android.os.Message; +import android.os.SystemClock; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.BaseAdapter; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import 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 StatusBarManager mStatusBar; + + private final Context mContext; + private final LocalPowerManager mPowerManager; + private final AudioManager mAudioManager; + private ArrayList mItems; + private AlertDialog mDialog; + + private ToggleAction mSilentModeToggle; + + private MyAdapter mAdapter; + + private boolean mKeyguardShowing = false; + private boolean mDeviceProvisioned = false; + + /** + * @param context everything needs a context :) + * @param powerManager used to turn the screen off (the lock action). + */ + public GlobalActions(Context context, LocalPowerManager powerManager) { + mContext = context; + mPowerManager = powerManager; + mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); + + // receive broadcasts + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); + context.registerReceiver(mBroadcastReceiver, filter); + } + + /** + * 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 onToggle(boolean on) { + mAudioManager.setRingerMode(on ? AudioManager.RINGER_MODE_SILENT + : AudioManager.RINGER_MODE_NORMAL); + } + + public boolean showDuringKeyguard() { + return true; + } + + public boolean showBeforeProvisioning() { + return false; + } + }; + + mItems = Lists.newArrayList( + // first: lock screen + new SinglePressAction(com.android.internal.R.drawable.ic_lock_lock, R.string.global_action_lock) { + + public void onPress() { + mPowerManager.goToSleep(SystemClock.uptimeMillis() + 1); + } + + public boolean showDuringKeyguard() { + return false; + } + + public boolean showBeforeProvisioning() { + return false; + } + }, + // next: silent mode + mSilentModeToggle, + // 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.shutdownAfterDisablingRadio(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); + dialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND, + WindowManager.LayoutParams.FLAG_BLUR_BEHIND); + + dialog.setOnDismissListener(this); + + return dialog; + } + + private void prepareDialog() { + // TODO: May need another 'Vibrate' toggle button, but for now treat them the same + final boolean silentModeOn = + mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL; + mSilentModeToggle.updateState(silentModeOn); + 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; + } + + 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, (LinearLayout) convertView, 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 { + LinearLayout create(Context context, LinearLayout convertView, 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(); + } + + /** + * 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; + } + + abstract public void onPress(); + + public LinearLayout create(Context context, LinearLayout convertView, LayoutInflater inflater) { + LinearLayout v = (LinearLayout) ((convertView != null) ? + convertView : + inflater.inflate(R.layout.global_actions_item, null)); + + 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. + */ + static abstract class ToggleAction implements Action { + + private boolean mOn = false; + + // prefs + private final int mEnabledIconResId; + private final int mDisabledIconResid; + private final int mMessageResId; + private final int mEnabledStatusMessageResId; + private final 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; + } + + public LinearLayout create(Context context, LinearLayout convertView, + LayoutInflater inflater) { + LinearLayout v = (LinearLayout) ((convertView != null) ? + convertView : + inflater.inflate(R + .layout.global_actions_item, null)); + + 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); + + icon.setImageDrawable(context.getResources().getDrawable( + (mOn ? mEnabledIconResId : mDisabledIconResid))); + statusView.setText(mOn ? mEnabledStatusMessageResId : mDisabledStatusMessageResId); + statusView.setVisibility(View.VISIBLE); + + return v; + } + + public void onPress() { + updateState(!mOn); + onToggle(mOn); + } + + abstract void onToggle(boolean on); + + public void updateState(boolean on) { + mOn = on; + } + } + + private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) { + String reason = intent.getStringExtra(MidWindowManager.SYSTEM_DIALOG_REASON_KEY); + if (! MidWindowManager.SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS.equals(reason)) { + mHandler.sendEmptyMessage(MESSAGE_DISMISS); + } + } + } + }; + + 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(); + } + } + } + }; +} diff --git a/mid/com/android/internal/policy/impl/MidLayoutInflater.java b/mid/com/android/internal/policy/impl/MidLayoutInflater.java new file mode 100644 index 0000000..ae61146 --- /dev/null +++ b/mid/com/android/internal/policy/impl/MidLayoutInflater.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2006-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 java.util.Map; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.view.LayoutInflater; + +public class MidLayoutInflater 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 MidLayoutInflater(Context context) { + super(context); + } + + protected MidLayoutInflater(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 MidLayoutInflater(this, newContext); + } +} + diff --git a/mid/com/android/internal/policy/impl/MidWindow.java b/mid/com/android/internal/policy/impl/MidWindow.java new file mode 100644 index 0000000..17293b5 --- /dev/null +++ b/mid/com/android/internal/policy/impl/MidWindow.java @@ -0,0 +1,2596 @@ +/* + * 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.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.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.Paint; +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.Handler; +import android.os.Message; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.SystemClock; +import android.provider.CallLog.Calls; +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.KeyCharacterMap; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewGroup; +import static android.view.ViewGroup.LayoutParams.FILL_PARENT; +import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; +import android.view.ViewManager; +import android.view.VolumePanel; +import android.view.Window; +import android.view.WindowManager; +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 android.view.animation.Animation; +import android.view.animation.AnimationUtils; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.ProgressBar; +import android.widget.TextView; + +/** + * Android-specific Window. + *

+ * todo(hackbod): need to pull the generic functionality out into a base class + * in android.widget. + */ +public class MidWindow extends Window implements MenuBuilder.Callback { + private final static String TAG = "MidWindow"; + + 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; + + 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 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 boolean mSearchKeyDownReceived; + + private final Handler mKeycodeCallTimeoutHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + if (!mKeycodeCallTimeoutActive) return; + mKeycodeCallTimeoutActive = false; + // launch the VoiceDialer + Intent intent = new Intent(Intent.ACTION_VOICE_COMMAND); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + try { + getContext().startActivity(intent); + } catch (ActivityNotFoundException e) { + startCallActivity(); + } + } + }; + private boolean mKeycodeCallTimeoutActive = false; + + // Ideally the call and camera buttons would share a common base class, and each implement their + // own onShortPress() and onLongPress() methods, but to reduce the chance of regressions I'm + // keeping them separate for now. + private final Handler mKeycodeCameraTimeoutHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + if (!mKeycodeCameraTimeoutActive) return; + mKeycodeCameraTimeoutActive = false; + // Broadcast an intent that the Camera button was longpressed + Intent intent = new Intent(Intent.ACTION_CAMERA_BUTTON, null); + intent.putExtra(Intent.EXTRA_KEY_EVENT, (KeyEvent) msg.obj); + getContext().sendOrderedBroadcast(intent, null); + } + }; + private boolean mKeycodeCameraTimeoutActive = false; + + public MidWindow(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"); + } + /* FEATURE_OPENGL disabled for 1.0 + 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(FILL_PARENT, FILL_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 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.FILL_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_PANEL, + WindowManager.LayoutParams.FLAG_DITHER, + 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) { + 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) { + // The panel key was pushed, so set the chording key + mPanelChordingKey = event.getKeyCode(); + + PanelFeatureState st = getPanelState(featureId, true); + if (!st.isOpen) { + return preparePanel(st, event); + } + + 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 + mPanelChordingKey = 0; + + 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(); + } + + private synchronized void closeContextMenu() { + 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; + 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) { + 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_HEADSETHOOK: + case KeyEvent.KEYCODE_PLAYPAUSE: + case KeyEvent.KEYCODE_STOP: + case KeyEvent.KEYCODE_NEXTSONG: + case KeyEvent.KEYCODE_PREVIOUSSONG: + case KeyEvent.KEYCODE_REWIND: + case KeyEvent.KEYCODE_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.getRepeatCount() > 0) break; + mKeycodeCameraTimeoutActive = true; + mKeycodeCameraTimeoutHandler.removeMessages(0); + Message message = mKeycodeCameraTimeoutHandler.obtainMessage(0); + message.obj = event; + mKeycodeCameraTimeoutHandler.sendMessageDelayed(message, + ViewConfiguration.getLongPressTimeout()); + return true; + } + + case KeyEvent.KEYCODE_MENU: { + if (event.getRepeatCount() > 0) break; + onKeyDownPanel((featureId < 0) ? FEATURE_OPTIONS_PANEL : featureId, event); + return true; + } + + case KeyEvent.KEYCODE_BACK: { + if (event.getRepeatCount() > 0) break; + if (featureId < 0) break; + 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; + } + + case KeyEvent.KEYCODE_CALL: { + if (getKeyguardManager().inKeyguardRestrictedInputMode()) { + break; + } + if (event.getRepeatCount() > 0) break; + mKeycodeCallTimeoutActive = true; + mKeycodeCallTimeoutHandler.removeMessages(0); + mKeycodeCallTimeoutHandler.sendMessageDelayed( + mKeycodeCallTimeoutHandler.obtainMessage(0), + ViewConfiguration.getLongPressTimeout()); + return true; + } + + case KeyEvent.KEYCODE_SEARCH: { + if (event.getRepeatCount() == 0) { + mSearchKeyDownReceived = true; + } + 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; + } + + /** + * 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) { + 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_HEADSETHOOK: + case KeyEvent.KEYCODE_PLAYPAUSE: + case KeyEvent.KEYCODE_STOP: + case KeyEvent.KEYCODE_NEXTSONG: + case KeyEvent.KEYCODE_PREVIOUSSONG: + case KeyEvent.KEYCODE_REWIND: + case KeyEvent.KEYCODE_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.getRepeatCount() > 0) break; // Can a key up event repeat? + mKeycodeCameraTimeoutHandler.removeMessages(0); + if (!mKeycodeCameraTimeoutActive) break; + mKeycodeCameraTimeoutActive = false; + // Add short press behavior here if desired + return true; + } + + case KeyEvent.KEYCODE_CALL: { + if (getKeyguardManager().inKeyguardRestrictedInputMode()) { + break; + } + if (event.getRepeatCount() > 0) break; + mKeycodeCallTimeoutHandler.removeMessages(0); + if (!mKeycodeCallTimeoutActive) break; + mKeycodeCallTimeoutActive = false; + 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() || + !mSearchKeyDownReceived) { + break; + } + mSearchKeyDownReceived = false; + launchDefaultSearch(); + return true; + } + } + + return false; + } + + private void startCallActivity() { + Intent intent = new Intent(Intent.ACTION_VIEW); + // Note: explicitly set MIME type, because we know what it + // should be and can't count on every process having access + // to the contacts provider. + intent.setDataAndType(Calls.CONTENT_URI, Calls.CONTENT_TYPE); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + // This intent normally takes us to the Call log. However, the + // "call_key" extra tells the DialtactsActivity that this action was + // triggered by a CALL keypress, which is handled specially: + // we immediately redirect the user to the in-call screen if a call + // is already in progress (see isSendKeyWhileInCall()). + intent.putExtra("call_key", true); + + 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 states = new SparseArray(); + 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 panelStates = new SparseArray(); + 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 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 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 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 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]; + if ((st != null) && st.isOpen) { + // Clear st.isOpen (openPanel will not open if it's already open) + st.isOpen = false; + openPanel(st, null); + } + } + } + + private final class DecorView extends FrameLayout { + /* 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 final Paint mBlackPaint = new Paint(); + + private boolean mChanging; + + private Drawable mMenuBackground; + private boolean mWatchingForMenu; + private int mDownY; + + public DecorView(Context context, int featureId) { + super(context); + mFeatureId = featureId; + mBlackPaint.setColor(0xFF000000); + } + + @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 = dispatchKeyShortcut(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 ? MidWindow.this.onKeyDown(mFeatureId, event.getKeyCode(), event) + : MidWindow.this.onKeyUp(mFeatureId, event.getKeyCode(), event); + } + + private boolean dispatchKeyShortcut(KeyEvent event) { + View focusedView = findFocus(); + return focusedView == null ? false : focusedView.dispatchKeyShortcutEvent(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 + 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.FILL_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); + + // no KEYCODE_CALL events active across focus changes + mKeycodeCallTimeoutHandler.removeMessages(0); + mKeycodeCallTimeoutActive = false; + mKeycodeCameraTimeoutHandler.removeMessages(0); + mKeycodeCameraTimeoutActive = 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(); + + 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(); + } + } + } + + 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. + + final Context c = getContext(); + 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); + + /* All dialogs should have the window dimmed */ + WindowManager.LayoutParams params = getAttributes(); + TypedArray attrs = c.obtainStyledAttributes( + com.android.internal.R.styleable.Theme); + params.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND; + params.dimAmount = attrs.getFloat( + android.R.styleable.Theme_backgroundDimAmount, 0.5f); + attrs.recycle(); + } 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())); + } + + // 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(FILL_PARENT, FILL_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.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 { + 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 MidWindow#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; + + /** + * 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; + isOpen = savedState.isOpen; + isInExpandedMode = 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 CREATOR + = new Parcelable.Creator() { + public SavedState createFromParcel(Parcel in) { + return readFromParcel(in); + } + + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + } + + } + + /** + * Simple implementation of MenuBuilder.Callback that: + *

  • Opens a submenu when selected. + *
  • Calls back to the callback's onMenuItemSelected when an item is + * selected. + */ + private final class ContextMenuCallback implements MenuBuilder.Callback { + private int mFeatureId; + + 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) { + closeContextMenu(); + } + } + } + + 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 + new MenuDialogHelper(subMenu).show(null); + + return true; + } + } +} diff --git a/mid/com/android/internal/policy/impl/MidWindowManager.java b/mid/com/android/internal/policy/impl/MidWindowManager.java new file mode 100644 index 0000000..3cf36a5 --- /dev/null +++ b/mid/com/android/internal/policy/impl/MidWindowManager.java @@ -0,0 +1,1043 @@ +/* + * Copyright (C) 2006-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.app.Activity; +import android.app.ActivityManagerNative; +import android.app.IActivityManager; +import android.app.IStatusBar; +import android.content.BroadcastReceiver; +import android.content.ContentQueryMap; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.database.Cursor; +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.provider.Settings; +import static android.provider.Settings.System.END_BUTTON_BEHAVIOR; + +import com.android.internal.policy.PolicyManager; +import com.android.internal.telephony.ITelephony; +import android.util.Config; +import android.util.EventLog; +import android.util.Log; +import android.view.IWindowManager; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.OrientationListener; +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.LAST_APPLICATION_WINDOW; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA; +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_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_ALERT; +import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR; +import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY; +import static android.view.WindowManager.LayoutParams.TYPE_TOAST; +import android.view.WindowManagerImpl; +import android.view.WindowManagerPolicy; +import android.media.IAudioService; +import android.media.AudioManager; + +import java.util.Observable; +import java.util.Observer; + +/** + * WindowManagerPolicy implementation for the Android MID UI. + */ +public class MidWindowManager implements WindowManagerPolicy { + private static final String TAG = "MidWindowManager"; + private static final boolean DEBUG = false; + private static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV; + private static final boolean SHOW_STARTING_ANIMATIONS = true; + + private static final int APPLICATION_LAYER = 1; + private static final int PHONE_LAYER = 2; + private static final int SEARCH_BAR_LAYER = 3; + private static final int STATUS_BAR_PANEL_LAYER = 4; + // toasts and the plugged-in battery thing + private static final int TOAST_LAYER = 5; + private static final int STATUS_BAR_LAYER = 6; + // SIM errors and unlock. Not sure if this really should be in a high layer. + private static final int PRIORITY_PHONE_LAYER = 7; + // like the ANR / app crashed dialogs + private static final int SYSTEM_ALERT_LAYER = 8; + // system-level error dialogs + private static final int SYSTEM_ERROR_LAYER = 9; + // the keyguard; nothing on top of these can take focus, since they are + // responsible for power management when displayed. + private static final int KEYGUARD_LAYER = 10; + private static final int KEYGUARD_DIALOG_LAYER = 11; + // things in here CAN NOT take focus, but are shown on top of everything else. + private static final int SYSTEM_OVERLAY_LAYER = 12; + + private static final int APPLICATION_PANEL_SUBLAYER = 1; + private static final int APPLICATION_MEDIA_SUBLAYER = -1; + private static final int APPLICATION_SUB_PANEL_SUBLAYER = 2; + + private static final boolean SINGLE_PRESS_OFF = false; + + private static final float SLIDE_TOUCH_EVENT_SIZE_LIMIT = 0.6f; + + 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"; + + private Context mContext; + private IWindowManager mWindowManager; + private LocalPowerManager mPowerManager; + + private WindowState mStatusBar = null; + private WindowState mSearchBar = null; + private WindowState mKeyguard = null; + private boolean mFirstConnect = true; + private GlobalActions mGlobalActions; + private boolean mShouldTurnOffOnKeyUp; + private RecentApplicationsDialog mRecentAppsDialog; + private Handler mHandler; + + private boolean mLidOpen; + private boolean mScreenOn = false; + private int mCurrentAppOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; + + private int mW, mH; + private int mCurLeft, mCurTop, mCurRight, mCurBottom; + + static final Rect mTmpParentFrame = new Rect(); + static final Rect mTmpDisplayFrame = new Rect(); + static final Rect mTmpVisibleFrame = new Rect(); + + private WindowState mTopFullscreenOpaqueWindowState; + private boolean mForceStatusBar; + private boolean mHomePressed; + private Intent mHomeIntent; + private boolean mSearchKeyPressed; + private boolean mConsumeSearchKeyUp; + + private static final int ENDCALL_HOME = 0x1; + private static final int ENDCALL_SLEEPS = 0x2; + private static final int DEFAULT_ENDCALL_BEHAVIOR = ENDCALL_SLEEPS; + private int mEndcallBehavior; + + private ShortcutManager mShortcutManager; + private PowerManager.WakeLock mBroadcastWakeLock; + + /* + * 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 + */ + + private Runnable mEndCallLongPress = new Runnable() { + public void run() { + mShouldTurnOffOnKeyUp = false; + sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS); + showGlobalActionsDialog(); + } + }; + + private void showGlobalActionsDialog() { + if (mGlobalActions == null) { + mGlobalActions = new GlobalActions(mContext, mPowerManager); + } + mGlobalActions.showDialog(false, isDeviceProvisioned()); + } + + private boolean isDeviceProvisioned() { + return Settings.System.getInt( + mContext.getContentResolver(), Settings.System.DEVICE_PROVISIONED, 0) != 0; + } + + /** + * When a home-key longpress expires, close other system windows and launch the recent apps + */ + private 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; + sendCloseSystemWindows(SYSTEM_DIALOG_REASON_RECENT_APPS); + showRecentAppsDialog(); + } + }; + + /** + * Create (if necessary) and launch the recent apps dialog + */ + private 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; + mHandler = new Handler(); + 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); + PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE); + mBroadcastWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, + "MidWindowManager.mBroadcastWakeLock"); + } + + /** {@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_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; + } + } + + /** {@inheritDoc} */ + public void adjustConfigurationLw(Configuration config) { + mPowerManager.setKeyboardVisibility(true); + config.keyboardHidden = Configuration.KEYBOARDHIDDEN_NO; + mPowerManager.userActivity(SystemClock.uptimeMillis(), false, + LocalPowerManager.OTHER_EVENT); + } + + public boolean isCheekPressedAgainstScreen(MotionEvent ev) { + return false; + } + + /** {@inheritDoc} */ + public int windowTypeToLayerLw(int type) { + if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) { + return APPLICATION_LAYER; + } + switch (type) { + case TYPE_APPLICATION_PANEL: + return APPLICATION_LAYER; + case TYPE_APPLICATION_SUB_PANEL: + return APPLICATION_LAYER; + case TYPE_STATUS_BAR: + return STATUS_BAR_LAYER; + case TYPE_STATUS_BAR_PANEL: + return STATUS_BAR_PANEL_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_SYSTEM_OVERLAY: + return SYSTEM_OVERLAY_LAYER; + case TYPE_PRIORITY_PHONE: + return PRIORITY_PHONE_LAYER; + case TYPE_TOAST: + return TOAST_LAYER; + } + Log.e(TAG, "Unknown window type: " + type); + return APPLICATION_LAYER; + } + + /** {@inheritDoc} */ + public int subWindowTypeToLayerLw(int type) { + switch (type) { + case TYPE_APPLICATION_PANEL: + return APPLICATION_PANEL_SUBLAYER; + case TYPE_APPLICATION_MEDIA: + return APPLICATION_MEDIA_SUBLAYER; + case TYPE_APPLICATION_SUB_PANEL: + return APPLICATION_SUB_PANEL_SUBLAYER; + } + Log.e(TAG, "Unknown sub-window type: " + type); + return 0; + } + + /** {@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; + } + + 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) { + } + } + if (!setTheme) { + context.setTheme(com.android.internal.R.style.Theme); + } + + Window win = PolicyManager.makeNewWindow(context); + Resources r = context.getResources(); + win.setTitle(r.getText(labelRes, nonLocalizedLabel)); + + win.setType( + WindowManager.LayoutParams.TYPE_APPLICATION_STARTING); + win.setFlags( + WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE| + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, + WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE| + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE); + + win.setLayout(WindowManager.LayoutParams.FILL_PARENT, + WindowManager.LayoutParams.FILL_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); + + try { + 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"); + } + + 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: + *
      + *
    • STATUS_BAR_TYPE
    • + *
    • SEARCH_BAR_TYPE
    • + *
    • KEYGUARD_TYPE
    • + *
    + * + * @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: + if (mStatusBar != null) { + return WindowManagerImpl.ADD_MULTIPLE_SINGLETON; + } + mStatusBar = win; + break; + case TYPE_SEARCH_BAR: + if (mSearchBar != null) { + return WindowManagerImpl.ADD_MULTIPLE_SINGLETON; + } + mSearchBar = 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 (mSearchBar == win) { + mSearchBar = null; + } + else if (mKeyguard == win) { + mKeyguard = null; + } + } + + private 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; + } + + private static IAudioService getAudioInterface() { + return IAudioService.Stub.asInterface(ServiceManager.checkService(Context.AUDIO_SERVICE)); + } + + /** {@inheritDoc} */ + public boolean interceptKeyTi(WindowState win, int code, int metaKeys, boolean down, + int repeatCount) { + if (false) { + Log.d(TAG, "interceptKeyTi code=" + code + " down=" + down + " repeatCount=" + + repeatCount); + } + + // 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; + launchHomeFromHotKey(); + } + } + + 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) { + int type = attrs.type; + if (type >= WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW + && type <= WindowManager.LayoutParams.LAST_SYSTEM_WINDOW) { + // Only do this once, so home-key-longpress doesn't close itself + if (repeatCount == 0 && down) { + sendCloseSystemWindows(); + } + return false; + } + } + + if (down && repeatCount == 0) { + 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; + final int chordProcess = KeyEvent.META_ALT_ON; + + if (down && repeatCount == 0) { + if ((metaKeys & chordBug) == chordBug) { + Intent intent = new Intent(Intent.ACTION_BUG_REPORT); + mContext.sendOrderedBroadcast(intent, null); + return true; + } else if ((metaKeys & chordProcess) == chordProcess) { + 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_NOTIFICATION) { + if (down) { + // this key doesn't exist on current hardware, but if a device + // didn't have a touchscreen, it would want one of these to open + // the status bar. + IStatusBar sbs = IStatusBar.Stub.asInterface(ServiceManager.getService("statusbar")); + if (sbs != null) { + try { + sbs.toggle(); + } catch (RemoteException e) { + // we're screwed anyway, since it's in this process + throw new RuntimeException(e); + } + } + } + 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) { + 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. + */ + private void launchHomeFromHotKey() { + // no keyguard stuff to worry about, just launch home! + mContext.startActivity(mHomeIntent); + sendCloseSystemWindows(); + } + + public void getContentInsetHintLw(WindowManager.LayoutParams attrs, Rect coveredInset) { + 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)) { + coveredInset.set(mCurLeft, mCurTop, mW - mCurRight, mH - mCurBottom); + } else { + coveredInset.setEmpty(); + } + } + + /** {@inheritDoc} */ + public void beginLayoutLw(int displayWidth, int displayHeight) { + mW = displayWidth; + mH = displayHeight; + mCurLeft = 0; + mCurTop = 0; + mCurRight = displayWidth; + mCurBottom = displayHeight; + + // 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); + mCurTop = mStatusBar.getFrameLw().bottom; + } + } + + /** {@inheritDoc} */ + public void layoutWindowLw(WindowState win, WindowManager.LayoutParams attrs, WindowState attached) { + // we've already done the status bar + if (win == mStatusBar) { + return; + } + + final int fl = attrs.flags; + + final Rect pf = mTmpParentFrame; + final Rect df = mTmpDisplayFrame; + final Rect vf = mTmpVisibleFrame; + + if ((fl & + (FLAG_LAYOUT_IN_SCREEN | FLAG_FULLSCREEN | FLAG_LAYOUT_INSET_DECOR)) + == (FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR)) { + df.left = 0; + df.top = 0; + df.right = mW; + df.bottom = mH; + vf.left = mCurLeft; + vf.top = mCurTop; + vf.right = mCurRight; + vf.bottom = mCurBottom; + } else if ((fl & FLAG_LAYOUT_IN_SCREEN) == 0) { + // Make sure this window doesn't intrude into the status bar. + df.left = vf.left = mCurLeft; + df.top = vf.top = mCurTop; + df.right = vf.right = mCurRight; + df.bottom = vf.bottom = mCurBottom; + } else { + df.left = vf.left = 0; + df.top = vf.top = 0; + df.right = vf.right = mW; + df.bottom = vf.bottom = mH; + } + + if (attached != null && (fl & (FLAG_LAYOUT_IN_SCREEN)) == 0) { + pf.set(attached.getFrameLw()); + } else { + pf.set(df); + } + + if ((fl & FLAG_LAYOUT_NO_LIMITS) != 0) { + df.left = df.top = vf.left = vf.top = -10000; + df.right = df.bottom = vf.right = vf.bottom = 10000; + } + + win.computeFrameLw(pf, df, vf, vf); + } + + /** {@inheritDoc} */ + public void finishLayoutLw() { + } + + /** {@inheritDoc} */ + public void beginAnimationLw(int displayWidth, int displayHeight) { + mTopFullscreenOpaqueWindowState = null; + mForceStatusBar = false; + } + + /** {@inheritDoc} */ + public void animatingWindowLw(WindowState win, + WindowManager.LayoutParams attrs) { + if (mTopFullscreenOpaqueWindowState == null + && attrs.type >= FIRST_APPLICATION_WINDOW + && attrs.type <= LAST_APPLICATION_WINDOW + && win.fillsScreenLw(mW, mH, true) + && win.isDisplayedLw()) { + mTopFullscreenOpaqueWindowState = win; + } else if ((attrs.flags & FLAG_FORCE_NOT_FULLSCREEN) != 0) { + mForceStatusBar = true; + } + } + + /** {@inheritDoc} */ + public boolean finishAnimationLw() { + if (mStatusBar != null) { + if (mForceStatusBar) { + mStatusBar.showLw(); + } else if (mTopFullscreenOpaqueWindowState != null) { + WindowManager.LayoutParams lp = + mTopFullscreenOpaqueWindowState.getAttrs(); + boolean hideStatusBar = + (lp.flags & WindowManager.LayoutParams.FLAG_FULLSCREEN) != 0; + if (hideStatusBar) { + mStatusBar.hideLw(); + } else { + mStatusBar.showLw(); + } + } + } + return false; + } + + /** {@inheritDoc} */ + public boolean preprocessInputEventTq(RawInputEvent event) { + return false; + } + + + /** {@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 music is being played right now. + */ + private boolean isMusicActive() { + final IAudioService audio = getAudioInterface(); + if (audio == null) { + Log.w(TAG, "isMusicActive: couldn't get IAudioService reference"); + return false; + } + try { + return audio.isMusicActive(); + } catch (RemoteException e) { + Log.w(TAG, "IAudioService.isMusicActive() threw RemoteException " + e); + return false; + } + } + + /** + * Tell the audio service to adjust the volume appropriate to the event. + * @param keycode + */ + private void sendVolToMusic(int keycode) { + final IAudioService audio = getAudioInterface(); + if (audio == null) { + Log.w(TAG, "sendVolToMusic: 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( + AudioManager.STREAM_MUSIC, + 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(); + } + } + + /** {@inheritDoc} */ + public int interceptKeyTq(RawInputEvent event, boolean screenIsOn) { + int result = ACTION_PASS_TO_USER | ACTION_POKE_USER_ACTIVITY; + return result; + } + + class PassHeadsetKey implements Runnable { + KeyEvent mKeyEvent; + + PassHeadsetKey(KeyEvent keyEvent) { + mKeyEvent = keyEvent; + } + + public void run() { + 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); + } + } + + private BroadcastReceiver mBroadcastDone = new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + mBroadcastWakeLock.release(); + } + }; + + /** {@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); + mScreenOn = false; + } + + /** {@inheritDoc} */ + public void screenTurnedOn() { + EventLog.writeEvent(70000, 1); + mScreenOn = true; + } + + /** {@inheritDoc} */ + public void enableKeyguard(boolean enabled) { + } + + /** {@inheritDoc} */ + public void exitKeyguardSecurely(OnKeyguardExitResult callback) { + } + + /** {@inheritDoc} */ + public boolean keyguardIsShowingTq() { + return false; + } + + /** {@inheritDoc} */ + public boolean inKeyguardRestrictedKeyInputMode() { + return false; + } + + /** + * Callback from {@link KeyguardViewMediator} + */ + public void onKeyguardShow() { + sendCloseSystemWindows(); + } + + private void sendCloseSystemWindows() { + sendCloseSystemWindows(null); + } + + private void sendCloseSystemWindows(String reason) { + Intent intent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); + if (reason != null) { + intent.putExtra(SYSTEM_DIALOG_REASON_KEY, reason); + } + mContext.sendBroadcast(intent); + } + + public int rotationForOrientation(int orientation) { + // get rid of rotation for now. Always rotation of 0 + return Surface.ROTATION_0; + } + + /** {@inheritDoc} */ + public void systemReady() { + try { + int menuState = mWindowManager.getKeycodeState(KeyEvent.KEYCODE_MENU); + Log.i(TAG, "Menu key state: " + menuState); + if (menuState > 0) { + // If the user is holding the menu key code, then we are + // going to boot into safe mode. + ActivityManagerNative.getDefault().enterSafeMode(); + } else { + // tell the keyguard + android.os.SystemProperties.set("dev.bootcomplete", "1"); + } + } catch (RemoteException e) { + } + } + + /** {@inheritDoc} */ + public void enableScreenAfterBoot() { + updateRotation(); + } + + private void updateRotation() { + mPowerManager.setKeyboardVisibility(true); + int rotation = Surface.ROTATION_0; + try { + //set orientation on WindowManager + mWindowManager.setRotation(rotation, true); + } catch (RemoteException e) { + } + + mPowerManager.userActivity(SystemClock.uptimeMillis(), false, + LocalPowerManager.OTHER_EVENT); + } + + /** + * goes to the home screen + * @return whether it did anything + */ + boolean goHome() { + if (false) { + // This code always brings home to the front. + mContext.startActivity(mHomeIntent); + } else { + // This code brings home to the front or, if it is already + // at the front, puts the device to sleep. + try { + 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 + } + } + sendCloseSystemWindows(); + return true; + } + + public void setCurrentOrientation(int newOrientation) { + if(newOrientation != mCurrentAppOrientation) { + mCurrentAppOrientation = newOrientation; + } + } +} diff --git a/mid/com/android/internal/policy/impl/Policy.java b/mid/com/android/internal/policy/impl/Policy.java new file mode 100644 index 0000000..dc968b8 --- /dev/null +++ b/mid/com/android/internal/policy/impl/Policy.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; + +import android.content.Context; + +import com.android.internal.policy.IPolicy; +import com.android.internal.policy.impl.MidLayoutInflater; +import com.android.internal.policy.impl.MidWindow; +import com.android.internal.policy.impl.MidWindowManager; + +/** + * {@hide} + */ + +// Simple implementation of the policy interface that spawns the right +// set of objects +public class Policy implements IPolicy { + + public MidWindow makeNewWindow(Context context) { + return new MidWindow(context); + } + + public MidLayoutInflater makeNewLayoutInflater(Context context) { + return new MidLayoutInflater(context); + } + + public MidWindowManager makeNewWindowManager() { + return new MidWindowManager(); + } +} diff --git a/mid/com/android/internal/policy/impl/PowerDialog.java b/mid/com/android/internal/policy/impl/PowerDialog.java new file mode 100644 index 0000000..8bd5399 --- /dev/null +++ b/mid/com/android/internal/policy/impl/PowerDialog.java @@ -0,0 +1,177 @@ +/* + * 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.IServiceManager; +import android.os.LocalPowerManager; +import android.os.ServiceManager; +import android.os.ServiceManagerNative; +import android.os.SystemClock; +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); + getWindow().setFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND, + WindowManager.LayoutParams.FLAG_BLUR_BEHIND); + + 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.shutdownAfterDisablingRadio(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: arve says 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/mid/com/android/internal/policy/impl/RecentApplicationsDialog.java b/mid/com/android/internal/policy/impl/RecentApplicationsDialog.java new file mode 100644 index 0000000..36ea65a --- /dev/null +++ b/mid/com/android/internal/policy/impl/RecentApplicationsDialog.java @@ -0,0 +1,252 @@ +/* + * 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.BroadcastReceiver; +import android.content.ComponentName; +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.pm.ResolveInfo; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; +import android.view.View.OnClickListener; +import android.widget.ImageView; +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 = 6; + private static final int MAX_RECENT_TASKS = NUM_BUTTONS * 2; // allow for some discards + + final View[] mButtons = new View[NUM_BUTTONS]; + View mNoAppsText; + IntentFilter mBroadcastIntentFilter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); + + public RecentApplicationsDialog(Context context) { + super(context); + } + + /** + * We create the recent applications dialog just once, and it stays around (hidden) + * until activated by the user. + * + * @see MidWindowManager#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 theWindow = getWindow(); + theWindow.requestFeature(Window.FEATURE_NO_TITLE); + theWindow.setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG); + theWindow.setFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND, + WindowManager.LayoutParams.FLAG_BLUR_BEHIND); + + setContentView(com.android.internal.R.layout.recent_apps_dialog); + + mButtons[0] = findViewById(com.android.internal.R.id.button1); + mButtons[1] = findViewById(com.android.internal.R.id.button2); + mButtons[2] = findViewById(com.android.internal.R.id.button3); + mButtons[3] = findViewById(com.android.internal.R.id.button4); + mButtons[4] = findViewById(com.android.internal.R.id.button5); + mButtons[5] = findViewById(com.android.internal.R.id.button6); + mNoAppsText = findViewById(com.android.internal.R.id.no_applications_message); + + for (View b : mButtons) { + b.setOnClickListener(this); + } + } + + /** + * Handler for user clicks. If a button was clicked, launch the corresponding activity. + */ + public void onClick(View v) { + + for (View b : mButtons) { + if (b == v) { + // prepare a launch intent and send it + Intent intent = (Intent)b.getTag(); + getContext().startActivity(intent); + } + } + 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 (View b : mButtons) { + setButtonAppearance(b, null, null); + b.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 recentTasks = + am.getRecentTasks(MAX_RECENT_TASKS, 0); + + ResolveInfo homeInfo = pm.resolveActivity( + new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME), + 0); + + // 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 button = 0; + int numTasks = recentTasks.size(); + for (int i = 0; i < numTasks && (button < 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.activityInfo.packageName.equals( + intent.getComponent().getPackageName()) + && homeInfo.activityInfo.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(); + final Drawable icon = activityInfo.loadIcon(pm); + + if (title != null && title.length() > 0 && icon != null) { + final View b = mButtons[button]; + setButtonAppearance(b, title, icon); + b.setTag(intent); + b.setVisibility(View.VISIBLE); + b.setPressed(false); + b.clearFocus(); + ++button; + } + } + } + + // handle the case of "no icons to show" + mNoAppsText.setVisibility((button == 0) ? View.VISIBLE : View.GONE); + + // hide the rest + for ( ; button < NUM_BUTTONS; ++button) { + mButtons[button].setVisibility(View.GONE); + } + } + + /** + * Adjust appearance of each icon-button + */ + private void setButtonAppearance(View theButton, final String theTitle, final Drawable icon) { + TextView tv = (TextView) theButton.findViewById(com.android.internal.R.id.label); + tv.setText(theTitle); + ImageView iv = (ImageView) theButton.findViewById(com.android.internal.R.id.icon); + iv.setImageDrawable(icon); + } + + /** + * 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). + * + * TODO: This is a really heavyweight solution for something that should be so simple. + * For example, we already have a handler, in our superclass, why aren't we sharing that? + * I think we need to investigate simplifying this entire methodology, or perhaps boosting + * it up into the Dialog class. + */ + 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(MidWindowManager.SYSTEM_DIALOG_REASON_KEY); + if (! MidWindowManager.SYSTEM_DIALOG_REASON_RECENT_APPS.equals(reason)) { + dismiss(); + } + } + } + }; +} diff --git a/mid/com/android/internal/policy/impl/ShortcutManager.java b/mid/com/android/internal/policy/impl/ShortcutManager.java new file mode 100644 index 0000000..daec9d7 --- /dev/null +++ b/mid/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: + *
  • Keeping the local copy in sync with the database (this is an observer) + *
  • 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 mShortcutIntents; + + public ShortcutManager(Context context, Handler handler) { + super(handler); + + mContext = context; + mShortcutIntents = new SparseArray(); + } + + /** 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. + *

    + * 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/mid/com/android/internal/policy/impl/ShutdownThread.java b/mid/com/android/internal/policy/impl/ShutdownThread.java new file mode 100644 index 0000000..4bff589 --- /dev/null +++ b/mid/com/android/internal/policy/impl/ShutdownThread.java @@ -0,0 +1,137 @@ + /* + * 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.ProgressDialog; +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.os.RemoteException; +import android.os.Power; +import android.os.ServiceManager; +import android.os.SystemClock; +import com.android.internal.telephony.ITelephony; +import android.util.Log; +import android.view.WindowManager; + + +final class ShutdownThread extends Thread { + // constants + private static final String TAG = "ShutdownThread"; + private static final int MAX_NUM_PHONE_STATE_READS = 16; + private static final int PHONE_STATE_POLL_SLEEP_MSEC = 500; + private static final ITelephony sPhone = + ITelephony.Stub.asInterface(ServiceManager.checkService("phone")); + + // state tracking + private static Object sIsStartedGuard = new Object(); + private static boolean sIsStarted = false; + + // static instance of this thread + private static final ShutdownThread sInstance = new ShutdownThread(); + + private ShutdownThread() { + } + + /** + * request a shutdown. + * + * @param context Context used to display the shutdown progress dialog. + */ + public static void shutdownAfterDisablingRadio(final Context context, boolean confirm){ + // ensure that only one thread is trying to power down. + // any additional calls are just returned + synchronized (sIsStartedGuard){ + if (sIsStarted) { + Log.d(TAG, "Request to shutdown already running, returning."); + return; + } + } + + Log.d(TAG, "Notifying thread to start radio shutdown"); + + if (confirm) { + final AlertDialog dialog = new AlertDialog.Builder(context) + .setIcon(android.R.drawable.ic_dialog_alert) + .setTitle(com.android.internal.R.string.power_off) + .setMessage(com.android.internal.R.string.shutdown_confirm) + .setPositiveButton(com.android.internal.R.string.yes, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + beginShutdownSequence(context); + } + }) + .setNegativeButton(com.android.internal.R.string.no, null) + .create(); + dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND); + dialog.show(); + } else { + beginShutdownSequence(context); + } + } + + private static void beginShutdownSequence(Context context) { + synchronized (sIsStartedGuard) { + sIsStarted = true; + } + + // throw up an indeterminate system dialog to indicate radio is + // shutting down. + ProgressDialog pd = new ProgressDialog(context); + pd.setTitle(context.getText(com.android.internal.R.string.power_off)); + pd.setMessage(context.getText(com.android.internal.R.string.shutdown_progress)); + pd.setIndeterminate(true); + pd.setCancelable(false); + pd.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + pd.getWindow().addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND); + + pd.show(); + + // start the thread that initiates shutdown + sInstance.start(); + } + + /** + * Makes sure we handle the shutdown gracefully. + * Shuts off power regardless of radio state if the alloted time has passed. + */ + public void run() { + //shutdown the phone radio if possible. + if (sPhone != null) { + try { + //shutdown radio + sPhone.setRadio(false); + + for (int i = 0; i < MAX_NUM_PHONE_STATE_READS; i++){ + // poll radio up to 64 times, with a 0.5 sec delay between each call, + // totaling 32 sec. + if (!sPhone.isRadioOn()) { + Log.d(TAG, "Radio shutdown complete."); + break; + } + SystemClock.sleep(PHONE_STATE_POLL_SLEEP_MSEC); + } + } catch (RemoteException ex) { + Log.e(TAG, "RemoteException caught from failed radio shutdown.", ex); + } + } + + //shutdown power + Log.d(TAG, "Shutting down power."); + Power.shutdown(); + } +} diff --git a/mid/com/android/internal/policy/impl/package.html b/mid/com/android/internal/policy/impl/package.html new file mode 100644 index 0000000..c9f96a6 --- /dev/null +++ b/mid/com/android/internal/policy/impl/package.html @@ -0,0 +1,5 @@ + + +{@hide} + + diff --git a/phone/Android.mk b/phone/Android.mk index 462431f..29f62b8 100644 --- a/phone/Android.mk +++ b/phone/Android.mk @@ -7,6 +7,7 @@ include $(CLEAR_VARS) LOCAL_SRC_FILES := \ $(call all-subdir-java-files) -LOCAL_MODULE:= android.policy +LOCAL_MODULE := android.policy_phone +LOCAL_UNINSTALLABLE_MODULE := true include $(BUILD_JAVA_LIBRARY) diff --git a/phone/com/android/internal/policy/impl/KeyguardScreenCallback.java b/phone/com/android/internal/policy/impl/KeyguardScreenCallback.java index 832288b..b46b37d 100644 --- a/phone/com/android/internal/policy/impl/KeyguardScreenCallback.java +++ b/phone/com/android/internal/policy/impl/KeyguardScreenCallback.java @@ -58,4 +58,9 @@ public interface KeyguardScreenCallback extends KeyguardViewCallback { */ void reportFailedPatternAttempt(); + /** + * Report whether we there's another way to unlock the device. + * @return true + */ + boolean doesFallbackUnlockScreenExist(); } diff --git a/phone/com/android/internal/policy/impl/KeyguardUpdateMonitor.java b/phone/com/android/internal/policy/impl/KeyguardUpdateMonitor.java index ab7c744..4671957 100644 --- a/phone/com/android/internal/policy/impl/KeyguardUpdateMonitor.java +++ b/phone/com/android/internal/policy/impl/KeyguardUpdateMonitor.java @@ -160,8 +160,8 @@ public class KeyguardUpdateMonitor { } }; - mDeviceProvisioned = Settings.System.getInt( - mContext.getContentResolver(), Settings.System.DEVICE_PROVISIONED, 0) != 0; + 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... @@ -170,8 +170,8 @@ public class KeyguardUpdateMonitor { @Override public void onChange(boolean selfChange) { super.onChange(selfChange); - mDeviceProvisioned = Settings.System.getInt(mContext.getContentResolver(), - Settings.System.DEVICE_PROVISIONED, 0) != 0; + 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); @@ -182,13 +182,13 @@ public class KeyguardUpdateMonitor { }; mContext.getContentResolver().registerContentObserver( - Settings.System.getUriFor(Settings.System.DEVICE_PROVISIONED), + 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.System.getInt(mContext.getContentResolver(), - Settings.System.DEVICE_PROVISIONED, 0) != 0; + mDeviceProvisioned = Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.DEVICE_PROVISIONED, 0) != 0; } mInPortrait = queryInPortrait(); diff --git a/phone/com/android/internal/policy/impl/KeyguardViewBase.java b/phone/com/android/internal/policy/impl/KeyguardViewBase.java index 491300e..5a3ebde 100644 --- a/phone/com/android/internal/policy/impl/KeyguardViewBase.java +++ b/phone/com/android/internal/policy/impl/KeyguardViewBase.java @@ -131,7 +131,13 @@ public abstract class KeyguardViewBase extends FrameLayout { final int keyCode = event.getKeyCode(); if (event.getAction() == KeyEvent.ACTION_DOWN) { switch (keyCode) { - case KeyEvent.KEYCODE_HEADSETHOOK: { + case KeyEvent.KEYCODE_HEADSETHOOK: + case KeyEvent.KEYCODE_PLAYPAUSE: + case KeyEvent.KEYCODE_STOP: + case KeyEvent.KEYCODE_NEXTSONG: + case KeyEvent.KEYCODE_PREVIOUSSONG: + case KeyEvent.KEYCODE_REWIND: + case KeyEvent.KEYCODE_FORWARD: { Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON, null); intent.putExtra(Intent.EXTRA_KEY_EVENT, event); getContext().sendOrderedBroadcast(intent, null); @@ -161,7 +167,13 @@ public abstract class KeyguardViewBase extends FrameLayout { } } else if (event.getAction() == KeyEvent.ACTION_UP) { switch (keyCode) { - case KeyEvent.KEYCODE_HEADSETHOOK: { + case KeyEvent.KEYCODE_HEADSETHOOK: + case KeyEvent.KEYCODE_PLAYPAUSE: + case KeyEvent.KEYCODE_STOP: + case KeyEvent.KEYCODE_NEXTSONG: + case KeyEvent.KEYCODE_PREVIOUSSONG: + case KeyEvent.KEYCODE_REWIND: + case KeyEvent.KEYCODE_FORWARD: { Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON, null); intent.putExtra(Intent.EXTRA_KEY_EVENT, event); getContext().sendOrderedBroadcast(intent, null); diff --git a/phone/com/android/internal/policy/impl/KeyguardViewMediator.java b/phone/com/android/internal/policy/impl/KeyguardViewMediator.java index ac816b0..2431ffe 100644 --- a/phone/com/android/internal/policy/impl/KeyguardViewMediator.java +++ b/phone/com/android/internal/policy/impl/KeyguardViewMediator.java @@ -131,6 +131,8 @@ public class KeyguardViewMediator implements KeyguardViewCallback, private Context mContext; private AlarmManager mAlarmManager; + private boolean mSystemReady; + /** Low level access to the power manager for enableUserActivity. Having this * requires that we run in the system process. */ LocalPowerManager mRealPowerManager; @@ -254,6 +256,7 @@ public class KeyguardViewMediator implements KeyguardViewCallback, public void onSystemReady() { synchronized (this) { if (DEBUG) Log.d(TAG, "onSystemReady"); + mSystemReady = true; doKeyguard(); } } @@ -638,7 +641,13 @@ public class KeyguardViewMediator implements KeyguardViewCallback, switch (keyCode) { case KeyEvent.KEYCODE_VOLUME_UP: case KeyEvent.KEYCODE_VOLUME_DOWN: - case KeyEvent.KEYCODE_HEADSETHOOK: + case KeyEvent.KEYCODE_HEADSETHOOK: + case KeyEvent.KEYCODE_PLAYPAUSE: + case KeyEvent.KEYCODE_STOP: + case KeyEvent.KEYCODE_NEXTSONG: + case KeyEvent.KEYCODE_PREVIOUSSONG: + case KeyEvent.KEYCODE_REWIND: + case KeyEvent.KEYCODE_FORWARD: case KeyEvent.KEYCODE_CAMERA: return false; } @@ -806,6 +815,8 @@ public class KeyguardViewMediator implements KeyguardViewCallback, private void handleShow() { synchronized (KeyguardViewMediator.this) { if (DEBUG) Log.d(TAG, "handleShow"); + if (!mSystemReady) return; + // while we're showing, we control the wake state, so ask the power // manager not to honor request for userActivity. mRealPowerManager.enableUserActivity(false); @@ -875,6 +886,7 @@ public class KeyguardViewMediator implements KeyguardViewCallback, synchronized (KeyguardViewMediator.this) { if (DEBUG) Log.d(TAG, "handleVerifyUnlock"); mKeyguardViewManager.verifyUnlock(); + mShowing = true; } } diff --git a/phone/com/android/internal/policy/impl/LockPatternKeyguardView.java b/phone/com/android/internal/policy/impl/LockPatternKeyguardView.java index 66a4c4e..4ff87e3 100644 --- a/phone/com/android/internal/policy/impl/LockPatternKeyguardView.java +++ b/phone/com/android/internal/policy/impl/LockPatternKeyguardView.java @@ -16,9 +16,15 @@ package com.android.internal.policy.impl; +import android.accounts.AccountsServiceConstants; +import android.accounts.IAccountsService; import android.app.AlertDialog; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.ServiceConnection; +import android.os.IBinder; +import android.os.RemoteException; import android.os.SystemProperties; import com.android.internal.telephony.SimCard; import android.text.TextUtils; @@ -51,6 +57,8 @@ public class LockPatternKeyguardView extends KeyguardViewBase { private View mUnlockScreen; private boolean mScreenOn = false; + private boolean mHasAccount = false; // assume they don't have an account until we know better + /** * The current {@link KeyguardScreen} will use this to communicate back to us. @@ -114,6 +122,10 @@ public class LockPatternKeyguardView extends KeyguardViewBase { */ private final LockPatternUtils mLockPatternUtils; + /** + * Used to fetch accounts from GLS. + */ + private ServiceConnection mServiceConnection; /** * @return Whether we are stuck on the lock screen because the sim is @@ -137,7 +149,9 @@ public class LockPatternKeyguardView extends KeyguardViewBase { KeyguardUpdateMonitor updateMonitor, LockPatternUtils lockPatternUtils) { super(context); - + + asyncCheckForAccount(); + mRequiresSim = TextUtils.isEmpty(SystemProperties.get("keyguard.no_require_sim")); @@ -145,6 +159,7 @@ public class LockPatternKeyguardView extends KeyguardViewBase { mLockPatternUtils = lockPatternUtils; mMode = getInitialMode(); + mKeyguardScreenCallback = new KeyguardScreenCallback() { public void goToLockScreen() { @@ -210,10 +225,11 @@ public class LockPatternKeyguardView extends KeyguardViewBase { public void reportFailedPatternAttempt() { mUpdateMonitor.reportFailedAttempt(); final int failedAttempts = mUpdateMonitor.getFailedAttempts(); - if (failedAttempts == - (LockPatternUtils.FAILED_ATTEMPTS_BEFORE_RESET - LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT)) { + if (mHasAccount && failedAttempts == + (LockPatternUtils.FAILED_ATTEMPTS_BEFORE_RESET + - LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT)) { showAlmostAtAccountLoginDialog(); - } else if (failedAttempts >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_RESET) { + } else if (mHasAccount && failedAttempts >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_RESET) { mLockPatternUtils.setPermanentlyLocked(true); updateScreen(mMode); } else if ((failedAttempts % LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT) @@ -221,6 +237,10 @@ public class LockPatternKeyguardView extends KeyguardViewBase { showTimeoutDialog(); } } + + public boolean doesFallbackUnlockScreenExist() { + return mHasAccount; + } }; /** @@ -241,6 +261,36 @@ public class LockPatternKeyguardView extends KeyguardViewBase { updateScreen(mMode); } + /** + * Asynchronously checks for at least one account. This will set mHasAccount + * to true if an account is found. + */ + private void asyncCheckForAccount() { + + mServiceConnection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, IBinder service) { + try { + IAccountsService accountsService = IAccountsService.Stub.asInterface(service); + String accounts[] = accountsService.getAccounts(); + mHasAccount = (accounts.length > 0); + } catch (RemoteException e) { + // Not much we can do here... + Log.e(TAG, "Gls died while attempting to get accounts: " + e); + } finally { + getContext().unbindService(mServiceConnection); + mServiceConnection = null; + } + } + + public void onServiceDisconnected(ComponentName className) { + // nothing to do here + } + }; + boolean status = getContext().bindService(AccountsServiceConstants.SERVICE_INTENT, + mServiceConnection, Context.BIND_AUTO_CREATE); + if (!status) Log.e(TAG, "Failed to bind to GLS while checking for account"); + } + @Override public void reset() { mIsVerifyUnlockOnly = false; diff --git a/phone/com/android/internal/policy/impl/LockScreen.java b/phone/com/android/internal/policy/impl/LockScreen.java index c717cc3..0825c3b 100644 --- a/phone/com/android/internal/policy/impl/LockScreen.java +++ b/phone/com/android/internal/policy/impl/LockScreen.java @@ -20,7 +20,7 @@ import com.android.internal.R; import com.android.internal.widget.LockPatternUtils; import android.content.Context; -import android.pim.DateFormat; +import android.text.format.DateFormat; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; @@ -299,7 +299,7 @@ class LockScreen extends LinearLayout implements KeyguardScreen, KeyguardUpdateM mHeaderSimOk1.setVisibility(View.VISIBLE); mHeaderSimOk1.setText(plmn); } else { - mHeaderSimOk2.setVisibility(View.GONE); + mHeaderSimOk1.setVisibility(View.GONE); } if (spn != null) { diff --git a/phone/com/android/internal/policy/impl/PhoneWindow.java b/phone/com/android/internal/policy/impl/PhoneWindow.java index 2d5bd31..dfc4b6c 100644 --- a/phone/com/android/internal/policy/impl/PhoneWindow.java +++ b/phone/com/android/internal/policy/impl/PhoneWindow.java @@ -41,7 +41,6 @@ import android.os.Message; import android.os.Parcel; import android.os.Parcelable; import android.os.SystemClock; -import android.provider.CallLog.Calls; import android.util.AndroidRuntimeException; import android.util.Config; import android.util.EventLog; @@ -66,6 +65,7 @@ import android.view.WindowManager; 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_RESTORED_STATE; import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.widget.FrameLayout; @@ -482,7 +482,8 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { WindowManager.LayoutParams lp = new WindowManager.LayoutParams( WRAP_CONTENT, WRAP_CONTENT, st.x, st.y, WindowManager.LayoutParams.TYPE_APPLICATION_PANEL, - WindowManager.LayoutParams.FLAG_DITHER, + WindowManager.LayoutParams.FLAG_DITHER + | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, st.decorView.mDefaultOpacity); lp.gravity = st.gravity; @@ -493,7 +494,11 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { @Override public final void closePanel(int featureId) { - closePanel(getPanelState(featureId, true), true); + if (featureId == FEATURE_CONTEXT_MENU) { + closeContextMenu(); + } else { + closePanel(getPanelState(featureId, true), true); + } } /** @@ -628,8 +633,23 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { 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) { @@ -1117,7 +1137,13 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { return true; } - case KeyEvent.KEYCODE_HEADSETHOOK: { + case KeyEvent.KEYCODE_HEADSETHOOK: + case KeyEvent.KEYCODE_PLAYPAUSE: + case KeyEvent.KEYCODE_STOP: + case KeyEvent.KEYCODE_NEXTSONG: + case KeyEvent.KEYCODE_PREVIOUSSONG: + case KeyEvent.KEYCODE_REWIND: + case KeyEvent.KEYCODE_FORWARD: { Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON, null); intent.putExtra(Intent.EXTRA_KEY_EVENT, event); getContext().sendOrderedBroadcast(intent, null); @@ -1226,7 +1252,13 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { return true; } - case KeyEvent.KEYCODE_HEADSETHOOK: { + case KeyEvent.KEYCODE_HEADSETHOOK: + case KeyEvent.KEYCODE_PLAYPAUSE: + case KeyEvent.KEYCODE_STOP: + case KeyEvent.KEYCODE_NEXTSONG: + case KeyEvent.KEYCODE_PREVIOUSSONG: + case KeyEvent.KEYCODE_REWIND: + case KeyEvent.KEYCODE_FORWARD: { Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON, null); intent.putExtra(Intent.EXTRA_KEY_EVENT, event); getContext().sendOrderedBroadcast(intent, null); @@ -1344,6 +1376,8 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { return; } + setFlags(FLAG_RESTORED_STATE, FLAG_RESTORED_STATE); + SparseArray savedStates = savedInstanceState.getSparseParcelableArray(VIEWS_TAG); if (savedStates != null) { @@ -1503,7 +1537,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { * If not handled, then pass it to the view hierarchy * and anyone else that may be interested. */ - handled = dispatchKeyShortcut(event); + handled = dispatchKeyShortcutEvent(event); if (handled && mPreparedPanel != null) { mPreparedPanel.isHandled = true; @@ -1534,11 +1568,6 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { : PhoneWindow.this.onKeyUp(mFeatureId, event.getKeyCode(), event); } - private boolean dispatchKeyShortcut(KeyEvent event) { - View focusedView = findFocus(); - return focusedView == null ? false : focusedView.dispatchKeyShortcutEvent(event); - } - @Override public boolean dispatchTouchEvent(MotionEvent ev) { final Callback cb = getCallback(); @@ -1884,7 +1913,6 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { protected ViewGroup generateLayout(DecorView decor) { // Apply data from current theme. - final Context c = getContext(); TypedArray a = getWindowStyle(); if (false) { @@ -1903,19 +1931,10 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { if (mIsFloating) { setLayout(WRAP_CONTENT, WRAP_CONTENT); setFlags(0, flagsToUpdate); - - /* All dialogs should have the window dimmed */ - WindowManager.LayoutParams params = getAttributes(); - TypedArray attrs = c.obtainStyledAttributes( - com.android.internal.R.styleable.Theme); - params.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND; - params.dimAmount = attrs.getFloat( - android.R.styleable.Theme_backgroundDimAmount, 0.5f); - attrs.recycle(); } 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); } @@ -1924,6 +1943,28 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN&(~getForcedWindowFlags())); } + WindowManager.LayoutParams params = getAttributes(); + + if (!hasSoftInputMode()) { + params.softInputMode = (byte)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); + } + + params.windowAnimations = a.getResourceId( + com.android.internal.R.styleable.Window_windowAnimationStyle, + params.windowAnimations); + // The rest are only done if this window is not embedded; otherwise, // the values are inherited from our container. if (getContainer() == null) { @@ -2529,6 +2570,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { */ private final class ContextMenuCallback implements MenuBuilder.Callback { private int mFeatureId; + private MenuDialogHelper mSubMenuHelper; public ContextMenuCallback(int featureId) { mFeatureId = featureId; @@ -2540,7 +2582,13 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { if (callback != null) callback.onPanelClosed(mFeatureId, menu); if (menu == mContextMenu) { - closeContextMenu(); + dismissContextMenu(); + } + + // Dismiss the submenu, if it is showing + if (mSubMenuHelper != null) { + mSubMenuHelper.dismiss(); + mSubMenuHelper = null; } } } @@ -2553,7 +2601,6 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) { Callback callback = getCallback(); return (callback != null) && callback.onMenuItemSelected(mFeatureId, item); - } public void onMenuModeChange(MenuBuilder menu) { @@ -2564,7 +2611,8 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { subMenu.setCallback(this); // The window manager will give us a valid window token - new MenuDialogHelper(subMenu).show(null); + mSubMenuHelper = new MenuDialogHelper(subMenu); + mSubMenuHelper.show(null); return true; } diff --git a/phone/com/android/internal/policy/impl/PhoneWindowManager.java b/phone/com/android/internal/policy/impl/PhoneWindowManager.java index 250d2d4..603b221 100644 --- a/phone/com/android/internal/policy/impl/PhoneWindowManager.java +++ b/phone/com/android/internal/policy/impl/PhoneWindowManager.java @@ -23,14 +23,13 @@ import android.app.IStatusBar; import android.content.BroadcastReceiver; import android.content.ContentQueryMap; import android.content.ContentResolver; -import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.res.Configuration; import android.content.res.Resources; -import android.database.Cursor; +import android.database.ContentObserver; import android.graphics.Rect; import android.os.Handler; import android.os.IBinder; @@ -41,13 +40,13 @@ import android.os.ServiceManager; import android.os.SystemClock; import android.os.SystemProperties; import android.provider.Settings; -import static android.provider.Settings.System.END_BUTTON_BEHAVIOR; import com.android.internal.policy.PolicyManager; import com.android.internal.telephony.ITelephony; import android.util.Config; import android.util.EventLog; import android.util.Log; +import android.view.Gravity; import android.view.IWindowManager; import android.view.KeyEvent; import android.view.MotionEvent; @@ -64,10 +63,13 @@ 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.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_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; @@ -77,6 +79,8 @@ 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_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 android.view.WindowManagerImpl; @@ -91,117 +95,159 @@ import java.util.Observer; * WindowManagerPolicy implementation for the Android phone UI. */ public class PhoneWindowManager implements WindowManagerPolicy { - private static final String TAG = "WindowManager"; - private static final boolean DEBUG = false; - private static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV; - private static final boolean SHOW_STARTING_ANIMATIONS = true; - private static final boolean SHOW_PROCESSES_ON_ALT_MENU = false; + static final String TAG = "WindowManager"; + static final boolean DEBUG = false; + static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV; + static final boolean SHOW_STARTING_ANIMATIONS = true; + static final boolean SHOW_PROCESSES_ON_ALT_MENU = false; - private static final int APPLICATION_LAYER = 1; - private static final int PHONE_LAYER = 2; - private static final int SEARCH_BAR_LAYER = 3; - private static final int STATUS_BAR_PANEL_LAYER = 4; + static final int APPLICATION_LAYER = 1; + static final int PHONE_LAYER = 2; + static final int SEARCH_BAR_LAYER = 3; + static final int STATUS_BAR_PANEL_LAYER = 4; // toasts and the plugged-in battery thing - private static final int TOAST_LAYER = 5; - private static final int STATUS_BAR_LAYER = 6; + static final int TOAST_LAYER = 5; + static final int STATUS_BAR_LAYER = 6; // SIM errors and unlock. Not sure if this really should be in a high layer. - private static final int PRIORITY_PHONE_LAYER = 7; + static final int PRIORITY_PHONE_LAYER = 7; // like the ANR / app crashed dialogs - private static final int SYSTEM_ALERT_LAYER = 8; + static final int SYSTEM_ALERT_LAYER = 8; // system-level error dialogs - private static final int SYSTEM_ERROR_LAYER = 9; + static final int SYSTEM_ERROR_LAYER = 9; + // on-screen keyboards and other such input method user interfaces go here. + static final int INPUT_METHOD_LAYER = 10; + // on-screen keyboards and other such input method user interfaces go here. + static final int INPUT_METHOD_DIALOG_LAYER = 11; // the keyguard; nothing on top of these can take focus, since they are // responsible for power management when displayed. - private static final int KEYGUARD_LAYER = 10; - private static final int KEYGUARD_DIALOG_LAYER = 11; + static final int KEYGUARD_LAYER = 12; + static final int KEYGUARD_DIALOG_LAYER = 13; // things in here CAN NOT take focus, but are shown on top of everything else. - private static final int SYSTEM_OVERLAY_LAYER = 12; + static final int SYSTEM_OVERLAY_LAYER = 14; - private static final int APPLICATION_PANEL_SUBLAYER = 1; - private static final int APPLICATION_MEDIA_SUBLAYER = -1; - private static final int APPLICATION_SUB_PANEL_SUBLAYER = 2; + static final int APPLICATION_PANEL_SUBLAYER = 1; + static final int APPLICATION_MEDIA_SUBLAYER = -1; + static final int APPLICATION_SUB_PANEL_SUBLAYER = 2; - private static final float SLIDE_TOUCH_EVENT_SIZE_LIMIT = 0.6f; + 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"; - private Context mContext; - private IWindowManager mWindowManager; - private LocalPowerManager mPowerManager; + Context mContext; + IWindowManager mWindowManager; + LocalPowerManager mPowerManager; /** If true, hitting shift & menu will broadcast Intent.ACTION_BUG_REPORT */ - private boolean mEnableShiftMenuBugReports = false; + boolean mEnableShiftMenuBugReports = false; + + WindowState mStatusBar = null; + WindowState mSearchBar = null; + WindowState mKeyguard = null; + KeyguardViewMediator mKeyguardMediator; + GlobalActions mGlobalActions; + boolean mShouldTurnOffOnKeyUp; + RecentApplicationsDialog mRecentAppsDialog; + Handler mHandler; + + boolean mLidOpen; + int mSensorRotation = -1; + 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; + + // 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; - private WindowState mStatusBar = null; - private WindowState mSearchBar = null; - private WindowState mKeyguard = null; - private KeyguardViewMediator mKeyguardMediator; - private GlobalActions mGlobalActions; - private boolean mShouldTurnOffOnKeyUp; - private RecentApplicationsDialog mRecentAppsDialog; - private Handler mHandler; - - private boolean mLidOpen; - private int mSensorOrientation = OrientationListener.ORIENTATION_UNKNOWN; - private int mSensorRotation = -1; - private boolean mScreenOn = false; - private boolean mOrientationSensorEnabled = false; - private int mCurrentAppOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; + static final Rect mTmpParentFrame = new Rect(); + static final Rect mTmpDisplayFrame = new Rect(); + static final Rect mTmpContentFrame = new Rect(); + static final Rect mTmpVisibleFrame = new Rect(); - private int mW, mH; - private int mCurLeft, mCurTop, mCurRight, mCurBottom; - private WindowState mTopFullscreenOpaqueWindowState; - private boolean mForceStatusBar; - private boolean mHomePressed; - private Intent mHomeIntent; - private boolean mSearchKeyPressed; - private boolean mConsumeSearchKeyUp; - - private static final int ENDCALL_HOME = 0x1; - private static final int ENDCALL_SLEEPS = 0x2; - private static final int DEFAULT_ENDCALL_BEHAVIOR = ENDCALL_SLEEPS; - private int mEndcallBehavior; - - private ShortcutManager mShortcutManager; - private PowerManager.WakeLock mBroadcastWakeLock; - - private class SettingsObserver implements Observer { + WindowState mTopFullscreenOpaqueWindowState; + boolean mForceStatusBar; + boolean mHomePressed; + Intent mHomeIntent; + boolean mSearchKeyPressed; + boolean mConsumeSearchKeyUp; + + static final int ENDCALL_HOME = 0x1; + static final int ENDCALL_SLEEPS = 0x2; + static final int DEFAULT_ENDCALL_BEHAVIOR = ENDCALL_SLEEPS; + int mEndcallBehavior; + + ShortcutManager mShortcutManager; + PowerManager.WakeLock mBroadcastWakeLock; + + class SettingsObserver extends ContentObserver { private ContentQueryMap mSettings; + SettingsObserver(Handler handler) { + super(handler); + } + void observe() { ContentResolver resolver = mContext.getContentResolver(); - Cursor settingsCursor = resolver.query(Settings.System.CONTENT_URI, null, - Settings.System.NAME + "=?", - new String[] { END_BUTTON_BEHAVIOR}, - null); - mSettings = new ContentQueryMap(settingsCursor, Settings.System.NAME, true, mHandler); - mSettings.addObserver(this); - - // pretend that the settings changed so we will get their initial state - update(mSettings, null); - } - - private int getInt(String name, int def) { - ContentValues row = mSettings.getValues(name); - if (row != null) { - Integer ret = row.getAsInteger(Settings.System.VALUE); - if(ret == null) { - return def; - } - return ret; - } else { - return def; + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.END_BUTTON_BEHAVIOR), false, this); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.ACCELEROMETER_ROTATION), false, this); + resolver.registerContentObserver(Settings.Secure.getUriFor( + Settings.Secure.DEFAULT_INPUT_METHOD), false, this); + update(); + } + + @Override public void onChange(boolean selfChange) { + update(); + try { + mWindowManager.setRotation(USE_LAST_ROTATION, false); + } catch (RemoteException e) { + // Ignore } } - - public void update(Observable o, Object arg) { - mEndcallBehavior = getInt(END_BUTTON_BEHAVIOR, DEFAULT_ENDCALL_BEHAVIOR); + + public void update() { + ContentResolver resolver = mContext.getContentResolver(); + mEndcallBehavior = Settings.System.getInt(resolver, + Settings.System.END_BUTTON_BEHAVIOR, DEFAULT_ENDCALL_BEHAVIOR); + int accelerometerDefault = Settings.System.getInt(resolver, + Settings.System.ACCELEROMETER_ROTATION, DEFAULT_ACCELEROMETER_ROTATION); + if (mAccelerometerDefault != accelerometerDefault) { + mAccelerometerDefault = accelerometerDefault; + updateOrientationListener(); + } + String imId = Settings.Secure.getString(resolver, + Settings.Secure.DEFAULT_INPUT_METHOD); + boolean hasSoftInput = imId != null && imId.length() > 0; + if (mHasSoftInput != hasSoftInput) { + mHasSoftInput = hasSoftInput; + updateRotation(); + } } } - private class MyOrientationListener extends OrientationListener { + class MyOrientationListener extends OrientationListener { MyOrientationListener(Context context) { super(context); @@ -214,7 +260,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { // portrait range is 270+45 to 359 and 0 to 45 // landscape range is 270-45 to 270+45 if ((orientation >= 0 && orientation <= 45) || (orientation >= 270 - 45)) { - mSensorOrientation = orientation; int rotation = (orientation >= 270 - 45 && orientation <= 270 + 45) ? Surface.ROTATION_90 : Surface.ROTATION_0; @@ -233,8 +278,20 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } } - private MyOrientationListener mOrientationListener; + MyOrientationListener mOrientationListener; + boolean useSensorForOrientation() { + if(mCurrentAppOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR) { + return true; + } + if (mAccelerometerDefault != 0 && ( + mCurrentAppOrientation == ActivityInfo.SCREEN_ORIENTATION_USER || + mCurrentAppOrientation == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED)) { + return true; + } + return false; + } + /* * Various use cases for invoking this function * screen turning off, should always disable listeners if already enabled @@ -245,7 +302,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { * 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 */ - private void updateOrientationListener() { + void updateOrientationListener() { //Could have been invoked due to screen turning on or off or //change of the currently visible window's orientation if(localLOGV) Log.i(TAG, "Screen status="+mScreenOn+ @@ -253,7 +310,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { ", SensorEnabled="+mOrientationSensorEnabled); boolean disable = true; if(mScreenOn) { - if(mCurrentAppOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR) { + if(useSensorForOrientation()) { disable = false; //enable listener if not already enabled if(!mOrientationSensorEnabled) { @@ -271,7 +328,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } - private Runnable mEndCallLongPress = new Runnable() { + Runnable mEndCallLongPress = new Runnable() { public void run() { mShouldTurnOffOnKeyUp = false; sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS); @@ -279,7 +336,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } }; - private void showGlobalActionsDialog() { + void showGlobalActionsDialog() { if (mGlobalActions == null) { mGlobalActions = new GlobalActions(mContext, mPowerManager); } @@ -292,15 +349,15 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } - private boolean isDeviceProvisioned() { - return Settings.System.getInt( - mContext.getContentResolver(), Settings.System.DEVICE_PROVISIONED, 0) != 0; + 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 */ - private Runnable mHomeLongPress = new Runnable() { + Runnable mHomeLongPress = new Runnable() { public void run() { /* * Eat the longpress so it won't dismiss the recent apps dialog when @@ -315,7 +372,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { /** * Create (if necessary) and launch the recent apps dialog */ - private void showRecentAppsDialog() { + void showRecentAppsDialog() { if (mRecentAppsDialog == null) { mRecentAppsDialog = new RecentApplicationsDialog(mContext); } @@ -331,7 +388,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { mKeyguardMediator = new KeyguardViewMediator(context, this, powerManager); mHandler = new Handler(); mOrientationListener = new MyOrientationListener(mContext); - SettingsObserver settingsObserver = new SettingsObserver(); + SettingsObserver settingsObserver = new SettingsObserver(mHandler); settingsObserver.observe(); mShortcutManager = new ShortcutManager(context, mHandler); mShortcutManager.observe(); @@ -359,6 +416,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { // this... should introduce a token to let the system // monitor/control what they are doing. break; + case TYPE_INPUT_METHOD: + // The window manager will check this. + break; case TYPE_PHONE: case TYPE_PRIORITY_PHONE: case TYPE_SYSTEM_ALERT: @@ -389,7 +449,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } - private void readLidState() { + void readLidState() { try { int sw = mWindowManager.getSwitchState(0); if (sw >= 0) { @@ -403,8 +463,12 @@ public class PhoneWindowManager implements WindowManagerPolicy { /** {@inheritDoc} */ public void adjustConfigurationLw(Configuration config) { readLidState(); - mPowerManager.setKeyboardVisibility(mLidOpen); - config.keyboardHidden = mLidOpen + final boolean lidOpen = !KEYBOARD_ALWAYS_HIDDEN && mLidOpen; + mPowerManager.setKeyboardVisibility(lidOpen); + config.keyboardHidden = (lidOpen || mHasSoftInput) + ? Configuration.KEYBOARDHIDDEN_NO + : Configuration.KEYBOARDHIDDEN_YES; + config.hardKeyboardHidden = lidOpen ? Configuration.KEYBOARDHIDDEN_NO : Configuration.KEYBOARDHIDDEN_YES; if (keyguardIsShowingTq()) { @@ -438,10 +502,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { return APPLICATION_LAYER; } switch (type) { - case TYPE_APPLICATION_PANEL: - return APPLICATION_LAYER; - case TYPE_APPLICATION_SUB_PANEL: - return APPLICATION_LAYER; case TYPE_STATUS_BAR: return STATUS_BAR_LAYER; case TYPE_STATUS_BAR_PANEL: @@ -458,6 +518,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { 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: @@ -473,6 +537,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { 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; @@ -514,6 +579,11 @@ public class PhoneWindowManager implements WindowManagerPolicy { } 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)); @@ -634,7 +704,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } - private static final boolean PRINT_ANIM = false; + static final boolean PRINT_ANIM = false; /** {@inheritDoc} */ public int selectAnimationLw(WindowState win, int transit) { @@ -650,15 +720,15 @@ public class PhoneWindowManager implements WindowManagerPolicy { return 0; } - private static ITelephony getPhoneInterface() { + static ITelephony getPhoneInterface() { return ITelephony.Stub.asInterface(ServiceManager.checkService(Context.TELEPHONY_SERVICE)); } - private static IAudioService getAudioInterface() { + static IAudioService getAudioInterface() { return IAudioService.Stub.asInterface(ServiceManager.checkService(Context.AUDIO_SERVICE)); } - private boolean keyguardOn() { + boolean keyguardOn() { return keyguardIsShowingTq() || inKeyguardRestrictedKeyInputMode(); } @@ -826,7 +896,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { * A home key -> launch home action was detected. Take the appropriate action * given the situation with the keyguard. */ - private void launchHomeFromHotKey() { + void launchHomeFromHotKey() { if (mKeyguardMediator.isShowing()) { // don't launch home if keyguard showing } else if (mKeyguardMediator.isInputRestricted()) { @@ -847,15 +917,15 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } - public void getCoveredInsetHintLw(WindowManager.LayoutParams attrs, Rect coveredInset) { + 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)) { - coveredInset.set(mCurLeft, mCurTop, mW - mCurRight, mH - mCurBottom); + contentInset.set(mCurLeft, mCurTop, mW - mCurRight, mH - mCurBottom); } else { - coveredInset.setEmpty(); + contentInset.setEmpty(); } } @@ -863,19 +933,75 @@ public class PhoneWindowManager implements WindowManagerPolicy { public void beginLayoutLw(int displayWidth, int displayHeight) { mW = displayWidth; mH = displayHeight; - mCurLeft = 0; - mCurTop = 0; - mCurRight = displayWidth; - mCurBottom = 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) { - mStatusBar.computeFrameLw(0, 0, displayWidth, displayHeight, - 0, 0, displayWidth, displayHeight); - mCurTop = mStatusBar.getFrameLw().bottom; + 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); + mDockTop = mContentTop = mCurTop = mStatusBar.getFrameLw().bottom; } } + 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 @@ -883,52 +1009,132 @@ public class PhoneWindowManager implements WindowManagerPolicy { 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; - int dl, dt, dr, db; - if ((fl & FLAG_LAYOUT_IN_SCREEN) == 0) { - // Make sure this window doesn't intrude into the status bar. - dl = mCurLeft; - dt = mCurTop; - dr = mCurRight; - db = mCurBottom; + 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 { - dl = 0; - dt = 0; - dr = mW; - db = mH; + 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_IN_SCREEN | FLAG_FULLSCREEN | FLAG_LAYOUT_INSET_DECOR)) - == (FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR)) { - win.setCoveredInsetsLw(mCurLeft, mCurTop, mW - mCurRight, mH - mCurBottom); - } else { - win.setCoveredInsetsLw(0, 0, 0, 0); + 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; } - int pl, pt, pr, pb; - if (attached != null && (fl & (FLAG_LAYOUT_IN_SCREEN)) == 0) { - final Rect r = attached.getFrameLw(); - pl = r.left; - pt = r.top; - pr = r.right; - pb = r.bottom; - } else { - pl = dl; - pt = dt; - pr = dr; - pb = db; + 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 + + ": pf=" + pf.toShortString() + " df=" + df.toShortString() + + " cf=" + cf.toShortString() + " vf=" + vf.toShortString()); + } } - if ((fl & FLAG_LAYOUT_NO_LIMITS) != 0) { - dl = -100000; - dt = -100000; - dr = 100000; - db = 100000; + 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; + } } - - win.computeFrameLw(pl, pt, pr, pb, dl, dt, dr, db); } /** {@inheritDoc} */ @@ -1011,7 +1217,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { /** * @return Whether a telephone call is in progress right now. */ - private boolean isInCall() { + boolean isInCall() { final ITelephony phone = getPhoneInterface(); if (phone == null) { Log.w(TAG, "couldn't get ITelephony reference"); @@ -1028,7 +1234,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { /** * @return Whether music is being played right now. */ - private boolean isMusicActive() { + boolean isMusicActive() { final IAudioService audio = getAudioInterface(); if (audio == null) { Log.w(TAG, "isMusicActive: couldn't get IAudioService reference"); @@ -1046,7 +1252,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { * Tell the audio service to adjust the volume appropriate to the event. * @param keycode */ - private void sendVolToMusic(int keycode) { + void sendVolToMusic(int keycode) { final IAudioService audio = getAudioInterface(); if (audio == null) { Log.w(TAG, "sendVolToMusic: couldn't get IAudioService reference"); @@ -1069,7 +1275,20 @@ public class PhoneWindowManager implements WindowManagerPolicy { mBroadcastWakeLock.release(); } } - + + static boolean isMediaKey(int code) { + if (code == KeyEvent.KEYCODE_HEADSETHOOK || + code == KeyEvent.KEYCODE_PLAYPAUSE || + code == KeyEvent.KEYCODE_STOP || + code == KeyEvent.KEYCODE_NEXTSONG || + code == KeyEvent.KEYCODE_PREVIOUSSONG || + code == KeyEvent.KEYCODE_PREVIOUSSONG || + code == KeyEvent.KEYCODE_FORWARD) { + return true; + } + return false; + } + /** {@inheritDoc} */ public int interceptKeyTq(RawInputEvent event, boolean screenIsOn) { int result = ACTION_PASS_TO_USER; @@ -1165,7 +1384,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { result &= ~ACTION_PASS_TO_USER; } } - } else if (code == KeyEvent.KEYCODE_HEADSETHOOK) { + } 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. @@ -1175,7 +1394,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { // 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, - KeyEvent.KEYCODE_HEADSETHOOK, 0); + code, 0); mBroadcastWakeLock.acquire(); mHandler.post(new PassHeadsetKey(keyEvent)); } @@ -1200,7 +1419,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } - private BroadcastReceiver mBroadcastDone = new BroadcastReceiver() { + BroadcastReceiver mBroadcastDone = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { mBroadcastWakeLock.release(); } @@ -1298,20 +1517,17 @@ public class PhoneWindowManager implements WindowManagerPolicy { case ActivityInfo.SCREEN_ORIENTATION_PORTRAIT: //always return portrait if orientation set to portrait return Surface.ROTATION_0; - case ActivityInfo.SCREEN_ORIENTATION_SENSOR: - if(mOrientationSensorEnabled) { - //consider only sensor based orientation keyboard slide ignored - return mSensorRotation >= 0 ? mSensorRotation : Surface.ROTATION_0; - } - //if orientation sensor is disabled fall back to default behaviour - //based on lid } // case for nosensor meaning ignore sensor and consider only lid // or orientation sensor disabled //or case.unspecified - if(mLidOpen) { + if (mLidOpen) { return Surface.ROTATION_90; } else { + if (useSensorForOrientation()) { + // If the user has enabled auto rotation by default, do it. + return mSensorRotation >= 0 ? mSensorRotation : Surface.ROTATION_0; + } return Surface.ROTATION_0; } } @@ -1325,11 +1541,11 @@ public class PhoneWindowManager implements WindowManagerPolicy { // If the user is holding the menu key code, then we are // going to boot into safe mode. ActivityManagerNative.getDefault().enterSafeMode(); - } else { - // tell the keyguard - mKeyguardMediator.onSystemReady(); - android.os.SystemProperties.set("dev.bootcomplete", "1"); } + // tell the keyguard + mKeyguardMediator.onSystemReady(); + android.os.SystemProperties.set("dev.bootcomplete", "1"); + updateOrientationListener(); } catch (RemoteException e) { // Ignore } @@ -1341,7 +1557,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { updateRotation(); } - private void updateRotation() { + void updateRotation() { mPowerManager.setKeyboardVisibility(mLidOpen); int rotation= Surface.ROTATION_0; if (mLidOpen) { diff --git a/phone/com/android/internal/policy/impl/Policy.java b/phone/com/android/internal/policy/impl/Policy.java index f537186..17f3e91 100644 --- a/phone/com/android/internal/policy/impl/Policy.java +++ b/phone/com/android/internal/policy/impl/Policy.java @@ -17,6 +17,7 @@ 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; @@ -30,6 +31,29 @@ import com.android.internal.policy.impl.PhoneWindowManager; // 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); diff --git a/phone/com/android/internal/policy/impl/SimUnlockScreen.java b/phone/com/android/internal/policy/impl/SimUnlockScreen.java index 4c71392..fceec5f 100644 --- a/phone/com/android/internal/policy/impl/SimUnlockScreen.java +++ b/phone/com/android/internal/policy/impl/SimUnlockScreen.java @@ -101,11 +101,19 @@ public class SimUnlockScreen extends LinearLayout implements KeyguardScreen, Vie 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; } /** {@inheritDoc} */ public void cleanUp() { + // hide the dialog. + if (mSimUnlockProgressDialog != null) { + mSimUnlockProgressDialog.hide(); + } mUpdateMonitor.removeCallback(this); } @@ -143,7 +151,7 @@ public class SimUnlockScreen extends LinearLayout implements KeyguardScreen, Vie } } } - + public void onClick(View v) { if (v == mBackSpaceButton) { final Editable digits = mPinText.getText(); @@ -191,7 +199,9 @@ public class SimUnlockScreen extends LinearLayout implements KeyguardScreen, Vie new CheckSimPin(mPinText.getText().toString()) { void onSimLockChangedResponse(boolean success) { - getSimUnlockProgressDialog().hide(); + if (mSimUnlockProgressDialog != null) { + mSimUnlockProgressDialog.hide(); + } if (success) { // before closing the keyguard, report back that // the sim is unlocked so it knows right away diff --git a/phone/com/android/internal/policy/impl/UnlockScreen.java b/phone/com/android/internal/policy/impl/UnlockScreen.java index bd0b6d5..65ab439 100644 --- a/phone/com/android/internal/policy/impl/UnlockScreen.java +++ b/phone/com/android/internal/policy/impl/UnlockScreen.java @@ -16,9 +16,14 @@ package com.android.internal.policy.impl; +import android.content.ComponentName; import android.content.Context; +import android.content.ServiceConnection; import android.os.CountDownTimer; +import android.os.IBinder; +import android.os.RemoteException; import android.os.SystemClock; +import android.util.Log; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; @@ -85,6 +90,8 @@ class UnlockScreen extends LinearLayoutWithDefaultTouchRecepient private Button mForgotPatternButton; + private ServiceConnection mServiceConnection; + enum FooterMode { Normal, @@ -177,6 +184,9 @@ class UnlockScreen extends LinearLayoutWithDefaultTouchRecepient // 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); @@ -237,6 +247,10 @@ class UnlockScreen extends LinearLayoutWithDefaultTouchRecepient 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(); -- 2.11.0