Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

auto import from //depot/cupcake/@135843

  • Loading branch information...
commit c5d93b3b6a0ba4831903f8e8d1664c4470cf15d7 1 parent 4614296
The Android Open Source Project authored
Showing with 15,011 additions and 0 deletions.
  1. +85 −0 PolicyConfig.mk
  2. +13 −0 mid/Android.mk
  3. +411 −0 mid/com/android/internal/policy/impl/GlobalActions.java
  4. +73 −0 mid/com/android/internal/policy/impl/MidLayoutInflater.java
  5. +2,597 −0 mid/com/android/internal/policy/impl/MidWindow.java
  6. +1,064 −0 mid/com/android/internal/policy/impl/MidWindowManager.java
  7. +45 −0 mid/com/android/internal/policy/impl/Policy.java
  8. +177 −0 mid/com/android/internal/policy/impl/PowerDialog.java
  9. +252 −0 mid/com/android/internal/policy/impl/RecentApplicationsDialog.java
  10. +120 −0 mid/com/android/internal/policy/impl/ShortcutManager.java
  11. +137 −0 mid/com/android/internal/policy/impl/ShutdownThread.java
  12. +5 −0 mid/com/android/internal/policy/impl/package.html
  13. +13 −0 phone/Android.mk
  14. +278 −0 phone/com/android/internal/policy/impl/AccountUnlockScreen.java
  15. +514 −0 phone/com/android/internal/policy/impl/GlobalActions.java
  16. +45 −0 phone/com/android/internal/policy/impl/KeyguardScreen.java
  17. +66 −0 phone/com/android/internal/policy/impl/KeyguardScreenCallback.java
  18. +552 −0 phone/com/android/internal/policy/impl/KeyguardUpdateMonitor.java
  19. +188 −0 phone/com/android/internal/policy/impl/KeyguardViewBase.java
  20. +49 −0 phone/com/android/internal/policy/impl/KeyguardViewCallback.java
  21. +226 −0 phone/com/android/internal/policy/impl/KeyguardViewManager.java
  22. +915 −0 phone/com/android/internal/policy/impl/KeyguardViewMediator.java
  23. +46 −0 phone/com/android/internal/policy/impl/KeyguardViewProperties.java
  24. +29 −0 phone/com/android/internal/policy/impl/KeyguardWindowController.java
  25. +639 −0 phone/com/android/internal/policy/impl/LockPatternKeyguardView.java
  26. +67 −0 phone/com/android/internal/policy/impl/LockPatternKeyguardViewProperties.java
  27. +371 −0 phone/com/android/internal/policy/impl/LockScreen.java
  28. +73 −0 phone/com/android/internal/policy/impl/PhoneLayoutInflater.java
  29. +2,688 −0 phone/com/android/internal/policy/impl/PhoneWindow.java
  30. +1,800 −0 phone/com/android/internal/policy/impl/PhoneWindowManager.java
  31. +69 −0 phone/com/android/internal/policy/impl/Policy.java
  32. +179 −0 phone/com/android/internal/policy/impl/PowerDialog.java
  33. +255 −0 phone/com/android/internal/policy/impl/RecentApplicationsDialog.java
  34. +120 −0 phone/com/android/internal/policy/impl/ShortcutManager.java
  35. +138 −0 phone/com/android/internal/policy/impl/ShutdownThread.java
  36. +366 −0 phone/com/android/internal/policy/impl/SimUnlockScreen.java
  37. +341 −0 phone/com/android/internal/policy/impl/UnlockScreen.java
  38. +5 −0 phone/com/android/internal/policy/impl/package.html
View
85 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
View
13 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)
View
411 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<Action> 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();
+ }
+ }
+ }
+ };
+}
View
73 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);
+ }
+}
+
View
2,597 mid/com/android/internal/policy/impl/MidWindow.java
@@ -0,0 +1,2597 @@
+/*
+ * 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.
+ * <p>
+ * 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_MUTE:
+ 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<Parcelable> states = new SparseArray<Parcelable>();
+ mContentParent.saveHierarchyState(states);
+ outState.putSparseParcelableArray(VIEWS_TAG, states);
+
+ // save the focused view id
+ View focusedView = mContentParent.findFocus();
+ if (focusedView != null) {
+ if (focusedView.getId() != View.NO_ID) {
+ outState.putInt(FOCUSED_ID_TAG, focusedView.getId());
+ } else {
+ if (Config.LOGD) {
+ Log.d(TAG, "couldn't save which view has focus because the focused view "
+ + focusedView + " has no id.");
+ }
+ }
+ }
+
+ // save the panels
+ SparseArray<Parcelable> panelStates = new SparseArray<Parcelable>();
+ savePanelState(panelStates);
+ if (panelStates.size() > 0) {
+ outState.putSparseParcelableArray(PANELS_TAG, panelStates);
+ }
+
+ return outState;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void restoreHierarchyState(Bundle savedInstanceState) {
+ if (mContentParent == null) {
+ return;
+ }
+
+ SparseArray<Parcelable> savedStates
+ = savedInstanceState.getSparseParcelableArray(VIEWS_TAG);
+ if (savedStates != null) {
+ mContentParent.restoreHierarchyState(savedStates);
+ }
+
+ // restore the focused view
+ int focusedViewId = savedInstanceState.getInt(FOCUSED_ID_TAG, View.NO_ID);
+ if (focusedViewId != View.NO_ID) {
+ View needsFocus = mContentParent.findViewById(focusedViewId);
+ if (needsFocus != null) {
+ needsFocus.requestFocus();
+ } else {
+ Log.w(TAG,
+ "Previously focused view reported id " + focusedViewId
+ + " during save, but can't be found during restore.");
+ }
+ }
+
+ // restore the panels
+ SparseArray<Parcelable> panelStates = savedInstanceState.getSparseParcelableArray(PANELS_TAG);
+ if (panelStates != null) {
+ restorePanelState(panelStates);
+ }
+ }
+
+ /**
+ * Invoked when the panels should freeze their state.
+ *
+ * @param icicles Save state into this. This is usually indexed by the
+ * featureId. This will be given to {@link #restorePanelState} in the
+ * future.
+ */
+ private void savePanelState(SparseArray<Parcelable> icicles) {
+ PanelFeatureState[] panels = mPanels;
+ if (panels == null) {
+ return;
+ }
+
+ for (int curFeatureId = panels.length - 1; curFeatureId >= 0; curFeatureId--) {
+ if (panels[curFeatureId] != null) {
+ icicles.put(curFeatureId, panels[curFeatureId].onSaveInstanceState());
+ }
+ }
+ }
+
+ /**
+ * Invoked when the panels should thaw their state from a previously frozen state.
+ *
+ * @param icicles The state saved by {@link #savePanelState} that needs to be thawed.
+ */
+ private void restorePanelState(SparseArray<Parcelable> icicles) {
+ PanelFeatureState st;
+ for (int curFeatureId = icicles.size() - 1; curFeatureId >= 0; curFeatureId--) {
+ st = getPanelState(curFeatureId, false /* required */);
+ if (st == null) {
+ // The panel must not have been required, and is currently not around, skip it
+ continue;
+ }
+
+ st.onRestoreInstanceState(icicles.get(curFeatureId));
+ }
+
+ /*
+ * Implementation note: call openPanelsAfterRestore later to actually open the
+ * restored panels.
+ */
+ }
+
+ /**
+ * Opens the panels that have had their state restored. This should be
+ * called sometime after {@link #restorePanelState} when it is safe to add
+ * to the window manager.
+ */
+ private void openPanelsAfterRestore() {
+ PanelFeatureState[] panels = mPanels;
+
+ if (panels == null) {
+ return;
+ }
+
+ PanelFeatureState st;
+ for (int i = panels.length - 1; i >= 0; i--) {
+ st = panels[i];
+ 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();