PhoneWindow.java revision f8419a0299680ed580975b0fcb758990b4367db8
1/* 2 * 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16package com.android.internal.policy.impl; 17 18import static android.view.View.MeasureSpec.AT_MOST; 19import static android.view.View.MeasureSpec.EXACTLY; 20import static android.view.View.MeasureSpec.getMode; 21import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; 22import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; 23import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN; 24import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR; 25import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; 26import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; 27import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH; 28 29import com.android.internal.view.RootViewSurfaceTaker; 30import com.android.internal.view.StandaloneActionMode; 31import com.android.internal.view.menu.ContextMenuBuilder; 32import com.android.internal.view.menu.IconMenuPresenter; 33import com.android.internal.view.menu.ListMenuPresenter; 34import com.android.internal.view.menu.MenuBuilder; 35import com.android.internal.view.menu.MenuDialogHelper; 36import com.android.internal.view.menu.MenuPresenter; 37import com.android.internal.view.menu.MenuView; 38import com.android.internal.widget.ActionBarContainer; 39import com.android.internal.widget.ActionBarContextView; 40import com.android.internal.widget.ActionBarView; 41 42import android.app.KeyguardManager; 43import android.content.Context; 44import android.content.pm.ActivityInfo; 45import android.content.res.Configuration; 46import android.content.res.Resources; 47import android.content.res.TypedArray; 48import android.graphics.Canvas; 49import android.graphics.PixelFormat; 50import android.graphics.Rect; 51import android.graphics.drawable.Drawable; 52import android.media.AudioManager; 53import android.net.Uri; 54import android.os.Bundle; 55import android.os.Handler; 56import android.os.Parcel; 57import android.os.Parcelable; 58import android.os.RemoteException; 59import android.os.ServiceManager; 60import android.util.AndroidRuntimeException; 61import android.util.DisplayMetrics; 62import android.util.EventLog; 63import android.util.Log; 64import android.util.SparseArray; 65import android.util.TypedValue; 66import android.view.ActionMode; 67import android.view.ContextThemeWrapper; 68import android.view.Gravity; 69import android.view.IRotationWatcher; 70import android.view.IWindowManager; 71import android.view.InputQueue; 72import android.view.KeyCharacterMap; 73import android.view.KeyEvent; 74import android.view.LayoutInflater; 75import android.view.Menu; 76import android.view.MenuItem; 77import android.view.MotionEvent; 78import android.view.SurfaceHolder; 79import android.view.View; 80import android.view.ViewGroup; 81import android.view.ViewManager; 82import android.view.ViewStub; 83import android.view.Window; 84import android.view.WindowManager; 85import android.view.accessibility.AccessibilityEvent; 86import android.view.accessibility.AccessibilityManager; 87import android.view.animation.Animation; 88import android.view.animation.AnimationUtils; 89import android.widget.FrameLayout; 90import android.widget.ImageView; 91import android.widget.PopupWindow; 92import android.widget.ProgressBar; 93import android.widget.TextView; 94 95import java.lang.ref.WeakReference; 96import java.util.ArrayList; 97 98/** 99 * Android-specific Window. 100 * <p> 101 * todo: need to pull the generic functionality out into a base class 102 * in android.widget. 103 */ 104public class PhoneWindow extends Window implements MenuBuilder.Callback { 105 106 private final static String TAG = "PhoneWindow"; 107 108 private final static boolean SWEEP_OPEN_MENU = false; 109 110 /** 111 * Simple callback used by the context menu and its submenus. The options 112 * menu submenus do not use this (their behavior is more complex). 113 */ 114 final DialogMenuCallback mContextMenuCallback = new DialogMenuCallback(FEATURE_CONTEXT_MENU); 115 116 final TypedValue mMinWidthMajor = new TypedValue(); 117 final TypedValue mMinWidthMinor = new TypedValue(); 118 119 // This is the top-level view of the window, containing the window decor. 120 private DecorView mDecor; 121 122 // This is the view in which the window contents are placed. It is either 123 // mDecor itself, or a child of mDecor where the contents go. 124 private ViewGroup mContentParent; 125 126 SurfaceHolder.Callback2 mTakeSurfaceCallback; 127 128 InputQueue.Callback mTakeInputQueueCallback; 129 130 private boolean mIsFloating; 131 132 private LayoutInflater mLayoutInflater; 133 134 private TextView mTitleView; 135 136 private ActionBarView mActionBar; 137 private ActionMenuPresenterCallback mActionMenuPresenterCallback; 138 private PanelMenuPresenterCallback mPanelMenuPresenterCallback; 139 140 private DrawableFeatureState[] mDrawables; 141 142 private PanelFeatureState[] mPanels; 143 144 /** 145 * The panel that is prepared or opened (the most recent one if there are 146 * multiple panels). Shortcuts will go to this panel. It gets set in 147 * {@link #preparePanel} and cleared in {@link #closePanel}. 148 */ 149 private PanelFeatureState mPreparedPanel; 150 151 /** 152 * The keycode that is currently held down (as a modifier) for chording. If 153 * this is 0, there is no key held down. 154 */ 155 private int mPanelChordingKey; 156 157 private ImageView mLeftIconView; 158 159 private ImageView mRightIconView; 160 161 private ProgressBar mCircularProgressBar; 162 163 private ProgressBar mHorizontalProgressBar; 164 165 private int mBackgroundResource = 0; 166 167 private Drawable mBackgroundDrawable; 168 169 private int mFrameResource = 0; 170 171 private int mTextColor = 0; 172 173 private CharSequence mTitle = null; 174 175 private int mTitleColor = 0; 176 177 private boolean mAlwaysReadCloseOnTouchAttr = false; 178 179 private ContextMenuBuilder mContextMenu; 180 private MenuDialogHelper mContextMenuHelper; 181 private boolean mClosingActionMenu; 182 183 private int mVolumeControlStreamType = AudioManager.USE_DEFAULT_STREAM_TYPE; 184 185 private AudioManager mAudioManager; 186 private KeyguardManager mKeyguardManager; 187 188 private int mUiOptions = 0; 189 190 static class WindowManagerHolder { 191 static final IWindowManager sWindowManager = IWindowManager.Stub.asInterface( 192 ServiceManager.getService("window")); 193 } 194 195 static final RotationWatcher sRotationWatcher = new RotationWatcher(); 196 197 public PhoneWindow(Context context) { 198 super(context); 199 mLayoutInflater = LayoutInflater.from(context); 200 } 201 202 @Override 203 public final void setContainer(Window container) { 204 super.setContainer(container); 205 } 206 207 @Override 208 public boolean requestFeature(int featureId) { 209 if (mContentParent != null) { 210 throw new AndroidRuntimeException("requestFeature() must be called before adding content"); 211 } 212 final int features = getFeatures(); 213 if ((features != DEFAULT_FEATURES) && (featureId == FEATURE_CUSTOM_TITLE)) { 214 215 /* Another feature is enabled and the user is trying to enable the custom title feature */ 216 throw new AndroidRuntimeException("You cannot combine custom titles with other title features"); 217 } 218 if (((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) && 219 (featureId != FEATURE_CUSTOM_TITLE) && (featureId != FEATURE_ACTION_MODE_OVERLAY)) { 220 221 /* Custom title feature is enabled and the user is trying to enable another feature */ 222 throw new AndroidRuntimeException("You cannot combine custom titles with other title features"); 223 } 224 if ((features & (1 << FEATURE_NO_TITLE)) != 0 && featureId == FEATURE_ACTION_BAR) { 225 return false; // Ignore. No title dominates. 226 } 227 if ((features & (1 << FEATURE_ACTION_BAR)) != 0 && featureId == FEATURE_NO_TITLE) { 228 // Remove the action bar feature if we have no title. No title dominates. 229 removeFeature(FEATURE_ACTION_BAR); 230 } 231 return super.requestFeature(featureId); 232 } 233 234 @Override 235 public void setUiOptions(int uiOptions) { 236 mUiOptions = uiOptions; 237 } 238 239 @Override 240 public void setUiOptions(int uiOptions, int mask) { 241 mUiOptions = (mUiOptions & ~mask) | (uiOptions & mask); 242 } 243 244 @Override 245 public void setContentView(int layoutResID) { 246 if (mContentParent == null) { 247 installDecor(); 248 } else { 249 mContentParent.removeAllViews(); 250 } 251 mLayoutInflater.inflate(layoutResID, mContentParent); 252 final Callback cb = getCallback(); 253 if (cb != null && !isDestroyed()) { 254 cb.onContentChanged(); 255 } 256 } 257 258 @Override 259 public void setContentView(View view) { 260 setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); 261 } 262 263 @Override 264 public void setContentView(View view, ViewGroup.LayoutParams params) { 265 if (mContentParent == null) { 266 installDecor(); 267 } else { 268 mContentParent.removeAllViews(); 269 } 270 mContentParent.addView(view, params); 271 final Callback cb = getCallback(); 272 if (cb != null && !isDestroyed()) { 273 cb.onContentChanged(); 274 } 275 } 276 277 @Override 278 public void addContentView(View view, ViewGroup.LayoutParams params) { 279 if (mContentParent == null) { 280 installDecor(); 281 } 282 mContentParent.addView(view, params); 283 final Callback cb = getCallback(); 284 if (cb != null && !isDestroyed()) { 285 cb.onContentChanged(); 286 } 287 } 288 289 @Override 290 public View getCurrentFocus() { 291 return mDecor != null ? mDecor.findFocus() : null; 292 } 293 294 @Override 295 public void takeSurface(SurfaceHolder.Callback2 callback) { 296 mTakeSurfaceCallback = callback; 297 } 298 299 public void takeInputQueue(InputQueue.Callback callback) { 300 mTakeInputQueueCallback = callback; 301 } 302 303 @Override 304 public boolean isFloating() { 305 return mIsFloating; 306 } 307 308 /** 309 * Return a LayoutInflater instance that can be used to inflate XML view layout 310 * resources for use in this Window. 311 * 312 * @return LayoutInflater The shared LayoutInflater. 313 */ 314 @Override 315 public LayoutInflater getLayoutInflater() { 316 return mLayoutInflater; 317 } 318 319 @Override 320 public void setTitle(CharSequence title) { 321 if (mTitleView != null) { 322 mTitleView.setText(title); 323 } else if (mActionBar != null) { 324 mActionBar.setWindowTitle(title); 325 } 326 mTitle = title; 327 } 328 329 @Override 330 public void setTitleColor(int textColor) { 331 if (mTitleView != null) { 332 mTitleView.setTextColor(textColor); 333 } 334 mTitleColor = textColor; 335 } 336 337 /** 338 * Prepares the panel to either be opened or chorded. This creates the Menu 339 * instance for the panel and populates it via the Activity callbacks. 340 * 341 * @param st The panel state to prepare. 342 * @param event The event that triggered the preparing of the panel. 343 * @return Whether the panel was prepared. If the panel should not be shown, 344 * returns false. 345 */ 346 public final boolean preparePanel(PanelFeatureState st, KeyEvent event) { 347 if (isDestroyed()) { 348 return false; 349 } 350 351 // Already prepared (isPrepared will be reset to false later) 352 if (st.isPrepared) 353 return true; 354 355 if ((mPreparedPanel != null) && (mPreparedPanel != st)) { 356 // Another Panel is prepared and possibly open, so close it 357 closePanel(mPreparedPanel, false); 358 } 359 360 final Callback cb = getCallback(); 361 362 if (cb != null) { 363 st.createdPanelView = cb.onCreatePanelView(st.featureId); 364 } 365 366 if (st.createdPanelView == null) { 367 // Init the panel state's menu--return false if init failed 368 if (st.menu == null || st.refreshMenuContent) { 369 if (st.menu == null) { 370 if (!initializePanelMenu(st) || (st.menu == null)) { 371 return false; 372 } 373 } 374 375 if (mActionBar != null) { 376 if (mActionMenuPresenterCallback == null) { 377 mActionMenuPresenterCallback = new ActionMenuPresenterCallback(); 378 } 379 mActionBar.setMenu(st.menu, mActionMenuPresenterCallback); 380 } 381 382 // Call callback, and return if it doesn't want to display menu. 383 384 // Creating the panel menu will involve a lot of manipulation; 385 // don't dispatch change events to presenters until we're done. 386 st.menu.stopDispatchingItemsChanged(); 387 if ((cb == null) || !cb.onCreatePanelMenu(st.featureId, st.menu)) { 388 // Ditch the menu created above 389 st.setMenu(null); 390 391 if (mActionBar != null) { 392 // Don't show it in the action bar either 393 mActionBar.setMenu(null, mActionMenuPresenterCallback); 394 } 395 396 return false; 397 } 398 399 st.refreshMenuContent = false; 400 } 401 402 // Callback and return if the callback does not want to show the menu 403 404 // Preparing the panel menu can involve a lot of manipulation; 405 // don't dispatch change events to presenters until we're done. 406 st.menu.stopDispatchingItemsChanged(); 407 408 // Restore action view state before we prepare. This gives apps 409 // an opportunity to override frozen/restored state in onPrepare. 410 if (st.frozenActionViewState != null) { 411 st.menu.restoreActionViewStates(st.frozenActionViewState); 412 st.frozenActionViewState = null; 413 } 414 415 if (!cb.onPreparePanel(st.featureId, st.createdPanelView, st.menu)) { 416 if (mActionBar != null) { 417 // The app didn't want to show the menu for now but it still exists. 418 // Clear it out of the action bar. 419 mActionBar.setMenu(null, mActionMenuPresenterCallback); 420 } 421 st.menu.startDispatchingItemsChanged(); 422 return false; 423 } 424 425 // Set the proper keymap 426 KeyCharacterMap kmap = KeyCharacterMap.load( 427 event != null ? event.getDeviceId() : KeyCharacterMap.VIRTUAL_KEYBOARD); 428 st.qwertyMode = kmap.getKeyboardType() != KeyCharacterMap.NUMERIC; 429 st.menu.setQwertyMode(st.qwertyMode); 430 st.menu.startDispatchingItemsChanged(); 431 } 432 433 // Set other state 434 st.isPrepared = true; 435 st.isHandled = false; 436 mPreparedPanel = st; 437 438 return true; 439 } 440 441 @Override 442 public void onConfigurationChanged(Configuration newConfig) { 443 // Action bars handle their own menu state 444 if (mActionBar == null) { 445 PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false); 446 if ((st != null) && (st.menu != null)) { 447 if (st.isOpen) { 448 // Freeze state 449 final Bundle state = new Bundle(); 450 if (st.iconMenuPresenter != null) { 451 st.iconMenuPresenter.saveHierarchyState(state); 452 } 453 if (st.listMenuPresenter != null) { 454 st.listMenuPresenter.saveHierarchyState(state); 455 } 456 457 // Remove the menu views since they need to be recreated 458 // according to the new configuration 459 clearMenuViews(st); 460 461 // Re-open the same menu 462 reopenMenu(false); 463 464 // Restore state 465 if (st.iconMenuPresenter != null) { 466 st.iconMenuPresenter.restoreHierarchyState(state); 467 } 468 if (st.listMenuPresenter != null) { 469 st.listMenuPresenter.restoreHierarchyState(state); 470 } 471 472 } else { 473 // Clear menu views so on next menu opening, it will use 474 // the proper layout 475 clearMenuViews(st); 476 } 477 } 478 } 479 } 480 481 private static void clearMenuViews(PanelFeatureState st) { 482 // This can be called on config changes, so we should make sure 483 // the views will be reconstructed based on the new orientation, etc. 484 485 // Allow the callback to create a new panel view 486 st.createdPanelView = null; 487 488 // Causes the decor view to be recreated 489 st.refreshDecorView = true; 490 491 st.clearMenuPresenters(); 492 } 493 494 @Override 495 public final void openPanel(int featureId, KeyEvent event) { 496 if (featureId == FEATURE_OPTIONS_PANEL && mActionBar != null && 497 mActionBar.isOverflowReserved()) { 498 if (mActionBar.getVisibility() == View.VISIBLE) { 499 mActionBar.showOverflowMenu(); 500 } 501 } else { 502 openPanel(getPanelState(featureId, true), event); 503 } 504 } 505 506 private void openPanel(PanelFeatureState st, KeyEvent event) { 507 // System.out.println("Open panel: isOpen=" + st.isOpen); 508 509 // Already open, return 510 if (st.isOpen || isDestroyed()) { 511 return; 512 } 513 514 // Don't open an options panel for honeycomb apps on xlarge devices. 515 // (The app should be using an action bar for menu items.) 516 if (st.featureId == FEATURE_OPTIONS_PANEL) { 517 Context context = getContext(); 518 Configuration config = context.getResources().getConfiguration(); 519 boolean isXLarge = (config.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) == 520 Configuration.SCREENLAYOUT_SIZE_XLARGE; 521 boolean isHoneycombApp = context.getApplicationInfo().targetSdkVersion >= 522 android.os.Build.VERSION_CODES.HONEYCOMB; 523 524 if (isXLarge && isHoneycombApp) { 525 return; 526 } 527 } 528 529 Callback cb = getCallback(); 530 if ((cb != null) && (!cb.onMenuOpened(st.featureId, st.menu))) { 531 // Callback doesn't want the menu to open, reset any state 532 closePanel(st, true); 533 return; 534 } 535 536 final WindowManager wm = getWindowManager(); 537 if (wm == null) { 538 return; 539 } 540 541 // Prepare panel (should have been done before, but just in case) 542 if (!preparePanel(st, event)) { 543 return; 544 } 545 546 int width = WRAP_CONTENT; 547 if (st.decorView == null || st.refreshDecorView) { 548 if (st.decorView == null) { 549 // Initialize the panel decor, this will populate st.decorView 550 if (!initializePanelDecor(st) || (st.decorView == null)) 551 return; 552 } else if (st.refreshDecorView && (st.decorView.getChildCount() > 0)) { 553 // Decor needs refreshing, so remove its views 554 st.decorView.removeAllViews(); 555 } 556 557 // This will populate st.shownPanelView 558 if (!initializePanelContent(st) || !st.hasPanelItems()) { 559 return; 560 } 561 562 ViewGroup.LayoutParams lp = st.shownPanelView.getLayoutParams(); 563 if (lp == null) { 564 lp = new ViewGroup.LayoutParams(WRAP_CONTENT, WRAP_CONTENT); 565 } 566 567 int backgroundResId; 568 if (lp.width == ViewGroup.LayoutParams.MATCH_PARENT) { 569 // If the contents is fill parent for the width, set the 570 // corresponding background 571 backgroundResId = st.fullBackground; 572 width = MATCH_PARENT; 573 } else { 574 // Otherwise, set the normal panel background 575 backgroundResId = st.background; 576 } 577 st.decorView.setWindowBackground(getContext().getResources().getDrawable( 578 backgroundResId)); 579 580 581 st.decorView.addView(st.shownPanelView, lp); 582 583 /* 584 * Give focus to the view, if it or one of its children does not 585 * already have it. 586 */ 587 if (!st.shownPanelView.hasFocus()) { 588 st.shownPanelView.requestFocus(); 589 } 590 } else if (!st.isInListMode()) { 591 width = MATCH_PARENT; 592 } else if (st.createdPanelView != null) { 593 // If we already had a panel view, carry width=MATCH_PARENT through 594 // as we did above when it was created. 595 ViewGroup.LayoutParams lp = st.createdPanelView.getLayoutParams(); 596 if (lp != null && lp.width == ViewGroup.LayoutParams.MATCH_PARENT) { 597 width = MATCH_PARENT; 598 } 599 } 600 601 st.isOpen = true; 602 st.isHandled = false; 603 604 WindowManager.LayoutParams lp = new WindowManager.LayoutParams( 605 width, WRAP_CONTENT, 606 st.x, st.y, WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG, 607 WindowManager.LayoutParams.FLAG_DITHER 608 | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM 609 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, 610 st.decorView.mDefaultOpacity); 611 612 if (st.isCompact) { 613 lp.gravity = getOptionsPanelGravity(); 614 sRotationWatcher.addWindow(this); 615 } else { 616 lp.gravity = st.gravity; 617 } 618 619 lp.windowAnimations = st.windowAnimations; 620 621 wm.addView(st.decorView, lp); 622 // Log.v(TAG, "Adding main menu to window manager."); 623 } 624 625 @Override 626 public final void closePanel(int featureId) { 627 if (featureId == FEATURE_OPTIONS_PANEL && mActionBar != null && 628 mActionBar.isOverflowReserved()) { 629 mActionBar.hideOverflowMenu(); 630 } else if (featureId == FEATURE_CONTEXT_MENU) { 631 closeContextMenu(); 632 } else { 633 closePanel(getPanelState(featureId, true), true); 634 } 635 } 636 637 /** 638 * Closes the given panel. 639 * 640 * @param st The panel to be closed. 641 * @param doCallback Whether to notify the callback that the panel was 642 * closed. If the panel is in the process of re-opening or 643 * opening another panel (e.g., menu opening a sub menu), the 644 * callback should not happen and this variable should be false. 645 * In addition, this method internally will only perform the 646 * callback if the panel is open. 647 */ 648 public final void closePanel(PanelFeatureState st, boolean doCallback) { 649 // System.out.println("Close panel: isOpen=" + st.isOpen); 650 if (doCallback && st.featureId == FEATURE_OPTIONS_PANEL && 651 mActionBar != null && mActionBar.isOverflowMenuShowing()) { 652 checkCloseActionMenu(st.menu); 653 return; 654 } 655 656 final ViewManager wm = getWindowManager(); 657 if ((wm != null) && st.isOpen) { 658 if (st.decorView != null) { 659 wm.removeView(st.decorView); 660 // Log.v(TAG, "Removing main menu from window manager."); 661 if (st.isCompact) { 662 sRotationWatcher.removeWindow(this); 663 } 664 } 665 666 if (doCallback) { 667 callOnPanelClosed(st.featureId, st, null); 668 } 669 } 670 671 st.isPrepared = false; 672 st.isHandled = false; 673 st.isOpen = false; 674 675 // This view is no longer shown, so null it out 676 st.shownPanelView = null; 677 678 if (st.isInExpandedMode) { 679 // Next time the menu opens, it should not be in expanded mode, so 680 // force a refresh of the decor 681 st.refreshDecorView = true; 682 st.isInExpandedMode = false; 683 } 684 685 if (mPreparedPanel == st) { 686 mPreparedPanel = null; 687 mPanelChordingKey = 0; 688 } 689 } 690 691 void checkCloseActionMenu(Menu menu) { 692 if (mClosingActionMenu) { 693 return; 694 } 695 696 mClosingActionMenu = true; 697 mActionBar.dismissPopupMenus(); 698 Callback cb = getCallback(); 699 if (cb != null && !isDestroyed()) { 700 cb.onPanelClosed(FEATURE_ACTION_BAR, menu); 701 } 702 mClosingActionMenu = false; 703 } 704 705 @Override 706 public final void togglePanel(int featureId, KeyEvent event) { 707 PanelFeatureState st = getPanelState(featureId, true); 708 if (st.isOpen) { 709 closePanel(st, true); 710 } else { 711 openPanel(st, event); 712 } 713 } 714 715 @Override 716 public void invalidatePanelMenu(int featureId) { 717 PanelFeatureState st = getPanelState(featureId, true); 718 Bundle savedActionViewStates = null; 719 if (st.menu != null) { 720 savedActionViewStates = new Bundle(); 721 st.menu.saveActionViewStates(savedActionViewStates); 722 if (savedActionViewStates.size() > 0) { 723 st.frozenActionViewState = savedActionViewStates; 724 } 725 // This will be started again when the panel is prepared. 726 st.menu.stopDispatchingItemsChanged(); 727 st.menu.clear(); 728 } 729 st.refreshMenuContent = true; 730 st.refreshDecorView = true; 731 732 // Prepare the options panel if we have an action bar 733 if ((featureId == FEATURE_ACTION_BAR || featureId == FEATURE_OPTIONS_PANEL) 734 && mActionBar != null) { 735 st = getPanelState(Window.FEATURE_OPTIONS_PANEL, false); 736 if (st != null) { 737 st.isPrepared = false; 738 preparePanel(st, null); 739 } 740 } 741 } 742 743 /** 744 * Called when the panel key is pushed down. 745 * @param featureId The feature ID of the relevant panel (defaults to FEATURE_OPTIONS_PANEL}. 746 * @param event The key event. 747 * @return Whether the key was handled. 748 */ 749 public final boolean onKeyDownPanel(int featureId, KeyEvent event) { 750 final int keyCode = event.getKeyCode(); 751 752 if (event.getRepeatCount() == 0) { 753 // The panel key was pushed, so set the chording key 754 mPanelChordingKey = keyCode; 755 756 PanelFeatureState st = getPanelState(featureId, true); 757 if (!st.isOpen) { 758 return preparePanel(st, event); 759 } 760 } 761 762 return false; 763 } 764 765 /** 766 * Called when the panel key is released. 767 * @param featureId The feature ID of the relevant panel (defaults to FEATURE_OPTIONS_PANEL}. 768 * @param event The key event. 769 */ 770 public final void onKeyUpPanel(int featureId, KeyEvent event) { 771 // The panel key was released, so clear the chording key 772 if (mPanelChordingKey != 0) { 773 mPanelChordingKey = 0; 774 775 if (event.isCanceled() || (mDecor != null && mDecor.mActionMode != null)) { 776 return; 777 } 778 779 boolean playSoundEffect = false; 780 final PanelFeatureState st = getPanelState(featureId, true); 781 if (featureId == FEATURE_OPTIONS_PANEL && mActionBar != null && 782 mActionBar.isOverflowReserved()) { 783 if (mActionBar.getVisibility() == View.VISIBLE) { 784 if (!mActionBar.isOverflowMenuShowing()) { 785 if (!isDestroyed() && preparePanel(st, event)) { 786 playSoundEffect = mActionBar.showOverflowMenu(); 787 } 788 } else { 789 playSoundEffect = mActionBar.hideOverflowMenu(); 790 } 791 } 792 } else { 793 if (st.isOpen || st.isHandled) { 794 795 // Play the sound effect if the user closed an open menu (and not if 796 // they just released a menu shortcut) 797 playSoundEffect = st.isOpen; 798 799 // Close menu 800 closePanel(st, true); 801 802 } else if (st.isPrepared) { 803 804 // Write 'menu opened' to event log 805 EventLog.writeEvent(50001, 0); 806 807 // Show menu 808 openPanel(st, event); 809 810 playSoundEffect = true; 811 } 812 } 813 814 if (playSoundEffect) { 815 AudioManager audioManager = (AudioManager) getContext().getSystemService( 816 Context.AUDIO_SERVICE); 817 if (audioManager != null) { 818 audioManager.playSoundEffect(AudioManager.FX_KEY_CLICK); 819 } else { 820 Log.w(TAG, "Couldn't get audio manager"); 821 } 822 } 823 } 824 } 825 826 @Override 827 public final void closeAllPanels() { 828 final ViewManager wm = getWindowManager(); 829 if (wm == null) { 830 return; 831 } 832 833 final PanelFeatureState[] panels = mPanels; 834 final int N = panels != null ? panels.length : 0; 835 for (int i = 0; i < N; i++) { 836 final PanelFeatureState panel = panels[i]; 837 if (panel != null) { 838 closePanel(panel, true); 839 } 840 } 841 842 closeContextMenu(); 843 } 844 845 /** 846 * Closes the context menu. This notifies the menu logic of the close, along 847 * with dismissing it from the UI. 848 */ 849 private synchronized void closeContextMenu() { 850 if (mContextMenu != null) { 851 mContextMenu.close(); 852 dismissContextMenu(); 853 } 854 } 855 856 /** 857 * Dismisses just the context menu UI. To close the context menu, use 858 * {@link #closeContextMenu()}. 859 */ 860 private synchronized void dismissContextMenu() { 861 mContextMenu = null; 862 863 if (mContextMenuHelper != null) { 864 mContextMenuHelper.dismiss(); 865 mContextMenuHelper = null; 866 } 867 } 868 869 @Override 870 public boolean performPanelShortcut(int featureId, int keyCode, KeyEvent event, int flags) { 871 return performPanelShortcut(getPanelState(featureId, true), keyCode, event, flags); 872 } 873 874 private boolean performPanelShortcut(PanelFeatureState st, int keyCode, KeyEvent event, 875 int flags) { 876 if (event.isSystem() || (st == null)) { 877 return false; 878 } 879 880 boolean handled = false; 881 882 // Only try to perform menu shortcuts if preparePanel returned true (possible false 883 // return value from application not wanting to show the menu). 884 if ((st.isPrepared || preparePanel(st, event)) && st.menu != null) { 885 // The menu is prepared now, perform the shortcut on it 886 handled = st.menu.performShortcut(keyCode, event, flags); 887 } 888 889 if (handled) { 890 // Mark as handled 891 st.isHandled = true; 892 893 // Only close down the menu if we don't have an action bar keeping it open. 894 if ((flags & Menu.FLAG_PERFORM_NO_CLOSE) == 0 && mActionBar == null) { 895 closePanel(st, true); 896 } 897 } 898 899 return handled; 900 } 901 902 @Override 903 public boolean performPanelIdentifierAction(int featureId, int id, int flags) { 904 905 PanelFeatureState st = getPanelState(featureId, true); 906 if (!preparePanel(st, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MENU))) { 907 return false; 908 } 909 if (st.menu == null) { 910 return false; 911 } 912 913 boolean res = st.menu.performIdentifierAction(id, flags); 914 915 // Only close down the menu if we don't have an action bar keeping it open. 916 if (mActionBar == null) { 917 closePanel(st, true); 918 } 919 920 return res; 921 } 922 923 public PanelFeatureState findMenuPanel(Menu menu) { 924 final PanelFeatureState[] panels = mPanels; 925 final int N = panels != null ? panels.length : 0; 926 for (int i = 0; i < N; i++) { 927 final PanelFeatureState panel = panels[i]; 928 if (panel != null && panel.menu == menu) { 929 return panel; 930 } 931 } 932 return null; 933 } 934 935 public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) { 936 final Callback cb = getCallback(); 937 if (cb != null && !isDestroyed()) { 938 final PanelFeatureState panel = findMenuPanel(menu.getRootMenu()); 939 if (panel != null) { 940 return cb.onMenuItemSelected(panel.featureId, item); 941 } 942 } 943 return false; 944 } 945 946 public void onMenuModeChange(MenuBuilder menu) { 947 reopenMenu(true); 948 } 949 950 private void reopenMenu(boolean toggleMenuMode) { 951 if (mActionBar != null && mActionBar.isOverflowReserved()) { 952 final Callback cb = getCallback(); 953 if (!mActionBar.isOverflowMenuShowing() || !toggleMenuMode) { 954 if (cb != null && !isDestroyed() && mActionBar.getVisibility() == View.VISIBLE) { 955 final PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, true); 956 if (cb.onPreparePanel(FEATURE_OPTIONS_PANEL, st.createdPanelView, st.menu)) { 957 cb.onMenuOpened(FEATURE_ACTION_BAR, st.menu); 958 mActionBar.showOverflowMenu(); 959 } 960 } 961 } else { 962 mActionBar.hideOverflowMenu(); 963 if (cb != null && !isDestroyed()) { 964 final PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, true); 965 cb.onPanelClosed(FEATURE_ACTION_BAR, st.menu); 966 } 967 } 968 return; 969 } 970 971 PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, true); 972 973 // Save the future expanded mode state since closePanel will reset it 974 boolean newExpandedMode = toggleMenuMode ? !st.isInExpandedMode : st.isInExpandedMode; 975 976 st.refreshDecorView = true; 977 closePanel(st, false); 978 979 // Set the expanded mode state 980 st.isInExpandedMode = newExpandedMode; 981 982 openPanel(st, null); 983 } 984 985 /** 986 * Initializes the menu associated with the given panel feature state. You 987 * must at the very least set PanelFeatureState.menu to the Menu to be 988 * associated with the given panel state. The default implementation creates 989 * a new menu for the panel state. 990 * 991 * @param st The panel whose menu is being initialized. 992 * @return Whether the initialization was successful. 993 */ 994 protected boolean initializePanelMenu(final PanelFeatureState st) { 995 Context context = getContext(); 996 997 // If we have an action bar, initialize the menu with a context themed for it. 998 if ((st.featureId == FEATURE_OPTIONS_PANEL || st.featureId == FEATURE_ACTION_BAR) && 999 mActionBar != null) { 1000 TypedValue outValue = new TypedValue(); 1001 Resources.Theme currentTheme = context.getTheme(); 1002 currentTheme.resolveAttribute(com.android.internal.R.attr.actionBarWidgetTheme, 1003 outValue, true); 1004 final int targetThemeRes = outValue.resourceId; 1005 1006 if (targetThemeRes != 0 && context.getThemeResId() != targetThemeRes) { 1007 context = new ContextThemeWrapper(context, targetThemeRes); 1008 } 1009 } 1010 1011 final MenuBuilder menu = new MenuBuilder(context); 1012 1013 menu.setCallback(this); 1014 st.setMenu(menu); 1015 1016 return true; 1017 } 1018 1019 /** 1020 * Perform initial setup of a panel. This should at the very least set the 1021 * style information in the PanelFeatureState and must set 1022 * PanelFeatureState.decor to the panel's window decor view. 1023 * 1024 * @param st The panel being initialized. 1025 */ 1026 protected boolean initializePanelDecor(PanelFeatureState st) { 1027 st.decorView = new DecorView(getContext(), st.featureId); 1028 st.gravity = Gravity.CENTER | Gravity.BOTTOM; 1029 st.setStyle(getContext()); 1030 1031 return true; 1032 } 1033 1034 /** 1035 * Determine the gravity value for the options panel. This can 1036 * differ in compact mode. 1037 * 1038 * @return gravity value to use for the panel window 1039 */ 1040 private int getOptionsPanelGravity() { 1041 try { 1042 return WindowManagerHolder.sWindowManager.getPreferredOptionsPanelGravity(); 1043 } catch (RemoteException ex) { 1044 Log.e(TAG, "Couldn't getOptionsPanelGravity; using default", ex); 1045 return Gravity.CENTER | Gravity.BOTTOM; 1046 } 1047 } 1048 1049 void onOptionsPanelRotationChanged() { 1050 final PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false); 1051 if (st == null) return; 1052 1053 final WindowManager.LayoutParams lp = st.decorView != null ? 1054 (WindowManager.LayoutParams) st.decorView.getLayoutParams() : null; 1055 if (lp != null) { 1056 lp.gravity = getOptionsPanelGravity(); 1057 final ViewManager wm = getWindowManager(); 1058 if (wm != null) { 1059 wm.updateViewLayout(st.decorView, lp); 1060 } 1061 } 1062 } 1063 1064 /** 1065 * Initializes the panel associated with the panel feature state. You must 1066 * at the very least set PanelFeatureState.panel to the View implementing 1067 * its contents. The default implementation gets the panel from the menu. 1068 * 1069 * @param st The panel state being initialized. 1070 * @return Whether the initialization was successful. 1071 */ 1072 protected boolean initializePanelContent(PanelFeatureState st) { 1073 if (st.createdPanelView != null) { 1074 st.shownPanelView = st.createdPanelView; 1075 return true; 1076 } 1077 1078 if (st.menu == null) { 1079 return false; 1080 } 1081 1082 if (mPanelMenuPresenterCallback == null) { 1083 mPanelMenuPresenterCallback = new PanelMenuPresenterCallback(); 1084 } 1085 1086 MenuView menuView = st.isInListMode() 1087 ? st.getListMenuView(mPanelMenuPresenterCallback) 1088 : st.getIconMenuView(mPanelMenuPresenterCallback); 1089 1090 st.shownPanelView = (View) menuView; 1091 1092 if (st.shownPanelView != null) { 1093 // Use the menu View's default animations if it has any 1094 final int defaultAnimations = menuView.getWindowAnimations(); 1095 if (defaultAnimations != 0) { 1096 st.windowAnimations = defaultAnimations; 1097 } 1098 return true; 1099 } else { 1100 return false; 1101 } 1102 } 1103 1104 @Override 1105 public boolean performContextMenuIdentifierAction(int id, int flags) { 1106 return (mContextMenu != null) ? mContextMenu.performIdentifierAction(id, flags) : false; 1107 } 1108 1109 @Override 1110 public final void setBackgroundDrawable(Drawable drawable) { 1111 if (drawable != mBackgroundDrawable || mBackgroundResource != 0) { 1112 mBackgroundResource = 0; 1113 mBackgroundDrawable = drawable; 1114 if (mDecor != null) { 1115 mDecor.setWindowBackground(drawable); 1116 } 1117 } 1118 } 1119 1120 @Override 1121 public final void setFeatureDrawableResource(int featureId, int resId) { 1122 if (resId != 0) { 1123 DrawableFeatureState st = getDrawableState(featureId, true); 1124 if (st.resid != resId) { 1125 st.resid = resId; 1126 st.uri = null; 1127 st.local = getContext().getResources().getDrawable(resId); 1128 updateDrawable(featureId, st, false); 1129 } 1130 } else { 1131 setFeatureDrawable(featureId, null); 1132 } 1133 } 1134 1135 @Override 1136 public final void setFeatureDrawableUri(int featureId, Uri uri) { 1137 if (uri != null) { 1138 DrawableFeatureState st = getDrawableState(featureId, true); 1139 if (st.uri == null || !st.uri.equals(uri)) { 1140 st.resid = 0; 1141 st.uri = uri; 1142 st.local = loadImageURI(uri); 1143 updateDrawable(featureId, st, false); 1144 } 1145 } else { 1146 setFeatureDrawable(featureId, null); 1147 } 1148 } 1149 1150 @Override 1151 public final void setFeatureDrawable(int featureId, Drawable drawable) { 1152 DrawableFeatureState st = getDrawableState(featureId, true); 1153 st.resid = 0; 1154 st.uri = null; 1155 if (st.local != drawable) { 1156 st.local = drawable; 1157 updateDrawable(featureId, st, false); 1158 } 1159 } 1160 1161 @Override 1162 public void setFeatureDrawableAlpha(int featureId, int alpha) { 1163 DrawableFeatureState st = getDrawableState(featureId, true); 1164 if (st.alpha != alpha) { 1165 st.alpha = alpha; 1166 updateDrawable(featureId, st, false); 1167 } 1168 } 1169 1170 protected final void setFeatureDefaultDrawable(int featureId, Drawable drawable) { 1171 DrawableFeatureState st = getDrawableState(featureId, true); 1172 if (st.def != drawable) { 1173 st.def = drawable; 1174 updateDrawable(featureId, st, false); 1175 } 1176 } 1177 1178 @Override 1179 public final void setFeatureInt(int featureId, int value) { 1180 // XXX Should do more management (as with drawable features) to 1181 // deal with interactions between multiple window policies. 1182 updateInt(featureId, value, false); 1183 } 1184 1185 /** 1186 * Update the state of a drawable feature. This should be called, for every 1187 * drawable feature supported, as part of onActive(), to make sure that the 1188 * contents of a containing window is properly updated. 1189 * 1190 * @see #onActive 1191 * @param featureId The desired drawable feature to change. 1192 * @param fromActive Always true when called from onActive(). 1193 */ 1194 protected final void updateDrawable(int featureId, boolean fromActive) { 1195 final DrawableFeatureState st = getDrawableState(featureId, false); 1196 if (st != null) { 1197 updateDrawable(featureId, st, fromActive); 1198 } 1199 } 1200 1201 /** 1202 * Called when a Drawable feature changes, for the window to update its 1203 * graphics. 1204 * 1205 * @param featureId The feature being changed. 1206 * @param drawable The new Drawable to show, or null if none. 1207 * @param alpha The new alpha blending of the Drawable. 1208 */ 1209 protected void onDrawableChanged(int featureId, Drawable drawable, int alpha) { 1210 ImageView view; 1211 if (featureId == FEATURE_LEFT_ICON) { 1212 view = getLeftIconView(); 1213 } else if (featureId == FEATURE_RIGHT_ICON) { 1214 view = getRightIconView(); 1215 } else { 1216 return; 1217 } 1218 1219 if (drawable != null) { 1220 drawable.setAlpha(alpha); 1221 view.setImageDrawable(drawable); 1222 view.setVisibility(View.VISIBLE); 1223 } else { 1224 view.setVisibility(View.GONE); 1225 } 1226 } 1227 1228 /** 1229 * Called when an int feature changes, for the window to update its 1230 * graphics. 1231 * 1232 * @param featureId The feature being changed. 1233 * @param value The new integer value. 1234 */ 1235 protected void onIntChanged(int featureId, int value) { 1236 if (featureId == FEATURE_PROGRESS || featureId == FEATURE_INDETERMINATE_PROGRESS) { 1237 updateProgressBars(value); 1238 } else if (featureId == FEATURE_CUSTOM_TITLE) { 1239 FrameLayout titleContainer = (FrameLayout) findViewById(com.android.internal.R.id.title_container); 1240 if (titleContainer != null) { 1241 mLayoutInflater.inflate(value, titleContainer); 1242 } 1243 } 1244 } 1245 1246 /** 1247 * Updates the progress bars that are shown in the title bar. 1248 * 1249 * @param value Can be one of {@link Window#PROGRESS_VISIBILITY_ON}, 1250 * {@link Window#PROGRESS_VISIBILITY_OFF}, 1251 * {@link Window#PROGRESS_INDETERMINATE_ON}, 1252 * {@link Window#PROGRESS_INDETERMINATE_OFF}, or a value 1253 * starting at {@link Window#PROGRESS_START} through 1254 * {@link Window#PROGRESS_END} for setting the default 1255 * progress (if {@link Window#PROGRESS_END} is given, 1256 * the progress bar widgets in the title will be hidden after an 1257 * animation), a value between 1258 * {@link Window#PROGRESS_SECONDARY_START} - 1259 * {@link Window#PROGRESS_SECONDARY_END} for the 1260 * secondary progress (if 1261 * {@link Window#PROGRESS_SECONDARY_END} is given, the 1262 * progress bar widgets will still be shown with the secondary 1263 * progress bar will be completely filled in.) 1264 */ 1265 private void updateProgressBars(int value) { 1266 ProgressBar circularProgressBar = getCircularProgressBar(true); 1267 ProgressBar horizontalProgressBar = getHorizontalProgressBar(true); 1268 1269 final int features = getLocalFeatures(); 1270 if (value == PROGRESS_VISIBILITY_ON) { 1271 if ((features & (1 << FEATURE_PROGRESS)) != 0) { 1272 int level = horizontalProgressBar.getProgress(); 1273 int visibility = (horizontalProgressBar.isIndeterminate() || level < 10000) ? 1274 View.VISIBLE : View.INVISIBLE; 1275 horizontalProgressBar.setVisibility(visibility); 1276 } 1277 if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) { 1278 circularProgressBar.setVisibility(View.VISIBLE); 1279 } 1280 } else if (value == PROGRESS_VISIBILITY_OFF) { 1281 if ((features & (1 << FEATURE_PROGRESS)) != 0) { 1282 horizontalProgressBar.setVisibility(View.GONE); 1283 } 1284 if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) { 1285 circularProgressBar.setVisibility(View.GONE); 1286 } 1287 } else if (value == PROGRESS_INDETERMINATE_ON) { 1288 horizontalProgressBar.setIndeterminate(true); 1289 } else if (value == PROGRESS_INDETERMINATE_OFF) { 1290 horizontalProgressBar.setIndeterminate(false); 1291 } else if (PROGRESS_START <= value && value <= PROGRESS_END) { 1292 // We want to set the progress value before testing for visibility 1293 // so that when the progress bar becomes visible again, it has the 1294 // correct level. 1295 horizontalProgressBar.setProgress(value - PROGRESS_START); 1296 1297 if (value < PROGRESS_END) { 1298 showProgressBars(horizontalProgressBar, circularProgressBar); 1299 } else { 1300 hideProgressBars(horizontalProgressBar, circularProgressBar); 1301 } 1302 } else if (PROGRESS_SECONDARY_START <= value && value <= PROGRESS_SECONDARY_END) { 1303 horizontalProgressBar.setSecondaryProgress(value - PROGRESS_SECONDARY_START); 1304 1305 showProgressBars(horizontalProgressBar, circularProgressBar); 1306 } 1307 1308 } 1309 1310 private void showProgressBars(ProgressBar horizontalProgressBar, ProgressBar spinnyProgressBar) { 1311 final int features = getLocalFeatures(); 1312 if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0 && 1313 spinnyProgressBar.getVisibility() == View.INVISIBLE) { 1314 spinnyProgressBar.setVisibility(View.VISIBLE); 1315 } 1316 // Only show the progress bars if the primary progress is not complete 1317 if ((features & (1 << FEATURE_PROGRESS)) != 0 && 1318 horizontalProgressBar.getProgress() < 10000) { 1319 horizontalProgressBar.setVisibility(View.VISIBLE); 1320 } 1321 } 1322 1323 private void hideProgressBars(ProgressBar horizontalProgressBar, ProgressBar spinnyProgressBar) { 1324 final int features = getLocalFeatures(); 1325 Animation anim = AnimationUtils.loadAnimation(getContext(), com.android.internal.R.anim.fade_out); 1326 anim.setDuration(1000); 1327 if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0 && 1328 spinnyProgressBar.getVisibility() == View.VISIBLE) { 1329 spinnyProgressBar.startAnimation(anim); 1330 spinnyProgressBar.setVisibility(View.INVISIBLE); 1331 } 1332 if ((features & (1 << FEATURE_PROGRESS)) != 0 && 1333 horizontalProgressBar.getVisibility() == View.VISIBLE) { 1334 horizontalProgressBar.startAnimation(anim); 1335 horizontalProgressBar.setVisibility(View.INVISIBLE); 1336 } 1337 } 1338 1339 /** 1340 * Request that key events come to this activity. Use this if your activity 1341 * has no views with focus, but the activity still wants a chance to process 1342 * key events. 1343 */ 1344 @Override 1345 public void takeKeyEvents(boolean get) { 1346 mDecor.setFocusable(get); 1347 } 1348 1349 @Override 1350 public boolean superDispatchKeyEvent(KeyEvent event) { 1351 return mDecor.superDispatchKeyEvent(event); 1352 } 1353 1354 @Override 1355 public boolean superDispatchKeyShortcutEvent(KeyEvent event) { 1356 return mDecor.superDispatchKeyShortcutEvent(event); 1357 } 1358 1359 @Override 1360 public boolean superDispatchTouchEvent(MotionEvent event) { 1361 return mDecor.superDispatchTouchEvent(event); 1362 } 1363 1364 @Override 1365 public boolean superDispatchTrackballEvent(MotionEvent event) { 1366 return mDecor.superDispatchTrackballEvent(event); 1367 } 1368 1369 @Override 1370 public boolean superDispatchGenericMotionEvent(MotionEvent event) { 1371 return mDecor.superDispatchGenericMotionEvent(event); 1372 } 1373 1374 /** 1375 * A key was pressed down and not handled by anything else in the window. 1376 * 1377 * @see #onKeyUp 1378 * @see android.view.KeyEvent 1379 */ 1380 protected boolean onKeyDown(int featureId, int keyCode, KeyEvent event) { 1381 /* **************************************************************************** 1382 * HOW TO DECIDE WHERE YOUR KEY HANDLING GOES. 1383 * 1384 * If your key handling must happen before the app gets a crack at the event, 1385 * it goes in PhoneWindowManager. 1386 * 1387 * If your key handling should happen in all windows, and does not depend on 1388 * the state of the current application, other than that the current 1389 * application can override the behavior by handling the event itself, it 1390 * should go in PhoneFallbackEventHandler. 1391 * 1392 * Only if your handling depends on the window, and the fact that it has 1393 * a DecorView, should it go here. 1394 * ****************************************************************************/ 1395 1396 final KeyEvent.DispatcherState dispatcher = 1397 mDecor != null ? mDecor.getKeyDispatcherState() : null; 1398 //Log.i(TAG, "Key down: repeat=" + event.getRepeatCount() 1399 // + " flags=0x" + Integer.toHexString(event.getFlags())); 1400 1401 switch (keyCode) { 1402 case KeyEvent.KEYCODE_VOLUME_UP: 1403 case KeyEvent.KEYCODE_VOLUME_DOWN: 1404 case KeyEvent.KEYCODE_VOLUME_MUTE: { 1405 // Similar code is in PhoneFallbackEventHandler in case the window 1406 // doesn't have one of these. In this case, we execute it here and 1407 // eat the event instead, because we have mVolumeControlStreamType 1408 // and they don't. 1409 getAudioManager().handleKeyDown(keyCode, mVolumeControlStreamType); 1410 return true; 1411 } 1412 1413 case KeyEvent.KEYCODE_MENU: { 1414 onKeyDownPanel((featureId < 0) ? FEATURE_OPTIONS_PANEL : featureId, event); 1415 return true; 1416 } 1417 1418 case KeyEvent.KEYCODE_BACK: { 1419 if (event.getRepeatCount() > 0) break; 1420 if (featureId < 0) break; 1421 // Currently don't do anything with long press. 1422 if (dispatcher != null) { 1423 dispatcher.startTracking(event, this); 1424 } 1425 return true; 1426 } 1427 1428 } 1429 1430 return false; 1431 } 1432 1433 private KeyguardManager getKeyguardManager() { 1434 if (mKeyguardManager == null) { 1435 mKeyguardManager = (KeyguardManager) getContext().getSystemService( 1436 Context.KEYGUARD_SERVICE); 1437 } 1438 return mKeyguardManager; 1439 } 1440 1441 AudioManager getAudioManager() { 1442 if (mAudioManager == null) { 1443 mAudioManager = (AudioManager)getContext().getSystemService(Context.AUDIO_SERVICE); 1444 } 1445 return mAudioManager; 1446 } 1447 1448 /** 1449 * A key was released and not handled by anything else in the window. 1450 * 1451 * @see #onKeyDown 1452 * @see android.view.KeyEvent 1453 */ 1454 protected boolean onKeyUp(int featureId, int keyCode, KeyEvent event) { 1455 final KeyEvent.DispatcherState dispatcher = 1456 mDecor != null ? mDecor.getKeyDispatcherState() : null; 1457 if (dispatcher != null) { 1458 dispatcher.handleUpEvent(event); 1459 } 1460 //Log.i(TAG, "Key up: repeat=" + event.getRepeatCount() 1461 // + " flags=0x" + Integer.toHexString(event.getFlags())); 1462 1463 switch (keyCode) { 1464 case KeyEvent.KEYCODE_VOLUME_UP: 1465 case KeyEvent.KEYCODE_VOLUME_DOWN: 1466 case KeyEvent.KEYCODE_VOLUME_MUTE: { 1467 // Similar code is in PhoneFallbackEventHandler in case the window 1468 // doesn't have one of these. In this case, we execute it here and 1469 // eat the event instead, because we have mVolumeControlStreamType 1470 // and they don't. 1471 getAudioManager().handleKeyUp(keyCode, mVolumeControlStreamType); 1472 return true; 1473 } 1474 1475 case KeyEvent.KEYCODE_MENU: { 1476 onKeyUpPanel(featureId < 0 ? FEATURE_OPTIONS_PANEL : featureId, 1477 event); 1478 return true; 1479 } 1480 1481 case KeyEvent.KEYCODE_BACK: { 1482 if (featureId < 0) break; 1483 if (event.isTracking() && !event.isCanceled()) { 1484 if (featureId == FEATURE_OPTIONS_PANEL) { 1485 PanelFeatureState st = getPanelState(featureId, false); 1486 if (st != null && st.isInExpandedMode) { 1487 // If the user is in an expanded menu and hits back, it 1488 // should go back to the icon menu 1489 reopenMenu(true); 1490 return true; 1491 } 1492 } 1493 closePanel(featureId); 1494 return true; 1495 } 1496 break; 1497 } 1498 1499 case KeyEvent.KEYCODE_SEARCH: { 1500 /* 1501 * Do this in onKeyUp since the Search key is also used for 1502 * chording quick launch shortcuts. 1503 */ 1504 if (getKeyguardManager().inKeyguardRestrictedInputMode()) { 1505 break; 1506 } 1507 if (event.isTracking() && !event.isCanceled()) { 1508 launchDefaultSearch(); 1509 } 1510 return true; 1511 } 1512 } 1513 1514 return false; 1515 } 1516 1517 @Override 1518 protected void onActive() { 1519 } 1520 1521 @Override 1522 public final View getDecorView() { 1523 if (mDecor == null) { 1524 installDecor(); 1525 } 1526 return mDecor; 1527 } 1528 1529 @Override 1530 public final View peekDecorView() { 1531 return mDecor; 1532 } 1533 1534 static private final String FOCUSED_ID_TAG = "android:focusedViewId"; 1535 static private final String VIEWS_TAG = "android:views"; 1536 static private final String PANELS_TAG = "android:Panels"; 1537 static private final String ACTION_BAR_TAG = "android:ActionBar"; 1538 1539 /** {@inheritDoc} */ 1540 @Override 1541 public Bundle saveHierarchyState() { 1542 Bundle outState = new Bundle(); 1543 if (mContentParent == null) { 1544 return outState; 1545 } 1546 1547 SparseArray<Parcelable> states = new SparseArray<Parcelable>(); 1548 mContentParent.saveHierarchyState(states); 1549 outState.putSparseParcelableArray(VIEWS_TAG, states); 1550 1551 // save the focused view id 1552 View focusedView = mContentParent.findFocus(); 1553 if (focusedView != null) { 1554 if (focusedView.getId() != View.NO_ID) { 1555 outState.putInt(FOCUSED_ID_TAG, focusedView.getId()); 1556 } else { 1557 if (false) { 1558 Log.d(TAG, "couldn't save which view has focus because the focused view " 1559 + focusedView + " has no id."); 1560 } 1561 } 1562 } 1563 1564 // save the panels 1565 SparseArray<Parcelable> panelStates = new SparseArray<Parcelable>(); 1566 savePanelState(panelStates); 1567 if (panelStates.size() > 0) { 1568 outState.putSparseParcelableArray(PANELS_TAG, panelStates); 1569 } 1570 1571 if (mActionBar != null) { 1572 SparseArray<Parcelable> actionBarStates = new SparseArray<Parcelable>(); 1573 mActionBar.saveHierarchyState(actionBarStates); 1574 outState.putSparseParcelableArray(ACTION_BAR_TAG, actionBarStates); 1575 } 1576 1577 return outState; 1578 } 1579 1580 /** {@inheritDoc} */ 1581 @Override 1582 public void restoreHierarchyState(Bundle savedInstanceState) { 1583 if (mContentParent == null) { 1584 return; 1585 } 1586 1587 SparseArray<Parcelable> savedStates 1588 = savedInstanceState.getSparseParcelableArray(VIEWS_TAG); 1589 if (savedStates != null) { 1590 mContentParent.restoreHierarchyState(savedStates); 1591 } 1592 1593 // restore the focused view 1594 int focusedViewId = savedInstanceState.getInt(FOCUSED_ID_TAG, View.NO_ID); 1595 if (focusedViewId != View.NO_ID) { 1596 View needsFocus = mContentParent.findViewById(focusedViewId); 1597 if (needsFocus != null) { 1598 needsFocus.requestFocus(); 1599 } else { 1600 Log.w(TAG, 1601 "Previously focused view reported id " + focusedViewId 1602 + " during save, but can't be found during restore."); 1603 } 1604 } 1605 1606 // restore the panels 1607 SparseArray<Parcelable> panelStates = savedInstanceState.getSparseParcelableArray(PANELS_TAG); 1608 if (panelStates != null) { 1609 restorePanelState(panelStates); 1610 } 1611 1612 if (mActionBar != null) { 1613 SparseArray<Parcelable> actionBarStates = 1614 savedInstanceState.getSparseParcelableArray(ACTION_BAR_TAG); 1615 mActionBar.restoreHierarchyState(actionBarStates); 1616 } 1617 } 1618 1619 /** 1620 * Invoked when the panels should freeze their state. 1621 * 1622 * @param icicles Save state into this. This is usually indexed by the 1623 * featureId. This will be given to {@link #restorePanelState} in the 1624 * future. 1625 */ 1626 private void savePanelState(SparseArray<Parcelable> icicles) { 1627 PanelFeatureState[] panels = mPanels; 1628 if (panels == null) { 1629 return; 1630 } 1631 1632 for (int curFeatureId = panels.length - 1; curFeatureId >= 0; curFeatureId--) { 1633 if (panels[curFeatureId] != null) { 1634 icicles.put(curFeatureId, panels[curFeatureId].onSaveInstanceState()); 1635 } 1636 } 1637 } 1638 1639 /** 1640 * Invoked when the panels should thaw their state from a previously frozen state. 1641 * 1642 * @param icicles The state saved by {@link #savePanelState} that needs to be thawed. 1643 */ 1644 private void restorePanelState(SparseArray<Parcelable> icicles) { 1645 PanelFeatureState st; 1646 for (int curFeatureId = icicles.size() - 1; curFeatureId >= 0; curFeatureId--) { 1647 st = getPanelState(curFeatureId, false /* required */); 1648 if (st == null) { 1649 // The panel must not have been required, and is currently not around, skip it 1650 continue; 1651 } 1652 1653 st.onRestoreInstanceState(icicles.get(curFeatureId)); 1654 invalidatePanelMenu(curFeatureId); 1655 } 1656 1657 /* 1658 * Implementation note: call openPanelsAfterRestore later to actually open the 1659 * restored panels. 1660 */ 1661 } 1662 1663 /** 1664 * Opens the panels that have had their state restored. This should be 1665 * called sometime after {@link #restorePanelState} when it is safe to add 1666 * to the window manager. 1667 */ 1668 private void openPanelsAfterRestore() { 1669 PanelFeatureState[] panels = mPanels; 1670 1671 if (panels == null) { 1672 return; 1673 } 1674 1675 PanelFeatureState st; 1676 for (int i = panels.length - 1; i >= 0; i--) { 1677 st = panels[i]; 1678 // We restore the panel if it was last open; we skip it if it 1679 // now is open, to avoid a race condition if the user immediately 1680 // opens it when we are resuming. 1681 if (st != null) { 1682 st.applyFrozenState(); 1683 if (!st.isOpen && st.wasLastOpen) { 1684 st.isInExpandedMode = st.wasLastExpanded; 1685 openPanel(st, null); 1686 } 1687 } 1688 } 1689 } 1690 1691 private class PanelMenuPresenterCallback implements MenuPresenter.Callback { 1692 @Override 1693 public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { 1694 final Menu parentMenu = menu.getRootMenu(); 1695 final boolean isSubMenu = parentMenu != menu; 1696 final PanelFeatureState panel = findMenuPanel(isSubMenu ? parentMenu : menu); 1697 if (panel != null) { 1698 if (isSubMenu) { 1699 callOnPanelClosed(panel.featureId, panel, parentMenu); 1700 closePanel(panel, true); 1701 } else { 1702 // Close the panel and only do the callback if the menu is being 1703 // closed completely, not if opening a sub menu 1704 closePanel(panel, allMenusAreClosing); 1705 } 1706 } 1707 } 1708 1709 @Override 1710 public boolean onOpenSubMenu(MenuBuilder subMenu) { 1711 if (subMenu == null && hasFeature(FEATURE_ACTION_BAR)) { 1712 Callback cb = getCallback(); 1713 if (cb != null && !isDestroyed()) { 1714 cb.onMenuOpened(FEATURE_ACTION_BAR, subMenu); 1715 } 1716 } 1717 1718 return true; 1719 } 1720 } 1721 1722 private final class ActionMenuPresenterCallback implements MenuPresenter.Callback { 1723 @Override 1724 public boolean onOpenSubMenu(MenuBuilder subMenu) { 1725 Callback cb = getCallback(); 1726 if (cb != null) { 1727 cb.onMenuOpened(FEATURE_ACTION_BAR, subMenu); 1728 return true; 1729 } 1730 return false; 1731 } 1732 1733 @Override 1734 public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { 1735 checkCloseActionMenu(menu); 1736 } 1737 } 1738 1739 private final class DecorView extends FrameLayout implements RootViewSurfaceTaker { 1740 /* package */int mDefaultOpacity = PixelFormat.OPAQUE; 1741 1742 /** The feature ID of the panel, or -1 if this is the application's DecorView */ 1743 private final int mFeatureId; 1744 1745 private final Rect mDrawingBounds = new Rect(); 1746 1747 private final Rect mBackgroundPadding = new Rect(); 1748 1749 private final Rect mFramePadding = new Rect(); 1750 1751 private final Rect mFrameOffsets = new Rect(); 1752 1753 private boolean mChanging; 1754 1755 private Drawable mMenuBackground; 1756 private boolean mWatchingForMenu; 1757 private int mDownY; 1758 1759 private ActionMode mActionMode; 1760 private ActionBarContextView mActionModeView; 1761 private PopupWindow mActionModePopup; 1762 private Runnable mShowActionModePopup; 1763 1764 public DecorView(Context context, int featureId) { 1765 super(context); 1766 mFeatureId = featureId; 1767 } 1768 1769 @Override 1770 public boolean dispatchKeyEvent(KeyEvent event) { 1771 final int keyCode = event.getKeyCode(); 1772 final int action = event.getAction(); 1773 final boolean isDown = action == KeyEvent.ACTION_DOWN; 1774 1775 if (isDown && (event.getRepeatCount() == 0)) { 1776 // First handle chording of panel key: if a panel key is held 1777 // but not released, try to execute a shortcut in it. 1778 if ((mPanelChordingKey > 0) && (mPanelChordingKey != keyCode)) { 1779 boolean handled = dispatchKeyShortcutEvent(event); 1780 if (handled) { 1781 return true; 1782 } 1783 } 1784 1785 // If a panel is open, perform a shortcut on it without the 1786 // chorded panel key 1787 if ((mPreparedPanel != null) && mPreparedPanel.isOpen) { 1788 if (performPanelShortcut(mPreparedPanel, keyCode, event, 0)) { 1789 return true; 1790 } 1791 } 1792 } 1793 1794 if (!isDestroyed()) { 1795 final Callback cb = getCallback(); 1796 final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event) 1797 : super.dispatchKeyEvent(event); 1798 if (handled) { 1799 return true; 1800 } 1801 } 1802 1803 return isDown ? PhoneWindow.this.onKeyDown(mFeatureId, event.getKeyCode(), event) 1804 : PhoneWindow.this.onKeyUp(mFeatureId, event.getKeyCode(), event); 1805 } 1806 1807 @Override 1808 public boolean dispatchKeyShortcutEvent(KeyEvent ev) { 1809 // Perform the shortcut (mPreparedPanel can be null since 1810 // global shortcuts (such as search) don't rely on a 1811 // prepared panel or menu). 1812 boolean handled = performPanelShortcut(mPreparedPanel, ev.getKeyCode(), ev, 1813 Menu.FLAG_PERFORM_NO_CLOSE); 1814 if (handled) { 1815 if (mPreparedPanel != null) { 1816 mPreparedPanel.isHandled = true; 1817 } 1818 return true; 1819 } 1820 1821 // Shortcut not handled by the panel. Dispatch to the view hierarchy. 1822 final Callback cb = getCallback(); 1823 return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchKeyShortcutEvent(ev) 1824 : super.dispatchKeyShortcutEvent(ev); 1825 } 1826 1827 @Override 1828 public boolean dispatchTouchEvent(MotionEvent ev) { 1829 final Callback cb = getCallback(); 1830 return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev) 1831 : super.dispatchTouchEvent(ev); 1832 } 1833 1834 @Override 1835 public boolean dispatchTrackballEvent(MotionEvent ev) { 1836 final Callback cb = getCallback(); 1837 return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchTrackballEvent(ev) 1838 : super.dispatchTrackballEvent(ev); 1839 } 1840 1841 @Override 1842 public boolean dispatchGenericMotionEvent(MotionEvent ev) { 1843 final Callback cb = getCallback(); 1844 return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchGenericMotionEvent(ev) 1845 : super.dispatchGenericMotionEvent(ev); 1846 } 1847 1848 public boolean superDispatchKeyEvent(KeyEvent event) { 1849 if (super.dispatchKeyEvent(event)) { 1850 return true; 1851 } 1852 1853 // Not handled by the view hierarchy, does the action bar want it 1854 // to cancel out of something special? 1855 if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) { 1856 final int action = event.getAction(); 1857 // Back cancels action modes first. 1858 if (mActionMode != null) { 1859 if (action == KeyEvent.ACTION_UP) { 1860 mActionMode.finish(); 1861 } 1862 return true; 1863 } 1864 1865 // Next collapse any expanded action views. 1866 if (mActionBar != null && mActionBar.hasExpandedActionView()) { 1867 if (action == KeyEvent.ACTION_UP) { 1868 mActionBar.collapseActionView(); 1869 } 1870 return true; 1871 } 1872 } 1873 1874 return false; 1875 } 1876 1877 public boolean superDispatchKeyShortcutEvent(KeyEvent event) { 1878 return super.dispatchKeyShortcutEvent(event); 1879 } 1880 1881 public boolean superDispatchTouchEvent(MotionEvent event) { 1882 return super.dispatchTouchEvent(event); 1883 } 1884 1885 public boolean superDispatchTrackballEvent(MotionEvent event) { 1886 return super.dispatchTrackballEvent(event); 1887 } 1888 1889 public boolean superDispatchGenericMotionEvent(MotionEvent event) { 1890 return super.dispatchGenericMotionEvent(event); 1891 } 1892 1893 @Override 1894 public boolean onTouchEvent(MotionEvent event) { 1895 return onInterceptTouchEvent(event); 1896 } 1897 1898 private boolean isOutOfBounds(int x, int y) { 1899 return x < -5 || y < -5 || x > (getWidth() + 5) 1900 || y > (getHeight() + 5); 1901 } 1902 1903 @Override 1904 public boolean onInterceptTouchEvent(MotionEvent event) { 1905 int action = event.getAction(); 1906 if (mFeatureId >= 0) { 1907 if (action == MotionEvent.ACTION_DOWN) { 1908 int x = (int)event.getX(); 1909 int y = (int)event.getY(); 1910 if (isOutOfBounds(x, y)) { 1911 closePanel(mFeatureId); 1912 return true; 1913 } 1914 } 1915 } 1916 1917 if (!SWEEP_OPEN_MENU) { 1918 return false; 1919 } 1920 1921 if (mFeatureId >= 0) { 1922 if (action == MotionEvent.ACTION_DOWN) { 1923 Log.i(TAG, "Watchiing!"); 1924 mWatchingForMenu = true; 1925 mDownY = (int) event.getY(); 1926 return false; 1927 } 1928 1929 if (!mWatchingForMenu) { 1930 return false; 1931 } 1932 1933 int y = (int)event.getY(); 1934 if (action == MotionEvent.ACTION_MOVE) { 1935 if (y > (mDownY+30)) { 1936 Log.i(TAG, "Closing!"); 1937 closePanel(mFeatureId); 1938 mWatchingForMenu = false; 1939 return true; 1940 } 1941 } else if (action == MotionEvent.ACTION_UP) { 1942 mWatchingForMenu = false; 1943 } 1944 1945 return false; 1946 } 1947 1948 //Log.i(TAG, "Intercept: action=" + action + " y=" + event.getY() 1949 // + " (in " + getHeight() + ")"); 1950 1951 if (action == MotionEvent.ACTION_DOWN) { 1952 int y = (int)event.getY(); 1953 if (y >= (getHeight()-5) && !hasChildren()) { 1954 Log.i(TAG, "Watchiing!"); 1955 mWatchingForMenu = true; 1956 } 1957 return false; 1958 } 1959 1960 if (!mWatchingForMenu) { 1961 return false; 1962 } 1963 1964 int y = (int)event.getY(); 1965 if (action == MotionEvent.ACTION_MOVE) { 1966 if (y < (getHeight()-30)) { 1967 Log.i(TAG, "Opening!"); 1968 openPanel(FEATURE_OPTIONS_PANEL, new KeyEvent( 1969 KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MENU)); 1970 mWatchingForMenu = false; 1971 return true; 1972 } 1973 } else if (action == MotionEvent.ACTION_UP) { 1974 mWatchingForMenu = false; 1975 } 1976 1977 return false; 1978 } 1979 1980 @Override 1981 public void sendAccessibilityEvent(int eventType) { 1982 if (!AccessibilityManager.getInstance(mContext).isEnabled()) { 1983 return; 1984 } 1985 1986 // if we are showing a feature that should be announced and one child 1987 // make this child the event source since this is the feature itself 1988 // otherwise the callback will take over and announce its client 1989 if ((mFeatureId == FEATURE_OPTIONS_PANEL || 1990 mFeatureId == FEATURE_CONTEXT_MENU || 1991 mFeatureId == FEATURE_PROGRESS || 1992 mFeatureId == FEATURE_INDETERMINATE_PROGRESS) 1993 && getChildCount() == 1) { 1994 getChildAt(0).sendAccessibilityEvent(eventType); 1995 } else { 1996 super.sendAccessibilityEvent(eventType); 1997 } 1998 } 1999 2000 @Override 2001 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { 2002 final Callback cb = getCallback(); 2003 if (cb != null && !isDestroyed()) { 2004 if (cb.dispatchPopulateAccessibilityEvent(event)) { 2005 return true; 2006 } 2007 } 2008 return super.dispatchPopulateAccessibilityEvent(event); 2009 } 2010 2011 @Override 2012 protected boolean setFrame(int l, int t, int r, int b) { 2013 boolean changed = super.setFrame(l, t, r, b); 2014 if (changed) { 2015 final Rect drawingBounds = mDrawingBounds; 2016 getDrawingRect(drawingBounds); 2017 2018 Drawable fg = getForeground(); 2019 if (fg != null) { 2020 final Rect frameOffsets = mFrameOffsets; 2021 drawingBounds.left += frameOffsets.left; 2022 drawingBounds.top += frameOffsets.top; 2023 drawingBounds.right -= frameOffsets.right; 2024 drawingBounds.bottom -= frameOffsets.bottom; 2025 fg.setBounds(drawingBounds); 2026 final Rect framePadding = mFramePadding; 2027 drawingBounds.left += framePadding.left - frameOffsets.left; 2028 drawingBounds.top += framePadding.top - frameOffsets.top; 2029 drawingBounds.right -= framePadding.right - frameOffsets.right; 2030 drawingBounds.bottom -= framePadding.bottom - frameOffsets.bottom; 2031 } 2032 2033 Drawable bg = getBackground(); 2034 if (bg != null) { 2035 bg.setBounds(drawingBounds); 2036 } 2037 2038 if (SWEEP_OPEN_MENU) { 2039 if (mMenuBackground == null && mFeatureId < 0 2040 && getAttributes().height 2041 == WindowManager.LayoutParams.MATCH_PARENT) { 2042 mMenuBackground = getContext().getResources().getDrawable( 2043 com.android.internal.R.drawable.menu_background); 2044 } 2045 if (mMenuBackground != null) { 2046 mMenuBackground.setBounds(drawingBounds.left, 2047 drawingBounds.bottom-6, drawingBounds.right, 2048 drawingBounds.bottom+20); 2049 } 2050 } 2051 } 2052 return changed; 2053 } 2054 2055 @Override 2056 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 2057 final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics(); 2058 final boolean isPortrait = metrics.widthPixels < metrics.heightPixels; 2059 2060 final int widthMode = getMode(widthMeasureSpec); 2061 2062 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 2063 2064 int width = getMeasuredWidth(); 2065 boolean measure = false; 2066 2067 widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, EXACTLY); 2068 2069 final TypedValue tv = isPortrait ? mMinWidthMinor : mMinWidthMajor; 2070 2071 if (widthMode == AT_MOST && tv.type != TypedValue.TYPE_NULL) { 2072 final int min; 2073 if (tv.type == TypedValue.TYPE_DIMENSION) { 2074 min = (int)tv.getDimension(metrics); 2075 } else if (tv.type == TypedValue.TYPE_FRACTION) { 2076 min = (int)tv.getFraction(metrics.widthPixels, metrics.widthPixels); 2077 } else { 2078 min = 0; 2079 } 2080 2081 if (width < min) { 2082 widthMeasureSpec = MeasureSpec.makeMeasureSpec(min, EXACTLY); 2083 measure = true; 2084 } 2085 } 2086 2087 // TODO: Support height? 2088 2089 if (measure) { 2090 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 2091 } 2092 } 2093 2094 @Override 2095 public void draw(Canvas canvas) { 2096 super.draw(canvas); 2097 2098 if (mMenuBackground != null) { 2099 mMenuBackground.draw(canvas); 2100 } 2101 } 2102 2103 2104 @Override 2105 public boolean showContextMenuForChild(View originalView) { 2106 // Reuse the context menu builder 2107 if (mContextMenu == null) { 2108 mContextMenu = new ContextMenuBuilder(getContext()); 2109 mContextMenu.setCallback(mContextMenuCallback); 2110 } else { 2111 mContextMenu.clearAll(); 2112 } 2113 2114 final MenuDialogHelper helper = mContextMenu.show(originalView, 2115 originalView.getWindowToken()); 2116 if (helper != null) { 2117 helper.setPresenterCallback(mContextMenuCallback); 2118 } 2119 mContextMenuHelper = helper; 2120 return helper != null; 2121 } 2122 2123 @Override 2124 public ActionMode startActionModeForChild(View originalView, 2125 ActionMode.Callback callback) { 2126 // originalView can be used here to be sure that we don't obscure 2127 // relevant content with the context mode UI. 2128 return startActionMode(callback); 2129 } 2130 2131 @Override 2132 public ActionMode startActionMode(ActionMode.Callback callback) { 2133 if (mActionMode != null) { 2134 mActionMode.finish(); 2135 } 2136 2137 final ActionMode.Callback wrappedCallback = new ActionModeCallbackWrapper(callback); 2138 ActionMode mode = null; 2139 if (getCallback() != null && !isDestroyed()) { 2140 try { 2141 mode = getCallback().onWindowStartingActionMode(wrappedCallback); 2142 } catch (AbstractMethodError ame) { 2143 // Older apps might not implement this callback method. 2144 } 2145 } 2146 if (mode != null) { 2147 mActionMode = mode; 2148 } else { 2149 if (mActionModeView == null) { 2150 if (isFloating()) { 2151 mActionModeView = new ActionBarContextView(mContext); 2152 mActionModePopup = new PopupWindow(mContext, null, 2153 com.android.internal.R.attr.actionModePopupWindowStyle); 2154 mActionModePopup.setLayoutInScreenEnabled(true); 2155 mActionModePopup.setLayoutInsetDecor(true); 2156 mActionModePopup.setWindowLayoutType( 2157 WindowManager.LayoutParams.TYPE_APPLICATION); 2158 mActionModePopup.setContentView(mActionModeView); 2159 mActionModePopup.setWidth(MATCH_PARENT); 2160 2161 TypedValue heightValue = new TypedValue(); 2162 mContext.getTheme().resolveAttribute( 2163 com.android.internal.R.attr.actionBarSize, heightValue, true); 2164 final int height = TypedValue.complexToDimensionPixelSize(heightValue.data, 2165 mContext.getResources().getDisplayMetrics()); 2166 mActionModeView.setContentHeight(height); 2167 mActionModePopup.setHeight(WRAP_CONTENT); 2168 mShowActionModePopup = new Runnable() { 2169 public void run() { 2170 mActionModePopup.showAtLocation( 2171 mActionModeView.getApplicationWindowToken(), 2172 Gravity.TOP | Gravity.FILL_HORIZONTAL, 0, 0); 2173 } 2174 }; 2175 } else { 2176 ViewStub stub = (ViewStub) findViewById( 2177 com.android.internal.R.id.action_mode_bar_stub); 2178 if (stub != null) { 2179 mActionModeView = (ActionBarContextView) stub.inflate(); 2180 } 2181 } 2182 } 2183 2184 if (mActionModeView != null) { 2185 mActionModeView.killMode(); 2186 mode = new StandaloneActionMode(getContext(), mActionModeView, wrappedCallback, 2187 mActionModePopup == null); 2188 if (callback.onCreateActionMode(mode, mode.getMenu())) { 2189 mode.invalidate(); 2190 mActionModeView.initForMode(mode); 2191 mActionModeView.setVisibility(View.VISIBLE); 2192 mActionMode = mode; 2193 if (mActionModePopup != null) { 2194 post(mShowActionModePopup); 2195 } 2196 mActionModeView.sendAccessibilityEvent( 2197 AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); 2198 } else { 2199 mActionMode = null; 2200 } 2201 } 2202 } 2203 if (mActionMode != null && getCallback() != null && !isDestroyed()) { 2204 try { 2205 getCallback().onActionModeStarted(mActionMode); 2206 } catch (AbstractMethodError ame) { 2207 // Older apps might not implement this callback method. 2208 } 2209 } 2210 return mActionMode; 2211 } 2212 2213 public void startChanging() { 2214 mChanging = true; 2215 } 2216 2217 public void finishChanging() { 2218 mChanging = false; 2219 drawableChanged(); 2220 } 2221 2222 public void setWindowBackground(Drawable drawable) { 2223 if (getBackground() != drawable) { 2224 setBackgroundDrawable(drawable); 2225 if (drawable != null) { 2226 drawable.getPadding(mBackgroundPadding); 2227 } else { 2228 mBackgroundPadding.setEmpty(); 2229 } 2230 drawableChanged(); 2231 } 2232 } 2233 2234 @Override 2235 public void setBackgroundDrawable(Drawable d) { 2236 super.setBackgroundDrawable(d); 2237 if (getWindowToken() != null) { 2238 updateWindowResizeState(); 2239 } 2240 } 2241 2242 public void setWindowFrame(Drawable drawable) { 2243 if (getForeground() != drawable) { 2244 setForeground(drawable); 2245 if (drawable != null) { 2246 drawable.getPadding(mFramePadding); 2247 } else { 2248 mFramePadding.setEmpty(); 2249 } 2250 drawableChanged(); 2251 } 2252 } 2253 2254 @Override 2255 protected boolean fitSystemWindows(Rect insets) { 2256 mFrameOffsets.set(insets); 2257 if (getForeground() != null) { 2258 drawableChanged(); 2259 } 2260 return super.fitSystemWindows(insets); 2261 } 2262 2263 private void drawableChanged() { 2264 if (mChanging) { 2265 return; 2266 } 2267 2268 setPadding(mFramePadding.left + mBackgroundPadding.left, mFramePadding.top 2269 + mBackgroundPadding.top, mFramePadding.right + mBackgroundPadding.right, 2270 mFramePadding.bottom + mBackgroundPadding.bottom); 2271 requestLayout(); 2272 invalidate(); 2273 2274 int opacity = PixelFormat.OPAQUE; 2275 2276 // Note: if there is no background, we will assume opaque. The 2277 // common case seems to be that an application sets there to be 2278 // no background so it can draw everything itself. For that, 2279 // we would like to assume OPAQUE and let the app force it to 2280 // the slower TRANSLUCENT mode if that is really what it wants. 2281 Drawable bg = getBackground(); 2282 Drawable fg = getForeground(); 2283 if (bg != null) { 2284 if (fg == null) { 2285 opacity = bg.getOpacity(); 2286 } else if (mFramePadding.left <= 0 && mFramePadding.top <= 0 2287 && mFramePadding.right <= 0 && mFramePadding.bottom <= 0) { 2288 // If the frame padding is zero, then we can be opaque 2289 // if either the frame -or- the background is opaque. 2290 int fop = fg.getOpacity(); 2291 int bop = bg.getOpacity(); 2292 if (false) 2293 Log.v(TAG, "Background opacity: " + bop + ", Frame opacity: " + fop); 2294 if (fop == PixelFormat.OPAQUE || bop == PixelFormat.OPAQUE) { 2295 opacity = PixelFormat.OPAQUE; 2296 } else if (fop == PixelFormat.UNKNOWN) { 2297 opacity = bop; 2298 } else if (bop == PixelFormat.UNKNOWN) { 2299 opacity = fop; 2300 } else { 2301 opacity = Drawable.resolveOpacity(fop, bop); 2302 } 2303 } else { 2304 // For now we have to assume translucent if there is a 2305 // frame with padding... there is no way to tell if the 2306 // frame and background together will draw all pixels. 2307 if (false) 2308 Log.v(TAG, "Padding: " + mFramePadding); 2309 opacity = PixelFormat.TRANSLUCENT; 2310 } 2311 } 2312 2313 if (false) 2314 Log.v(TAG, "Background: " + bg + ", Frame: " + fg); 2315 if (false) 2316 Log.v(TAG, "Selected default opacity: " + opacity); 2317 2318 mDefaultOpacity = opacity; 2319 if (mFeatureId < 0) { 2320 setDefaultWindowFormat(opacity); 2321 } 2322 } 2323 2324 @Override 2325 public void onWindowFocusChanged(boolean hasWindowFocus) { 2326 super.onWindowFocusChanged(hasWindowFocus); 2327 2328 // If the user is chording a menu shortcut, release the chord since 2329 // this window lost focus 2330 if (!hasWindowFocus && mPanelChordingKey != 0) { 2331 closePanel(FEATURE_OPTIONS_PANEL); 2332 } 2333 2334 final Callback cb = getCallback(); 2335 if (cb != null && !isDestroyed() && mFeatureId < 0) { 2336 cb.onWindowFocusChanged(hasWindowFocus); 2337 } 2338 } 2339 2340 void updateWindowResizeState() { 2341 Drawable bg = getBackground(); 2342 hackTurnOffWindowResizeAnim(bg == null || bg.getOpacity() 2343 != PixelFormat.OPAQUE); 2344 } 2345 2346 @Override 2347 protected void onAttachedToWindow() { 2348 super.onAttachedToWindow(); 2349 2350 updateWindowResizeState(); 2351 2352 final Callback cb = getCallback(); 2353 if (cb != null && !isDestroyed() && mFeatureId < 0) { 2354 cb.onAttachedToWindow(); 2355 } 2356 2357 if (mFeatureId == -1) { 2358 /* 2359 * The main window has been attached, try to restore any panels 2360 * that may have been open before. This is called in cases where 2361 * an activity is being killed for configuration change and the 2362 * menu was open. When the activity is recreated, the menu 2363 * should be shown again. 2364 */ 2365 openPanelsAfterRestore(); 2366 } 2367 } 2368 2369 @Override 2370 protected void onDetachedFromWindow() { 2371 super.onDetachedFromWindow(); 2372 2373 final Callback cb = getCallback(); 2374 if (cb != null && mFeatureId < 0) { 2375 cb.onDetachedFromWindow(); 2376 } 2377 2378 if (mActionBar != null) { 2379 mActionBar.dismissPopupMenus(); 2380 } 2381 2382 if (mActionModePopup != null) { 2383 removeCallbacks(mShowActionModePopup); 2384 if (mActionModePopup.isShowing()) { 2385 mActionModePopup.dismiss(); 2386 } 2387 mActionModePopup = null; 2388 } 2389 2390 PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false); 2391 if (st != null && st.menu != null && mFeatureId < 0) { 2392 st.menu.close(); 2393 } 2394 } 2395 2396 @Override 2397 public void onCloseSystemDialogs(String reason) { 2398 if (mFeatureId >= 0) { 2399 closeAllPanels(); 2400 } 2401 } 2402 2403 public android.view.SurfaceHolder.Callback2 willYouTakeTheSurface() { 2404 return mFeatureId < 0 ? mTakeSurfaceCallback : null; 2405 } 2406 2407 public InputQueue.Callback willYouTakeTheInputQueue() { 2408 return mFeatureId < 0 ? mTakeInputQueueCallback : null; 2409 } 2410 2411 public void setSurfaceType(int type) { 2412 PhoneWindow.this.setType(type); 2413 } 2414 2415 public void setSurfaceFormat(int format) { 2416 PhoneWindow.this.setFormat(format); 2417 } 2418 2419 public void setSurfaceKeepScreenOn(boolean keepOn) { 2420 if (keepOn) PhoneWindow.this.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 2421 else PhoneWindow.this.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 2422 } 2423 2424 /** 2425 * Clears out internal reference when the action mode is destroyed. 2426 */ 2427 private class ActionModeCallbackWrapper implements ActionMode.Callback { 2428 private ActionMode.Callback mWrapped; 2429 2430 public ActionModeCallbackWrapper(ActionMode.Callback wrapped) { 2431 mWrapped = wrapped; 2432 } 2433 2434 public boolean onCreateActionMode(ActionMode mode, Menu menu) { 2435 return mWrapped.onCreateActionMode(mode, menu); 2436 } 2437 2438 public boolean onPrepareActionMode(ActionMode mode, Menu menu) { 2439 return mWrapped.onPrepareActionMode(mode, menu); 2440 } 2441 2442 public boolean onActionItemClicked(ActionMode mode, MenuItem item) { 2443 return mWrapped.onActionItemClicked(mode, item); 2444 } 2445 2446 public void onDestroyActionMode(ActionMode mode) { 2447 mWrapped.onDestroyActionMode(mode); 2448 if (mActionModePopup != null) { 2449 removeCallbacks(mShowActionModePopup); 2450 mActionModePopup.dismiss(); 2451 } else if (mActionModeView != null) { 2452 mActionModeView.setVisibility(GONE); 2453 } 2454 if (mActionModeView != null) { 2455 mActionModeView.removeAllViews(); 2456 } 2457 if (getCallback() != null && !isDestroyed()) { 2458 try { 2459 getCallback().onActionModeFinished(mActionMode); 2460 } catch (AbstractMethodError ame) { 2461 // Older apps might not implement this callback method. 2462 } 2463 } 2464 mActionMode = null; 2465 } 2466 } 2467 } 2468 2469 protected DecorView generateDecor() { 2470 return new DecorView(getContext(), -1); 2471 } 2472 2473 protected void setFeatureFromAttrs(int featureId, TypedArray attrs, 2474 int drawableAttr, int alphaAttr) { 2475 Drawable d = attrs.getDrawable(drawableAttr); 2476 if (d != null) { 2477 requestFeature(featureId); 2478 setFeatureDefaultDrawable(featureId, d); 2479 } 2480 if ((getFeatures() & (1 << featureId)) != 0) { 2481 int alpha = attrs.getInt(alphaAttr, -1); 2482 if (alpha >= 0) { 2483 setFeatureDrawableAlpha(featureId, alpha); 2484 } 2485 } 2486 } 2487 2488 protected ViewGroup generateLayout(DecorView decor) { 2489 // Apply data from current theme. 2490 2491 TypedArray a = getWindowStyle(); 2492 2493 if (false) { 2494 System.out.println("From style:"); 2495 String s = "Attrs:"; 2496 for (int i = 0; i < com.android.internal.R.styleable.Window.length; i++) { 2497 s = s + " " + Integer.toHexString(com.android.internal.R.styleable.Window[i]) + "=" 2498 + a.getString(i); 2499 } 2500 System.out.println(s); 2501 } 2502 2503 mIsFloating = a.getBoolean(com.android.internal.R.styleable.Window_windowIsFloating, false); 2504 int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR) 2505 & (~getForcedWindowFlags()); 2506 if (mIsFloating) { 2507 setLayout(WRAP_CONTENT, WRAP_CONTENT); 2508 setFlags(0, flagsToUpdate); 2509 } else { 2510 setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate); 2511 } 2512 2513 if (a.getBoolean(com.android.internal.R.styleable.Window_windowNoTitle, false)) { 2514 requestFeature(FEATURE_NO_TITLE); 2515 } else if (a.getBoolean(com.android.internal.R.styleable.Window_windowActionBar, false)) { 2516 // Don't allow an action bar if there is no title. 2517 requestFeature(FEATURE_ACTION_BAR); 2518 } 2519 2520 if (a.getBoolean(com.android.internal.R.styleable.Window_windowActionBarOverlay, false)) { 2521 requestFeature(FEATURE_ACTION_BAR_OVERLAY); 2522 } 2523 2524 if (a.getBoolean(com.android.internal.R.styleable.Window_windowActionModeOverlay, false)) { 2525 requestFeature(FEATURE_ACTION_MODE_OVERLAY); 2526 } 2527 2528 if (a.getBoolean(com.android.internal.R.styleable.Window_windowFullscreen, false)) { 2529 setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN&(~getForcedWindowFlags())); 2530 } 2531 2532 if (a.getBoolean(com.android.internal.R.styleable.Window_windowShowWallpaper, false)) { 2533 setFlags(FLAG_SHOW_WALLPAPER, FLAG_SHOW_WALLPAPER&(~getForcedWindowFlags())); 2534 } 2535 2536 if (a.getBoolean(com.android.internal.R.styleable.Window_windowEnableSplitTouch, 2537 getContext().getApplicationInfo().targetSdkVersion 2538 >= android.os.Build.VERSION_CODES.HONEYCOMB)) { 2539 setFlags(FLAG_SPLIT_TOUCH, FLAG_SPLIT_TOUCH&(~getForcedWindowFlags())); 2540 } 2541 2542 a.getValue(com.android.internal.R.styleable.Window_windowMinWidthMajor, mMinWidthMajor); 2543 a.getValue(com.android.internal.R.styleable.Window_windowMinWidthMinor, mMinWidthMinor); 2544 2545 final Context context = getContext(); 2546 final int targetSdk = context.getApplicationInfo().targetSdkVersion; 2547 final boolean targetPreHoneycomb = targetSdk < android.os.Build.VERSION_CODES.HONEYCOMB; 2548 final boolean targetPreIcs = targetSdk < android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH; 2549 final boolean targetHcNeedsOptions = context.getResources().getBoolean( 2550 com.android.internal.R.bool.target_honeycomb_needs_options_menu); 2551 final boolean noActionBar = !hasFeature(FEATURE_ACTION_BAR) || hasFeature(FEATURE_NO_TITLE); 2552 2553 if (targetPreHoneycomb || (targetPreIcs && targetHcNeedsOptions && noActionBar)) { 2554 addFlags(WindowManager.LayoutParams.FLAG_NEEDS_MENU_KEY); 2555 } 2556 2557 if (mAlwaysReadCloseOnTouchAttr || getContext().getApplicationInfo().targetSdkVersion 2558 >= android.os.Build.VERSION_CODES.HONEYCOMB) { 2559 if (a.getBoolean( 2560 com.android.internal.R.styleable.Window_windowCloseOnTouchOutside, 2561 false)) { 2562 setCloseOnTouchOutsideIfNotSet(true); 2563 } 2564 } 2565 2566 WindowManager.LayoutParams params = getAttributes(); 2567 2568 if (!hasSoftInputMode()) { 2569 params.softInputMode = a.getInt( 2570 com.android.internal.R.styleable.Window_windowSoftInputMode, 2571 params.softInputMode); 2572 } 2573 2574 if (a.getBoolean(com.android.internal.R.styleable.Window_backgroundDimEnabled, 2575 mIsFloating)) { 2576 /* All dialogs should have the window dimmed */ 2577 if ((getForcedWindowFlags()&WindowManager.LayoutParams.FLAG_DIM_BEHIND) == 0) { 2578 params.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND; 2579 } 2580 if (!haveDimAmount()) { 2581 params.dimAmount = a.getFloat( 2582 android.R.styleable.Window_backgroundDimAmount, 0.5f); 2583 } 2584 } 2585 2586 if (params.windowAnimations == 0) { 2587 params.windowAnimations = a.getResourceId( 2588 com.android.internal.R.styleable.Window_windowAnimationStyle, 0); 2589 } 2590 2591 // The rest are only done if this window is not embedded; otherwise, 2592 // the values are inherited from our container. 2593 if (getContainer() == null) { 2594 if (mBackgroundDrawable == null) { 2595 if (mBackgroundResource == 0) { 2596 mBackgroundResource = a.getResourceId( 2597 com.android.internal.R.styleable.Window_windowBackground, 0); 2598 } 2599 if (mFrameResource == 0) { 2600 mFrameResource = a.getResourceId(com.android.internal.R.styleable.Window_windowFrame, 0); 2601 } 2602 if (false) { 2603 System.out.println("Background: " 2604 + Integer.toHexString(mBackgroundResource) + " Frame: " 2605 + Integer.toHexString(mFrameResource)); 2606 } 2607 } 2608 mTextColor = a.getColor(com.android.internal.R.styleable.Window_textColor, 0xFF000000); 2609 } 2610 2611 // Inflate the window decor. 2612 2613 int layoutResource; 2614 int features = getLocalFeatures(); 2615 // System.out.println("Features: 0x" + Integer.toHexString(features)); 2616 if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) { 2617 if (mIsFloating) { 2618 TypedValue res = new TypedValue(); 2619 getContext().getTheme().resolveAttribute( 2620 com.android.internal.R.attr.dialogTitleIconsDecorLayout, res, true); 2621 layoutResource = res.resourceId; 2622 } else { 2623 layoutResource = com.android.internal.R.layout.screen_title_icons; 2624 } 2625 // XXX Remove this once action bar supports these features. 2626 removeFeature(FEATURE_ACTION_BAR); 2627 // System.out.println("Title Icons!"); 2628 } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0 2629 && (features & (1 << FEATURE_ACTION_BAR)) == 0) { 2630 // Special case for a window with only a progress bar (and title). 2631 // XXX Need to have a no-title version of embedded windows. 2632 layoutResource = com.android.internal.R.layout.screen_progress; 2633 // System.out.println("Progress!"); 2634 } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) { 2635 // Special case for a window with a custom title. 2636 // If the window is floating, we need a dialog layout 2637 if (mIsFloating) { 2638 TypedValue res = new TypedValue(); 2639 getContext().getTheme().resolveAttribute( 2640 com.android.internal.R.attr.dialogCustomTitleDecorLayout, res, true); 2641 layoutResource = res.resourceId; 2642 } else { 2643 layoutResource = com.android.internal.R.layout.screen_custom_title; 2644 } 2645 // XXX Remove this once action bar supports these features. 2646 removeFeature(FEATURE_ACTION_BAR); 2647 } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) { 2648 // If no other features and not embedded, only need a title. 2649 // If the window is floating, we need a dialog layout 2650 if (mIsFloating) { 2651 TypedValue res = new TypedValue(); 2652 getContext().getTheme().resolveAttribute( 2653 com.android.internal.R.attr.dialogTitleDecorLayout, res, true); 2654 layoutResource = res.resourceId; 2655 } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) { 2656 if ((features & (1 << FEATURE_ACTION_BAR_OVERLAY)) != 0) { 2657 layoutResource = com.android.internal.R.layout.screen_action_bar_overlay; 2658 } else { 2659 layoutResource = com.android.internal.R.layout.screen_action_bar; 2660 } 2661 } else { 2662 layoutResource = com.android.internal.R.layout.screen_title; 2663 } 2664 // System.out.println("Title!"); 2665 } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) { 2666 layoutResource = com.android.internal.R.layout.screen_simple_overlay_action_mode; 2667 } else { 2668 // Embedded, so no decoration is needed. 2669 layoutResource = com.android.internal.R.layout.screen_simple; 2670 // System.out.println("Simple!"); 2671 } 2672 2673 mDecor.startChanging(); 2674 2675 View in = mLayoutInflater.inflate(layoutResource, null); 2676 decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); 2677 2678 ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); 2679 if (contentParent == null) { 2680 throw new RuntimeException("Window couldn't find content container view"); 2681 } 2682 2683 if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) { 2684 ProgressBar progress = getCircularProgressBar(false); 2685 if (progress != null) { 2686 progress.setIndeterminate(true); 2687 } 2688 } 2689 2690 // Remaining setup -- of background and title -- that only applies 2691 // to top-level windows. 2692 if (getContainer() == null) { 2693 Drawable drawable = mBackgroundDrawable; 2694 if (mBackgroundResource != 0) { 2695 drawable = getContext().getResources().getDrawable(mBackgroundResource); 2696 } 2697 mDecor.setWindowBackground(drawable); 2698 drawable = null; 2699 if (mFrameResource != 0) { 2700 drawable = getContext().getResources().getDrawable(mFrameResource); 2701 } 2702 mDecor.setWindowFrame(drawable); 2703 2704 // System.out.println("Text=" + Integer.toHexString(mTextColor) + 2705 // " Sel=" + Integer.toHexString(mTextSelectedColor) + 2706 // " Title=" + Integer.toHexString(mTitleColor)); 2707 2708 if (mTitleColor == 0) { 2709 mTitleColor = mTextColor; 2710 } 2711 2712 if (mTitle != null) { 2713 setTitle(mTitle); 2714 } 2715 setTitleColor(mTitleColor); 2716 } 2717 2718 mDecor.finishChanging(); 2719 2720 return contentParent; 2721 } 2722 2723 /** @hide */ 2724 public void alwaysReadCloseOnTouchAttr() { 2725 mAlwaysReadCloseOnTouchAttr = true; 2726 } 2727 2728 private void installDecor() { 2729 if (mDecor == null) { 2730 mDecor = generateDecor(); 2731 mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); 2732 mDecor.setIsRootNamespace(true); 2733 } 2734 if (mContentParent == null) { 2735 mContentParent = generateLayout(mDecor); 2736 2737 mTitleView = (TextView)findViewById(com.android.internal.R.id.title); 2738 if (mTitleView != null) { 2739 if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) { 2740 View titleContainer = findViewById(com.android.internal.R.id.title_container); 2741 if (titleContainer != null) { 2742 titleContainer.setVisibility(View.GONE); 2743 } else { 2744 mTitleView.setVisibility(View.GONE); 2745 } 2746 if (mContentParent instanceof FrameLayout) { 2747 ((FrameLayout)mContentParent).setForeground(null); 2748 } 2749 } else { 2750 mTitleView.setText(mTitle); 2751 } 2752 } else { 2753 mActionBar = (ActionBarView) findViewById(com.android.internal.R.id.action_bar); 2754 if (mActionBar != null) { 2755 mActionBar.setWindowCallback(getCallback()); 2756 if (mActionBar.getTitle() == null) { 2757 mActionBar.setWindowTitle(mTitle); 2758 } 2759 final int localFeatures = getLocalFeatures(); 2760 if ((localFeatures & (1 << FEATURE_PROGRESS)) != 0) { 2761 mActionBar.initProgress(); 2762 } 2763 if ((localFeatures & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) { 2764 mActionBar.initIndeterminateProgress(); 2765 } 2766 2767 boolean splitActionBar = false; 2768 final boolean splitWhenNarrow = 2769 (mUiOptions & ActivityInfo.UIOPTION_SPLIT_ACTION_BAR_WHEN_NARROW) != 0; 2770 if (splitWhenNarrow) { 2771 splitActionBar = getContext().getResources().getBoolean( 2772 com.android.internal.R.bool.split_action_bar_is_narrow); 2773 } else { 2774 splitActionBar = getWindowStyle().getBoolean( 2775 com.android.internal.R.styleable.Window_windowSplitActionBar, false); 2776 } 2777 final ActionBarContainer splitView = (ActionBarContainer) findViewById( 2778 com.android.internal.R.id.split_action_bar); 2779 if (splitView != null) { 2780 mActionBar.setSplitView(splitView); 2781 mActionBar.setSplitActionBar(splitActionBar); 2782 mActionBar.setSplitWhenNarrow(splitWhenNarrow); 2783 2784 final ActionBarContextView cab = (ActionBarContextView) findViewById( 2785 com.android.internal.R.id.action_context_bar); 2786 cab.setSplitView(splitView); 2787 cab.setSplitActionBar(splitActionBar); 2788 cab.setSplitWhenNarrow(splitWhenNarrow); 2789 } else if (splitActionBar) { 2790 Log.e(TAG, "Requested split action bar with " + 2791 "incompatible window decor! Ignoring request."); 2792 } 2793 2794 // Post the panel invalidate for later; avoid application onCreateOptionsMenu 2795 // being called in the middle of onCreate or similar. 2796 mDecor.post(new Runnable() { 2797 public void run() { 2798 // Invalidate if the panel menu hasn't been created before this. 2799 PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false); 2800 if (!isDestroyed() && (st == null || st.menu == null)) { 2801 invalidatePanelMenu(FEATURE_ACTION_BAR); 2802 } 2803 } 2804 }); 2805 } 2806 } 2807 } 2808 } 2809 2810 private Drawable loadImageURI(Uri uri) { 2811 try { 2812 return Drawable.createFromStream( 2813 getContext().getContentResolver().openInputStream(uri), null); 2814 } catch (Exception e) { 2815 Log.w(TAG, "Unable to open content: " + uri); 2816 } 2817 return null; 2818 } 2819 2820 private DrawableFeatureState getDrawableState(int featureId, boolean required) { 2821 if ((getFeatures() & (1 << featureId)) == 0) { 2822 if (!required) { 2823 return null; 2824 } 2825 throw new RuntimeException("The feature has not been requested"); 2826 } 2827 2828 DrawableFeatureState[] ar; 2829 if ((ar = mDrawables) == null || ar.length <= featureId) { 2830 DrawableFeatureState[] nar = new DrawableFeatureState[featureId + 1]; 2831 if (ar != null) { 2832 System.arraycopy(ar, 0, nar, 0, ar.length); 2833 } 2834 mDrawables = ar = nar; 2835 } 2836 2837 DrawableFeatureState st = ar[featureId]; 2838 if (st == null) { 2839 ar[featureId] = st = new DrawableFeatureState(featureId); 2840 } 2841 return st; 2842 } 2843 2844 /** 2845 * Gets a panel's state based on its feature ID. 2846 * 2847 * @param featureId The feature ID of the panel. 2848 * @param required Whether the panel is required (if it is required and it 2849 * isn't in our features, this throws an exception). 2850 * @return The panel state. 2851 */ 2852 private PanelFeatureState getPanelState(int featureId, boolean required) { 2853 return getPanelState(featureId, required, null); 2854 } 2855 2856 /** 2857 * Gets a panel's state based on its feature ID. 2858 * 2859 * @param featureId The feature ID of the panel. 2860 * @param required Whether the panel is required (if it is required and it 2861 * isn't in our features, this throws an exception). 2862 * @param convertPanelState Optional: If the panel state does not exist, use 2863 * this as the panel state. 2864 * @return The panel state. 2865 */ 2866 private PanelFeatureState getPanelState(int featureId, boolean required, 2867 PanelFeatureState convertPanelState) { 2868 if ((getFeatures() & (1 << featureId)) == 0) { 2869 if (!required) { 2870 return null; 2871 } 2872 throw new RuntimeException("The feature has not been requested"); 2873 } 2874 2875 PanelFeatureState[] ar; 2876 if ((ar = mPanels) == null || ar.length <= featureId) { 2877 PanelFeatureState[] nar = new PanelFeatureState[featureId + 1]; 2878 if (ar != null) { 2879 System.arraycopy(ar, 0, nar, 0, ar.length); 2880 } 2881 mPanels = ar = nar; 2882 } 2883 2884 PanelFeatureState st = ar[featureId]; 2885 if (st == null) { 2886 ar[featureId] = st = (convertPanelState != null) 2887 ? convertPanelState 2888 : new PanelFeatureState(featureId); 2889 } 2890 return st; 2891 } 2892 2893 @Override 2894 public final void setChildDrawable(int featureId, Drawable drawable) { 2895 DrawableFeatureState st = getDrawableState(featureId, true); 2896 st.child = drawable; 2897 updateDrawable(featureId, st, false); 2898 } 2899 2900 @Override 2901 public final void setChildInt(int featureId, int value) { 2902 updateInt(featureId, value, false); 2903 } 2904 2905 @Override 2906 public boolean isShortcutKey(int keyCode, KeyEvent event) { 2907 PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, true); 2908 return st.menu != null && st.menu.isShortcutKey(keyCode, event); 2909 } 2910 2911 private void updateDrawable(int featureId, DrawableFeatureState st, boolean fromResume) { 2912 // Do nothing if the decor is not yet installed... an update will 2913 // need to be forced when we eventually become active. 2914 if (mContentParent == null) { 2915 return; 2916 } 2917 2918 final int featureMask = 1 << featureId; 2919 2920 if ((getFeatures() & featureMask) == 0 && !fromResume) { 2921 return; 2922 } 2923 2924 Drawable drawable = null; 2925 if (st != null) { 2926 drawable = st.child; 2927 if (drawable == null) 2928 drawable = st.local; 2929 if (drawable == null) 2930 drawable = st.def; 2931 } 2932 if ((getLocalFeatures() & featureMask) == 0) { 2933 if (getContainer() != null) { 2934 if (isActive() || fromResume) { 2935 getContainer().setChildDrawable(featureId, drawable); 2936 } 2937 } 2938 } else if (st != null && (st.cur != drawable || st.curAlpha != st.alpha)) { 2939 // System.out.println("Drawable changed: old=" + st.cur 2940 // + ", new=" + drawable); 2941 st.cur = drawable; 2942 st.curAlpha = st.alpha; 2943 onDrawableChanged(featureId, drawable, st.alpha); 2944 } 2945 } 2946 2947 private void updateInt(int featureId, int value, boolean fromResume) { 2948 2949 // Do nothing if the decor is not yet installed... an update will 2950 // need to be forced when we eventually become active. 2951 if (mContentParent == null) { 2952 return; 2953 } 2954 2955 final int featureMask = 1 << featureId; 2956 2957 if ((getFeatures() & featureMask) == 0 && !fromResume) { 2958 return; 2959 } 2960 2961 if ((getLocalFeatures() & featureMask) == 0) { 2962 if (getContainer() != null) { 2963 getContainer().setChildInt(featureId, value); 2964 } 2965 } else { 2966 onIntChanged(featureId, value); 2967 } 2968 } 2969 2970 private ImageView getLeftIconView() { 2971 if (mLeftIconView != null) { 2972 return mLeftIconView; 2973 } 2974 if (mContentParent == null) { 2975 installDecor(); 2976 } 2977 return (mLeftIconView = (ImageView)findViewById(com.android.internal.R.id.left_icon)); 2978 } 2979 2980 private ProgressBar getCircularProgressBar(boolean shouldInstallDecor) { 2981 if (mCircularProgressBar != null) { 2982 return mCircularProgressBar; 2983 } 2984 if (mContentParent == null && shouldInstallDecor) { 2985 installDecor(); 2986 } 2987 mCircularProgressBar = (ProgressBar) findViewById(com.android.internal.R.id.progress_circular); 2988 if (mCircularProgressBar != null) { 2989 mCircularProgressBar.setVisibility(View.INVISIBLE); 2990 } 2991 return mCircularProgressBar; 2992 } 2993 2994 private ProgressBar getHorizontalProgressBar(boolean shouldInstallDecor) { 2995 if (mHorizontalProgressBar != null) { 2996 return mHorizontalProgressBar; 2997 } 2998 if (mContentParent == null && shouldInstallDecor) { 2999 installDecor(); 3000 } 3001 mHorizontalProgressBar = (ProgressBar) findViewById(com.android.internal.R.id.progress_horizontal); 3002 if (mHorizontalProgressBar != null) { 3003 mHorizontalProgressBar.setVisibility(View.INVISIBLE); 3004 } 3005 return mHorizontalProgressBar; 3006 } 3007 3008 private ImageView getRightIconView() { 3009 if (mRightIconView != null) { 3010 return mRightIconView; 3011 } 3012 if (mContentParent == null) { 3013 installDecor(); 3014 } 3015 return (mRightIconView = (ImageView)findViewById(com.android.internal.R.id.right_icon)); 3016 } 3017 3018 /** 3019 * Helper method for calling the {@link Callback#onPanelClosed(int, Menu)} 3020 * callback. This method will grab whatever extra state is needed for the 3021 * callback that isn't given in the parameters. If the panel is not open, 3022 * this will not perform the callback. 3023 * 3024 * @param featureId Feature ID of the panel that was closed. Must be given. 3025 * @param panel Panel that was closed. Optional but useful if there is no 3026 * menu given. 3027 * @param menu The menu that was closed. Optional, but give if you have. 3028 */ 3029 private void callOnPanelClosed(int featureId, PanelFeatureState panel, Menu menu) { 3030 final Callback cb = getCallback(); 3031 if (cb == null) 3032 return; 3033 3034 // Try to get a menu 3035 if (menu == null) { 3036 // Need a panel to grab the menu, so try to get that 3037 if (panel == null) { 3038 if ((featureId >= 0) && (featureId < mPanels.length)) { 3039 panel = mPanels[featureId]; 3040 } 3041 } 3042 3043 if (panel != null) { 3044 // menu still may be null, which is okay--we tried our best 3045 menu = panel.menu; 3046 } 3047 } 3048 3049 // If the panel is not open, do not callback 3050 if ((panel != null) && (!panel.isOpen)) 3051 return; 3052 3053 if (!isDestroyed()) { 3054 cb.onPanelClosed(featureId, menu); 3055 } 3056 } 3057 3058 /** 3059 * Helper method for adding launch-search to most applications. Opens the 3060 * search window using default settings. 3061 * 3062 * @return true if search window opened 3063 */ 3064 private boolean launchDefaultSearch() { 3065 final Callback cb = getCallback(); 3066 if (cb == null || isDestroyed()) { 3067 return false; 3068 } else { 3069 sendCloseSystemWindows("search"); 3070 return cb.onSearchRequested(); 3071 } 3072 } 3073 3074 @Override 3075 public void setVolumeControlStream(int streamType) { 3076 mVolumeControlStreamType = streamType; 3077 } 3078 3079 @Override 3080 public int getVolumeControlStream() { 3081 return mVolumeControlStreamType; 3082 } 3083 3084 private static final class DrawableFeatureState { 3085 DrawableFeatureState(int _featureId) { 3086 featureId = _featureId; 3087 } 3088 3089 final int featureId; 3090 3091 int resid; 3092 3093 Uri uri; 3094 3095 Drawable local; 3096 3097 Drawable child; 3098 3099 Drawable def; 3100 3101 Drawable cur; 3102 3103 int alpha = 255; 3104 3105 int curAlpha = 255; 3106 } 3107 3108 private static final class PanelFeatureState { 3109 3110 /** Feature ID for this panel. */ 3111 int featureId; 3112 3113 // Information pulled from the style for this panel. 3114 3115 int background; 3116 3117 /** The background when the panel spans the entire available width. */ 3118 int fullBackground; 3119 3120 int gravity; 3121 3122 int x; 3123 3124 int y; 3125 3126 int windowAnimations; 3127 3128 /** Dynamic state of the panel. */ 3129 DecorView decorView; 3130 3131 /** The panel that was returned by onCreatePanelView(). */ 3132 View createdPanelView; 3133 3134 /** The panel that we are actually showing. */ 3135 View shownPanelView; 3136 3137 /** Use {@link #setMenu} to set this. */ 3138 MenuBuilder menu; 3139 3140 IconMenuPresenter iconMenuPresenter; 3141 ListMenuPresenter listMenuPresenter; 3142 3143 /** true if this menu will show in single-list compact mode */ 3144 boolean isCompact; 3145 3146 /** Theme resource ID for list elements of the panel menu */ 3147 int listPresenterTheme; 3148 3149 /** 3150 * Whether the panel has been prepared (see 3151 * {@link PhoneWindow#preparePanel}). 3152 */ 3153 boolean isPrepared; 3154 3155 /** 3156 * Whether an item's action has been performed. This happens in obvious 3157 * scenarios (user clicks on menu item), but can also happen with 3158 * chording menu+(shortcut key). 3159 */ 3160 boolean isHandled; 3161 3162 boolean isOpen; 3163 3164 /** 3165 * True if the menu is in expanded mode, false if the menu is in icon 3166 * mode 3167 */ 3168 boolean isInExpandedMode; 3169 3170 public boolean qwertyMode; 3171 3172 boolean refreshDecorView; 3173 3174 boolean refreshMenuContent; 3175 3176 boolean wasLastOpen; 3177 3178 boolean wasLastExpanded; 3179 3180 /** 3181 * Contains the state of the menu when told to freeze. 3182 */ 3183 Bundle frozenMenuState; 3184 3185 /** 3186 * Contains the state of associated action views when told to freeze. 3187 * These are saved across invalidations. 3188 */ 3189 Bundle frozenActionViewState; 3190 3191 PanelFeatureState(int featureId) { 3192 this.featureId = featureId; 3193 3194 refreshDecorView = false; 3195 } 3196 3197 public boolean isInListMode() { 3198 return isInExpandedMode || isCompact; 3199 } 3200 3201 public boolean hasPanelItems() { 3202 if (shownPanelView == null) return false; 3203 if (createdPanelView != null) return true; 3204 3205 if (isCompact || isInExpandedMode) { 3206 return listMenuPresenter.getAdapter().getCount() > 0; 3207 } else { 3208 return ((ViewGroup) shownPanelView).getChildCount() > 0; 3209 } 3210 } 3211 3212 /** 3213 * Unregister and free attached MenuPresenters. They will be recreated as needed. 3214 */ 3215 public void clearMenuPresenters() { 3216 if (menu != null) { 3217 menu.removeMenuPresenter(iconMenuPresenter); 3218 menu.removeMenuPresenter(listMenuPresenter); 3219 } 3220 iconMenuPresenter = null; 3221 listMenuPresenter = null; 3222 } 3223 3224 void setStyle(Context context) { 3225 TypedArray a = context.obtainStyledAttributes(com.android.internal.R.styleable.Theme); 3226 background = a.getResourceId( 3227 com.android.internal.R.styleable.Theme_panelBackground, 0); 3228 fullBackground = a.getResourceId( 3229 com.android.internal.R.styleable.Theme_panelFullBackground, 0); 3230 windowAnimations = a.getResourceId( 3231 com.android.internal.R.styleable.Theme_windowAnimationStyle, 0); 3232 isCompact = a.getBoolean( 3233 com.android.internal.R.styleable.Theme_panelMenuIsCompact, false); 3234 listPresenterTheme = a.getResourceId( 3235 com.android.internal.R.styleable.Theme_panelMenuListTheme, 3236 com.android.internal.R.style.Theme_ExpandedMenu); 3237 a.recycle(); 3238 } 3239 3240 void setMenu(MenuBuilder menu) { 3241 if (menu == this.menu) return; 3242 3243 if (this.menu != null) { 3244 this.menu.removeMenuPresenter(iconMenuPresenter); 3245 this.menu.removeMenuPresenter(listMenuPresenter); 3246 } 3247 this.menu = menu; 3248 if (menu != null) { 3249 if (iconMenuPresenter != null) menu.addMenuPresenter(iconMenuPresenter); 3250 if (listMenuPresenter != null) menu.addMenuPresenter(listMenuPresenter); 3251 } 3252 } 3253 3254 MenuView getListMenuView(MenuPresenter.Callback cb) { 3255 if (menu == null) return null; 3256 3257 if (!isCompact) { 3258 getIconMenuView(cb); // Need this initialized to know where our offset goes 3259 } 3260 3261 if (listMenuPresenter == null) { 3262 listMenuPresenter = new ListMenuPresenter( 3263 com.android.internal.R.layout.list_menu_item_layout, listPresenterTheme); 3264 listMenuPresenter.setCallback(cb); 3265 listMenuPresenter.setId(com.android.internal.R.id.list_menu_presenter); 3266 menu.addMenuPresenter(listMenuPresenter); 3267 } 3268 3269 if (iconMenuPresenter != null) { 3270 listMenuPresenter.setItemIndexOffset( 3271 iconMenuPresenter.getNumActualItemsShown()); 3272 } 3273 MenuView result = listMenuPresenter.getMenuView(decorView); 3274 3275 return result; 3276 } 3277 3278 MenuView getIconMenuView(MenuPresenter.Callback cb) { 3279 if (menu == null) return null; 3280 3281 if (iconMenuPresenter == null) { 3282 iconMenuPresenter = new IconMenuPresenter(); 3283 iconMenuPresenter.setCallback(cb); 3284 iconMenuPresenter.setId(com.android.internal.R.id.icon_menu_presenter); 3285 menu.addMenuPresenter(iconMenuPresenter); 3286 } 3287 3288 MenuView result = iconMenuPresenter.getMenuView(decorView); 3289 3290 return result; 3291 } 3292 3293 Parcelable onSaveInstanceState() { 3294 SavedState savedState = new SavedState(); 3295 savedState.featureId = featureId; 3296 savedState.isOpen = isOpen; 3297 savedState.isInExpandedMode = isInExpandedMode; 3298 3299 if (menu != null) { 3300 savedState.menuState = new Bundle(); 3301 menu.savePresenterStates(savedState.menuState); 3302 } 3303 3304 return savedState; 3305 } 3306 3307 void onRestoreInstanceState(Parcelable state) { 3308 SavedState savedState = (SavedState) state; 3309 featureId = savedState.featureId; 3310 wasLastOpen = savedState.isOpen; 3311 wasLastExpanded = savedState.isInExpandedMode; 3312 frozenMenuState = savedState.menuState; 3313 3314 /* 3315 * A LocalActivityManager keeps the same instance of this class around. 3316 * The first time the menu is being shown after restoring, the 3317 * Activity.onCreateOptionsMenu should be called. But, if it is the 3318 * same instance then menu != null and we won't call that method. 3319 * We clear any cached views here. The caller should invalidatePanelMenu. 3320 */ 3321 createdPanelView = null; 3322 shownPanelView = null; 3323 decorView = null; 3324 } 3325 3326 void applyFrozenState() { 3327 if (menu != null && frozenMenuState != null) { 3328 menu.restorePresenterStates(frozenMenuState); 3329 frozenMenuState = null; 3330 } 3331 } 3332 3333 private static class SavedState implements Parcelable { 3334 int featureId; 3335 boolean isOpen; 3336 boolean isInExpandedMode; 3337 Bundle menuState; 3338 3339 public int describeContents() { 3340 return 0; 3341 } 3342 3343 public void writeToParcel(Parcel dest, int flags) { 3344 dest.writeInt(featureId); 3345 dest.writeInt(isOpen ? 1 : 0); 3346 dest.writeInt(isInExpandedMode ? 1 : 0); 3347 3348 if (isOpen) { 3349 dest.writeBundle(menuState); 3350 } 3351 } 3352 3353 private static SavedState readFromParcel(Parcel source) { 3354 SavedState savedState = new SavedState(); 3355 savedState.featureId = source.readInt(); 3356 savedState.isOpen = source.readInt() == 1; 3357 savedState.isInExpandedMode = source.readInt() == 1; 3358 3359 if (savedState.isOpen) { 3360 savedState.menuState = source.readBundle(); 3361 } 3362 3363 return savedState; 3364 } 3365 3366 public static final Parcelable.Creator<SavedState> CREATOR 3367 = new Parcelable.Creator<SavedState>() { 3368 public SavedState createFromParcel(Parcel in) { 3369 return readFromParcel(in); 3370 } 3371 3372 public SavedState[] newArray(int size) { 3373 return new SavedState[size]; 3374 } 3375 }; 3376 } 3377 3378 } 3379 3380 static class RotationWatcher extends IRotationWatcher.Stub { 3381 private Handler mHandler; 3382 private final Runnable mRotationChanged = new Runnable() { 3383 public void run() { 3384 dispatchRotationChanged(); 3385 } 3386 }; 3387 private final ArrayList<WeakReference<PhoneWindow>> mWindows = 3388 new ArrayList<WeakReference<PhoneWindow>>(); 3389 private boolean mIsWatching; 3390 3391 @Override 3392 public void onRotationChanged(int rotation) throws RemoteException { 3393 mHandler.post(mRotationChanged); 3394 } 3395 3396 public void addWindow(PhoneWindow phoneWindow) { 3397 synchronized (mWindows) { 3398 if (!mIsWatching) { 3399 try { 3400 WindowManagerHolder.sWindowManager.watchRotation(this); 3401 mHandler = new Handler(); 3402 mIsWatching = true; 3403 } catch (RemoteException ex) { 3404 Log.e(TAG, "Couldn't start watching for device rotation", ex); 3405 } 3406 } 3407 mWindows.add(new WeakReference<PhoneWindow>(phoneWindow)); 3408 } 3409 } 3410 3411 public void removeWindow(PhoneWindow phoneWindow) { 3412 synchronized (mWindows) { 3413 int i = 0; 3414 while (i < mWindows.size()) { 3415 final WeakReference<PhoneWindow> ref = mWindows.get(i); 3416 final PhoneWindow win = ref.get(); 3417 if (win == null || win == phoneWindow) { 3418 mWindows.remove(i); 3419 } else { 3420 i++; 3421 } 3422 } 3423 } 3424 } 3425 3426 void dispatchRotationChanged() { 3427 synchronized (mWindows) { 3428 int i = 0; 3429 while (i < mWindows.size()) { 3430 final WeakReference<PhoneWindow> ref = mWindows.get(i); 3431 final PhoneWindow win = ref.get(); 3432 if (win != null) { 3433 win.onOptionsPanelRotationChanged(); 3434 i++; 3435 } else { 3436 mWindows.remove(i); 3437 } 3438 } 3439 } 3440 } 3441 } 3442 3443 /** 3444 * Simple implementation of MenuBuilder.Callback that: 3445 * <li> Opens a submenu when selected. 3446 * <li> Calls back to the callback's onMenuItemSelected when an item is 3447 * selected. 3448 */ 3449 private final class DialogMenuCallback implements MenuBuilder.Callback, MenuPresenter.Callback { 3450 private int mFeatureId; 3451 private MenuDialogHelper mSubMenuHelper; 3452 3453 public DialogMenuCallback(int featureId) { 3454 mFeatureId = featureId; 3455 } 3456 3457 public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { 3458 if (menu.getRootMenu() != menu) { 3459 onCloseSubMenu(menu); 3460 } 3461 3462 if (allMenusAreClosing) { 3463 Callback callback = getCallback(); 3464 if (callback != null && !isDestroyed()) { 3465 callback.onPanelClosed(mFeatureId, menu); 3466 } 3467 3468 if (menu == mContextMenu) { 3469 dismissContextMenu(); 3470 } 3471 3472 // Dismiss the submenu, if it is showing 3473 if (mSubMenuHelper != null) { 3474 mSubMenuHelper.dismiss(); 3475 mSubMenuHelper = null; 3476 } 3477 } 3478 } 3479 3480 public void onCloseSubMenu(MenuBuilder menu) { 3481 Callback callback = getCallback(); 3482 if (callback != null && !isDestroyed()) { 3483 callback.onPanelClosed(mFeatureId, menu.getRootMenu()); 3484 } 3485 } 3486 3487 public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) { 3488 Callback callback = getCallback(); 3489 return (callback != null && !isDestroyed()) 3490 && callback.onMenuItemSelected(mFeatureId, item); 3491 } 3492 3493 public void onMenuModeChange(MenuBuilder menu) { 3494 } 3495 3496 public boolean onOpenSubMenu(MenuBuilder subMenu) { 3497 if (subMenu == null) return false; 3498 3499 // Set a simple callback for the submenu 3500 subMenu.setCallback(this); 3501 3502 // The window manager will give us a valid window token 3503 mSubMenuHelper = new MenuDialogHelper(subMenu); 3504 mSubMenuHelper.show(null); 3505 3506 return true; 3507 } 3508 } 3509 3510 void sendCloseSystemWindows() { 3511 PhoneWindowManager.sendCloseSystemWindows(getContext(), null); 3512 } 3513 3514 void sendCloseSystemWindows(String reason) { 3515 PhoneWindowManager.sendCloseSystemWindows(getContext(), reason); 3516 } 3517} 3518