PhoneWindow.java revision d6107a3170df61d9e776fcd5666acfc9135c6f16
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.*; 24 25import android.animation.Animator; 26import android.animation.ObjectAnimator; 27import android.app.ActivityOptions; 28import android.os.Looper; 29import android.transition.Fade; 30import android.transition.Scene; 31import android.transition.Transition; 32import android.transition.TransitionInflater; 33import android.transition.TransitionManager; 34import android.transition.TransitionSet; 35import android.util.ArrayMap; 36import android.view.ViewConfiguration; 37 38import com.android.internal.R; 39import com.android.internal.view.RootViewSurfaceTaker; 40import com.android.internal.view.StandaloneActionMode; 41import com.android.internal.view.menu.ContextMenuBuilder; 42import com.android.internal.view.menu.IconMenuPresenter; 43import com.android.internal.view.menu.ListMenuPresenter; 44import com.android.internal.view.menu.MenuBuilder; 45import com.android.internal.view.menu.MenuDialogHelper; 46import com.android.internal.view.menu.MenuPresenter; 47import com.android.internal.view.menu.MenuView; 48import com.android.internal.widget.ActionBarContainer; 49import com.android.internal.widget.ActionBarContextView; 50import com.android.internal.widget.ActionBarOverlayLayout; 51import com.android.internal.widget.ActionBarView; 52import com.android.internal.widget.SwipeDismissLayout; 53 54import android.app.KeyguardManager; 55import android.content.Context; 56import android.content.pm.ActivityInfo; 57import android.content.res.Configuration; 58import android.content.res.Resources; 59import android.content.res.TypedArray; 60import android.graphics.Canvas; 61import android.graphics.PixelFormat; 62import android.graphics.Rect; 63import android.graphics.drawable.Drawable; 64import android.media.AudioManager; 65import android.net.Uri; 66import android.os.Bundle; 67import android.os.Handler; 68import android.os.Parcel; 69import android.os.Parcelable; 70import android.os.RemoteException; 71import android.os.ServiceManager; 72import android.util.AndroidRuntimeException; 73import android.util.DisplayMetrics; 74import android.util.EventLog; 75import android.util.Log; 76import android.util.SparseArray; 77import android.util.TypedValue; 78import android.view.ActionMode; 79import android.view.ContextThemeWrapper; 80import android.view.Gravity; 81import android.view.IRotationWatcher; 82import android.view.IWindowManager; 83import android.view.InputEvent; 84import android.view.InputQueue; 85import android.view.KeyCharacterMap; 86import android.view.KeyEvent; 87import android.view.LayoutInflater; 88import android.view.Menu; 89import android.view.MenuItem; 90import android.view.MotionEvent; 91import android.view.SurfaceHolder; 92import android.view.View; 93import android.view.ViewGroup; 94import android.view.ViewManager; 95import android.view.ViewParent; 96import android.view.ViewRootImpl; 97import android.view.ViewStub; 98import android.view.ViewTreeObserver; 99import android.view.Window; 100import android.view.WindowManager; 101import android.view.accessibility.AccessibilityEvent; 102import android.view.accessibility.AccessibilityManager; 103import android.view.animation.Animation; 104import android.view.animation.AnimationUtils; 105import android.widget.FrameLayout; 106import android.widget.ImageView; 107import android.widget.PopupWindow; 108import android.widget.ProgressBar; 109import android.widget.TextView; 110 111import java.lang.ref.WeakReference; 112import java.util.ArrayList; 113import java.util.Collection; 114import java.util.Map; 115 116/** 117 * Android-specific Window. 118 * <p> 119 * todo: need to pull the generic functionality out into a base class 120 * in android.widget. 121 */ 122public class PhoneWindow extends Window implements MenuBuilder.Callback { 123 124 private final static String TAG = "PhoneWindow"; 125 126 private final static boolean SWEEP_OPEN_MENU = false; 127 private static final long MAX_TRANSITION_START_WAIT = 500; 128 private static final long MAX_TRANSITION_FINISH_WAIT = 1000; 129 130 private static final String KEY_SCREEN_X = "shared_element:screenX"; 131 private static final String KEY_SCREEN_Y = "shared_element:screenY"; 132 private static final String KEY_TRANSLATION_Z = "shared_element:translationZ"; 133 private static final String KEY_WIDTH = "shared_element:width"; 134 private static final String KEY_HEIGHT = "shared_element:height"; 135 private static final String KEY_NAME = "shared_element:name"; 136 137 /** 138 * Simple callback used by the context menu and its submenus. The options 139 * menu submenus do not use this (their behavior is more complex). 140 */ 141 final DialogMenuCallback mContextMenuCallback = new DialogMenuCallback(FEATURE_CONTEXT_MENU); 142 143 final TypedValue mMinWidthMajor = new TypedValue(); 144 final TypedValue mMinWidthMinor = new TypedValue(); 145 TypedValue mFixedWidthMajor; 146 TypedValue mFixedWidthMinor; 147 TypedValue mFixedHeightMajor; 148 TypedValue mFixedHeightMinor; 149 TypedValue mOutsetBottom; 150 151 // This is the top-level view of the window, containing the window decor. 152 private DecorView mDecor; 153 154 // This is the view in which the window contents are placed. It is either 155 // mDecor itself, or a child of mDecor where the contents go. 156 private ViewGroup mContentParent; 157 158 SurfaceHolder.Callback2 mTakeSurfaceCallback; 159 160 InputQueue.Callback mTakeInputQueueCallback; 161 162 private boolean mIsFloating; 163 164 private LayoutInflater mLayoutInflater; 165 166 private TextView mTitleView; 167 168 private ActionBarView mActionBar; 169 private ActionMenuPresenterCallback mActionMenuPresenterCallback; 170 private PanelMenuPresenterCallback mPanelMenuPresenterCallback; 171 172 private TransitionManager mTransitionManager; 173 private Scene mContentScene; 174 175 // The icon resource has been explicitly set elsewhere 176 // and should not be overwritten with a default. 177 static final int FLAG_RESOURCE_SET_ICON = 1 << 0; 178 179 // The logo resource has been explicitly set elsewhere 180 // and should not be overwritten with a default. 181 static final int FLAG_RESOURCE_SET_LOGO = 1 << 1; 182 183 // The icon resource is currently configured to use the system fallback 184 // as no default was previously specified. Anything can override this. 185 static final int FLAG_RESOURCE_SET_ICON_FALLBACK = 1 << 2; 186 187 int mResourcesSetFlags; 188 int mIconRes; 189 int mLogoRes; 190 191 private DrawableFeatureState[] mDrawables; 192 193 private PanelFeatureState[] mPanels; 194 195 /** 196 * The panel that is prepared or opened (the most recent one if there are 197 * multiple panels). Shortcuts will go to this panel. It gets set in 198 * {@link #preparePanel} and cleared in {@link #closePanel}. 199 */ 200 private PanelFeatureState mPreparedPanel; 201 202 /** 203 * The keycode that is currently held down (as a modifier) for chording. If 204 * this is 0, there is no key held down. 205 */ 206 private int mPanelChordingKey; 207 208 private ImageView mLeftIconView; 209 210 private ImageView mRightIconView; 211 212 private ProgressBar mCircularProgressBar; 213 214 private ProgressBar mHorizontalProgressBar; 215 216 private int mBackgroundResource = 0; 217 218 private Drawable mBackgroundDrawable; 219 220 private int mFrameResource = 0; 221 222 private int mTextColor = 0; 223 224 private CharSequence mTitle = null; 225 226 private int mTitleColor = 0; 227 228 private boolean mAlwaysReadCloseOnTouchAttr = false; 229 230 private ContextMenuBuilder mContextMenu; 231 private MenuDialogHelper mContextMenuHelper; 232 private boolean mClosingActionMenu; 233 234 private int mVolumeControlStreamType = AudioManager.USE_DEFAULT_STREAM_TYPE; 235 236 private AudioManager mAudioManager; 237 private KeyguardManager mKeyguardManager; 238 239 private int mUiOptions = 0; 240 241 private boolean mInvalidatePanelMenuPosted; 242 private int mInvalidatePanelMenuFeatures; 243 private final Runnable mInvalidatePanelMenuRunnable = new Runnable() { 244 @Override public void run() { 245 for (int i = 0; i <= FEATURE_MAX; i++) { 246 if ((mInvalidatePanelMenuFeatures & 1 << i) != 0) { 247 doInvalidatePanelMenu(i); 248 } 249 } 250 mInvalidatePanelMenuPosted = false; 251 mInvalidatePanelMenuFeatures = 0; 252 } 253 }; 254 255 private ActivityOptions mActivityOptions; 256 private SceneTransitionListener mSceneTransitionListener; 257 private boolean mAllowEnterOverlap = true; 258 private boolean mAllowExitOverlap = true; 259 private Map<String, String> mSharedElementsMap; 260 private ArrayList<View> mTransitioningViews; 261 262 static class WindowManagerHolder { 263 static final IWindowManager sWindowManager = IWindowManager.Stub.asInterface( 264 ServiceManager.getService("window")); 265 } 266 267 static final RotationWatcher sRotationWatcher = new RotationWatcher(); 268 269 public PhoneWindow(Context context) { 270 super(context); 271 mLayoutInflater = LayoutInflater.from(context); 272 } 273 274 @Override 275 public final void setContainer(Window container) { 276 super.setContainer(container); 277 } 278 279 @Override 280 public boolean requestFeature(int featureId) { 281 if (mContentParent != null) { 282 throw new AndroidRuntimeException("requestFeature() must be called before adding content"); 283 } 284 final int features = getFeatures(); 285 if ((features != DEFAULT_FEATURES) && (featureId == FEATURE_CUSTOM_TITLE)) { 286 287 /* Another feature is enabled and the user is trying to enable the custom title feature */ 288 throw new AndroidRuntimeException("You cannot combine custom titles with other title features"); 289 } 290 if (((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) && 291 (featureId != FEATURE_CUSTOM_TITLE) && (featureId != FEATURE_ACTION_MODE_OVERLAY)) { 292 293 /* Custom title feature is enabled and the user is trying to enable another feature */ 294 throw new AndroidRuntimeException("You cannot combine custom titles with other title features"); 295 } 296 if ((features & (1 << FEATURE_NO_TITLE)) != 0 && featureId == FEATURE_ACTION_BAR) { 297 return false; // Ignore. No title dominates. 298 } 299 if ((features & (1 << FEATURE_ACTION_BAR)) != 0 && featureId == FEATURE_NO_TITLE) { 300 // Remove the action bar feature if we have no title. No title dominates. 301 removeFeature(FEATURE_ACTION_BAR); 302 } 303 304 if ((features & (1 << FEATURE_ACTION_BAR)) != 0 && featureId == FEATURE_SWIPE_TO_DISMISS) { 305 throw new AndroidRuntimeException( 306 "You cannot combine swipe dismissal and the action bar."); 307 } 308 if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0 && featureId == FEATURE_ACTION_BAR) { 309 throw new AndroidRuntimeException( 310 "You cannot combine swipe dismissal and the action bar."); 311 } 312 return super.requestFeature(featureId); 313 } 314 315 @Override 316 public void setUiOptions(int uiOptions) { 317 mUiOptions = uiOptions; 318 } 319 320 @Override 321 public void setUiOptions(int uiOptions, int mask) { 322 mUiOptions = (mUiOptions & ~mask) | (uiOptions & mask); 323 } 324 325 @Override 326 public TransitionManager getTransitionManager() { 327 return mTransitionManager; 328 } 329 330 @Override 331 public void setTransitionManager(TransitionManager tm) { 332 mTransitionManager = tm; 333 } 334 335 @Override 336 public Scene getContentScene() { 337 return mContentScene; 338 } 339 340 @Override 341 public void setContentView(int layoutResID) { 342 // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window 343 // decor, when theme attributes and the like are crystalized. Do not check the feature 344 // before this happens. 345 if (mContentParent == null) { 346 installDecor(); 347 } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) { 348 mContentParent.removeAllViews(); 349 } 350 351 if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) { 352 final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID, 353 getContext()); 354 transitionTo(newScene); 355 } else { 356 mLayoutInflater.inflate(layoutResID, mContentParent); 357 } 358 final Callback cb = getCallback(); 359 if (cb != null && !isDestroyed()) { 360 cb.onContentChanged(); 361 } 362 } 363 364 @Override 365 public void setContentView(View view) { 366 setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); 367 } 368 369 @Override 370 public void setContentView(View view, ViewGroup.LayoutParams params) { 371 // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window 372 // decor, when theme attributes and the like are crystalized. Do not check the feature 373 // before this happens. 374 if (mContentParent == null) { 375 installDecor(); 376 } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) { 377 mContentParent.removeAllViews(); 378 } 379 380 if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) { 381 view.setLayoutParams(params); 382 final Scene newScene = new Scene(mContentParent, view); 383 transitionTo(newScene); 384 } else { 385 mContentParent.addView(view, params); 386 } 387 final Callback cb = getCallback(); 388 if (cb != null && !isDestroyed()) { 389 cb.onContentChanged(); 390 } 391 } 392 393 @Override 394 public void addContentView(View view, ViewGroup.LayoutParams params) { 395 if (mContentParent == null) { 396 installDecor(); 397 } 398 if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) { 399 // TODO Augment the scenes/transitions API to support this. 400 throw new UnsupportedOperationException( 401 "addContentView does not support content transitions"); 402 } 403 mContentParent.addView(view, params); 404 final Callback cb = getCallback(); 405 if (cb != null && !isDestroyed()) { 406 cb.onContentChanged(); 407 } 408 } 409 410 private void transitionTo(Scene scene) { 411 if (mContentScene == null) { 412 scene.enter(); 413 if (mActivityOptions != null) { 414 new EnterScene().start(); 415 } 416 } else { 417 mTransitionManager.transitionTo(scene); 418 } 419 mContentScene = scene; 420 } 421 422 @Override 423 public View getCurrentFocus() { 424 return mDecor != null ? mDecor.findFocus() : null; 425 } 426 427 @Override 428 public void takeSurface(SurfaceHolder.Callback2 callback) { 429 mTakeSurfaceCallback = callback; 430 } 431 432 public void takeInputQueue(InputQueue.Callback callback) { 433 mTakeInputQueueCallback = callback; 434 } 435 436 @Override 437 public boolean isFloating() { 438 return mIsFloating; 439 } 440 441 /** 442 * Return a LayoutInflater instance that can be used to inflate XML view layout 443 * resources for use in this Window. 444 * 445 * @return LayoutInflater The shared LayoutInflater. 446 */ 447 @Override 448 public LayoutInflater getLayoutInflater() { 449 return mLayoutInflater; 450 } 451 452 @Override 453 public void setTitle(CharSequence title) { 454 if (mTitleView != null) { 455 mTitleView.setText(title); 456 } else if (mActionBar != null) { 457 mActionBar.setWindowTitle(title); 458 } 459 mTitle = title; 460 } 461 462 @Override 463 @Deprecated 464 public void setTitleColor(int textColor) { 465 if (mTitleView != null) { 466 mTitleView.setTextColor(textColor); 467 } 468 mTitleColor = textColor; 469 } 470 471 /** 472 * Prepares the panel to either be opened or chorded. This creates the Menu 473 * instance for the panel and populates it via the Activity callbacks. 474 * 475 * @param st The panel state to prepare. 476 * @param event The event that triggered the preparing of the panel. 477 * @return Whether the panel was prepared. If the panel should not be shown, 478 * returns false. 479 */ 480 public final boolean preparePanel(PanelFeatureState st, KeyEvent event) { 481 if (isDestroyed()) { 482 return false; 483 } 484 485 // Already prepared (isPrepared will be reset to false later) 486 if (st.isPrepared) { 487 return true; 488 } 489 490 if ((mPreparedPanel != null) && (mPreparedPanel != st)) { 491 // Another Panel is prepared and possibly open, so close it 492 closePanel(mPreparedPanel, false); 493 } 494 495 final Callback cb = getCallback(); 496 497 if (cb != null) { 498 st.createdPanelView = cb.onCreatePanelView(st.featureId); 499 } 500 501 final boolean isActionBarMenu = 502 (st.featureId == FEATURE_OPTIONS_PANEL || st.featureId == FEATURE_ACTION_BAR); 503 504 if (isActionBarMenu && mActionBar != null) { 505 // Enforce ordering guarantees around events so that the action bar never 506 // dispatches menu-related events before the panel is prepared. 507 mActionBar.setMenuPrepared(); 508 } 509 510 if (st.createdPanelView == null) { 511 // Init the panel state's menu--return false if init failed 512 if (st.menu == null || st.refreshMenuContent) { 513 if (st.menu == null) { 514 if (!initializePanelMenu(st) || (st.menu == null)) { 515 return false; 516 } 517 } 518 519 if (isActionBarMenu && mActionBar != null) { 520 if (mActionMenuPresenterCallback == null) { 521 mActionMenuPresenterCallback = new ActionMenuPresenterCallback(); 522 } 523 mActionBar.setMenu(st.menu, mActionMenuPresenterCallback); 524 } 525 526 // Call callback, and return if it doesn't want to display menu. 527 528 // Creating the panel menu will involve a lot of manipulation; 529 // don't dispatch change events to presenters until we're done. 530 st.menu.stopDispatchingItemsChanged(); 531 if ((cb == null) || !cb.onCreatePanelMenu(st.featureId, st.menu)) { 532 // Ditch the menu created above 533 st.setMenu(null); 534 535 if (isActionBarMenu && mActionBar != null) { 536 // Don't show it in the action bar either 537 mActionBar.setMenu(null, mActionMenuPresenterCallback); 538 } 539 540 return false; 541 } 542 543 st.refreshMenuContent = false; 544 } 545 546 // Callback and return if the callback does not want to show the menu 547 548 // Preparing the panel menu can involve a lot of manipulation; 549 // don't dispatch change events to presenters until we're done. 550 st.menu.stopDispatchingItemsChanged(); 551 552 // Restore action view state before we prepare. This gives apps 553 // an opportunity to override frozen/restored state in onPrepare. 554 if (st.frozenActionViewState != null) { 555 st.menu.restoreActionViewStates(st.frozenActionViewState); 556 st.frozenActionViewState = null; 557 } 558 559 if (!cb.onPreparePanel(st.featureId, st.createdPanelView, st.menu)) { 560 if (isActionBarMenu && mActionBar != null) { 561 // The app didn't want to show the menu for now but it still exists. 562 // Clear it out of the action bar. 563 mActionBar.setMenu(null, mActionMenuPresenterCallback); 564 } 565 st.menu.startDispatchingItemsChanged(); 566 return false; 567 } 568 569 // Set the proper keymap 570 KeyCharacterMap kmap = KeyCharacterMap.load( 571 event != null ? event.getDeviceId() : KeyCharacterMap.VIRTUAL_KEYBOARD); 572 st.qwertyMode = kmap.getKeyboardType() != KeyCharacterMap.NUMERIC; 573 st.menu.setQwertyMode(st.qwertyMode); 574 st.menu.startDispatchingItemsChanged(); 575 } 576 577 // Set other state 578 st.isPrepared = true; 579 st.isHandled = false; 580 mPreparedPanel = st; 581 582 return true; 583 } 584 585 @Override 586 public void onConfigurationChanged(Configuration newConfig) { 587 // Action bars handle their own menu state 588 if (mActionBar == null) { 589 PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false); 590 if ((st != null) && (st.menu != null)) { 591 if (st.isOpen) { 592 // Freeze state 593 final Bundle state = new Bundle(); 594 if (st.iconMenuPresenter != null) { 595 st.iconMenuPresenter.saveHierarchyState(state); 596 } 597 if (st.listMenuPresenter != null) { 598 st.listMenuPresenter.saveHierarchyState(state); 599 } 600 601 // Remove the menu views since they need to be recreated 602 // according to the new configuration 603 clearMenuViews(st); 604 605 // Re-open the same menu 606 reopenMenu(false); 607 608 // Restore state 609 if (st.iconMenuPresenter != null) { 610 st.iconMenuPresenter.restoreHierarchyState(state); 611 } 612 if (st.listMenuPresenter != null) { 613 st.listMenuPresenter.restoreHierarchyState(state); 614 } 615 616 } else { 617 // Clear menu views so on next menu opening, it will use 618 // the proper layout 619 clearMenuViews(st); 620 } 621 } 622 } 623 } 624 625 private static void clearMenuViews(PanelFeatureState st) { 626 // This can be called on config changes, so we should make sure 627 // the views will be reconstructed based on the new orientation, etc. 628 629 // Allow the callback to create a new panel view 630 st.createdPanelView = null; 631 632 // Causes the decor view to be recreated 633 st.refreshDecorView = true; 634 635 st.clearMenuPresenters(); 636 } 637 638 @Override 639 public final void openPanel(int featureId, KeyEvent event) { 640 if (featureId == FEATURE_OPTIONS_PANEL && mActionBar != null && 641 mActionBar.isOverflowReserved() && 642 !ViewConfiguration.get(getContext()).hasPermanentMenuKey()) { 643 if (mActionBar.getVisibility() == View.VISIBLE) { 644 mActionBar.showOverflowMenu(); 645 } 646 } else { 647 openPanel(getPanelState(featureId, true), event); 648 } 649 } 650 651 private void openPanel(final PanelFeatureState st, KeyEvent event) { 652 // System.out.println("Open panel: isOpen=" + st.isOpen); 653 654 // Already open, return 655 if (st.isOpen || isDestroyed()) { 656 return; 657 } 658 659 // Don't open an options panel for honeycomb apps on xlarge devices. 660 // (The app should be using an action bar for menu items.) 661 if (st.featureId == FEATURE_OPTIONS_PANEL) { 662 Context context = getContext(); 663 Configuration config = context.getResources().getConfiguration(); 664 boolean isXLarge = (config.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) == 665 Configuration.SCREENLAYOUT_SIZE_XLARGE; 666 boolean isHoneycombApp = context.getApplicationInfo().targetSdkVersion >= 667 android.os.Build.VERSION_CODES.HONEYCOMB; 668 669 if (isXLarge && isHoneycombApp) { 670 return; 671 } 672 } 673 674 Callback cb = getCallback(); 675 if ((cb != null) && (!cb.onMenuOpened(st.featureId, st.menu))) { 676 // Callback doesn't want the menu to open, reset any state 677 closePanel(st, true); 678 return; 679 } 680 681 final WindowManager wm = getWindowManager(); 682 if (wm == null) { 683 return; 684 } 685 686 // Prepare panel (should have been done before, but just in case) 687 if (!preparePanel(st, event)) { 688 return; 689 } 690 691 int width = WRAP_CONTENT; 692 if (st.decorView == null || st.refreshDecorView) { 693 if (st.decorView == null) { 694 // Initialize the panel decor, this will populate st.decorView 695 if (!initializePanelDecor(st) || (st.decorView == null)) 696 return; 697 } else if (st.refreshDecorView && (st.decorView.getChildCount() > 0)) { 698 // Decor needs refreshing, so remove its views 699 st.decorView.removeAllViews(); 700 } 701 702 // This will populate st.shownPanelView 703 if (!initializePanelContent(st) || !st.hasPanelItems()) { 704 return; 705 } 706 707 ViewGroup.LayoutParams lp = st.shownPanelView.getLayoutParams(); 708 if (lp == null) { 709 lp = new ViewGroup.LayoutParams(WRAP_CONTENT, WRAP_CONTENT); 710 } 711 712 int backgroundResId; 713 if (lp.width == ViewGroup.LayoutParams.MATCH_PARENT) { 714 // If the contents is fill parent for the width, set the 715 // corresponding background 716 backgroundResId = st.fullBackground; 717 width = MATCH_PARENT; 718 } else { 719 // Otherwise, set the normal panel background 720 backgroundResId = st.background; 721 } 722 st.decorView.setWindowBackground(getContext().getDrawable( 723 backgroundResId)); 724 725 ViewParent shownPanelParent = st.shownPanelView.getParent(); 726 if (shownPanelParent != null && shownPanelParent instanceof ViewGroup) { 727 ((ViewGroup) shownPanelParent).removeView(st.shownPanelView); 728 } 729 st.decorView.addView(st.shownPanelView, lp); 730 731 /* 732 * Give focus to the view, if it or one of its children does not 733 * already have it. 734 */ 735 if (!st.shownPanelView.hasFocus()) { 736 st.shownPanelView.requestFocus(); 737 } 738 } else if (!st.isInListMode()) { 739 width = MATCH_PARENT; 740 } else if (st.createdPanelView != null) { 741 // If we already had a panel view, carry width=MATCH_PARENT through 742 // as we did above when it was created. 743 ViewGroup.LayoutParams lp = st.createdPanelView.getLayoutParams(); 744 if (lp != null && lp.width == ViewGroup.LayoutParams.MATCH_PARENT) { 745 width = MATCH_PARENT; 746 } 747 } 748 749 st.isHandled = false; 750 751 WindowManager.LayoutParams lp = new WindowManager.LayoutParams( 752 width, WRAP_CONTENT, 753 st.x, st.y, WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG, 754 WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM 755 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, 756 st.decorView.mDefaultOpacity); 757 758 if (st.isCompact) { 759 lp.gravity = getOptionsPanelGravity(); 760 sRotationWatcher.addWindow(this); 761 } else { 762 lp.gravity = st.gravity; 763 } 764 765 lp.windowAnimations = st.windowAnimations; 766 767 wm.addView(st.decorView, lp); 768 st.isOpen = true; 769 // Log.v(TAG, "Adding main menu to window manager."); 770 } 771 772 @Override 773 public final void closePanel(int featureId) { 774 if (featureId == FEATURE_OPTIONS_PANEL && mActionBar != null && 775 mActionBar.isOverflowReserved() && 776 !ViewConfiguration.get(getContext()).hasPermanentMenuKey()) { 777 mActionBar.hideOverflowMenu(); 778 } else if (featureId == FEATURE_CONTEXT_MENU) { 779 closeContextMenu(); 780 } else { 781 closePanel(getPanelState(featureId, true), true); 782 } 783 } 784 785 /** 786 * Closes the given panel. 787 * 788 * @param st The panel to be closed. 789 * @param doCallback Whether to notify the callback that the panel was 790 * closed. If the panel is in the process of re-opening or 791 * opening another panel (e.g., menu opening a sub menu), the 792 * callback should not happen and this variable should be false. 793 * In addition, this method internally will only perform the 794 * callback if the panel is open. 795 */ 796 public final void closePanel(PanelFeatureState st, boolean doCallback) { 797 // System.out.println("Close panel: isOpen=" + st.isOpen); 798 if (doCallback && st.featureId == FEATURE_OPTIONS_PANEL && 799 mActionBar != null && mActionBar.isOverflowMenuShowing()) { 800 checkCloseActionMenu(st.menu); 801 return; 802 } 803 804 final ViewManager wm = getWindowManager(); 805 if ((wm != null) && st.isOpen) { 806 if (st.decorView != null) { 807 wm.removeView(st.decorView); 808 // Log.v(TAG, "Removing main menu from window manager."); 809 if (st.isCompact) { 810 sRotationWatcher.removeWindow(this); 811 } 812 } 813 814 if (doCallback) { 815 callOnPanelClosed(st.featureId, st, null); 816 } 817 } 818 819 st.isPrepared = false; 820 st.isHandled = false; 821 st.isOpen = false; 822 823 // This view is no longer shown, so null it out 824 st.shownPanelView = null; 825 826 if (st.isInExpandedMode) { 827 // Next time the menu opens, it should not be in expanded mode, so 828 // force a refresh of the decor 829 st.refreshDecorView = true; 830 st.isInExpandedMode = false; 831 } 832 833 if (mPreparedPanel == st) { 834 mPreparedPanel = null; 835 mPanelChordingKey = 0; 836 } 837 } 838 839 void checkCloseActionMenu(Menu menu) { 840 if (mClosingActionMenu) { 841 return; 842 } 843 844 mClosingActionMenu = true; 845 mActionBar.dismissPopupMenus(); 846 Callback cb = getCallback(); 847 if (cb != null && !isDestroyed()) { 848 cb.onPanelClosed(FEATURE_ACTION_BAR, menu); 849 } 850 mClosingActionMenu = false; 851 } 852 853 @Override 854 public final void togglePanel(int featureId, KeyEvent event) { 855 PanelFeatureState st = getPanelState(featureId, true); 856 if (st.isOpen) { 857 closePanel(st, true); 858 } else { 859 openPanel(st, event); 860 } 861 } 862 863 @Override 864 public void invalidatePanelMenu(int featureId) { 865 mInvalidatePanelMenuFeatures |= 1 << featureId; 866 867 if (!mInvalidatePanelMenuPosted && mDecor != null) { 868 mDecor.postOnAnimation(mInvalidatePanelMenuRunnable); 869 mInvalidatePanelMenuPosted = true; 870 } 871 } 872 873 void doInvalidatePanelMenu(int featureId) { 874 PanelFeatureState st = getPanelState(featureId, true); 875 Bundle savedActionViewStates = null; 876 if (st.menu != null) { 877 savedActionViewStates = new Bundle(); 878 st.menu.saveActionViewStates(savedActionViewStates); 879 if (savedActionViewStates.size() > 0) { 880 st.frozenActionViewState = savedActionViewStates; 881 } 882 // This will be started again when the panel is prepared. 883 st.menu.stopDispatchingItemsChanged(); 884 st.menu.clear(); 885 } 886 st.refreshMenuContent = true; 887 st.refreshDecorView = true; 888 889 // Prepare the options panel if we have an action bar 890 if ((featureId == FEATURE_ACTION_BAR || featureId == FEATURE_OPTIONS_PANEL) 891 && mActionBar != null) { 892 st = getPanelState(Window.FEATURE_OPTIONS_PANEL, false); 893 if (st != null) { 894 st.isPrepared = false; 895 preparePanel(st, null); 896 } 897 } 898 } 899 900 /** 901 * Called when the panel key is pushed down. 902 * @param featureId The feature ID of the relevant panel (defaults to FEATURE_OPTIONS_PANEL}. 903 * @param event The key event. 904 * @return Whether the key was handled. 905 */ 906 public final boolean onKeyDownPanel(int featureId, KeyEvent event) { 907 final int keyCode = event.getKeyCode(); 908 909 if (event.getRepeatCount() == 0) { 910 // The panel key was pushed, so set the chording key 911 mPanelChordingKey = keyCode; 912 913 PanelFeatureState st = getPanelState(featureId, true); 914 if (!st.isOpen) { 915 return preparePanel(st, event); 916 } 917 } 918 919 return false; 920 } 921 922 /** 923 * Called when the panel key is released. 924 * @param featureId The feature ID of the relevant panel (defaults to FEATURE_OPTIONS_PANEL}. 925 * @param event The key event. 926 */ 927 public final void onKeyUpPanel(int featureId, KeyEvent event) { 928 // The panel key was released, so clear the chording key 929 if (mPanelChordingKey != 0) { 930 mPanelChordingKey = 0; 931 932 if (event.isCanceled() || (mDecor != null && mDecor.mActionMode != null)) { 933 return; 934 } 935 936 boolean playSoundEffect = false; 937 final PanelFeatureState st = getPanelState(featureId, true); 938 if (featureId == FEATURE_OPTIONS_PANEL && mActionBar != null && 939 mActionBar.isOverflowReserved() && 940 !ViewConfiguration.get(getContext()).hasPermanentMenuKey()) { 941 if (mActionBar.getVisibility() == View.VISIBLE) { 942 if (!mActionBar.isOverflowMenuShowing()) { 943 if (!isDestroyed() && preparePanel(st, event)) { 944 playSoundEffect = mActionBar.showOverflowMenu(); 945 } 946 } else { 947 playSoundEffect = mActionBar.hideOverflowMenu(); 948 } 949 } 950 } else { 951 if (st.isOpen || st.isHandled) { 952 953 // Play the sound effect if the user closed an open menu (and not if 954 // they just released a menu shortcut) 955 playSoundEffect = st.isOpen; 956 957 // Close menu 958 closePanel(st, true); 959 960 } else if (st.isPrepared) { 961 boolean show = true; 962 if (st.refreshMenuContent) { 963 // Something may have invalidated the menu since we prepared it. 964 // Re-prepare it to refresh. 965 st.isPrepared = false; 966 show = preparePanel(st, event); 967 } 968 969 if (show) { 970 // Write 'menu opened' to event log 971 EventLog.writeEvent(50001, 0); 972 973 // Show menu 974 openPanel(st, event); 975 976 playSoundEffect = true; 977 } 978 } 979 } 980 981 if (playSoundEffect) { 982 AudioManager audioManager = (AudioManager) getContext().getSystemService( 983 Context.AUDIO_SERVICE); 984 if (audioManager != null) { 985 audioManager.playSoundEffect(AudioManager.FX_KEY_CLICK); 986 } else { 987 Log.w(TAG, "Couldn't get audio manager"); 988 } 989 } 990 } 991 } 992 993 @Override 994 public final void closeAllPanels() { 995 final ViewManager wm = getWindowManager(); 996 if (wm == null) { 997 return; 998 } 999 1000 final PanelFeatureState[] panels = mPanels; 1001 final int N = panels != null ? panels.length : 0; 1002 for (int i = 0; i < N; i++) { 1003 final PanelFeatureState panel = panels[i]; 1004 if (panel != null) { 1005 closePanel(panel, true); 1006 } 1007 } 1008 1009 closeContextMenu(); 1010 } 1011 1012 /** 1013 * Closes the context menu. This notifies the menu logic of the close, along 1014 * with dismissing it from the UI. 1015 */ 1016 private synchronized void closeContextMenu() { 1017 if (mContextMenu != null) { 1018 mContextMenu.close(); 1019 dismissContextMenu(); 1020 } 1021 } 1022 1023 /** 1024 * Dismisses just the context menu UI. To close the context menu, use 1025 * {@link #closeContextMenu()}. 1026 */ 1027 private synchronized void dismissContextMenu() { 1028 mContextMenu = null; 1029 1030 if (mContextMenuHelper != null) { 1031 mContextMenuHelper.dismiss(); 1032 mContextMenuHelper = null; 1033 } 1034 } 1035 1036 @Override 1037 public boolean performPanelShortcut(int featureId, int keyCode, KeyEvent event, int flags) { 1038 return performPanelShortcut(getPanelState(featureId, true), keyCode, event, flags); 1039 } 1040 1041 private boolean performPanelShortcut(PanelFeatureState st, int keyCode, KeyEvent event, 1042 int flags) { 1043 if (event.isSystem() || (st == null)) { 1044 return false; 1045 } 1046 1047 boolean handled = false; 1048 1049 // Only try to perform menu shortcuts if preparePanel returned true (possible false 1050 // return value from application not wanting to show the menu). 1051 if ((st.isPrepared || preparePanel(st, event)) && st.menu != null) { 1052 // The menu is prepared now, perform the shortcut on it 1053 handled = st.menu.performShortcut(keyCode, event, flags); 1054 } 1055 1056 if (handled) { 1057 // Mark as handled 1058 st.isHandled = true; 1059 1060 // Only close down the menu if we don't have an action bar keeping it open. 1061 if ((flags & Menu.FLAG_PERFORM_NO_CLOSE) == 0 && mActionBar == null) { 1062 closePanel(st, true); 1063 } 1064 } 1065 1066 return handled; 1067 } 1068 1069 @Override 1070 public boolean performPanelIdentifierAction(int featureId, int id, int flags) { 1071 1072 PanelFeatureState st = getPanelState(featureId, true); 1073 if (!preparePanel(st, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MENU))) { 1074 return false; 1075 } 1076 if (st.menu == null) { 1077 return false; 1078 } 1079 1080 boolean res = st.menu.performIdentifierAction(id, flags); 1081 1082 // Only close down the menu if we don't have an action bar keeping it open. 1083 if (mActionBar == null) { 1084 closePanel(st, true); 1085 } 1086 1087 return res; 1088 } 1089 1090 public PanelFeatureState findMenuPanel(Menu menu) { 1091 final PanelFeatureState[] panels = mPanels; 1092 final int N = panels != null ? panels.length : 0; 1093 for (int i = 0; i < N; i++) { 1094 final PanelFeatureState panel = panels[i]; 1095 if (panel != null && panel.menu == menu) { 1096 return panel; 1097 } 1098 } 1099 return null; 1100 } 1101 1102 public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) { 1103 final Callback cb = getCallback(); 1104 if (cb != null && !isDestroyed()) { 1105 final PanelFeatureState panel = findMenuPanel(menu.getRootMenu()); 1106 if (panel != null) { 1107 return cb.onMenuItemSelected(panel.featureId, item); 1108 } 1109 } 1110 return false; 1111 } 1112 1113 public void onMenuModeChange(MenuBuilder menu) { 1114 reopenMenu(true); 1115 } 1116 1117 private void reopenMenu(boolean toggleMenuMode) { 1118 if (mActionBar != null && mActionBar.isOverflowReserved() && 1119 (!ViewConfiguration.get(getContext()).hasPermanentMenuKey() || 1120 mActionBar.isOverflowMenuShowPending())) { 1121 final Callback cb = getCallback(); 1122 if (!mActionBar.isOverflowMenuShowing() || !toggleMenuMode) { 1123 if (cb != null && !isDestroyed() && mActionBar.getVisibility() == View.VISIBLE) { 1124 // If we have a menu invalidation pending, do it now. 1125 if (mInvalidatePanelMenuPosted && 1126 (mInvalidatePanelMenuFeatures & (1 << FEATURE_OPTIONS_PANEL)) != 0) { 1127 mDecor.removeCallbacks(mInvalidatePanelMenuRunnable); 1128 mInvalidatePanelMenuRunnable.run(); 1129 } 1130 1131 final PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, true); 1132 1133 // If we don't have a menu or we're waiting for a full content refresh, 1134 // forget it. This is a lingering event that no longer matters. 1135 if (st.menu != null && !st.refreshMenuContent && 1136 cb.onPreparePanel(FEATURE_OPTIONS_PANEL, st.createdPanelView, st.menu)) { 1137 cb.onMenuOpened(FEATURE_ACTION_BAR, st.menu); 1138 mActionBar.showOverflowMenu(); 1139 } 1140 } 1141 } else { 1142 mActionBar.hideOverflowMenu(); 1143 if (cb != null && !isDestroyed()) { 1144 final PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, true); 1145 cb.onPanelClosed(FEATURE_ACTION_BAR, st.menu); 1146 } 1147 } 1148 return; 1149 } 1150 1151 PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, true); 1152 1153 // Save the future expanded mode state since closePanel will reset it 1154 boolean newExpandedMode = toggleMenuMode ? !st.isInExpandedMode : st.isInExpandedMode; 1155 1156 st.refreshDecorView = true; 1157 closePanel(st, false); 1158 1159 // Set the expanded mode state 1160 st.isInExpandedMode = newExpandedMode; 1161 1162 openPanel(st, null); 1163 } 1164 1165 /** 1166 * Initializes the menu associated with the given panel feature state. You 1167 * must at the very least set PanelFeatureState.menu to the Menu to be 1168 * associated with the given panel state. The default implementation creates 1169 * a new menu for the panel state. 1170 * 1171 * @param st The panel whose menu is being initialized. 1172 * @return Whether the initialization was successful. 1173 */ 1174 protected boolean initializePanelMenu(final PanelFeatureState st) { 1175 Context context = getContext(); 1176 1177 // If we have an action bar, initialize the menu with a context themed for it. 1178 if ((st.featureId == FEATURE_OPTIONS_PANEL || st.featureId == FEATURE_ACTION_BAR) && 1179 mActionBar != null) { 1180 TypedValue outValue = new TypedValue(); 1181 Resources.Theme currentTheme = context.getTheme(); 1182 currentTheme.resolveAttribute(com.android.internal.R.attr.actionBarWidgetTheme, 1183 outValue, true); 1184 final int targetThemeRes = outValue.resourceId; 1185 1186 if (targetThemeRes != 0 && context.getThemeResId() != targetThemeRes) { 1187 context = new ContextThemeWrapper(context, targetThemeRes); 1188 } 1189 } 1190 1191 final MenuBuilder menu = new MenuBuilder(context); 1192 1193 menu.setCallback(this); 1194 st.setMenu(menu); 1195 1196 return true; 1197 } 1198 1199 /** 1200 * Perform initial setup of a panel. This should at the very least set the 1201 * style information in the PanelFeatureState and must set 1202 * PanelFeatureState.decor to the panel's window decor view. 1203 * 1204 * @param st The panel being initialized. 1205 */ 1206 protected boolean initializePanelDecor(PanelFeatureState st) { 1207 st.decorView = new DecorView(getContext(), st.featureId); 1208 st.gravity = Gravity.CENTER | Gravity.BOTTOM; 1209 st.setStyle(getContext()); 1210 1211 return true; 1212 } 1213 1214 /** 1215 * Determine the gravity value for the options panel. This can 1216 * differ in compact mode. 1217 * 1218 * @return gravity value to use for the panel window 1219 */ 1220 private int getOptionsPanelGravity() { 1221 try { 1222 return WindowManagerHolder.sWindowManager.getPreferredOptionsPanelGravity(); 1223 } catch (RemoteException ex) { 1224 Log.e(TAG, "Couldn't getOptionsPanelGravity; using default", ex); 1225 return Gravity.CENTER | Gravity.BOTTOM; 1226 } 1227 } 1228 1229 void onOptionsPanelRotationChanged() { 1230 final PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false); 1231 if (st == null) return; 1232 1233 final WindowManager.LayoutParams lp = st.decorView != null ? 1234 (WindowManager.LayoutParams) st.decorView.getLayoutParams() : null; 1235 if (lp != null) { 1236 lp.gravity = getOptionsPanelGravity(); 1237 final ViewManager wm = getWindowManager(); 1238 if (wm != null) { 1239 wm.updateViewLayout(st.decorView, lp); 1240 } 1241 } 1242 } 1243 1244 /** 1245 * Initializes the panel associated with the panel feature state. You must 1246 * at the very least set PanelFeatureState.panel to the View implementing 1247 * its contents. The default implementation gets the panel from the menu. 1248 * 1249 * @param st The panel state being initialized. 1250 * @return Whether the initialization was successful. 1251 */ 1252 protected boolean initializePanelContent(PanelFeatureState st) { 1253 if (st.createdPanelView != null) { 1254 st.shownPanelView = st.createdPanelView; 1255 return true; 1256 } 1257 1258 if (st.menu == null) { 1259 return false; 1260 } 1261 1262 if (mPanelMenuPresenterCallback == null) { 1263 mPanelMenuPresenterCallback = new PanelMenuPresenterCallback(); 1264 } 1265 1266 MenuView menuView = st.isInListMode() 1267 ? st.getListMenuView(getContext(), mPanelMenuPresenterCallback) 1268 : st.getIconMenuView(getContext(), mPanelMenuPresenterCallback); 1269 1270 st.shownPanelView = (View) menuView; 1271 1272 if (st.shownPanelView != null) { 1273 // Use the menu View's default animations if it has any 1274 final int defaultAnimations = menuView.getWindowAnimations(); 1275 if (defaultAnimations != 0) { 1276 st.windowAnimations = defaultAnimations; 1277 } 1278 return true; 1279 } else { 1280 return false; 1281 } 1282 } 1283 1284 @Override 1285 public boolean performContextMenuIdentifierAction(int id, int flags) { 1286 return (mContextMenu != null) ? mContextMenu.performIdentifierAction(id, flags) : false; 1287 } 1288 1289 @Override 1290 public final void setBackgroundDrawable(Drawable drawable) { 1291 if (drawable != mBackgroundDrawable || mBackgroundResource != 0) { 1292 mBackgroundResource = 0; 1293 mBackgroundDrawable = drawable; 1294 if (mDecor != null) { 1295 mDecor.setWindowBackground(drawable); 1296 } 1297 } 1298 } 1299 1300 @Override 1301 public final void setFeatureDrawableResource(int featureId, int resId) { 1302 if (resId != 0) { 1303 DrawableFeatureState st = getDrawableState(featureId, true); 1304 if (st.resid != resId) { 1305 st.resid = resId; 1306 st.uri = null; 1307 st.local = getContext().getDrawable(resId); 1308 updateDrawable(featureId, st, false); 1309 } 1310 } else { 1311 setFeatureDrawable(featureId, null); 1312 } 1313 } 1314 1315 @Override 1316 public final void setFeatureDrawableUri(int featureId, Uri uri) { 1317 if (uri != null) { 1318 DrawableFeatureState st = getDrawableState(featureId, true); 1319 if (st.uri == null || !st.uri.equals(uri)) { 1320 st.resid = 0; 1321 st.uri = uri; 1322 st.local = loadImageURI(uri); 1323 updateDrawable(featureId, st, false); 1324 } 1325 } else { 1326 setFeatureDrawable(featureId, null); 1327 } 1328 } 1329 1330 @Override 1331 public final void setFeatureDrawable(int featureId, Drawable drawable) { 1332 DrawableFeatureState st = getDrawableState(featureId, true); 1333 st.resid = 0; 1334 st.uri = null; 1335 if (st.local != drawable) { 1336 st.local = drawable; 1337 updateDrawable(featureId, st, false); 1338 } 1339 } 1340 1341 @Override 1342 public void setFeatureDrawableAlpha(int featureId, int alpha) { 1343 DrawableFeatureState st = getDrawableState(featureId, true); 1344 if (st.alpha != alpha) { 1345 st.alpha = alpha; 1346 updateDrawable(featureId, st, false); 1347 } 1348 } 1349 1350 protected final void setFeatureDefaultDrawable(int featureId, Drawable drawable) { 1351 DrawableFeatureState st = getDrawableState(featureId, true); 1352 if (st.def != drawable) { 1353 st.def = drawable; 1354 updateDrawable(featureId, st, false); 1355 } 1356 } 1357 1358 @Override 1359 public final void setFeatureInt(int featureId, int value) { 1360 // XXX Should do more management (as with drawable features) to 1361 // deal with interactions between multiple window policies. 1362 updateInt(featureId, value, false); 1363 } 1364 1365 /** 1366 * Update the state of a drawable feature. This should be called, for every 1367 * drawable feature supported, as part of onActive(), to make sure that the 1368 * contents of a containing window is properly updated. 1369 * 1370 * @see #onActive 1371 * @param featureId The desired drawable feature to change. 1372 * @param fromActive Always true when called from onActive(). 1373 */ 1374 protected final void updateDrawable(int featureId, boolean fromActive) { 1375 final DrawableFeatureState st = getDrawableState(featureId, false); 1376 if (st != null) { 1377 updateDrawable(featureId, st, fromActive); 1378 } 1379 } 1380 1381 /** 1382 * Called when a Drawable feature changes, for the window to update its 1383 * graphics. 1384 * 1385 * @param featureId The feature being changed. 1386 * @param drawable The new Drawable to show, or null if none. 1387 * @param alpha The new alpha blending of the Drawable. 1388 */ 1389 protected void onDrawableChanged(int featureId, Drawable drawable, int alpha) { 1390 ImageView view; 1391 if (featureId == FEATURE_LEFT_ICON) { 1392 view = getLeftIconView(); 1393 } else if (featureId == FEATURE_RIGHT_ICON) { 1394 view = getRightIconView(); 1395 } else { 1396 return; 1397 } 1398 1399 if (drawable != null) { 1400 drawable.setAlpha(alpha); 1401 view.setImageDrawable(drawable); 1402 view.setVisibility(View.VISIBLE); 1403 } else { 1404 view.setVisibility(View.GONE); 1405 } 1406 } 1407 1408 /** 1409 * Called when an int feature changes, for the window to update its 1410 * graphics. 1411 * 1412 * @param featureId The feature being changed. 1413 * @param value The new integer value. 1414 */ 1415 protected void onIntChanged(int featureId, int value) { 1416 if (featureId == FEATURE_PROGRESS || featureId == FEATURE_INDETERMINATE_PROGRESS) { 1417 updateProgressBars(value); 1418 } else if (featureId == FEATURE_CUSTOM_TITLE) { 1419 FrameLayout titleContainer = (FrameLayout) findViewById(com.android.internal.R.id.title_container); 1420 if (titleContainer != null) { 1421 mLayoutInflater.inflate(value, titleContainer); 1422 } 1423 } 1424 } 1425 1426 /** 1427 * Updates the progress bars that are shown in the title bar. 1428 * 1429 * @param value Can be one of {@link Window#PROGRESS_VISIBILITY_ON}, 1430 * {@link Window#PROGRESS_VISIBILITY_OFF}, 1431 * {@link Window#PROGRESS_INDETERMINATE_ON}, 1432 * {@link Window#PROGRESS_INDETERMINATE_OFF}, or a value 1433 * starting at {@link Window#PROGRESS_START} through 1434 * {@link Window#PROGRESS_END} for setting the default 1435 * progress (if {@link Window#PROGRESS_END} is given, 1436 * the progress bar widgets in the title will be hidden after an 1437 * animation), a value between 1438 * {@link Window#PROGRESS_SECONDARY_START} - 1439 * {@link Window#PROGRESS_SECONDARY_END} for the 1440 * secondary progress (if 1441 * {@link Window#PROGRESS_SECONDARY_END} is given, the 1442 * progress bar widgets will still be shown with the secondary 1443 * progress bar will be completely filled in.) 1444 */ 1445 private void updateProgressBars(int value) { 1446 ProgressBar circularProgressBar = getCircularProgressBar(true); 1447 ProgressBar horizontalProgressBar = getHorizontalProgressBar(true); 1448 1449 final int features = getLocalFeatures(); 1450 if (value == PROGRESS_VISIBILITY_ON) { 1451 if ((features & (1 << FEATURE_PROGRESS)) != 0) { 1452 int level = horizontalProgressBar.getProgress(); 1453 int visibility = (horizontalProgressBar.isIndeterminate() || level < 10000) ? 1454 View.VISIBLE : View.INVISIBLE; 1455 horizontalProgressBar.setVisibility(visibility); 1456 } 1457 if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) { 1458 circularProgressBar.setVisibility(View.VISIBLE); 1459 } 1460 } else if (value == PROGRESS_VISIBILITY_OFF) { 1461 if ((features & (1 << FEATURE_PROGRESS)) != 0) { 1462 horizontalProgressBar.setVisibility(View.GONE); 1463 } 1464 if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) { 1465 circularProgressBar.setVisibility(View.GONE); 1466 } 1467 } else if (value == PROGRESS_INDETERMINATE_ON) { 1468 horizontalProgressBar.setIndeterminate(true); 1469 } else if (value == PROGRESS_INDETERMINATE_OFF) { 1470 horizontalProgressBar.setIndeterminate(false); 1471 } else if (PROGRESS_START <= value && value <= PROGRESS_END) { 1472 // We want to set the progress value before testing for visibility 1473 // so that when the progress bar becomes visible again, it has the 1474 // correct level. 1475 horizontalProgressBar.setProgress(value - PROGRESS_START); 1476 1477 if (value < PROGRESS_END) { 1478 showProgressBars(horizontalProgressBar, circularProgressBar); 1479 } else { 1480 hideProgressBars(horizontalProgressBar, circularProgressBar); 1481 } 1482 } else if (PROGRESS_SECONDARY_START <= value && value <= PROGRESS_SECONDARY_END) { 1483 horizontalProgressBar.setSecondaryProgress(value - PROGRESS_SECONDARY_START); 1484 1485 showProgressBars(horizontalProgressBar, circularProgressBar); 1486 } 1487 1488 } 1489 1490 private void showProgressBars(ProgressBar horizontalProgressBar, ProgressBar spinnyProgressBar) { 1491 final int features = getLocalFeatures(); 1492 if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0 && 1493 spinnyProgressBar.getVisibility() == View.INVISIBLE) { 1494 spinnyProgressBar.setVisibility(View.VISIBLE); 1495 } 1496 // Only show the progress bars if the primary progress is not complete 1497 if ((features & (1 << FEATURE_PROGRESS)) != 0 && 1498 horizontalProgressBar.getProgress() < 10000) { 1499 horizontalProgressBar.setVisibility(View.VISIBLE); 1500 } 1501 } 1502 1503 private void hideProgressBars(ProgressBar horizontalProgressBar, ProgressBar spinnyProgressBar) { 1504 final int features = getLocalFeatures(); 1505 Animation anim = AnimationUtils.loadAnimation(getContext(), com.android.internal.R.anim.fade_out); 1506 anim.setDuration(1000); 1507 if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0 && 1508 spinnyProgressBar.getVisibility() == View.VISIBLE) { 1509 spinnyProgressBar.startAnimation(anim); 1510 spinnyProgressBar.setVisibility(View.INVISIBLE); 1511 } 1512 if ((features & (1 << FEATURE_PROGRESS)) != 0 && 1513 horizontalProgressBar.getVisibility() == View.VISIBLE) { 1514 horizontalProgressBar.startAnimation(anim); 1515 horizontalProgressBar.setVisibility(View.INVISIBLE); 1516 } 1517 } 1518 1519 @Override 1520 public void setIcon(int resId) { 1521 mIconRes = resId; 1522 mResourcesSetFlags |= FLAG_RESOURCE_SET_ICON; 1523 mResourcesSetFlags &= ~FLAG_RESOURCE_SET_ICON_FALLBACK; 1524 if (mActionBar != null) { 1525 mActionBar.setIcon(resId); 1526 } 1527 } 1528 1529 @Override 1530 public void setDefaultIcon(int resId) { 1531 if ((mResourcesSetFlags & FLAG_RESOURCE_SET_ICON) != 0) { 1532 return; 1533 } 1534 mIconRes = resId; 1535 if (mActionBar != null && (!mActionBar.hasIcon() || 1536 (mResourcesSetFlags & FLAG_RESOURCE_SET_ICON_FALLBACK) != 0)) { 1537 if (resId != 0) { 1538 mActionBar.setIcon(resId); 1539 mResourcesSetFlags &= ~FLAG_RESOURCE_SET_ICON_FALLBACK; 1540 } else { 1541 mActionBar.setIcon(getContext().getPackageManager().getDefaultActivityIcon()); 1542 mResourcesSetFlags |= FLAG_RESOURCE_SET_ICON_FALLBACK; 1543 } 1544 } 1545 } 1546 1547 @Override 1548 public void setLogo(int resId) { 1549 mLogoRes = resId; 1550 mResourcesSetFlags |= FLAG_RESOURCE_SET_LOGO; 1551 if (mActionBar != null) { 1552 mActionBar.setLogo(resId); 1553 } 1554 } 1555 1556 @Override 1557 public void setDefaultLogo(int resId) { 1558 if ((mResourcesSetFlags & FLAG_RESOURCE_SET_LOGO) != 0) { 1559 return; 1560 } 1561 mLogoRes = resId; 1562 if (mActionBar != null && !mActionBar.hasLogo()) { 1563 mActionBar.setLogo(resId); 1564 } 1565 } 1566 1567 @Override 1568 public void setLocalFocus(boolean hasFocus, boolean inTouchMode) { 1569 getViewRootImpl().windowFocusChanged(hasFocus, inTouchMode); 1570 1571 } 1572 1573 @Override 1574 public void injectInputEvent(InputEvent event) { 1575 getViewRootImpl().dispatchInputEvent(event); 1576 } 1577 1578 private ViewRootImpl getViewRootImpl() { 1579 if (mDecor != null) { 1580 ViewRootImpl viewRootImpl = mDecor.getViewRootImpl(); 1581 if (viewRootImpl != null) { 1582 return viewRootImpl; 1583 } 1584 } 1585 throw new IllegalStateException("view not added"); 1586 } 1587 1588 /** 1589 * Request that key events come to this activity. Use this if your activity 1590 * has no views with focus, but the activity still wants a chance to process 1591 * key events. 1592 */ 1593 @Override 1594 public void takeKeyEvents(boolean get) { 1595 mDecor.setFocusable(get); 1596 } 1597 1598 @Override 1599 public boolean superDispatchKeyEvent(KeyEvent event) { 1600 return mDecor.superDispatchKeyEvent(event); 1601 } 1602 1603 @Override 1604 public boolean superDispatchKeyShortcutEvent(KeyEvent event) { 1605 return mDecor.superDispatchKeyShortcutEvent(event); 1606 } 1607 1608 @Override 1609 public boolean superDispatchTouchEvent(MotionEvent event) { 1610 return mDecor.superDispatchTouchEvent(event); 1611 } 1612 1613 @Override 1614 public boolean superDispatchTrackballEvent(MotionEvent event) { 1615 return mDecor.superDispatchTrackballEvent(event); 1616 } 1617 1618 @Override 1619 public boolean superDispatchGenericMotionEvent(MotionEvent event) { 1620 return mDecor.superDispatchGenericMotionEvent(event); 1621 } 1622 1623 /** 1624 * A key was pressed down and not handled by anything else in the window. 1625 * 1626 * @see #onKeyUp 1627 * @see android.view.KeyEvent 1628 */ 1629 protected boolean onKeyDown(int featureId, int keyCode, KeyEvent event) { 1630 /* **************************************************************************** 1631 * HOW TO DECIDE WHERE YOUR KEY HANDLING GOES. 1632 * 1633 * If your key handling must happen before the app gets a crack at the event, 1634 * it goes in PhoneWindowManager. 1635 * 1636 * If your key handling should happen in all windows, and does not depend on 1637 * the state of the current application, other than that the current 1638 * application can override the behavior by handling the event itself, it 1639 * should go in PhoneFallbackEventHandler. 1640 * 1641 * Only if your handling depends on the window, and the fact that it has 1642 * a DecorView, should it go here. 1643 * ****************************************************************************/ 1644 1645 final KeyEvent.DispatcherState dispatcher = 1646 mDecor != null ? mDecor.getKeyDispatcherState() : null; 1647 //Log.i(TAG, "Key down: repeat=" + event.getRepeatCount() 1648 // + " flags=0x" + Integer.toHexString(event.getFlags())); 1649 1650 switch (keyCode) { 1651 case KeyEvent.KEYCODE_VOLUME_UP: 1652 case KeyEvent.KEYCODE_VOLUME_DOWN: 1653 case KeyEvent.KEYCODE_VOLUME_MUTE: { 1654 // Similar code is in PhoneFallbackEventHandler in case the window 1655 // doesn't have one of these. In this case, we execute it here and 1656 // eat the event instead, because we have mVolumeControlStreamType 1657 // and they don't. 1658 getAudioManager().handleKeyDown(event, mVolumeControlStreamType); 1659 return true; 1660 } 1661 1662 case KeyEvent.KEYCODE_MENU: { 1663 onKeyDownPanel((featureId < 0) ? FEATURE_OPTIONS_PANEL : featureId, event); 1664 return true; 1665 } 1666 1667 case KeyEvent.KEYCODE_BACK: { 1668 if (event.getRepeatCount() > 0) break; 1669 if (featureId < 0) break; 1670 // Currently don't do anything with long press. 1671 if (dispatcher != null) { 1672 dispatcher.startTracking(event, this); 1673 } 1674 return true; 1675 } 1676 1677 } 1678 1679 return false; 1680 } 1681 1682 private KeyguardManager getKeyguardManager() { 1683 if (mKeyguardManager == null) { 1684 mKeyguardManager = (KeyguardManager) getContext().getSystemService( 1685 Context.KEYGUARD_SERVICE); 1686 } 1687 return mKeyguardManager; 1688 } 1689 1690 AudioManager getAudioManager() { 1691 if (mAudioManager == null) { 1692 mAudioManager = (AudioManager)getContext().getSystemService(Context.AUDIO_SERVICE); 1693 } 1694 return mAudioManager; 1695 } 1696 1697 /** 1698 * A key was released and not handled by anything else in the window. 1699 * 1700 * @see #onKeyDown 1701 * @see android.view.KeyEvent 1702 */ 1703 protected boolean onKeyUp(int featureId, int keyCode, KeyEvent event) { 1704 final KeyEvent.DispatcherState dispatcher = 1705 mDecor != null ? mDecor.getKeyDispatcherState() : null; 1706 if (dispatcher != null) { 1707 dispatcher.handleUpEvent(event); 1708 } 1709 //Log.i(TAG, "Key up: repeat=" + event.getRepeatCount() 1710 // + " flags=0x" + Integer.toHexString(event.getFlags())); 1711 1712 switch (keyCode) { 1713 case KeyEvent.KEYCODE_VOLUME_UP: 1714 case KeyEvent.KEYCODE_VOLUME_DOWN: 1715 case KeyEvent.KEYCODE_VOLUME_MUTE: { 1716 // Similar code is in PhoneFallbackEventHandler in case the window 1717 // doesn't have one of these. In this case, we execute it here and 1718 // eat the event instead, because we have mVolumeControlStreamType 1719 // and they don't. 1720 getAudioManager().handleKeyUp(event, mVolumeControlStreamType); 1721 return true; 1722 } 1723 1724 case KeyEvent.KEYCODE_MENU: { 1725 onKeyUpPanel(featureId < 0 ? FEATURE_OPTIONS_PANEL : featureId, 1726 event); 1727 return true; 1728 } 1729 1730 case KeyEvent.KEYCODE_BACK: { 1731 if (featureId < 0) break; 1732 if (event.isTracking() && !event.isCanceled()) { 1733 if (featureId == FEATURE_OPTIONS_PANEL) { 1734 PanelFeatureState st = getPanelState(featureId, false); 1735 if (st != null && st.isInExpandedMode) { 1736 // If the user is in an expanded menu and hits back, it 1737 // should go back to the icon menu 1738 reopenMenu(true); 1739 return true; 1740 } 1741 } 1742 closePanel(featureId); 1743 return true; 1744 } 1745 break; 1746 } 1747 1748 case KeyEvent.KEYCODE_SEARCH: { 1749 /* 1750 * Do this in onKeyUp since the Search key is also used for 1751 * chording quick launch shortcuts. 1752 */ 1753 if (getKeyguardManager().inKeyguardRestrictedInputMode()) { 1754 break; 1755 } 1756 if (event.isTracking() && !event.isCanceled()) { 1757 launchDefaultSearch(); 1758 } 1759 return true; 1760 } 1761 } 1762 1763 return false; 1764 } 1765 1766 @Override 1767 protected void onActive() { 1768 } 1769 1770 @Override 1771 public final View getDecorView() { 1772 if (mDecor == null) { 1773 installDecor(); 1774 } 1775 return mDecor; 1776 } 1777 1778 @Override 1779 public final View peekDecorView() { 1780 return mDecor; 1781 } 1782 1783 static private final String FOCUSED_ID_TAG = "android:focusedViewId"; 1784 static private final String VIEWS_TAG = "android:views"; 1785 static private final String PANELS_TAG = "android:Panels"; 1786 static private final String ACTION_BAR_TAG = "android:ActionBar"; 1787 1788 /** {@inheritDoc} */ 1789 @Override 1790 public Bundle saveHierarchyState() { 1791 Bundle outState = new Bundle(); 1792 if (mContentParent == null) { 1793 return outState; 1794 } 1795 1796 SparseArray<Parcelable> states = new SparseArray<Parcelable>(); 1797 mContentParent.saveHierarchyState(states); 1798 outState.putSparseParcelableArray(VIEWS_TAG, states); 1799 1800 // save the focused view id 1801 View focusedView = mContentParent.findFocus(); 1802 if (focusedView != null) { 1803 if (focusedView.getId() != View.NO_ID) { 1804 outState.putInt(FOCUSED_ID_TAG, focusedView.getId()); 1805 } else { 1806 if (false) { 1807 Log.d(TAG, "couldn't save which view has focus because the focused view " 1808 + focusedView + " has no id."); 1809 } 1810 } 1811 } 1812 1813 // save the panels 1814 SparseArray<Parcelable> panelStates = new SparseArray<Parcelable>(); 1815 savePanelState(panelStates); 1816 if (panelStates.size() > 0) { 1817 outState.putSparseParcelableArray(PANELS_TAG, panelStates); 1818 } 1819 1820 if (mActionBar != null) { 1821 SparseArray<Parcelable> actionBarStates = new SparseArray<Parcelable>(); 1822 mActionBar.saveHierarchyState(actionBarStates); 1823 outState.putSparseParcelableArray(ACTION_BAR_TAG, actionBarStates); 1824 } 1825 1826 return outState; 1827 } 1828 1829 /** {@inheritDoc} */ 1830 @Override 1831 public void restoreHierarchyState(Bundle savedInstanceState) { 1832 if (mContentParent == null) { 1833 return; 1834 } 1835 1836 SparseArray<Parcelable> savedStates 1837 = savedInstanceState.getSparseParcelableArray(VIEWS_TAG); 1838 if (savedStates != null) { 1839 mContentParent.restoreHierarchyState(savedStates); 1840 } 1841 1842 // restore the focused view 1843 int focusedViewId = savedInstanceState.getInt(FOCUSED_ID_TAG, View.NO_ID); 1844 if (focusedViewId != View.NO_ID) { 1845 View needsFocus = mContentParent.findViewById(focusedViewId); 1846 if (needsFocus != null) { 1847 needsFocus.requestFocus(); 1848 } else { 1849 Log.w(TAG, 1850 "Previously focused view reported id " + focusedViewId 1851 + " during save, but can't be found during restore."); 1852 } 1853 } 1854 1855 // restore the panels 1856 SparseArray<Parcelable> panelStates = savedInstanceState.getSparseParcelableArray(PANELS_TAG); 1857 if (panelStates != null) { 1858 restorePanelState(panelStates); 1859 } 1860 1861 if (mActionBar != null) { 1862 SparseArray<Parcelable> actionBarStates = 1863 savedInstanceState.getSparseParcelableArray(ACTION_BAR_TAG); 1864 if (actionBarStates != null) { 1865 mActionBar.restoreHierarchyState(actionBarStates); 1866 } else { 1867 Log.w(TAG, "Missing saved instance states for action bar views! " + 1868 "State will not be restored."); 1869 } 1870 } 1871 } 1872 1873 /** 1874 * Invoked when the panels should freeze their state. 1875 * 1876 * @param icicles Save state into this. This is usually indexed by the 1877 * featureId. This will be given to {@link #restorePanelState} in the 1878 * future. 1879 */ 1880 private void savePanelState(SparseArray<Parcelable> icicles) { 1881 PanelFeatureState[] panels = mPanels; 1882 if (panels == null) { 1883 return; 1884 } 1885 1886 for (int curFeatureId = panels.length - 1; curFeatureId >= 0; curFeatureId--) { 1887 if (panels[curFeatureId] != null) { 1888 icicles.put(curFeatureId, panels[curFeatureId].onSaveInstanceState()); 1889 } 1890 } 1891 } 1892 1893 /** 1894 * Invoked when the panels should thaw their state from a previously frozen state. 1895 * 1896 * @param icicles The state saved by {@link #savePanelState} that needs to be thawed. 1897 */ 1898 private void restorePanelState(SparseArray<Parcelable> icicles) { 1899 PanelFeatureState st; 1900 int curFeatureId; 1901 for (int i = icicles.size() - 1; i >= 0; i--) { 1902 curFeatureId = icicles.keyAt(i); 1903 st = getPanelState(curFeatureId, false /* required */); 1904 if (st == null) { 1905 // The panel must not have been required, and is currently not around, skip it 1906 continue; 1907 } 1908 1909 st.onRestoreInstanceState(icicles.get(curFeatureId)); 1910 invalidatePanelMenu(curFeatureId); 1911 } 1912 1913 /* 1914 * Implementation note: call openPanelsAfterRestore later to actually open the 1915 * restored panels. 1916 */ 1917 } 1918 1919 /** 1920 * Opens the panels that have had their state restored. This should be 1921 * called sometime after {@link #restorePanelState} when it is safe to add 1922 * to the window manager. 1923 */ 1924 private void openPanelsAfterRestore() { 1925 PanelFeatureState[] panels = mPanels; 1926 1927 if (panels == null) { 1928 return; 1929 } 1930 1931 PanelFeatureState st; 1932 for (int i = panels.length - 1; i >= 0; i--) { 1933 st = panels[i]; 1934 // We restore the panel if it was last open; we skip it if it 1935 // now is open, to avoid a race condition if the user immediately 1936 // opens it when we are resuming. 1937 if (st != null) { 1938 st.applyFrozenState(); 1939 if (!st.isOpen && st.wasLastOpen) { 1940 st.isInExpandedMode = st.wasLastExpanded; 1941 openPanel(st, null); 1942 } 1943 } 1944 } 1945 } 1946 1947 private class PanelMenuPresenterCallback implements MenuPresenter.Callback { 1948 @Override 1949 public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { 1950 final Menu parentMenu = menu.getRootMenu(); 1951 final boolean isSubMenu = parentMenu != menu; 1952 final PanelFeatureState panel = findMenuPanel(isSubMenu ? parentMenu : menu); 1953 if (panel != null) { 1954 if (isSubMenu) { 1955 callOnPanelClosed(panel.featureId, panel, parentMenu); 1956 closePanel(panel, true); 1957 } else { 1958 // Close the panel and only do the callback if the menu is being 1959 // closed completely, not if opening a sub menu 1960 closePanel(panel, allMenusAreClosing); 1961 } 1962 } 1963 } 1964 1965 @Override 1966 public boolean onOpenSubMenu(MenuBuilder subMenu) { 1967 if (subMenu == null && hasFeature(FEATURE_ACTION_BAR)) { 1968 Callback cb = getCallback(); 1969 if (cb != null && !isDestroyed()) { 1970 cb.onMenuOpened(FEATURE_ACTION_BAR, subMenu); 1971 } 1972 } 1973 1974 return true; 1975 } 1976 } 1977 1978 private final class ActionMenuPresenterCallback implements MenuPresenter.Callback { 1979 @Override 1980 public boolean onOpenSubMenu(MenuBuilder subMenu) { 1981 Callback cb = getCallback(); 1982 if (cb != null) { 1983 cb.onMenuOpened(FEATURE_ACTION_BAR, subMenu); 1984 return true; 1985 } 1986 return false; 1987 } 1988 1989 @Override 1990 public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { 1991 checkCloseActionMenu(menu); 1992 } 1993 } 1994 1995 private final class DecorView extends FrameLayout implements RootViewSurfaceTaker { 1996 /* package */int mDefaultOpacity = PixelFormat.OPAQUE; 1997 1998 /** The feature ID of the panel, or -1 if this is the application's DecorView */ 1999 private final int mFeatureId; 2000 2001 private final Rect mDrawingBounds = new Rect(); 2002 2003 private final Rect mBackgroundPadding = new Rect(); 2004 2005 private final Rect mFramePadding = new Rect(); 2006 2007 private final Rect mFrameOffsets = new Rect(); 2008 2009 private boolean mChanging; 2010 2011 private Drawable mMenuBackground; 2012 private boolean mWatchingForMenu; 2013 private int mDownY; 2014 2015 private ActionMode mActionMode; 2016 private ActionBarContextView mActionModeView; 2017 private PopupWindow mActionModePopup; 2018 private Runnable mShowActionModePopup; 2019 2020 // View added at runtime to draw under the status bar area 2021 private View mStatusGuard; 2022 // View added at runtime to draw under the navigation bar area 2023 private View mNavigationGuard; 2024 2025 public DecorView(Context context, int featureId) { 2026 super(context); 2027 mFeatureId = featureId; 2028 } 2029 2030 @Override 2031 public boolean dispatchKeyEvent(KeyEvent event) { 2032 final int keyCode = event.getKeyCode(); 2033 final int action = event.getAction(); 2034 final boolean isDown = action == KeyEvent.ACTION_DOWN; 2035 2036 if (isDown && (event.getRepeatCount() == 0)) { 2037 // First handle chording of panel key: if a panel key is held 2038 // but not released, try to execute a shortcut in it. 2039 if ((mPanelChordingKey > 0) && (mPanelChordingKey != keyCode)) { 2040 boolean handled = dispatchKeyShortcutEvent(event); 2041 if (handled) { 2042 return true; 2043 } 2044 } 2045 2046 // If a panel is open, perform a shortcut on it without the 2047 // chorded panel key 2048 if ((mPreparedPanel != null) && mPreparedPanel.isOpen) { 2049 if (performPanelShortcut(mPreparedPanel, keyCode, event, 0)) { 2050 return true; 2051 } 2052 } 2053 } 2054 2055 if (!isDestroyed()) { 2056 final Callback cb = getCallback(); 2057 final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event) 2058 : super.dispatchKeyEvent(event); 2059 if (handled) { 2060 return true; 2061 } 2062 } 2063 2064 return isDown ? PhoneWindow.this.onKeyDown(mFeatureId, event.getKeyCode(), event) 2065 : PhoneWindow.this.onKeyUp(mFeatureId, event.getKeyCode(), event); 2066 } 2067 2068 @Override 2069 public boolean dispatchKeyShortcutEvent(KeyEvent ev) { 2070 // If the panel is already prepared, then perform the shortcut using it. 2071 boolean handled; 2072 if (mPreparedPanel != null) { 2073 handled = performPanelShortcut(mPreparedPanel, ev.getKeyCode(), ev, 2074 Menu.FLAG_PERFORM_NO_CLOSE); 2075 if (handled) { 2076 if (mPreparedPanel != null) { 2077 mPreparedPanel.isHandled = true; 2078 } 2079 return true; 2080 } 2081 } 2082 2083 // Shortcut not handled by the panel. Dispatch to the view hierarchy. 2084 final Callback cb = getCallback(); 2085 handled = cb != null && !isDestroyed() && mFeatureId < 0 2086 ? cb.dispatchKeyShortcutEvent(ev) : super.dispatchKeyShortcutEvent(ev); 2087 if (handled) { 2088 return true; 2089 } 2090 2091 // If the panel is not prepared, then we may be trying to handle a shortcut key 2092 // combination such as Control+C. Temporarily prepare the panel then mark it 2093 // unprepared again when finished to ensure that the panel will again be prepared 2094 // the next time it is shown for real. 2095 if (mPreparedPanel == null) { 2096 PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, true); 2097 preparePanel(st, ev); 2098 handled = performPanelShortcut(st, ev.getKeyCode(), ev, 2099 Menu.FLAG_PERFORM_NO_CLOSE); 2100 st.isPrepared = false; 2101 if (handled) { 2102 return true; 2103 } 2104 } 2105 return false; 2106 } 2107 2108 @Override 2109 public boolean dispatchTouchEvent(MotionEvent ev) { 2110 final Callback cb = getCallback(); 2111 return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev) 2112 : super.dispatchTouchEvent(ev); 2113 } 2114 2115 @Override 2116 public boolean dispatchTrackballEvent(MotionEvent ev) { 2117 final Callback cb = getCallback(); 2118 return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchTrackballEvent(ev) 2119 : super.dispatchTrackballEvent(ev); 2120 } 2121 2122 @Override 2123 public boolean dispatchGenericMotionEvent(MotionEvent ev) { 2124 final Callback cb = getCallback(); 2125 return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchGenericMotionEvent(ev) 2126 : super.dispatchGenericMotionEvent(ev); 2127 } 2128 2129 public boolean superDispatchKeyEvent(KeyEvent event) { 2130 if (super.dispatchKeyEvent(event)) { 2131 return true; 2132 } 2133 2134 // Not handled by the view hierarchy, does the action bar want it 2135 // to cancel out of something special? 2136 if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) { 2137 final int action = event.getAction(); 2138 // Back cancels action modes first. 2139 if (mActionMode != null) { 2140 if (action == KeyEvent.ACTION_UP) { 2141 mActionMode.finish(); 2142 } 2143 return true; 2144 } 2145 2146 // Next collapse any expanded action views. 2147 if (mActionBar != null && mActionBar.hasExpandedActionView()) { 2148 if (action == KeyEvent.ACTION_UP) { 2149 mActionBar.collapseActionView(); 2150 } 2151 return true; 2152 } 2153 } 2154 2155 return false; 2156 } 2157 2158 public boolean superDispatchKeyShortcutEvent(KeyEvent event) { 2159 return super.dispatchKeyShortcutEvent(event); 2160 } 2161 2162 public boolean superDispatchTouchEvent(MotionEvent event) { 2163 return super.dispatchTouchEvent(event); 2164 } 2165 2166 public boolean superDispatchTrackballEvent(MotionEvent event) { 2167 return super.dispatchTrackballEvent(event); 2168 } 2169 2170 public boolean superDispatchGenericMotionEvent(MotionEvent event) { 2171 return super.dispatchGenericMotionEvent(event); 2172 } 2173 2174 @Override 2175 public boolean onTouchEvent(MotionEvent event) { 2176 return onInterceptTouchEvent(event); 2177 } 2178 2179 private boolean isOutOfBounds(int x, int y) { 2180 return x < -5 || y < -5 || x > (getWidth() + 5) 2181 || y > (getHeight() + 5); 2182 } 2183 2184 @Override 2185 public boolean onInterceptTouchEvent(MotionEvent event) { 2186 int action = event.getAction(); 2187 if (mFeatureId >= 0) { 2188 if (action == MotionEvent.ACTION_DOWN) { 2189 int x = (int)event.getX(); 2190 int y = (int)event.getY(); 2191 if (isOutOfBounds(x, y)) { 2192 closePanel(mFeatureId); 2193 return true; 2194 } 2195 } 2196 } 2197 2198 if (!SWEEP_OPEN_MENU) { 2199 return false; 2200 } 2201 2202 if (mFeatureId >= 0) { 2203 if (action == MotionEvent.ACTION_DOWN) { 2204 Log.i(TAG, "Watchiing!"); 2205 mWatchingForMenu = true; 2206 mDownY = (int) event.getY(); 2207 return false; 2208 } 2209 2210 if (!mWatchingForMenu) { 2211 return false; 2212 } 2213 2214 int y = (int)event.getY(); 2215 if (action == MotionEvent.ACTION_MOVE) { 2216 if (y > (mDownY+30)) { 2217 Log.i(TAG, "Closing!"); 2218 closePanel(mFeatureId); 2219 mWatchingForMenu = false; 2220 return true; 2221 } 2222 } else if (action == MotionEvent.ACTION_UP) { 2223 mWatchingForMenu = false; 2224 } 2225 2226 return false; 2227 } 2228 2229 //Log.i(TAG, "Intercept: action=" + action + " y=" + event.getY() 2230 // + " (in " + getHeight() + ")"); 2231 2232 if (action == MotionEvent.ACTION_DOWN) { 2233 int y = (int)event.getY(); 2234 if (y >= (getHeight()-5) && !hasChildren()) { 2235 Log.i(TAG, "Watchiing!"); 2236 mWatchingForMenu = true; 2237 } 2238 return false; 2239 } 2240 2241 if (!mWatchingForMenu) { 2242 return false; 2243 } 2244 2245 int y = (int)event.getY(); 2246 if (action == MotionEvent.ACTION_MOVE) { 2247 if (y < (getHeight()-30)) { 2248 Log.i(TAG, "Opening!"); 2249 openPanel(FEATURE_OPTIONS_PANEL, new KeyEvent( 2250 KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MENU)); 2251 mWatchingForMenu = false; 2252 return true; 2253 } 2254 } else if (action == MotionEvent.ACTION_UP) { 2255 mWatchingForMenu = false; 2256 } 2257 2258 return false; 2259 } 2260 2261 @Override 2262 public void sendAccessibilityEvent(int eventType) { 2263 if (!AccessibilityManager.getInstance(mContext).isEnabled()) { 2264 return; 2265 } 2266 2267 // if we are showing a feature that should be announced and one child 2268 // make this child the event source since this is the feature itself 2269 // otherwise the callback will take over and announce its client 2270 if ((mFeatureId == FEATURE_OPTIONS_PANEL || 2271 mFeatureId == FEATURE_CONTEXT_MENU || 2272 mFeatureId == FEATURE_PROGRESS || 2273 mFeatureId == FEATURE_INDETERMINATE_PROGRESS) 2274 && getChildCount() == 1) { 2275 getChildAt(0).sendAccessibilityEvent(eventType); 2276 } else { 2277 super.sendAccessibilityEvent(eventType); 2278 } 2279 } 2280 2281 @Override 2282 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { 2283 final Callback cb = getCallback(); 2284 if (cb != null && !isDestroyed()) { 2285 if (cb.dispatchPopulateAccessibilityEvent(event)) { 2286 return true; 2287 } 2288 } 2289 return super.dispatchPopulateAccessibilityEvent(event); 2290 } 2291 2292 @Override 2293 protected boolean setFrame(int l, int t, int r, int b) { 2294 boolean changed = super.setFrame(l, t, r, b); 2295 if (changed) { 2296 final Rect drawingBounds = mDrawingBounds; 2297 getDrawingRect(drawingBounds); 2298 2299 Drawable fg = getForeground(); 2300 if (fg != null) { 2301 final Rect frameOffsets = mFrameOffsets; 2302 drawingBounds.left += frameOffsets.left; 2303 drawingBounds.top += frameOffsets.top; 2304 drawingBounds.right -= frameOffsets.right; 2305 drawingBounds.bottom -= frameOffsets.bottom; 2306 fg.setBounds(drawingBounds); 2307 final Rect framePadding = mFramePadding; 2308 drawingBounds.left += framePadding.left - frameOffsets.left; 2309 drawingBounds.top += framePadding.top - frameOffsets.top; 2310 drawingBounds.right -= framePadding.right - frameOffsets.right; 2311 drawingBounds.bottom -= framePadding.bottom - frameOffsets.bottom; 2312 } 2313 2314 Drawable bg = getBackground(); 2315 if (bg != null) { 2316 bg.setBounds(drawingBounds); 2317 } 2318 2319 if (SWEEP_OPEN_MENU) { 2320 if (mMenuBackground == null && mFeatureId < 0 2321 && getAttributes().height 2322 == WindowManager.LayoutParams.MATCH_PARENT) { 2323 mMenuBackground = getContext().getDrawable( 2324 com.android.internal.R.drawable.menu_background); 2325 } 2326 if (mMenuBackground != null) { 2327 mMenuBackground.setBounds(drawingBounds.left, 2328 drawingBounds.bottom-6, drawingBounds.right, 2329 drawingBounds.bottom+20); 2330 } 2331 } 2332 } 2333 return changed; 2334 } 2335 2336 @Override 2337 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 2338 final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics(); 2339 final boolean isPortrait = metrics.widthPixels < metrics.heightPixels; 2340 2341 final int widthMode = getMode(widthMeasureSpec); 2342 final int heightMode = getMode(heightMeasureSpec); 2343 2344 boolean fixedWidth = false; 2345 if (widthMode == AT_MOST) { 2346 final TypedValue tvw = isPortrait ? mFixedWidthMinor : mFixedWidthMajor; 2347 if (tvw != null && tvw.type != TypedValue.TYPE_NULL) { 2348 final int w; 2349 if (tvw.type == TypedValue.TYPE_DIMENSION) { 2350 w = (int) tvw.getDimension(metrics); 2351 } else if (tvw.type == TypedValue.TYPE_FRACTION) { 2352 w = (int) tvw.getFraction(metrics.widthPixels, metrics.widthPixels); 2353 } else { 2354 w = 0; 2355 } 2356 2357 if (w > 0) { 2358 final int widthSize = MeasureSpec.getSize(widthMeasureSpec); 2359 widthMeasureSpec = MeasureSpec.makeMeasureSpec( 2360 Math.min(w, widthSize), EXACTLY); 2361 fixedWidth = true; 2362 } 2363 } 2364 } 2365 2366 if (heightMode == AT_MOST) { 2367 final TypedValue tvh = isPortrait ? mFixedHeightMajor : mFixedHeightMinor; 2368 if (tvh != null && tvh.type != TypedValue.TYPE_NULL) { 2369 final int h; 2370 if (tvh.type == TypedValue.TYPE_DIMENSION) { 2371 h = (int) tvh.getDimension(metrics); 2372 } else if (tvh.type == TypedValue.TYPE_FRACTION) { 2373 h = (int) tvh.getFraction(metrics.heightPixels, metrics.heightPixels); 2374 } else { 2375 h = 0; 2376 } 2377 if (h > 0) { 2378 final int heightSize = MeasureSpec.getSize(heightMeasureSpec); 2379 heightMeasureSpec = MeasureSpec.makeMeasureSpec( 2380 Math.min(h, heightSize), EXACTLY); 2381 } 2382 } 2383 } 2384 2385 if (mOutsetBottom != null) { 2386 int mode = MeasureSpec.getMode(heightMeasureSpec); 2387 if (mode != MeasureSpec.UNSPECIFIED) { 2388 int outset = (int) mOutsetBottom.getDimension(metrics); 2389 int height = MeasureSpec.getSize(heightMeasureSpec); 2390 heightMeasureSpec = MeasureSpec.makeMeasureSpec(height + outset, mode); 2391 } 2392 } 2393 2394 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 2395 2396 int width = getMeasuredWidth(); 2397 boolean measure = false; 2398 2399 widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, EXACTLY); 2400 2401 if (!fixedWidth && widthMode == AT_MOST) { 2402 final TypedValue tv = isPortrait ? mMinWidthMinor : mMinWidthMajor; 2403 if (tv.type != TypedValue.TYPE_NULL) { 2404 final int min; 2405 if (tv.type == TypedValue.TYPE_DIMENSION) { 2406 min = (int)tv.getDimension(metrics); 2407 } else if (tv.type == TypedValue.TYPE_FRACTION) { 2408 min = (int)tv.getFraction(metrics.widthPixels, metrics.widthPixels); 2409 } else { 2410 min = 0; 2411 } 2412 2413 if (width < min) { 2414 widthMeasureSpec = MeasureSpec.makeMeasureSpec(min, EXACTLY); 2415 measure = true; 2416 } 2417 } 2418 } 2419 2420 // TODO: Support height? 2421 2422 if (measure) { 2423 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 2424 } 2425 } 2426 2427 @Override 2428 public void draw(Canvas canvas) { 2429 super.draw(canvas); 2430 2431 if (mMenuBackground != null) { 2432 mMenuBackground.draw(canvas); 2433 } 2434 } 2435 2436 2437 @Override 2438 public boolean showContextMenuForChild(View originalView) { 2439 // Reuse the context menu builder 2440 if (mContextMenu == null) { 2441 mContextMenu = new ContextMenuBuilder(getContext()); 2442 mContextMenu.setCallback(mContextMenuCallback); 2443 } else { 2444 mContextMenu.clearAll(); 2445 } 2446 2447 final MenuDialogHelper helper = mContextMenu.show(originalView, 2448 originalView.getWindowToken()); 2449 if (helper != null) { 2450 helper.setPresenterCallback(mContextMenuCallback); 2451 } else if (mContextMenuHelper != null) { 2452 // No menu to show, but if we have a menu currently showing it just became blank. 2453 // Close it. 2454 mContextMenuHelper.dismiss(); 2455 } 2456 mContextMenuHelper = helper; 2457 return helper != null; 2458 } 2459 2460 @Override 2461 public ActionMode startActionModeForChild(View originalView, 2462 ActionMode.Callback callback) { 2463 // originalView can be used here to be sure that we don't obscure 2464 // relevant content with the context mode UI. 2465 return startActionMode(callback); 2466 } 2467 2468 @Override 2469 public ActionMode startActionMode(ActionMode.Callback callback) { 2470 if (mActionMode != null) { 2471 mActionMode.finish(); 2472 } 2473 2474 final ActionMode.Callback wrappedCallback = new ActionModeCallbackWrapper(callback); 2475 ActionMode mode = null; 2476 if (getCallback() != null && !isDestroyed()) { 2477 try { 2478 mode = getCallback().onWindowStartingActionMode(wrappedCallback); 2479 } catch (AbstractMethodError ame) { 2480 // Older apps might not implement this callback method. 2481 } 2482 } 2483 if (mode != null) { 2484 mActionMode = mode; 2485 } else { 2486 if (mActionModeView == null) { 2487 if (isFloating()) { 2488 mActionModeView = new ActionBarContextView(mContext); 2489 mActionModePopup = new PopupWindow(mContext, null, 2490 com.android.internal.R.attr.actionModePopupWindowStyle); 2491 mActionModePopup.setWindowLayoutType( 2492 WindowManager.LayoutParams.TYPE_APPLICATION); 2493 mActionModePopup.setContentView(mActionModeView); 2494 mActionModePopup.setWidth(MATCH_PARENT); 2495 2496 TypedValue heightValue = new TypedValue(); 2497 mContext.getTheme().resolveAttribute( 2498 com.android.internal.R.attr.actionBarSize, heightValue, true); 2499 final int height = TypedValue.complexToDimensionPixelSize(heightValue.data, 2500 mContext.getResources().getDisplayMetrics()); 2501 mActionModeView.setContentHeight(height); 2502 mActionModePopup.setHeight(WRAP_CONTENT); 2503 mShowActionModePopup = new Runnable() { 2504 public void run() { 2505 mActionModePopup.showAtLocation( 2506 mActionModeView.getApplicationWindowToken(), 2507 Gravity.TOP | Gravity.FILL_HORIZONTAL, 0, 0); 2508 } 2509 }; 2510 } else { 2511 ViewStub stub = (ViewStub) findViewById( 2512 com.android.internal.R.id.action_mode_bar_stub); 2513 if (stub != null) { 2514 mActionModeView = (ActionBarContextView) stub.inflate(); 2515 } 2516 } 2517 } 2518 2519 if (mActionModeView != null) { 2520 mActionModeView.killMode(); 2521 mode = new StandaloneActionMode(getContext(), mActionModeView, wrappedCallback, 2522 mActionModePopup == null); 2523 if (callback.onCreateActionMode(mode, mode.getMenu())) { 2524 mode.invalidate(); 2525 mActionModeView.initForMode(mode); 2526 mActionModeView.setVisibility(View.VISIBLE); 2527 mActionMode = mode; 2528 if (mActionModePopup != null) { 2529 post(mShowActionModePopup); 2530 } 2531 mActionModeView.sendAccessibilityEvent( 2532 AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); 2533 } else { 2534 mActionMode = null; 2535 } 2536 } 2537 } 2538 if (mActionMode != null && getCallback() != null && !isDestroyed()) { 2539 try { 2540 getCallback().onActionModeStarted(mActionMode); 2541 } catch (AbstractMethodError ame) { 2542 // Older apps might not implement this callback method. 2543 } 2544 } 2545 return mActionMode; 2546 } 2547 2548 public void startChanging() { 2549 mChanging = true; 2550 } 2551 2552 public void finishChanging() { 2553 mChanging = false; 2554 drawableChanged(); 2555 } 2556 2557 public void setWindowBackground(Drawable drawable) { 2558 if (getBackground() != drawable) { 2559 setBackgroundDrawable(drawable); 2560 if (drawable != null) { 2561 drawable.getPadding(mBackgroundPadding); 2562 } else { 2563 mBackgroundPadding.setEmpty(); 2564 } 2565 drawableChanged(); 2566 } 2567 } 2568 2569 @Override 2570 public void setBackgroundDrawable(Drawable d) { 2571 super.setBackgroundDrawable(d); 2572 if (getWindowToken() != null) { 2573 updateWindowResizeState(); 2574 } 2575 } 2576 2577 public void setWindowFrame(Drawable drawable) { 2578 if (getForeground() != drawable) { 2579 setForeground(drawable); 2580 if (drawable != null) { 2581 drawable.getPadding(mFramePadding); 2582 } else { 2583 mFramePadding.setEmpty(); 2584 } 2585 drawableChanged(); 2586 } 2587 } 2588 2589 @Override 2590 protected boolean fitSystemWindows(Rect insets) { 2591 mFrameOffsets.set(insets); 2592 updateStatusGuard(insets); 2593 updateNavigationGuard(insets); 2594 if (getForeground() != null) { 2595 drawableChanged(); 2596 } 2597 return super.fitSystemWindows(insets); 2598 } 2599 2600 @Override 2601 public boolean isTransitionGroup() { 2602 return false; 2603 } 2604 2605 private void updateStatusGuard(Rect insets) { 2606 boolean showStatusGuard = false; 2607 // Show the status guard when the non-overlay contextual action bar is showing 2608 if (mActionModeView != null) { 2609 if (mActionModeView.getLayoutParams() instanceof MarginLayoutParams) { 2610 MarginLayoutParams mlp = (MarginLayoutParams) mActionModeView.getLayoutParams(); 2611 boolean mlpChanged = false; 2612 final boolean nonOverlayShown = 2613 (getLocalFeatures() & (1 << FEATURE_ACTION_MODE_OVERLAY)) == 0 2614 && mActionModeView.isShown(); 2615 if (nonOverlayShown) { 2616 // set top margin to top insets, show status guard 2617 if (mlp.topMargin != insets.top) { 2618 mlpChanged = true; 2619 mlp.topMargin = insets.top; 2620 if (mStatusGuard == null) { 2621 mStatusGuard = new View(mContext); 2622 mStatusGuard.setBackgroundColor(mContext.getResources() 2623 .getColor(R.color.input_method_navigation_guard)); 2624 addView(mStatusGuard, new LayoutParams( 2625 LayoutParams.MATCH_PARENT, mlp.topMargin, 2626 Gravity.START | Gravity.TOP)); 2627 } else { 2628 LayoutParams lp = (LayoutParams) mStatusGuard.getLayoutParams(); 2629 if (lp.height != mlp.topMargin) { 2630 lp.height = mlp.topMargin; 2631 mStatusGuard.setLayoutParams(lp); 2632 } 2633 } 2634 } 2635 insets.top = 0; // consume top insets 2636 showStatusGuard = true; 2637 } else { 2638 // reset top margin 2639 if (mlp.topMargin != 0) { 2640 mlpChanged = true; 2641 mlp.topMargin = 0; 2642 } 2643 } 2644 if (mlpChanged) { 2645 mActionModeView.setLayoutParams(mlp); 2646 } 2647 } 2648 } 2649 if (mStatusGuard != null) { 2650 mStatusGuard.setVisibility(showStatusGuard ? View.VISIBLE : View.GONE); 2651 } 2652 } 2653 2654 private void updateNavigationGuard(Rect insets) { 2655 // IMEs lay out below the nav bar, but the content view must not (for back compat) 2656 if (getAttributes().type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) { 2657 // prevent the content view from including the nav bar height 2658 if (mContentParent != null) { 2659 if (mContentParent.getLayoutParams() instanceof MarginLayoutParams) { 2660 MarginLayoutParams mlp = 2661 (MarginLayoutParams) mContentParent.getLayoutParams(); 2662 mlp.bottomMargin = insets.bottom; 2663 mContentParent.setLayoutParams(mlp); 2664 } 2665 } 2666 // position the navigation guard view, creating it if necessary 2667 if (mNavigationGuard == null) { 2668 mNavigationGuard = new View(mContext); 2669 mNavigationGuard.setBackgroundColor(mContext.getResources() 2670 .getColor(R.color.input_method_navigation_guard)); 2671 addView(mNavigationGuard, new LayoutParams( 2672 LayoutParams.MATCH_PARENT, insets.bottom, 2673 Gravity.START | Gravity.BOTTOM)); 2674 } else { 2675 LayoutParams lp = (LayoutParams) mNavigationGuard.getLayoutParams(); 2676 lp.height = insets.bottom; 2677 mNavigationGuard.setLayoutParams(lp); 2678 } 2679 } 2680 } 2681 2682 private void drawableChanged() { 2683 if (mChanging) { 2684 return; 2685 } 2686 2687 setPadding(mFramePadding.left + mBackgroundPadding.left, mFramePadding.top 2688 + mBackgroundPadding.top, mFramePadding.right + mBackgroundPadding.right, 2689 mFramePadding.bottom + mBackgroundPadding.bottom); 2690 requestLayout(); 2691 invalidate(); 2692 2693 int opacity = PixelFormat.OPAQUE; 2694 2695 // Note: if there is no background, we will assume opaque. The 2696 // common case seems to be that an application sets there to be 2697 // no background so it can draw everything itself. For that, 2698 // we would like to assume OPAQUE and let the app force it to 2699 // the slower TRANSLUCENT mode if that is really what it wants. 2700 Drawable bg = getBackground(); 2701 Drawable fg = getForeground(); 2702 if (bg != null) { 2703 if (fg == null) { 2704 opacity = bg.getOpacity(); 2705 } else if (mFramePadding.left <= 0 && mFramePadding.top <= 0 2706 && mFramePadding.right <= 0 && mFramePadding.bottom <= 0) { 2707 // If the frame padding is zero, then we can be opaque 2708 // if either the frame -or- the background is opaque. 2709 int fop = fg.getOpacity(); 2710 int bop = bg.getOpacity(); 2711 if (false) 2712 Log.v(TAG, "Background opacity: " + bop + ", Frame opacity: " + fop); 2713 if (fop == PixelFormat.OPAQUE || bop == PixelFormat.OPAQUE) { 2714 opacity = PixelFormat.OPAQUE; 2715 } else if (fop == PixelFormat.UNKNOWN) { 2716 opacity = bop; 2717 } else if (bop == PixelFormat.UNKNOWN) { 2718 opacity = fop; 2719 } else { 2720 opacity = Drawable.resolveOpacity(fop, bop); 2721 } 2722 } else { 2723 // For now we have to assume translucent if there is a 2724 // frame with padding... there is no way to tell if the 2725 // frame and background together will draw all pixels. 2726 if (false) 2727 Log.v(TAG, "Padding: " + mFramePadding); 2728 opacity = PixelFormat.TRANSLUCENT; 2729 } 2730 } 2731 2732 if (false) 2733 Log.v(TAG, "Background: " + bg + ", Frame: " + fg); 2734 if (false) 2735 Log.v(TAG, "Selected default opacity: " + opacity); 2736 2737 mDefaultOpacity = opacity; 2738 if (mFeatureId < 0) { 2739 setDefaultWindowFormat(opacity); 2740 } 2741 } 2742 2743 @Override 2744 public void onWindowFocusChanged(boolean hasWindowFocus) { 2745 super.onWindowFocusChanged(hasWindowFocus); 2746 2747 // If the user is chording a menu shortcut, release the chord since 2748 // this window lost focus 2749 if (!hasWindowFocus && mPanelChordingKey != 0) { 2750 closePanel(FEATURE_OPTIONS_PANEL); 2751 } 2752 2753 final Callback cb = getCallback(); 2754 if (cb != null && !isDestroyed() && mFeatureId < 0) { 2755 cb.onWindowFocusChanged(hasWindowFocus); 2756 } 2757 } 2758 2759 void updateWindowResizeState() { 2760 Drawable bg = getBackground(); 2761 hackTurnOffWindowResizeAnim(bg == null || bg.getOpacity() 2762 != PixelFormat.OPAQUE); 2763 } 2764 2765 @Override 2766 protected void onAttachedToWindow() { 2767 super.onAttachedToWindow(); 2768 2769 updateWindowResizeState(); 2770 2771 final Callback cb = getCallback(); 2772 if (cb != null && !isDestroyed() && mFeatureId < 0) { 2773 cb.onAttachedToWindow(); 2774 } 2775 2776 if (mFeatureId == -1) { 2777 /* 2778 * The main window has been attached, try to restore any panels 2779 * that may have been open before. This is called in cases where 2780 * an activity is being killed for configuration change and the 2781 * menu was open. When the activity is recreated, the menu 2782 * should be shown again. 2783 */ 2784 openPanelsAfterRestore(); 2785 } 2786 } 2787 2788 @Override 2789 protected void onDetachedFromWindow() { 2790 super.onDetachedFromWindow(); 2791 2792 final Callback cb = getCallback(); 2793 if (cb != null && mFeatureId < 0) { 2794 cb.onDetachedFromWindow(); 2795 } 2796 2797 if (mActionBar != null) { 2798 mActionBar.dismissPopupMenus(); 2799 } 2800 2801 if (mActionModePopup != null) { 2802 removeCallbacks(mShowActionModePopup); 2803 if (mActionModePopup.isShowing()) { 2804 mActionModePopup.dismiss(); 2805 } 2806 mActionModePopup = null; 2807 } 2808 2809 PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false); 2810 if (st != null && st.menu != null && mFeatureId < 0) { 2811 st.menu.close(); 2812 } 2813 } 2814 2815 @Override 2816 public void onCloseSystemDialogs(String reason) { 2817 if (mFeatureId >= 0) { 2818 closeAllPanels(); 2819 } 2820 } 2821 2822 public android.view.SurfaceHolder.Callback2 willYouTakeTheSurface() { 2823 return mFeatureId < 0 ? mTakeSurfaceCallback : null; 2824 } 2825 2826 public InputQueue.Callback willYouTakeTheInputQueue() { 2827 return mFeatureId < 0 ? mTakeInputQueueCallback : null; 2828 } 2829 2830 public void setSurfaceType(int type) { 2831 PhoneWindow.this.setType(type); 2832 } 2833 2834 public void setSurfaceFormat(int format) { 2835 PhoneWindow.this.setFormat(format); 2836 } 2837 2838 public void setSurfaceKeepScreenOn(boolean keepOn) { 2839 if (keepOn) PhoneWindow.this.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 2840 else PhoneWindow.this.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 2841 } 2842 2843 /** 2844 * Clears out internal reference when the action mode is destroyed. 2845 */ 2846 private class ActionModeCallbackWrapper implements ActionMode.Callback { 2847 private ActionMode.Callback mWrapped; 2848 2849 public ActionModeCallbackWrapper(ActionMode.Callback wrapped) { 2850 mWrapped = wrapped; 2851 } 2852 2853 public boolean onCreateActionMode(ActionMode mode, Menu menu) { 2854 return mWrapped.onCreateActionMode(mode, menu); 2855 } 2856 2857 public boolean onPrepareActionMode(ActionMode mode, Menu menu) { 2858 requestFitSystemWindows(); 2859 return mWrapped.onPrepareActionMode(mode, menu); 2860 } 2861 2862 public boolean onActionItemClicked(ActionMode mode, MenuItem item) { 2863 return mWrapped.onActionItemClicked(mode, item); 2864 } 2865 2866 public void onDestroyActionMode(ActionMode mode) { 2867 mWrapped.onDestroyActionMode(mode); 2868 if (mActionModePopup != null) { 2869 removeCallbacks(mShowActionModePopup); 2870 mActionModePopup.dismiss(); 2871 } else if (mActionModeView != null) { 2872 mActionModeView.setVisibility(GONE); 2873 } 2874 if (mActionModeView != null) { 2875 mActionModeView.removeAllViews(); 2876 } 2877 if (getCallback() != null && !isDestroyed()) { 2878 try { 2879 getCallback().onActionModeFinished(mActionMode); 2880 } catch (AbstractMethodError ame) { 2881 // Older apps might not implement this callback method. 2882 } 2883 } 2884 mActionMode = null; 2885 requestFitSystemWindows(); 2886 } 2887 } 2888 } 2889 2890 protected DecorView generateDecor() { 2891 return new DecorView(getContext(), -1); 2892 } 2893 2894 protected void setFeatureFromAttrs(int featureId, TypedArray attrs, 2895 int drawableAttr, int alphaAttr) { 2896 Drawable d = attrs.getDrawable(drawableAttr); 2897 if (d != null) { 2898 requestFeature(featureId); 2899 setFeatureDefaultDrawable(featureId, d); 2900 } 2901 if ((getFeatures() & (1 << featureId)) != 0) { 2902 int alpha = attrs.getInt(alphaAttr, -1); 2903 if (alpha >= 0) { 2904 setFeatureDrawableAlpha(featureId, alpha); 2905 } 2906 } 2907 } 2908 2909 protected ViewGroup generateLayout(DecorView decor) { 2910 // Apply data from current theme. 2911 2912 TypedArray a = getWindowStyle(); 2913 2914 if (false) { 2915 System.out.println("From style:"); 2916 String s = "Attrs:"; 2917 for (int i = 0; i < com.android.internal.R.styleable.Window.length; i++) { 2918 s = s + " " + Integer.toHexString(com.android.internal.R.styleable.Window[i]) + "=" 2919 + a.getString(i); 2920 } 2921 System.out.println(s); 2922 } 2923 2924 mIsFloating = a.getBoolean(com.android.internal.R.styleable.Window_windowIsFloating, false); 2925 int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR) 2926 & (~getForcedWindowFlags()); 2927 if (mIsFloating) { 2928 setLayout(WRAP_CONTENT, WRAP_CONTENT); 2929 setFlags(0, flagsToUpdate); 2930 } else { 2931 setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate); 2932 } 2933 2934 if (a.getBoolean(com.android.internal.R.styleable.Window_windowNoTitle, false)) { 2935 requestFeature(FEATURE_NO_TITLE); 2936 } else if (a.getBoolean(com.android.internal.R.styleable.Window_windowActionBar, false)) { 2937 // Don't allow an action bar if there is no title. 2938 requestFeature(FEATURE_ACTION_BAR); 2939 } 2940 2941 if (a.getBoolean(com.android.internal.R.styleable.Window_windowActionBarOverlay, false)) { 2942 requestFeature(FEATURE_ACTION_BAR_OVERLAY); 2943 } 2944 2945 if (a.getBoolean(com.android.internal.R.styleable.Window_windowActionModeOverlay, false)) { 2946 requestFeature(FEATURE_ACTION_MODE_OVERLAY); 2947 } 2948 2949 if (a.getBoolean(com.android.internal.R.styleable.Window_windowSwipeToDismiss, false)) { 2950 requestFeature(FEATURE_SWIPE_TO_DISMISS); 2951 } 2952 2953 if (a.getBoolean(com.android.internal.R.styleable.Window_windowFullscreen, false)) { 2954 setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags())); 2955 } 2956 2957 if (a.getBoolean(com.android.internal.R.styleable.Window_windowTranslucentStatus, 2958 false)) { 2959 setFlags(FLAG_TRANSLUCENT_STATUS, FLAG_TRANSLUCENT_STATUS 2960 & (~getForcedWindowFlags())); 2961 } 2962 2963 if (a.getBoolean(com.android.internal.R.styleable.Window_windowTranslucentNavigation, 2964 false)) { 2965 setFlags(FLAG_TRANSLUCENT_NAVIGATION, FLAG_TRANSLUCENT_NAVIGATION 2966 & (~getForcedWindowFlags())); 2967 } 2968 2969 if (a.getBoolean(com.android.internal.R.styleable.Window_windowOverscan, false)) { 2970 setFlags(FLAG_LAYOUT_IN_OVERSCAN, FLAG_LAYOUT_IN_OVERSCAN&(~getForcedWindowFlags())); 2971 } 2972 2973 if (a.getBoolean(com.android.internal.R.styleable.Window_windowShowWallpaper, false)) { 2974 setFlags(FLAG_SHOW_WALLPAPER, FLAG_SHOW_WALLPAPER&(~getForcedWindowFlags())); 2975 } 2976 2977 if (a.getBoolean(com.android.internal.R.styleable.Window_windowEnableSplitTouch, 2978 getContext().getApplicationInfo().targetSdkVersion 2979 >= android.os.Build.VERSION_CODES.HONEYCOMB)) { 2980 setFlags(FLAG_SPLIT_TOUCH, FLAG_SPLIT_TOUCH&(~getForcedWindowFlags())); 2981 } 2982 2983 a.getValue(com.android.internal.R.styleable.Window_windowMinWidthMajor, mMinWidthMajor); 2984 a.getValue(com.android.internal.R.styleable.Window_windowMinWidthMinor, mMinWidthMinor); 2985 if (a.hasValue(com.android.internal.R.styleable.Window_windowFixedWidthMajor)) { 2986 if (mFixedWidthMajor == null) mFixedWidthMajor = new TypedValue(); 2987 a.getValue(com.android.internal.R.styleable.Window_windowFixedWidthMajor, 2988 mFixedWidthMajor); 2989 } 2990 if (a.hasValue(com.android.internal.R.styleable.Window_windowFixedWidthMinor)) { 2991 if (mFixedWidthMinor == null) mFixedWidthMinor = new TypedValue(); 2992 a.getValue(com.android.internal.R.styleable.Window_windowFixedWidthMinor, 2993 mFixedWidthMinor); 2994 } 2995 if (a.hasValue(com.android.internal.R.styleable.Window_windowFixedHeightMajor)) { 2996 if (mFixedHeightMajor == null) mFixedHeightMajor = new TypedValue(); 2997 a.getValue(com.android.internal.R.styleable.Window_windowFixedHeightMajor, 2998 mFixedHeightMajor); 2999 } 3000 if (a.hasValue(com.android.internal.R.styleable.Window_windowFixedHeightMinor)) { 3001 if (mFixedHeightMinor == null) mFixedHeightMinor = new TypedValue(); 3002 a.getValue(com.android.internal.R.styleable.Window_windowFixedHeightMinor, 3003 mFixedHeightMinor); 3004 } 3005 if (a.getBoolean(com.android.internal.R.styleable.Window_windowContentTransitions, false)) { 3006 requestFeature(FEATURE_CONTENT_TRANSITIONS); 3007 } 3008 if (a.hasValue(com.android.internal.R.styleable.Window_windowOutsetBottom)) { 3009 if (mOutsetBottom == null) mOutsetBottom = new TypedValue(); 3010 a.getValue(com.android.internal.R.styleable.Window_windowOutsetBottom, mOutsetBottom); 3011 } 3012 3013 final Context context = getContext(); 3014 final int targetSdk = context.getApplicationInfo().targetSdkVersion; 3015 final boolean targetPreHoneycomb = targetSdk < android.os.Build.VERSION_CODES.HONEYCOMB; 3016 final boolean targetPreIcs = targetSdk < android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH; 3017 final boolean targetHcNeedsOptions = context.getResources().getBoolean( 3018 com.android.internal.R.bool.target_honeycomb_needs_options_menu); 3019 final boolean noActionBar = !hasFeature(FEATURE_ACTION_BAR) || hasFeature(FEATURE_NO_TITLE); 3020 3021 if (targetPreHoneycomb || (targetPreIcs && targetHcNeedsOptions && noActionBar)) { 3022 addFlags(WindowManager.LayoutParams.FLAG_NEEDS_MENU_KEY); 3023 } else { 3024 clearFlags(WindowManager.LayoutParams.FLAG_NEEDS_MENU_KEY); 3025 } 3026 3027 if (mAlwaysReadCloseOnTouchAttr || getContext().getApplicationInfo().targetSdkVersion 3028 >= android.os.Build.VERSION_CODES.HONEYCOMB) { 3029 if (a.getBoolean( 3030 com.android.internal.R.styleable.Window_windowCloseOnTouchOutside, 3031 false)) { 3032 setCloseOnTouchOutsideIfNotSet(true); 3033 } 3034 } 3035 3036 WindowManager.LayoutParams params = getAttributes(); 3037 3038 if (!hasSoftInputMode()) { 3039 params.softInputMode = a.getInt( 3040 com.android.internal.R.styleable.Window_windowSoftInputMode, 3041 params.softInputMode); 3042 } 3043 3044 if (a.getBoolean(com.android.internal.R.styleable.Window_backgroundDimEnabled, 3045 mIsFloating)) { 3046 /* All dialogs should have the window dimmed */ 3047 if ((getForcedWindowFlags()&WindowManager.LayoutParams.FLAG_DIM_BEHIND) == 0) { 3048 params.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND; 3049 } 3050 if (!haveDimAmount()) { 3051 params.dimAmount = a.getFloat( 3052 android.R.styleable.Window_backgroundDimAmount, 0.5f); 3053 } 3054 } 3055 3056 if (params.windowAnimations == 0) { 3057 params.windowAnimations = a.getResourceId( 3058 com.android.internal.R.styleable.Window_windowAnimationStyle, 0); 3059 } 3060 3061 // The rest are only done if this window is not embedded; otherwise, 3062 // the values are inherited from our container. 3063 if (getContainer() == null) { 3064 if (mBackgroundDrawable == null) { 3065 if (mBackgroundResource == 0) { 3066 mBackgroundResource = a.getResourceId( 3067 com.android.internal.R.styleable.Window_windowBackground, 0); 3068 } 3069 if (mFrameResource == 0) { 3070 mFrameResource = a.getResourceId(com.android.internal.R.styleable.Window_windowFrame, 0); 3071 } 3072 if (false) { 3073 System.out.println("Background: " 3074 + Integer.toHexString(mBackgroundResource) + " Frame: " 3075 + Integer.toHexString(mFrameResource)); 3076 } 3077 } 3078 mTextColor = a.getColor(com.android.internal.R.styleable.Window_textColor, 0xFF000000); 3079 } 3080 3081 // Inflate the window decor. 3082 3083 int layoutResource; 3084 int features = getLocalFeatures(); 3085 // System.out.println("Features: 0x" + Integer.toHexString(features)); 3086 if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) { 3087 layoutResource = com.android.internal.R.layout.screen_swipe_dismiss; 3088 } else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) { 3089 if (mIsFloating) { 3090 TypedValue res = new TypedValue(); 3091 getContext().getTheme().resolveAttribute( 3092 com.android.internal.R.attr.dialogTitleIconsDecorLayout, res, true); 3093 layoutResource = res.resourceId; 3094 } else { 3095 layoutResource = com.android.internal.R.layout.screen_title_icons; 3096 } 3097 // XXX Remove this once action bar supports these features. 3098 removeFeature(FEATURE_ACTION_BAR); 3099 // System.out.println("Title Icons!"); 3100 } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0 3101 && (features & (1 << FEATURE_ACTION_BAR)) == 0) { 3102 // Special case for a window with only a progress bar (and title). 3103 // XXX Need to have a no-title version of embedded windows. 3104 layoutResource = com.android.internal.R.layout.screen_progress; 3105 // System.out.println("Progress!"); 3106 } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) { 3107 // Special case for a window with a custom title. 3108 // If the window is floating, we need a dialog layout 3109 if (mIsFloating) { 3110 TypedValue res = new TypedValue(); 3111 getContext().getTheme().resolveAttribute( 3112 com.android.internal.R.attr.dialogCustomTitleDecorLayout, res, true); 3113 layoutResource = res.resourceId; 3114 } else { 3115 layoutResource = com.android.internal.R.layout.screen_custom_title; 3116 } 3117 // XXX Remove this once action bar supports these features. 3118 removeFeature(FEATURE_ACTION_BAR); 3119 } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) { 3120 // If no other features and not embedded, only need a title. 3121 // If the window is floating, we need a dialog layout 3122 if (mIsFloating) { 3123 TypedValue res = new TypedValue(); 3124 getContext().getTheme().resolveAttribute( 3125 com.android.internal.R.attr.dialogTitleDecorLayout, res, true); 3126 layoutResource = res.resourceId; 3127 } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) { 3128 layoutResource = com.android.internal.R.layout.screen_action_bar; 3129 } else { 3130 layoutResource = com.android.internal.R.layout.screen_title; 3131 } 3132 // System.out.println("Title!"); 3133 } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) { 3134 layoutResource = com.android.internal.R.layout.screen_simple_overlay_action_mode; 3135 } else { 3136 // Embedded, so no decoration is needed. 3137 layoutResource = com.android.internal.R.layout.screen_simple; 3138 // System.out.println("Simple!"); 3139 } 3140 3141 mDecor.startChanging(); 3142 3143 View in = mLayoutInflater.inflate(layoutResource, null); 3144 decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); 3145 3146 ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); 3147 if (contentParent == null) { 3148 throw new RuntimeException("Window couldn't find content container view"); 3149 } 3150 3151 if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) { 3152 ProgressBar progress = getCircularProgressBar(false); 3153 if (progress != null) { 3154 progress.setIndeterminate(true); 3155 } 3156 } 3157 3158 if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) { 3159 registerSwipeCallbacks(); 3160 } 3161 3162 // Remaining setup -- of background and title -- that only applies 3163 // to top-level windows. 3164 if (getContainer() == null) { 3165 Drawable drawable = mBackgroundDrawable; 3166 if (mBackgroundResource != 0) { 3167 drawable = getContext().getDrawable(mBackgroundResource); 3168 } 3169 mDecor.setWindowBackground(drawable); 3170 drawable = null; 3171 if (mFrameResource != 0) { 3172 drawable = getContext().getDrawable(mFrameResource); 3173 } 3174 mDecor.setWindowFrame(drawable); 3175 3176 // System.out.println("Text=" + Integer.toHexString(mTextColor) + 3177 // " Sel=" + Integer.toHexString(mTextSelectedColor) + 3178 // " Title=" + Integer.toHexString(mTitleColor)); 3179 3180 if (mTitleColor == 0) { 3181 mTitleColor = mTextColor; 3182 } 3183 3184 if (mTitle != null) { 3185 setTitle(mTitle); 3186 } 3187 setTitleColor(mTitleColor); 3188 } 3189 3190 mDecor.finishChanging(); 3191 3192 return contentParent; 3193 } 3194 3195 /** @hide */ 3196 public void alwaysReadCloseOnTouchAttr() { 3197 mAlwaysReadCloseOnTouchAttr = true; 3198 } 3199 3200 private void installDecor() { 3201 if (mDecor == null) { 3202 mDecor = generateDecor(); 3203 mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); 3204 mDecor.setIsRootNamespace(true); 3205 if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) { 3206 mDecor.postOnAnimation(mInvalidatePanelMenuRunnable); 3207 } 3208 } 3209 if (mContentParent == null) { 3210 mContentParent = generateLayout(mDecor); 3211 3212 // Set up decor part of UI to ignore fitsSystemWindows if appropriate. 3213 mDecor.makeOptionalFitsSystemWindows(); 3214 3215 mTitleView = (TextView)findViewById(com.android.internal.R.id.title); 3216 if (mTitleView != null) { 3217 mTitleView.setLayoutDirection(mDecor.getLayoutDirection()); 3218 if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) { 3219 View titleContainer = findViewById(com.android.internal.R.id.title_container); 3220 if (titleContainer != null) { 3221 titleContainer.setVisibility(View.GONE); 3222 } else { 3223 mTitleView.setVisibility(View.GONE); 3224 } 3225 if (mContentParent instanceof FrameLayout) { 3226 ((FrameLayout)mContentParent).setForeground(null); 3227 } 3228 } else { 3229 mTitleView.setText(mTitle); 3230 } 3231 } else { 3232 mActionBar = (ActionBarView) findViewById(com.android.internal.R.id.action_bar); 3233 if (mActionBar != null) { 3234 mActionBar.setWindowCallback(getCallback()); 3235 if (mActionBar.getTitle() == null) { 3236 mActionBar.setWindowTitle(mTitle); 3237 } 3238 final int localFeatures = getLocalFeatures(); 3239 if ((localFeatures & (1 << FEATURE_PROGRESS)) != 0) { 3240 mActionBar.initProgress(); 3241 } 3242 if ((localFeatures & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) { 3243 mActionBar.initIndeterminateProgress(); 3244 } 3245 3246 final ActionBarOverlayLayout abol = (ActionBarOverlayLayout) findViewById( 3247 com.android.internal.R.id.action_bar_overlay_layout); 3248 if (abol != null) { 3249 abol.setOverlayMode( 3250 (localFeatures & (1 << FEATURE_ACTION_BAR_OVERLAY)) != 0); 3251 } 3252 3253 boolean splitActionBar = false; 3254 final boolean splitWhenNarrow = 3255 (mUiOptions & ActivityInfo.UIOPTION_SPLIT_ACTION_BAR_WHEN_NARROW) != 0; 3256 if (splitWhenNarrow) { 3257 splitActionBar = getContext().getResources().getBoolean( 3258 com.android.internal.R.bool.split_action_bar_is_narrow); 3259 } else { 3260 splitActionBar = getWindowStyle().getBoolean( 3261 com.android.internal.R.styleable.Window_windowSplitActionBar, false); 3262 } 3263 final ActionBarContainer splitView = (ActionBarContainer) findViewById( 3264 com.android.internal.R.id.split_action_bar); 3265 if (splitView != null) { 3266 mActionBar.setSplitView(splitView); 3267 mActionBar.setSplitActionBar(splitActionBar); 3268 mActionBar.setSplitWhenNarrow(splitWhenNarrow); 3269 3270 final ActionBarContextView cab = (ActionBarContextView) findViewById( 3271 com.android.internal.R.id.action_context_bar); 3272 cab.setSplitView(splitView); 3273 cab.setSplitActionBar(splitActionBar); 3274 cab.setSplitWhenNarrow(splitWhenNarrow); 3275 } else if (splitActionBar) { 3276 Log.e(TAG, "Requested split action bar with " + 3277 "incompatible window decor! Ignoring request."); 3278 } 3279 3280 if ((mResourcesSetFlags & FLAG_RESOURCE_SET_ICON) != 0 || 3281 (mIconRes != 0 && !mActionBar.hasIcon())) { 3282 mActionBar.setIcon(mIconRes); 3283 } else if ((mResourcesSetFlags & FLAG_RESOURCE_SET_ICON) == 0 && 3284 mIconRes == 0 && !mActionBar.hasIcon()) { 3285 mActionBar.setIcon( 3286 getContext().getPackageManager().getDefaultActivityIcon()); 3287 mResourcesSetFlags |= FLAG_RESOURCE_SET_ICON_FALLBACK; 3288 } 3289 if ((mResourcesSetFlags & FLAG_RESOURCE_SET_LOGO) != 0 || 3290 (mLogoRes != 0 && !mActionBar.hasLogo())) { 3291 mActionBar.setLogo(mLogoRes); 3292 } 3293 3294 // Post the panel invalidate for later; avoid application onCreateOptionsMenu 3295 // being called in the middle of onCreate or similar. 3296 mDecor.post(new Runnable() { 3297 public void run() { 3298 // Invalidate if the panel menu hasn't been created before this. 3299 PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false); 3300 if (!isDestroyed() && (st == null || st.menu == null)) { 3301 invalidatePanelMenu(FEATURE_ACTION_BAR); 3302 } 3303 } 3304 }); 3305 } 3306 } 3307 3308 // Only inflate or create a new TransitionManager if the caller hasn't 3309 // already set a custom one. 3310 if (hasFeature(FEATURE_CONTENT_TRANSITIONS) && mTransitionManager == null) { 3311 final int transitionRes = getWindowStyle().getResourceId( 3312 com.android.internal.R.styleable.Window_windowContentTransitionManager, 0); 3313 if (transitionRes != 0) { 3314 final TransitionInflater inflater = TransitionInflater.from(getContext()); 3315 mTransitionManager = inflater.inflateTransitionManager(transitionRes, 3316 mContentParent); 3317 } else { 3318 mTransitionManager = new TransitionManager(); 3319 } 3320 } 3321 } 3322 } 3323 3324 private Drawable loadImageURI(Uri uri) { 3325 try { 3326 final Context context = getContext(); 3327 return Drawable.createFromStreamThemed( 3328 context.getContentResolver().openInputStream(uri), null, context.getTheme()); 3329 } catch (Exception e) { 3330 Log.w(TAG, "Unable to open content: " + uri); 3331 } 3332 return null; 3333 } 3334 3335 private DrawableFeatureState getDrawableState(int featureId, boolean required) { 3336 if ((getFeatures() & (1 << featureId)) == 0) { 3337 if (!required) { 3338 return null; 3339 } 3340 throw new RuntimeException("The feature has not been requested"); 3341 } 3342 3343 DrawableFeatureState[] ar; 3344 if ((ar = mDrawables) == null || ar.length <= featureId) { 3345 DrawableFeatureState[] nar = new DrawableFeatureState[featureId + 1]; 3346 if (ar != null) { 3347 System.arraycopy(ar, 0, nar, 0, ar.length); 3348 } 3349 mDrawables = ar = nar; 3350 } 3351 3352 DrawableFeatureState st = ar[featureId]; 3353 if (st == null) { 3354 ar[featureId] = st = new DrawableFeatureState(featureId); 3355 } 3356 return st; 3357 } 3358 3359 /** 3360 * Gets a panel's state based on its feature ID. 3361 * 3362 * @param featureId The feature ID of the panel. 3363 * @param required Whether the panel is required (if it is required and it 3364 * isn't in our features, this throws an exception). 3365 * @return The panel state. 3366 */ 3367 private PanelFeatureState getPanelState(int featureId, boolean required) { 3368 return getPanelState(featureId, required, null); 3369 } 3370 3371 /** 3372 * Gets a panel's state based on its feature ID. 3373 * 3374 * @param featureId The feature ID of the panel. 3375 * @param required Whether the panel is required (if it is required and it 3376 * isn't in our features, this throws an exception). 3377 * @param convertPanelState Optional: If the panel state does not exist, use 3378 * this as the panel state. 3379 * @return The panel state. 3380 */ 3381 private PanelFeatureState getPanelState(int featureId, boolean required, 3382 PanelFeatureState convertPanelState) { 3383 if ((getFeatures() & (1 << featureId)) == 0) { 3384 if (!required) { 3385 return null; 3386 } 3387 throw new RuntimeException("The feature has not been requested"); 3388 } 3389 3390 PanelFeatureState[] ar; 3391 if ((ar = mPanels) == null || ar.length <= featureId) { 3392 PanelFeatureState[] nar = new PanelFeatureState[featureId + 1]; 3393 if (ar != null) { 3394 System.arraycopy(ar, 0, nar, 0, ar.length); 3395 } 3396 mPanels = ar = nar; 3397 } 3398 3399 PanelFeatureState st = ar[featureId]; 3400 if (st == null) { 3401 ar[featureId] = st = (convertPanelState != null) 3402 ? convertPanelState 3403 : new PanelFeatureState(featureId); 3404 } 3405 return st; 3406 } 3407 3408 @Override 3409 public final void setChildDrawable(int featureId, Drawable drawable) { 3410 DrawableFeatureState st = getDrawableState(featureId, true); 3411 st.child = drawable; 3412 updateDrawable(featureId, st, false); 3413 } 3414 3415 @Override 3416 public final void setChildInt(int featureId, int value) { 3417 updateInt(featureId, value, false); 3418 } 3419 3420 @Override 3421 public boolean isShortcutKey(int keyCode, KeyEvent event) { 3422 PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, true); 3423 return st.menu != null && st.menu.isShortcutKey(keyCode, event); 3424 } 3425 3426 private void updateDrawable(int featureId, DrawableFeatureState st, boolean fromResume) { 3427 // Do nothing if the decor is not yet installed... an update will 3428 // need to be forced when we eventually become active. 3429 if (mContentParent == null) { 3430 return; 3431 } 3432 3433 final int featureMask = 1 << featureId; 3434 3435 if ((getFeatures() & featureMask) == 0 && !fromResume) { 3436 return; 3437 } 3438 3439 Drawable drawable = null; 3440 if (st != null) { 3441 drawable = st.child; 3442 if (drawable == null) 3443 drawable = st.local; 3444 if (drawable == null) 3445 drawable = st.def; 3446 } 3447 if ((getLocalFeatures() & featureMask) == 0) { 3448 if (getContainer() != null) { 3449 if (isActive() || fromResume) { 3450 getContainer().setChildDrawable(featureId, drawable); 3451 } 3452 } 3453 } else if (st != null && (st.cur != drawable || st.curAlpha != st.alpha)) { 3454 // System.out.println("Drawable changed: old=" + st.cur 3455 // + ", new=" + drawable); 3456 st.cur = drawable; 3457 st.curAlpha = st.alpha; 3458 onDrawableChanged(featureId, drawable, st.alpha); 3459 } 3460 } 3461 3462 private void updateInt(int featureId, int value, boolean fromResume) { 3463 3464 // Do nothing if the decor is not yet installed... an update will 3465 // need to be forced when we eventually become active. 3466 if (mContentParent == null) { 3467 return; 3468 } 3469 3470 final int featureMask = 1 << featureId; 3471 3472 if ((getFeatures() & featureMask) == 0 && !fromResume) { 3473 return; 3474 } 3475 3476 if ((getLocalFeatures() & featureMask) == 0) { 3477 if (getContainer() != null) { 3478 getContainer().setChildInt(featureId, value); 3479 } 3480 } else { 3481 onIntChanged(featureId, value); 3482 } 3483 } 3484 3485 private ImageView getLeftIconView() { 3486 if (mLeftIconView != null) { 3487 return mLeftIconView; 3488 } 3489 if (mContentParent == null) { 3490 installDecor(); 3491 } 3492 return (mLeftIconView = (ImageView)findViewById(com.android.internal.R.id.left_icon)); 3493 } 3494 3495 private ProgressBar getCircularProgressBar(boolean shouldInstallDecor) { 3496 if (mCircularProgressBar != null) { 3497 return mCircularProgressBar; 3498 } 3499 if (mContentParent == null && shouldInstallDecor) { 3500 installDecor(); 3501 } 3502 mCircularProgressBar = (ProgressBar) findViewById(com.android.internal.R.id.progress_circular); 3503 if (mCircularProgressBar != null) { 3504 mCircularProgressBar.setVisibility(View.INVISIBLE); 3505 } 3506 return mCircularProgressBar; 3507 } 3508 3509 private ProgressBar getHorizontalProgressBar(boolean shouldInstallDecor) { 3510 if (mHorizontalProgressBar != null) { 3511 return mHorizontalProgressBar; 3512 } 3513 if (mContentParent == null && shouldInstallDecor) { 3514 installDecor(); 3515 } 3516 mHorizontalProgressBar = (ProgressBar) findViewById(com.android.internal.R.id.progress_horizontal); 3517 if (mHorizontalProgressBar != null) { 3518 mHorizontalProgressBar.setVisibility(View.INVISIBLE); 3519 } 3520 return mHorizontalProgressBar; 3521 } 3522 3523 private ImageView getRightIconView() { 3524 if (mRightIconView != null) { 3525 return mRightIconView; 3526 } 3527 if (mContentParent == null) { 3528 installDecor(); 3529 } 3530 return (mRightIconView = (ImageView)findViewById(com.android.internal.R.id.right_icon)); 3531 } 3532 3533 private void registerSwipeCallbacks() { 3534 SwipeDismissLayout swipeDismiss = 3535 (SwipeDismissLayout) findViewById(com.android.internal.R.id.content); 3536 swipeDismiss.setOnDismissedListener(new SwipeDismissLayout.OnDismissedListener() { 3537 @Override 3538 public void onDismissed(SwipeDismissLayout layout) { 3539 Callback cb = getCallback(); 3540 if (cb != null) { 3541 try { 3542 cb.onWindowDismissed(); 3543 } catch (AbstractMethodError e) { 3544 Log.e(TAG, "onWindowDismissed not implemented in " + 3545 cb.getClass().getSimpleName(), e); 3546 } 3547 } 3548 } 3549 }); 3550 swipeDismiss.setOnSwipeProgressChangedListener( 3551 new SwipeDismissLayout.OnSwipeProgressChangedListener() { 3552 private boolean mIsTranslucent = false; 3553 3554 @Override 3555 public void onSwipeProgressChanged( 3556 SwipeDismissLayout layout, float progress, float translate) { 3557 WindowManager.LayoutParams newParams = getAttributes(); 3558 newParams.x = (int) translate; 3559 setAttributes(newParams); 3560 3561 int flags = 0; 3562 if (newParams.x == 0) { 3563 flags = FLAG_FULLSCREEN; 3564 } else { 3565 flags = FLAG_LAYOUT_NO_LIMITS; 3566 } 3567 setFlags(flags, FLAG_FULLSCREEN | FLAG_LAYOUT_NO_LIMITS); 3568 } 3569 3570 @Override 3571 public void onSwipeCancelled(SwipeDismissLayout layout) { 3572 WindowManager.LayoutParams newParams = getAttributes(); 3573 newParams.x = 0; 3574 setAttributes(newParams); 3575 setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN | FLAG_LAYOUT_NO_LIMITS); 3576 } 3577 }); 3578 } 3579 3580 /** 3581 * Helper method for calling the {@link Callback#onPanelClosed(int, Menu)} 3582 * callback. This method will grab whatever extra state is needed for the 3583 * callback that isn't given in the parameters. If the panel is not open, 3584 * this will not perform the callback. 3585 * 3586 * @param featureId Feature ID of the panel that was closed. Must be given. 3587 * @param panel Panel that was closed. Optional but useful if there is no 3588 * menu given. 3589 * @param menu The menu that was closed. Optional, but give if you have. 3590 */ 3591 private void callOnPanelClosed(int featureId, PanelFeatureState panel, Menu menu) { 3592 final Callback cb = getCallback(); 3593 if (cb == null) 3594 return; 3595 3596 // Try to get a menu 3597 if (menu == null) { 3598 // Need a panel to grab the menu, so try to get that 3599 if (panel == null) { 3600 if ((featureId >= 0) && (featureId < mPanels.length)) { 3601 panel = mPanels[featureId]; 3602 } 3603 } 3604 3605 if (panel != null) { 3606 // menu still may be null, which is okay--we tried our best 3607 menu = panel.menu; 3608 } 3609 } 3610 3611 // If the panel is not open, do not callback 3612 if ((panel != null) && (!panel.isOpen)) 3613 return; 3614 3615 if (!isDestroyed()) { 3616 cb.onPanelClosed(featureId, menu); 3617 } 3618 } 3619 3620 /** 3621 * Helper method for adding launch-search to most applications. Opens the 3622 * search window using default settings. 3623 * 3624 * @return true if search window opened 3625 */ 3626 private boolean launchDefaultSearch() { 3627 final Callback cb = getCallback(); 3628 if (cb == null || isDestroyed()) { 3629 return false; 3630 } else { 3631 sendCloseSystemWindows("search"); 3632 return cb.onSearchRequested(); 3633 } 3634 } 3635 3636 @Override 3637 public void setVolumeControlStream(int streamType) { 3638 mVolumeControlStreamType = streamType; 3639 } 3640 3641 @Override 3642 public int getVolumeControlStream() { 3643 return mVolumeControlStreamType; 3644 } 3645 3646 private boolean isTranslucent() { 3647 TypedArray a = getWindowStyle(); 3648 return a.getBoolean(a.getResourceId( 3649 com.android.internal.R.styleable.Window_windowIsTranslucent, 0), false); 3650 } 3651 3652 private static final class DrawableFeatureState { 3653 DrawableFeatureState(int _featureId) { 3654 featureId = _featureId; 3655 } 3656 3657 final int featureId; 3658 3659 int resid; 3660 3661 Uri uri; 3662 3663 Drawable local; 3664 3665 Drawable child; 3666 3667 Drawable def; 3668 3669 Drawable cur; 3670 3671 int alpha = 255; 3672 3673 int curAlpha = 255; 3674 } 3675 3676 private static final class PanelFeatureState { 3677 3678 /** Feature ID for this panel. */ 3679 int featureId; 3680 3681 // Information pulled from the style for this panel. 3682 3683 int background; 3684 3685 /** The background when the panel spans the entire available width. */ 3686 int fullBackground; 3687 3688 int gravity; 3689 3690 int x; 3691 3692 int y; 3693 3694 int windowAnimations; 3695 3696 /** Dynamic state of the panel. */ 3697 DecorView decorView; 3698 3699 /** The panel that was returned by onCreatePanelView(). */ 3700 View createdPanelView; 3701 3702 /** The panel that we are actually showing. */ 3703 View shownPanelView; 3704 3705 /** Use {@link #setMenu} to set this. */ 3706 MenuBuilder menu; 3707 3708 IconMenuPresenter iconMenuPresenter; 3709 ListMenuPresenter listMenuPresenter; 3710 3711 /** true if this menu will show in single-list compact mode */ 3712 boolean isCompact; 3713 3714 /** Theme resource ID for list elements of the panel menu */ 3715 int listPresenterTheme; 3716 3717 /** 3718 * Whether the panel has been prepared (see 3719 * {@link PhoneWindow#preparePanel}). 3720 */ 3721 boolean isPrepared; 3722 3723 /** 3724 * Whether an item's action has been performed. This happens in obvious 3725 * scenarios (user clicks on menu item), but can also happen with 3726 * chording menu+(shortcut key). 3727 */ 3728 boolean isHandled; 3729 3730 boolean isOpen; 3731 3732 /** 3733 * True if the menu is in expanded mode, false if the menu is in icon 3734 * mode 3735 */ 3736 boolean isInExpandedMode; 3737 3738 public boolean qwertyMode; 3739 3740 boolean refreshDecorView; 3741 3742 boolean refreshMenuContent; 3743 3744 boolean wasLastOpen; 3745 3746 boolean wasLastExpanded; 3747 3748 /** 3749 * Contains the state of the menu when told to freeze. 3750 */ 3751 Bundle frozenMenuState; 3752 3753 /** 3754 * Contains the state of associated action views when told to freeze. 3755 * These are saved across invalidations. 3756 */ 3757 Bundle frozenActionViewState; 3758 3759 PanelFeatureState(int featureId) { 3760 this.featureId = featureId; 3761 3762 refreshDecorView = false; 3763 } 3764 3765 public boolean isInListMode() { 3766 return isInExpandedMode || isCompact; 3767 } 3768 3769 public boolean hasPanelItems() { 3770 if (shownPanelView == null) return false; 3771 if (createdPanelView != null) return true; 3772 3773 if (isCompact || isInExpandedMode) { 3774 return listMenuPresenter.getAdapter().getCount() > 0; 3775 } else { 3776 return ((ViewGroup) shownPanelView).getChildCount() > 0; 3777 } 3778 } 3779 3780 /** 3781 * Unregister and free attached MenuPresenters. They will be recreated as needed. 3782 */ 3783 public void clearMenuPresenters() { 3784 if (menu != null) { 3785 menu.removeMenuPresenter(iconMenuPresenter); 3786 menu.removeMenuPresenter(listMenuPresenter); 3787 } 3788 iconMenuPresenter = null; 3789 listMenuPresenter = null; 3790 } 3791 3792 void setStyle(Context context) { 3793 TypedArray a = context.obtainStyledAttributes(com.android.internal.R.styleable.Theme); 3794 background = a.getResourceId( 3795 com.android.internal.R.styleable.Theme_panelBackground, 0); 3796 fullBackground = a.getResourceId( 3797 com.android.internal.R.styleable.Theme_panelFullBackground, 0); 3798 windowAnimations = a.getResourceId( 3799 com.android.internal.R.styleable.Theme_windowAnimationStyle, 0); 3800 isCompact = a.getBoolean( 3801 com.android.internal.R.styleable.Theme_panelMenuIsCompact, false); 3802 listPresenterTheme = a.getResourceId( 3803 com.android.internal.R.styleable.Theme_panelMenuListTheme, 3804 com.android.internal.R.style.Theme_ExpandedMenu); 3805 a.recycle(); 3806 } 3807 3808 void setMenu(MenuBuilder menu) { 3809 if (menu == this.menu) return; 3810 3811 if (this.menu != null) { 3812 this.menu.removeMenuPresenter(iconMenuPresenter); 3813 this.menu.removeMenuPresenter(listMenuPresenter); 3814 } 3815 this.menu = menu; 3816 if (menu != null) { 3817 if (iconMenuPresenter != null) menu.addMenuPresenter(iconMenuPresenter); 3818 if (listMenuPresenter != null) menu.addMenuPresenter(listMenuPresenter); 3819 } 3820 } 3821 3822 MenuView getListMenuView(Context context, MenuPresenter.Callback cb) { 3823 if (menu == null) return null; 3824 3825 if (!isCompact) { 3826 getIconMenuView(context, cb); // Need this initialized to know where our offset goes 3827 } 3828 3829 if (listMenuPresenter == null) { 3830 listMenuPresenter = new ListMenuPresenter( 3831 com.android.internal.R.layout.list_menu_item_layout, listPresenterTheme); 3832 listMenuPresenter.setCallback(cb); 3833 listMenuPresenter.setId(com.android.internal.R.id.list_menu_presenter); 3834 menu.addMenuPresenter(listMenuPresenter); 3835 } 3836 3837 if (iconMenuPresenter != null) { 3838 listMenuPresenter.setItemIndexOffset( 3839 iconMenuPresenter.getNumActualItemsShown()); 3840 } 3841 MenuView result = listMenuPresenter.getMenuView(decorView); 3842 3843 return result; 3844 } 3845 3846 MenuView getIconMenuView(Context context, MenuPresenter.Callback cb) { 3847 if (menu == null) return null; 3848 3849 if (iconMenuPresenter == null) { 3850 iconMenuPresenter = new IconMenuPresenter(context); 3851 iconMenuPresenter.setCallback(cb); 3852 iconMenuPresenter.setId(com.android.internal.R.id.icon_menu_presenter); 3853 menu.addMenuPresenter(iconMenuPresenter); 3854 } 3855 3856 MenuView result = iconMenuPresenter.getMenuView(decorView); 3857 3858 return result; 3859 } 3860 3861 Parcelable onSaveInstanceState() { 3862 SavedState savedState = new SavedState(); 3863 savedState.featureId = featureId; 3864 savedState.isOpen = isOpen; 3865 savedState.isInExpandedMode = isInExpandedMode; 3866 3867 if (menu != null) { 3868 savedState.menuState = new Bundle(); 3869 menu.savePresenterStates(savedState.menuState); 3870 } 3871 3872 return savedState; 3873 } 3874 3875 void onRestoreInstanceState(Parcelable state) { 3876 SavedState savedState = (SavedState) state; 3877 featureId = savedState.featureId; 3878 wasLastOpen = savedState.isOpen; 3879 wasLastExpanded = savedState.isInExpandedMode; 3880 frozenMenuState = savedState.menuState; 3881 3882 /* 3883 * A LocalActivityManager keeps the same instance of this class around. 3884 * The first time the menu is being shown after restoring, the 3885 * Activity.onCreateOptionsMenu should be called. But, if it is the 3886 * same instance then menu != null and we won't call that method. 3887 * We clear any cached views here. The caller should invalidatePanelMenu. 3888 */ 3889 createdPanelView = null; 3890 shownPanelView = null; 3891 decorView = null; 3892 } 3893 3894 void applyFrozenState() { 3895 if (menu != null && frozenMenuState != null) { 3896 menu.restorePresenterStates(frozenMenuState); 3897 frozenMenuState = null; 3898 } 3899 } 3900 3901 private static class SavedState implements Parcelable { 3902 int featureId; 3903 boolean isOpen; 3904 boolean isInExpandedMode; 3905 Bundle menuState; 3906 3907 public int describeContents() { 3908 return 0; 3909 } 3910 3911 public void writeToParcel(Parcel dest, int flags) { 3912 dest.writeInt(featureId); 3913 dest.writeInt(isOpen ? 1 : 0); 3914 dest.writeInt(isInExpandedMode ? 1 : 0); 3915 3916 if (isOpen) { 3917 dest.writeBundle(menuState); 3918 } 3919 } 3920 3921 private static SavedState readFromParcel(Parcel source) { 3922 SavedState savedState = new SavedState(); 3923 savedState.featureId = source.readInt(); 3924 savedState.isOpen = source.readInt() == 1; 3925 savedState.isInExpandedMode = source.readInt() == 1; 3926 3927 if (savedState.isOpen) { 3928 savedState.menuState = source.readBundle(); 3929 } 3930 3931 return savedState; 3932 } 3933 3934 public static final Parcelable.Creator<SavedState> CREATOR 3935 = new Parcelable.Creator<SavedState>() { 3936 public SavedState createFromParcel(Parcel in) { 3937 return readFromParcel(in); 3938 } 3939 3940 public SavedState[] newArray(int size) { 3941 return new SavedState[size]; 3942 } 3943 }; 3944 } 3945 3946 } 3947 3948 static class RotationWatcher extends IRotationWatcher.Stub { 3949 private Handler mHandler; 3950 private final Runnable mRotationChanged = new Runnable() { 3951 public void run() { 3952 dispatchRotationChanged(); 3953 } 3954 }; 3955 private final ArrayList<WeakReference<PhoneWindow>> mWindows = 3956 new ArrayList<WeakReference<PhoneWindow>>(); 3957 private boolean mIsWatching; 3958 3959 @Override 3960 public void onRotationChanged(int rotation) throws RemoteException { 3961 mHandler.post(mRotationChanged); 3962 } 3963 3964 public void addWindow(PhoneWindow phoneWindow) { 3965 synchronized (mWindows) { 3966 if (!mIsWatching) { 3967 try { 3968 WindowManagerHolder.sWindowManager.watchRotation(this); 3969 mHandler = new Handler(); 3970 mIsWatching = true; 3971 } catch (RemoteException ex) { 3972 Log.e(TAG, "Couldn't start watching for device rotation", ex); 3973 } 3974 } 3975 mWindows.add(new WeakReference<PhoneWindow>(phoneWindow)); 3976 } 3977 } 3978 3979 public void removeWindow(PhoneWindow phoneWindow) { 3980 synchronized (mWindows) { 3981 int i = 0; 3982 while (i < mWindows.size()) { 3983 final WeakReference<PhoneWindow> ref = mWindows.get(i); 3984 final PhoneWindow win = ref.get(); 3985 if (win == null || win == phoneWindow) { 3986 mWindows.remove(i); 3987 } else { 3988 i++; 3989 } 3990 } 3991 } 3992 } 3993 3994 void dispatchRotationChanged() { 3995 synchronized (mWindows) { 3996 int i = 0; 3997 while (i < mWindows.size()) { 3998 final WeakReference<PhoneWindow> ref = mWindows.get(i); 3999 final PhoneWindow win = ref.get(); 4000 if (win != null) { 4001 win.onOptionsPanelRotationChanged(); 4002 i++; 4003 } else { 4004 mWindows.remove(i); 4005 } 4006 } 4007 } 4008 } 4009 } 4010 4011 /** 4012 * Simple implementation of MenuBuilder.Callback that: 4013 * <li> Opens a submenu when selected. 4014 * <li> Calls back to the callback's onMenuItemSelected when an item is 4015 * selected. 4016 */ 4017 private final class DialogMenuCallback implements MenuBuilder.Callback, MenuPresenter.Callback { 4018 private int mFeatureId; 4019 private MenuDialogHelper mSubMenuHelper; 4020 4021 public DialogMenuCallback(int featureId) { 4022 mFeatureId = featureId; 4023 } 4024 4025 public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { 4026 if (menu.getRootMenu() != menu) { 4027 onCloseSubMenu(menu); 4028 } 4029 4030 if (allMenusAreClosing) { 4031 Callback callback = getCallback(); 4032 if (callback != null && !isDestroyed()) { 4033 callback.onPanelClosed(mFeatureId, menu); 4034 } 4035 4036 if (menu == mContextMenu) { 4037 dismissContextMenu(); 4038 } 4039 4040 // Dismiss the submenu, if it is showing 4041 if (mSubMenuHelper != null) { 4042 mSubMenuHelper.dismiss(); 4043 mSubMenuHelper = null; 4044 } 4045 } 4046 } 4047 4048 public void onCloseSubMenu(MenuBuilder menu) { 4049 Callback callback = getCallback(); 4050 if (callback != null && !isDestroyed()) { 4051 callback.onPanelClosed(mFeatureId, menu.getRootMenu()); 4052 } 4053 } 4054 4055 public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) { 4056 Callback callback = getCallback(); 4057 return (callback != null && !isDestroyed()) 4058 && callback.onMenuItemSelected(mFeatureId, item); 4059 } 4060 4061 public void onMenuModeChange(MenuBuilder menu) { 4062 } 4063 4064 public boolean onOpenSubMenu(MenuBuilder subMenu) { 4065 if (subMenu == null) return false; 4066 4067 // Set a simple callback for the submenu 4068 subMenu.setCallback(this); 4069 4070 // The window manager will give us a valid window token 4071 mSubMenuHelper = new MenuDialogHelper(subMenu); 4072 mSubMenuHelper.show(null); 4073 4074 return true; 4075 } 4076 } 4077 4078 void sendCloseSystemWindows() { 4079 PhoneWindowManager.sendCloseSystemWindows(getContext(), null); 4080 } 4081 4082 void sendCloseSystemWindows(String reason) { 4083 PhoneWindowManager.sendCloseSystemWindows(getContext(), reason); 4084 } 4085 4086 @Override 4087 public void setTransitionOptions(Bundle options, SceneTransitionListener listener) { 4088 mSceneTransitionListener = listener; 4089 ActivityOptions activityOptions = null; 4090 if (options != null) { 4091 activityOptions = new ActivityOptions(options); 4092 if (activityOptions.getAnimationType() != ActivityOptions.ANIM_SCENE_TRANSITION) { 4093 activityOptions = null; 4094 } 4095 } 4096 mActivityOptions = activityOptions; 4097 } 4098 4099 @Override 4100 public void setAllowOverlappingEnterTransition(boolean allow) { 4101 mAllowEnterOverlap = allow; 4102 } 4103 4104 @Override 4105 public void setAllowOverlappingExitTransition(boolean allow) { 4106 mAllowExitOverlap = allow; 4107 } 4108 4109 @Override 4110 public void mapTransitionTargets(Map<String, String> sharedElementNames) { 4111 mSharedElementsMap = sharedElementNames; 4112 } 4113 4114 @Override 4115 public void restoreViewVisibilityAfterTransitionToCallee() { 4116 if (mTransitioningViews != null) { 4117 setViewVisibility(mTransitioningViews, View.VISIBLE); 4118 } 4119 } 4120 4121 @Override 4122 public void startExitTransitionToCaller(final Runnable onTransitionEnd) { 4123 Transition transition; 4124 if (mContentScene == null || mTransitionManager == null || mActivityOptions == null 4125 || (transition = mTransitionManager.getEnterTransition(mContentScene)) == null) { 4126 onTransitionEnd.run(); 4127 return; 4128 } 4129 if (mAllowExitOverlap) { 4130 TransitionSet transitionSet = new TransitionSet(); 4131 transitionSet.addTransition(transition); 4132 Fade fade = new Fade(); 4133 transitionSet.addTransition(fade); 4134 transition = transitionSet; 4135 } 4136 4137 final ArrayMap<String, View> sharedElements = new ArrayMap<String, View>(); 4138 mapSharedElements(sharedElements); 4139 final Bundle sharedElementArgs = new Bundle(); 4140 captureTerminalSharedElementState(sharedElements, sharedElementArgs); 4141 4142 final ArrayList<View> transitioningViews = new ArrayList<View>(); 4143 mDecor.captureTransitioningViews(transitioningViews); 4144 transitioningViews.removeAll(sharedElements.values()); 4145 4146 mSceneTransitionListener.convertToTranslucent(); 4147 transition = transition.clone(); 4148 Rect epicenter = calcEpicenter(sharedElements, mActivityOptions.getSharedElementNames()); 4149 transition.setEpicenterCallback(new FixedEpicenterCallback(epicenter)); 4150 ExitSceneBack exitScene = 4151 new ExitSceneBack(onTransitionEnd, sharedElementArgs, sharedElements.values()); 4152 exitScene.start(transition); 4153 mTransitionManager.beginDelayedTransition(mDecor, transition); 4154 setViewVisibility(transitioningViews, View.INVISIBLE); 4155 } 4156 4157 @Override 4158 public Bundle startExitTransitionToCallee(Bundle options) { 4159 if (mContentScene == null) { 4160 return null; 4161 } 4162 Transition transition = mTransitionManager.getExitTransition(mContentScene); 4163 if (transition == null) { 4164 return null; 4165 } 4166 4167 ActivityOptions activityOptions = new ActivityOptions(options); 4168 ArrayMap<String, View> sharedElements = findSharedElements(activityOptions); 4169 4170 // Find exiting Views and shared elements 4171 ArrayList<View> transitioningViews = captureTransitioningViews(sharedElements.values()); 4172 4173 Transition exitTransition = addTransitionTargets(transition, 4174 transitioningViews, true); 4175 Transition sharedElementTransition = addTransitionTargets(transition, 4176 transitioningViews, false); 4177 4178 // transitionSet is the total exit transition, including hero animation. 4179 TransitionSet transitionSet = new TransitionSet(); 4180 transitionSet.addTransition(exitTransition); 4181 transitionSet.addTransition(sharedElementTransition); 4182 4183 Rect epicenter = calcEpicenter(sharedElements, activityOptions.getSharedElementNames()); 4184 FixedEpicenterCallback epicenterCallback = new FixedEpicenterCallback(epicenter); 4185 transitionSet.setEpicenterCallback(epicenterCallback); 4186 4187 updateExitActivityOptions(activityOptions, sharedElements, 4188 sharedElementTransition, transitioningViews, exitTransition, epicenterCallback); 4189 4190 // Start exiting the Views that need to exit 4191 TransitionManager.beginDelayedTransition(mDecor, transitionSet); 4192 setViewVisibility(transitioningViews, View.INVISIBLE); 4193 4194 return activityOptions.toBundle(); 4195 } 4196 4197 private ArrayList<View> captureTransitioningViews(Collection<View> sharedElements) { 4198 mTransitioningViews = new ArrayList<View>(); 4199 mDecor.captureTransitioningViews(mTransitioningViews); 4200 ArrayList<View> transitioningViews = (ArrayList<View>) mTransitioningViews.clone(); 4201 transitioningViews.removeAll(sharedElements); 4202 return transitioningViews; 4203 } 4204 4205 private ArrayMap<String, View> findSharedElements(ActivityOptions activityOptions) { 4206 ArrayMap<String, View> sharedElements = new ArrayMap<String, View>(); 4207 mDecor.findSharedElements(sharedElements); 4208 ArrayList<String> localNames = activityOptions.getLocalElementNames(); 4209 sharedElements.keySet().retainAll(localNames); 4210 4211 ArrayList<String> targetNames = activityOptions.getSharedElementNames(); 4212 for (int i = 0; i < localNames.size(); i++) { 4213 String localName = localNames.get(i); 4214 View sharedElement = sharedElements.remove(localName); 4215 String targetName = targetNames.get(i); 4216 sharedElements.put(targetName, sharedElement); 4217 } 4218 return sharedElements; 4219 } 4220 4221 private static void runOnUiThread(Handler handler, Runnable runnable) { 4222 if (handler.getLooper() != Looper.myLooper()) { 4223 handler.post(runnable); 4224 } else { 4225 runnable.run(); 4226 } 4227 } 4228 4229 private void updateExitActivityOptions(ActivityOptions activityOptions, 4230 final Map<String, View> sharedElements, Transition sharedElementTransition, 4231 final ArrayList<View> transitioningViews, Transition exitTransition, 4232 final Transition.EpicenterCallback epicenterCallback) { 4233 4234 // Schedule capturing of the shared element state 4235 final Bundle sharedElementArgs = new Bundle(); 4236 captureTerminalSharedElementState(sharedElements, sharedElementArgs); 4237 4238 ActivityOptions.SharedElementSource sharedElementSource 4239 = new ActivityOptions.SharedElementSource() { 4240 private Handler mHandler = new Handler(); 4241 4242 @Override 4243 public Bundle getSharedElementExitState() { 4244 return sharedElementArgs; 4245 } 4246 4247 @Override 4248 public void acceptedSharedElements(final ArrayList<String> sharedElementNames) { 4249 if (sharedElementNames.size() == sharedElements.size()) { 4250 return; // They were all accepted 4251 } 4252 runOnUiThread(mHandler, new Runnable() { 4253 @Override 4254 public void run() { 4255 Transition transition = mTransitionManager.getExitTransition(mContentScene); 4256 transition = transition.clone(); 4257 transition.setEpicenterCallback(epicenterCallback); 4258 TransitionManager.beginDelayedTransition(mDecor, transition); 4259 for (String name : sharedElements.keySet()) { 4260 if (!sharedElementNames.contains(name)) { 4261 sharedElements.get(name).setVisibility(View.INVISIBLE); 4262 } 4263 } 4264 sharedElements.keySet().retainAll(sharedElementNames); 4265 } 4266 }); 4267 } 4268 4269 @Override 4270 public void hideSharedElements() { 4271 if (sharedElements != null) { 4272 runOnUiThread(mHandler, new Runnable() { 4273 @Override 4274 public void run() { 4275 setViewVisibility(sharedElements.values(), View.INVISIBLE); 4276 } 4277 }); 4278 } 4279 } 4280 4281 @Override 4282 public void restore(final Bundle sharedElementState) { 4283 runOnUiThread(mHandler, new Runnable() { 4284 @Override 4285 public void run() { 4286 mTransitioningViews = null; 4287 Transition transition = mTransitionManager.getExitTransition(mContentScene); 4288 transition = transition.clone(); 4289 transition.setEpicenterCallback(epicenterCallback); 4290 setSharedElementState(sharedElements, sharedElementState); 4291 setViewVisibility(sharedElements.values(), View.VISIBLE); 4292 if (mSceneTransitionListener != null) { 4293 mSceneTransitionListener.sharedElementStart(transition); 4294 mDecor.getViewTreeObserver().addOnPreDrawListener( 4295 new ViewTreeObserver.OnPreDrawListener() { 4296 @Override 4297 public boolean onPreDraw() { 4298 mDecor.getViewTreeObserver().removeOnPreDrawListener(this); 4299 mSceneTransitionListener.sharedElementEnd(); 4300 return true; 4301 } 4302 }); 4303 } 4304 TransitionManager.beginDelayedTransition(mDecor, transition); 4305 setViewVisibility(transitioningViews, View.VISIBLE); 4306 for (View sharedElement: sharedElements.values()) { 4307 sharedElement.requestLayout(); 4308 } 4309 } 4310 }); 4311 } 4312 4313 @Override 4314 public void prepareForRestore() { 4315 if (mTransitioningViews != null) { 4316 runOnUiThread(mHandler, new Runnable() { 4317 @Override 4318 public void run() { 4319 setViewVisibility(mTransitioningViews, View.INVISIBLE); 4320 } 4321 }); 4322 } 4323 } 4324 }; 4325 4326 activityOptions.updateSceneTransitionAnimation( 4327 exitTransition, sharedElementTransition, sharedElementSource); 4328 } 4329 4330 private void captureTerminalSharedElementState(final Map<String, View> sharedElements, 4331 final Bundle sharedElementArgs) { 4332 mDecor.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { 4333 @Override 4334 public boolean onPreDraw() { 4335 mDecor.getViewTreeObserver().removeOnPreDrawListener(this); 4336 int[] tempLoc = new int[2]; 4337 for (String name : sharedElements.keySet()) { 4338 View sharedElement = sharedElements.get(name); 4339 captureSharedElementState(sharedElement, name, sharedElementArgs, tempLoc); 4340 } 4341 return true; 4342 } 4343 }); 4344 } 4345 4346 private static Transition addTransitionTargets(Transition transition, Collection<View> views, 4347 boolean add) { 4348 TransitionSet set = new TransitionSet(); 4349 set.addTransition(transition.clone()); 4350 for (View view: views) { 4351 if (add) { 4352 set.addTarget(view); 4353 } else { 4354 set.excludeTarget(view, true); 4355 } 4356 } 4357 return set; 4358 } 4359 4360 private static void setViewVisibility(Collection<View> views, int visibility) { 4361 for (View view : views) { 4362 view.setVisibility(visibility); 4363 } 4364 } 4365 4366 private static void setSharedElementState(Map<String, View> sharedElements, 4367 Bundle sharedElementState) { 4368 int[] tempLoc = new int[2]; 4369 for (Map.Entry<String, View> entry: sharedElements.entrySet()) { 4370 setSharedElementState(entry.getValue(), entry.getKey(), sharedElementState, tempLoc); 4371 } 4372 } 4373 4374 /** 4375 * Sets the captured values from a previous 4376 * {@link #captureSharedElementState(android.view.View, String, android.os.Bundle, int[])} 4377 * @param view The View to apply placement changes to. 4378 * @param name The shared element name given from the source Activity. 4379 * @param transitionArgs A <code>Bundle</code> containing all placementinformation for named 4380 * shared elements in the scene. 4381 * @param tempLoc A temporary int[2] for capturing the current location of views. 4382 */ 4383 private static void setSharedElementState(View view, String name, Bundle transitionArgs, 4384 int[] tempLoc) { 4385 Bundle sharedElementBundle = transitionArgs.getBundle(name); 4386 if (sharedElementBundle == null) { 4387 return; 4388 } 4389 4390 float z = sharedElementBundle.getFloat(KEY_TRANSLATION_Z); 4391 view.setTranslationZ(z); 4392 4393 int x = sharedElementBundle.getInt(KEY_SCREEN_X); 4394 int y = sharedElementBundle.getInt(KEY_SCREEN_Y); 4395 int width = sharedElementBundle.getInt(KEY_WIDTH); 4396 int height = sharedElementBundle.getInt(KEY_HEIGHT); 4397 4398 int widthSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY); 4399 int heightSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY); 4400 view.measure(widthSpec, heightSpec); 4401 4402 ViewGroup parent = (ViewGroup) view.getParent(); 4403 parent.getLocationOnScreen(tempLoc); 4404 int left = x - tempLoc[0]; 4405 int top = y - tempLoc[1]; 4406 int right = left + width; 4407 int bottom = top + height; 4408 view.layout(left, top, right, bottom); 4409 4410 view.requestLayout(); 4411 } 4412 4413 /** 4414 * Captures placement information for Views with a shared element name for 4415 * Activity Transitions. 4416 * @param view The View to capture the placement information for. 4417 * @param name The shared element name in the target Activity to apply the placement 4418 * information for. 4419 * @param transitionArgs Bundle to store shared element placement information. 4420 * @param tempLoc A temporary int[2] for capturing the current location of views. 4421 * @see #setSharedElementState(android.view.View, String, android.os.Bundle, int[]) 4422 */ 4423 private static void captureSharedElementState(View view, String name, Bundle transitionArgs, 4424 int[] tempLoc) { 4425 Bundle sharedElementBundle = new Bundle(); 4426 view.getLocationOnScreen(tempLoc); 4427 float scaleX = view.getScaleX(); 4428 sharedElementBundle.putInt(KEY_SCREEN_X, tempLoc[0]); 4429 int width = Math.round(view.getWidth() * scaleX); 4430 sharedElementBundle.putInt(KEY_WIDTH, width); 4431 4432 float scaleY = view.getScaleY(); 4433 sharedElementBundle.putInt(KEY_SCREEN_Y, tempLoc[1]); 4434 int height= Math.round(view.getHeight() * scaleY); 4435 sharedElementBundle.putInt(KEY_HEIGHT, height); 4436 4437 sharedElementBundle.putFloat(KEY_TRANSLATION_Z, view.getTranslationZ()); 4438 4439 sharedElementBundle.putString(KEY_NAME, view.getSharedElementName()); 4440 4441 transitionArgs.putBundle(name, sharedElementBundle); 4442 } 4443 4444 private void mapSharedElements(ArrayMap<String, View> sharedElements) { 4445 ArrayList<String> sharedElementNames = mActivityOptions.getSharedElementNames(); 4446 if (sharedElementNames != null) { 4447 mDecor.findSharedElements(sharedElements); 4448 if (mSharedElementsMap != null) { 4449 for (Map.Entry<String, String> entry : mSharedElementsMap.entrySet()) { 4450 View sharedElement = sharedElements.remove(entry.getValue()); 4451 if (sharedElement != null) { 4452 sharedElements.put(entry.getKey(), sharedElement); 4453 } 4454 } 4455 } 4456 sharedElements.keySet().retainAll(sharedElementNames); 4457 } 4458 } 4459 4460 private static Rect calcEpicenter(ArrayMap<String, View> sharedElements, 4461 ArrayList<String> sharedElementNames) { 4462 if (sharedElementNames != null) { 4463 for (String name: sharedElementNames) { 4464 if (name.startsWith("android:")) { 4465 return null; 4466 } 4467 View view = sharedElements.get(name); 4468 if (view != null) { 4469 int[] loc = new int[2]; 4470 view.getLocationOnScreen(loc); 4471 int left = loc[0] + Math.round(view.getTranslationX()); 4472 int top = loc[1] + Math.round(view.getTranslationY()); 4473 int right = left + view.getWidth(); 4474 int bottom = top + view.getHeight(); 4475 return new Rect(left, top, right, bottom); 4476 } 4477 } 4478 } 4479 return null; 4480 } 4481 4482 private class ExitSceneBack extends Transition.TransitionListenerAdapter implements 4483 Animator.AnimatorListener { 4484 private boolean mExitTransitionComplete; 4485 private boolean mBackgroundFadeComplete; 4486 private boolean mOnCompleteExecuted; 4487 private boolean mSharedElementTransitioned; 4488 private Runnable mOnComplete; 4489 private Bundle mSharedElementArgs; 4490 private Collection<View> mSharedElements; 4491 4492 public ExitSceneBack(Runnable onComplete, Bundle sharedElementArgs, 4493 Collection<View> sharedElements) { 4494 mOnComplete = onComplete; 4495 mSharedElementArgs = sharedElementArgs; 4496 mSharedElements = sharedElements; 4497 } 4498 4499 public void start(Transition exitTransition) { 4500 if (mActivityOptions != null) { 4501 mActivityOptions.dispatchPrepareRestore(); 4502 } 4503 exitTransition.addListener(this); 4504 Drawable background = mDecor.getBackground(); 4505 if (background != null) { 4506 ObjectAnimator animator = ObjectAnimator.ofInt(background, "alpha", 0); 4507 animator.addListener(this); 4508 animator.start(); 4509 } else { 4510 mBackgroundFadeComplete = true; 4511 startCalledActivityEnter(); 4512 } 4513 } 4514 4515 @Override 4516 public void onTransitionEnd(Transition transition) { 4517 transition.removeListener(this); 4518 mExitTransitionComplete = true; 4519 notifyComplete(); 4520 if (!mAllowExitOverlap) { 4521 startCalledActivityEnter(); 4522 } 4523 } 4524 4525 private void notifyComplete() { 4526 if (mExitTransitionComplete && mBackgroundFadeComplete 4527 && mSharedElementTransitioned && !mOnCompleteExecuted) { 4528 mOnComplete.run(); 4529 mSceneTransitionListener.nullPendingTransition(); 4530 mOnCompleteExecuted = true; 4531 } 4532 } 4533 4534 @Override 4535 public void onAnimationStart(Animator animation) { 4536 } 4537 4538 @Override 4539 public void onAnimationEnd(Animator animation) { 4540 mBackgroundFadeComplete = true; 4541 if (mAllowExitOverlap) { 4542 startCalledActivityEnter(); 4543 } 4544 } 4545 4546 @Override 4547 public void onAnimationCancel(Animator animation) { 4548 } 4549 4550 @Override 4551 public void onAnimationRepeat(Animator animation) { 4552 } 4553 4554 private void startCalledActivityEnter() { 4555 mActivityOptions.dispatchRestore(mSharedElementArgs); 4556 setViewVisibility(mSharedElements, View.INVISIBLE); 4557 mSharedElementTransitioned = true; 4558 notifyComplete(); 4559 } 4560 } 4561 4562 /** 4563 * Provides code for handling the Activity transition entering scene. 4564 * When the first scene is laid out (onPreDraw), it makes views invisible. 4565 * It then starts the entering transition by making non-shared elements visible. When 4566 * the entering transition is started, the calling Activity is notified that 4567 * this Activity is ready to receive the shared element. When the calling Activity notifies 4568 * that the shared element is ready, this Activity is notified through the 4569 * SceneTransitionListener. 4570 * 4571 * This class also takes into account fading the background -- either waiting until the 4572 * shared element is ready or the calling Activity's exit transition is complete. 4573 */ 4574 private class EnterScene implements ViewTreeObserver.OnPreDrawListener, Runnable, 4575 ActivityOptions.ActivityTransitionTarget, Animator.AnimatorListener { 4576 private boolean mSharedElementReadyReceived; 4577 private boolean mAllDone; 4578 private Handler mHandler = new Handler(); 4579 private boolean mEnterTransitionStarted; 4580 private ArrayMap<String, View> mSharedElementTargets = new ArrayMap<String, View>(); 4581 private ArrayList<View> mEnteringViews = new ArrayList<View>(); 4582 private Transition.EpicenterCallback mEpicenterCallback; 4583 4584 public EnterScene() { 4585 mSceneTransitionListener.nullPendingTransition(); 4586 Drawable background = getDecorView().getBackground(); 4587 if (background != null) { 4588 background.setAlpha(0); 4589 mDecor.drawableChanged(); 4590 } 4591 mSceneTransitionListener.convertToTranslucent(); 4592 } 4593 4594 @Override 4595 public boolean onPreDraw() { 4596 ViewTreeObserver observer = mDecor.getViewTreeObserver(); 4597 observer.removeOnPreDrawListener(this); 4598 if (!mEnterTransitionStarted && mSceneTransitionListener != null) { 4599 mEnterTransitionStarted = true; 4600 mDecor.captureTransitioningViews(mEnteringViews); 4601 mapSharedElements(mSharedElementTargets); 4602 mEnteringViews.removeAll(mSharedElementTargets.values()); 4603 Rect epicenter = calcEpicenter(mSharedElementTargets, 4604 mActivityOptions.getSharedElementNames()); 4605 mEpicenterCallback = new FixedEpicenterCallback(epicenter); 4606 4607 setViewVisibility(mEnteringViews, View.INVISIBLE); 4608 setViewVisibility(mSharedElementTargets.values(), View.INVISIBLE); 4609 if (mAllowEnterOverlap) { 4610 beginEnterScene(); 4611 } 4612 observer.addOnPreDrawListener(this); 4613 return false; 4614 } else { 4615 mHandler.postDelayed(this, MAX_TRANSITION_START_WAIT); 4616 mActivityOptions.dispatchSceneTransitionStarted(this, 4617 new ArrayList<String>(mSharedElementTargets.keySet())); 4618 return !mSharedElementReadyReceived; 4619 } 4620 } 4621 4622 public void start() { 4623 ViewTreeObserver observer = mDecor.getViewTreeObserver(); 4624 observer.addOnPreDrawListener(this); 4625 } 4626 4627 @Override 4628 public void run() { 4629 exitTransitionComplete(); 4630 } 4631 4632 @Override 4633 public void sharedElementTransitionComplete(final Bundle transitionArgs) { 4634 if (!mSharedElementReadyReceived) { 4635 mSharedElementReadyReceived = true; 4636 mHandler.removeCallbacks(this); 4637 mHandler.postDelayed(this, MAX_TRANSITION_FINISH_WAIT); 4638 if (!mSharedElementTargets.isEmpty()) { 4639 runOnUiThread(mHandler, new Runnable() { 4640 @Override 4641 public void run() { 4642 Transition transition = getTransitionManager().getEnterTransition( 4643 mContentScene); 4644 if (transition == null) { 4645 transition = TransitionManager.getDefaultTransition(); 4646 } 4647 transition = addTransitionTargets(transition, 4648 mSharedElementTargets.values(), 4649 true); 4650 transition.setEpicenterCallback(mEpicenterCallback); 4651 if (transitionArgs == null) { 4652 TransitionManager.beginDelayedTransition(mDecor, transition); 4653 setViewVisibility(mSharedElementTargets.values(), View.VISIBLE); 4654 } else { 4655 mSceneTransitionListener.sharedElementStart(transition); 4656 setSharedElementState(mSharedElementTargets, transitionArgs); 4657 setViewVisibility(mSharedElementTargets.values(), View.VISIBLE); 4658 mDecor.getViewTreeObserver().addOnPreDrawListener( 4659 new ViewTreeObserver.OnPreDrawListener() { 4660 @Override 4661 public boolean onPreDraw() { 4662 mDecor.getViewTreeObserver() 4663 .removeOnPreDrawListener(this); 4664 mSceneTransitionListener.sharedElementEnd(); 4665 mActivityOptions.dispatchSharedElementsReady(); 4666 return true; 4667 } 4668 }); 4669 TransitionManager.beginDelayedTransition(mDecor, transition); 4670 } 4671 } 4672 }); 4673 } 4674 if (mAllowEnterOverlap) { 4675 fadeInBackground(); 4676 } 4677 } 4678 } 4679 4680 private void fadeInBackground() { 4681 Drawable background = getDecorView().getBackground(); 4682 if (background == null) { 4683 mSceneTransitionListener.convertFromTranslucent(); 4684 } else { 4685 ObjectAnimator animator = ObjectAnimator.ofInt(background, "alpha", 255); 4686 animator.addListener(this); 4687 animator.start(); 4688 } 4689 } 4690 4691 @Override 4692 public void exitTransitionComplete() { 4693 if (mAllDone) { 4694 return; 4695 } 4696 mAllDone = true; 4697 sharedElementTransitionComplete(null); 4698 mHandler.removeCallbacks(this); 4699 if (!mAllowEnterOverlap) { 4700 runOnUiThread(mHandler, new Runnable() { 4701 @Override 4702 public void run() { 4703 beginEnterScene(); 4704 fadeInBackground(); 4705 } 4706 }); 4707 } 4708 } 4709 4710 @Override 4711 public void onAnimationStart(Animator animation) { 4712 } 4713 4714 @Override 4715 public void onAnimationEnd(Animator animation) { 4716 mSceneTransitionListener.convertFromTranslucent(); 4717 } 4718 4719 @Override 4720 public void onAnimationCancel(Animator animation) { 4721 } 4722 4723 @Override 4724 public void onAnimationRepeat(Animator animation) { 4725 } 4726 4727 private void beginEnterScene() { 4728 Transition transition = getTransitionManager().getEnterTransition(mContentScene); 4729 if (transition == null) { 4730 transition = TransitionManager.getDefaultTransition(); 4731 } 4732 transition = addTransitionTargets(transition, mEnteringViews, true); 4733 transition.setEpicenterCallback(mEpicenterCallback); 4734 TransitionManager.beginDelayedTransition(mDecor, transition); 4735 setViewVisibility(mEnteringViews, View.VISIBLE); 4736 } 4737 } 4738 4739 private static class FixedEpicenterCallback extends Transition.EpicenterCallback { 4740 private Rect mEpicenter; 4741 4742 public FixedEpicenterCallback(Rect epicenter) { 4743 mEpicenter = epicenter; 4744 } 4745 4746 @Override 4747 public Rect getEpicenter(Transition transition) { 4748 return mEpicenter; 4749 } 4750 }; 4751} 4752