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