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