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