1 /* 2 * Copyright (C) 2006 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.internal.policy; 18 19 import static android.provider.Settings.Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES; 20 import static android.provider.Settings.Global.DEVELOPMENT_RENDER_SHADOWS_IN_COMPOSITOR; 21 import static android.view.View.SYSTEM_UI_LAYOUT_FLAGS; 22 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; 23 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; 24 import static android.view.WindowInsetsController.APPEARANCE_FORCE_LIGHT_NAVIGATION_BARS; 25 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS; 26 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS; 27 import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; 28 import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN; 29 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR; 30 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; 31 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; 32 import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH; 33 import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION; 34 import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS; 35 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; 36 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT; 37 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED; 38 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS; 39 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION; 40 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OVERRIDE_LAYOUT_IN_DISPLAY_CUTOUT_MODE; 41 42 import android.annotation.NonNull; 43 import android.annotation.Nullable; 44 import android.annotation.UiContext; 45 import android.app.ActivityManager; 46 import android.app.KeyguardManager; 47 import android.app.SearchManager; 48 import android.app.compat.CompatChanges; 49 import android.compat.annotation.ChangeId; 50 import android.compat.annotation.Disabled; 51 import android.compat.annotation.EnabledSince; 52 import android.compat.annotation.Overridable; 53 import android.compat.annotation.UnsupportedAppUsage; 54 import android.content.Context; 55 import android.content.Intent; 56 import android.content.pm.ApplicationInfo; 57 import android.content.pm.PackageManager; 58 import android.content.res.Configuration; 59 import android.content.res.Resources.Theme; 60 import android.content.res.TypedArray; 61 import android.graphics.Color; 62 import android.graphics.Insets; 63 import android.graphics.Rect; 64 import android.graphics.drawable.Drawable; 65 import android.media.AudioManager; 66 import android.media.session.MediaController; 67 import android.media.session.MediaSessionManager; 68 import android.net.Uri; 69 import android.os.Build; 70 import android.os.Bundle; 71 import android.os.Handler; 72 import android.os.Parcel; 73 import android.os.Parcelable; 74 import android.os.RemoteException; 75 import android.os.ServiceManager; 76 import android.os.SystemProperties; 77 import android.provider.Settings; 78 import android.text.TextUtils; 79 import android.transition.Scene; 80 import android.transition.Transition; 81 import android.transition.TransitionInflater; 82 import android.transition.TransitionManager; 83 import android.transition.TransitionSet; 84 import android.util.AndroidRuntimeException; 85 import android.util.EventLog; 86 import android.util.Log; 87 import android.util.Pair; 88 import android.util.SparseArray; 89 import android.util.TypedValue; 90 import android.view.AttachedSurfaceControl; 91 import android.view.ContextThemeWrapper; 92 import android.view.CrossWindowBlurListeners; 93 import android.view.Gravity; 94 import android.view.IRotationWatcher.Stub; 95 import android.view.IScrollCaptureResponseListener; 96 import android.view.IWindowManager; 97 import android.view.InputDevice; 98 import android.view.InputEvent; 99 import android.view.InputQueue; 100 import android.view.KeyCharacterMap; 101 import android.view.KeyEvent; 102 import android.view.LayoutInflater; 103 import android.view.Menu; 104 import android.view.MenuItem; 105 import android.view.MotionEvent; 106 import android.view.ScrollCaptureCallback; 107 import android.view.SearchEvent; 108 import android.view.SurfaceHolder.Callback2; 109 import android.view.View; 110 import android.view.ViewConfiguration; 111 import android.view.ViewGroup; 112 import android.view.ViewManager; 113 import android.view.ViewParent; 114 import android.view.ViewRootImpl; 115 import android.view.ViewRootImpl.ActivityConfigCallback; 116 import android.view.Window; 117 import android.view.WindowInsetsController; 118 import android.view.WindowManager; 119 import android.view.animation.Animation; 120 import android.view.animation.AnimationUtils; 121 import android.widget.FrameLayout; 122 import android.widget.ImageView; 123 import android.widget.ProgressBar; 124 import android.widget.TextView; 125 import android.window.OnBackInvokedDispatcher; 126 import android.window.ProxyOnBackInvokedDispatcher; 127 128 import com.android.internal.R; 129 import com.android.internal.view.menu.ContextMenuBuilder; 130 import com.android.internal.view.menu.IconMenuPresenter; 131 import com.android.internal.view.menu.ListMenuPresenter; 132 import com.android.internal.view.menu.MenuBuilder; 133 import com.android.internal.view.menu.MenuDialogHelper; 134 import com.android.internal.view.menu.MenuHelper; 135 import com.android.internal.view.menu.MenuPresenter; 136 import com.android.internal.view.menu.MenuView; 137 import com.android.internal.widget.DecorContentParent; 138 import com.android.window.flags.Flags; 139 140 import java.lang.ref.WeakReference; 141 import java.util.ArrayList; 142 import java.util.List; 143 144 /** 145 * Android-specific Window. 146 * <p> 147 * todo: need to pull the generic functionality out into a base class 148 * in android.widget. 149 * 150 * @hide 151 */ 152 public class PhoneWindow extends Window implements MenuBuilder.Callback { 153 154 private final static String TAG = "PhoneWindow"; 155 156 /** 157 * @see Window#setDecorFitsSystemWindows 158 */ 159 private static final OnContentApplyWindowInsetsListener sDefaultContentInsetsApplier = 160 (view, insets) -> { 161 if ((view.getWindowSystemUiVisibility() & SYSTEM_UI_LAYOUT_FLAGS) != 0) { 162 return new Pair<>(Insets.NONE, insets); 163 } 164 Insets insetsToApply = insets.getSystemWindowInsets(); 165 return new Pair<>(insetsToApply, 166 insets.inset(insetsToApply).consumeSystemWindowInsets()); 167 }; 168 169 /* If true, shadows drawn around the window will be rendered by the system compositor. If 170 * false, shadows will be drawn by the client by setting an elevation on the root view and 171 * the contents will be inset by the shadow radius. */ 172 public final boolean mRenderShadowsInCompositor; 173 174 private static final boolean DEBUG = false; 175 176 private final static int DEFAULT_BACKGROUND_FADE_DURATION_MS = 300; 177 178 /** 179 * Make app go edge-to-edge by default if the target SDK is 180 * {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} or above. 181 */ 182 @ChangeId 183 @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM) 184 private static final long ENFORCE_EDGE_TO_EDGE = 309578419; 185 186 /** 187 * Override the layout in display cutout mode behavior. This will only apply if the edge to edge 188 * is not enforced. 189 */ 190 @ChangeId 191 @Disabled 192 @Overridable 193 private static final long OVERRIDE_LAYOUT_IN_DISPLAY_CUTOUT_MODE = 332679525L; 194 195 private static final int CUSTOM_TITLE_COMPATIBLE_FEATURES = DEFAULT_FEATURES | 196 (1 << FEATURE_CUSTOM_TITLE) | 197 (1 << FEATURE_CONTENT_TRANSITIONS) | 198 (1 << FEATURE_ACTIVITY_TRANSITIONS) | 199 (1 << FEATURE_ACTION_MODE_OVERLAY); 200 201 private static final Transition USE_DEFAULT_TRANSITION = new TransitionSet(); 202 203 /** 204 * Since which target SDK version this window is enforced to go edge-to-edge. 205 */ 206 private static final int ENFORCE_EDGE_TO_EDGE_SDK_VERSION = 207 SystemProperties.getInt("persist.wm.debug.default_e2e_since_sdk", Integer.MAX_VALUE); 208 209 /** 210 * Simple callback used by the context menu and its submenus. The options 211 * menu submenus do not use this (their behavior is more complex). 212 */ 213 final PhoneWindowMenuCallback mContextMenuCallback = new PhoneWindowMenuCallback(this); 214 215 final TypedValue mMinWidthMajor = new TypedValue(); 216 final TypedValue mMinWidthMinor = new TypedValue(); 217 TypedValue mFixedWidthMajor; 218 TypedValue mFixedWidthMinor; 219 TypedValue mFixedHeightMajor; 220 TypedValue mFixedHeightMinor; 221 222 // This is the top-level view of the window, containing the window decor. 223 private DecorView mDecor; 224 225 // When we reuse decor views, we need to recreate the content root. This happens when the decor 226 // view is requested, so we need to force the recreating without introducing an infinite loop. 227 private boolean mForceDecorInstall = false; 228 229 // This is the view in which the window contents are placed. It is either 230 // mDecor itself, or a child of mDecor where the contents go. 231 ViewGroup mContentParent; 232 // Whether the client has explicitly set the content view. If false and mContentParent is not 233 // null, then the content parent was set due to window preservation. 234 private boolean mContentParentExplicitlySet = false; 235 236 Callback2 mTakeSurfaceCallback; 237 238 InputQueue.Callback mTakeInputQueueCallback; 239 240 boolean mIsFloating; 241 private boolean mIsTranslucent; 242 243 private LayoutInflater mLayoutInflater; 244 245 private TextView mTitleView; 246 247 DecorContentParent mDecorContentParent; 248 private ActionMenuPresenterCallback mActionMenuPresenterCallback; 249 private PanelMenuPresenterCallback mPanelMenuPresenterCallback; 250 251 private TransitionManager mTransitionManager; 252 private Scene mContentScene; 253 254 // The icon resource has been explicitly set elsewhere 255 // and should not be overwritten with a default. 256 static final int FLAG_RESOURCE_SET_ICON = 1 << 0; 257 258 // The logo resource has been explicitly set elsewhere 259 // and should not be overwritten with a default. 260 static final int FLAG_RESOURCE_SET_LOGO = 1 << 1; 261 262 // The icon resource is currently configured to use the system fallback 263 // as no default was previously specified. Anything can override this. 264 static final int FLAG_RESOURCE_SET_ICON_FALLBACK = 1 << 2; 265 266 int mResourcesSetFlags; 267 int mIconRes; 268 int mLogoRes; 269 270 private DrawableFeatureState[] mDrawables; 271 272 private PanelFeatureState[] mPanels; 273 274 /** 275 * The panel that is prepared or opened (the most recent one if there are 276 * multiple panels). Shortcuts will go to this panel. It gets set in 277 * {@link #preparePanel} and cleared in {@link #closePanel}. 278 */ 279 PanelFeatureState mPreparedPanel; 280 281 /** 282 * The keycode that is currently held down (as a modifier) for chording. If 283 * this is 0, there is no key held down. 284 */ 285 int mPanelChordingKey; 286 287 // This stores if the system supports Picture-in-Picture 288 // to see if KEYCODE_WINDOW should be handled here or not. 289 private boolean mSupportsPictureInPicture; 290 291 private ImageView mLeftIconView; 292 293 private ImageView mRightIconView; 294 295 private ProgressBar mCircularProgressBar; 296 297 private ProgressBar mHorizontalProgressBar; 298 299 Drawable mBackgroundDrawable = null; 300 Drawable mBackgroundFallbackDrawable = null; 301 302 private int mBackgroundBlurRadius = 0; 303 304 private boolean mLoadElevation = true; 305 private float mElevation; 306 307 /** Whether window content should be clipped to the background outline. */ 308 private boolean mClipToOutline; 309 310 private int mFrameResource = 0; 311 312 private int mTextColor = 0; 313 int mStatusBarColor = Color.TRANSPARENT; 314 int mNavigationBarColor = Color.TRANSPARENT; 315 int mNavigationBarDividerColor = Color.TRANSPARENT; 316 boolean mNavigationBarColorSpecified = false; 317 private boolean mForcedStatusBarColor = false; 318 private boolean mForcedNavigationBarColor = false; 319 320 boolean mEnsureStatusBarContrastWhenTransparent; 321 boolean mEnsureNavigationBarContrastWhenTransparent; 322 323 @UnsupportedAppUsage 324 private CharSequence mTitle = null; 325 326 private int mTitleColor = 0; 327 328 private boolean mAlwaysReadCloseOnTouchAttr = false; 329 330 ContextMenuBuilder mContextMenu; 331 MenuHelper mContextMenuHelper; 332 private boolean mClosingActionMenu; 333 334 private int mVolumeControlStreamType = AudioManager.USE_DEFAULT_STREAM_TYPE; 335 private int mAudioMode = AudioManager.MODE_NORMAL; 336 private MediaController mMediaController; 337 338 private AudioManager mAudioManager; 339 private KeyguardManager mKeyguardManager; 340 private MediaSessionManager mMediaSessionManager; 341 342 private int mUiOptions = 0; 343 344 private boolean mInvalidatePanelMenuPosted; 345 private int mInvalidatePanelMenuFeatures; 346 private final Runnable mInvalidatePanelMenuRunnable = new Runnable() { 347 @Override public void run() { 348 for (int i = 0; i <= FEATURE_MAX; i++) { 349 if ((mInvalidatePanelMenuFeatures & 1 << i) != 0) { 350 doInvalidatePanelMenu(i); 351 } 352 } 353 mInvalidatePanelMenuPosted = false; 354 mInvalidatePanelMenuFeatures = 0; 355 } 356 }; 357 358 private AudioManager.OnModeChangedListener mOnModeChangedListener; 359 360 private Transition mEnterTransition = null; 361 private Transition mReturnTransition = USE_DEFAULT_TRANSITION; 362 private Transition mExitTransition = null; 363 private Transition mReenterTransition = USE_DEFAULT_TRANSITION; 364 private Transition mSharedElementEnterTransition = null; 365 private Transition mSharedElementReturnTransition = USE_DEFAULT_TRANSITION; 366 private Transition mSharedElementExitTransition = null; 367 private Transition mSharedElementReenterTransition = USE_DEFAULT_TRANSITION; 368 private Boolean mAllowReturnTransitionOverlap; 369 private Boolean mAllowEnterTransitionOverlap; 370 private long mBackgroundFadeDurationMillis = -1; 371 private Boolean mSharedElementsUseOverlay; 372 373 private Boolean mAllowFloatingWindowsFillScreen; 374 375 private boolean mIsStartingWindow; 376 private int mTheme = -1; 377 378 private boolean mUseDecorContext = false; 379 380 /** @see ViewRootImpl#mActivityConfigCallback */ 381 private ActivityConfigCallback mActivityConfigCallback; 382 383 boolean mDecorFitsSystemWindows = true; 384 385 boolean mEdgeToEdgeEnforced; 386 387 private final ProxyOnBackInvokedDispatcher mProxyOnBackInvokedDispatcher; 388 389 static class WindowManagerHolder { 390 static final IWindowManager sWindowManager = IWindowManager.Stub.asInterface( 391 ServiceManager.getService("window")); 392 } 393 394 static final RotationWatcher sRotationWatcher = new RotationWatcher(); 395 396 @UnsupportedAppUsage PhoneWindow(@iContext Context context)397 public PhoneWindow(@UiContext Context context) { 398 super(context); 399 mLayoutInflater = LayoutInflater.from(context); 400 mRenderShadowsInCompositor = Settings.Global.getInt(context.getContentResolver(), 401 DEVELOPMENT_RENDER_SHADOWS_IN_COMPOSITOR, 1) != 0; 402 mProxyOnBackInvokedDispatcher = new ProxyOnBackInvokedDispatcher(context); 403 mAllowFloatingWindowsFillScreen = context.getResources().getBoolean( 404 com.android.internal.R.bool.config_allowFloatingWindowsFillScreen); 405 } 406 407 /** 408 * Constructor for main window of an activity. 409 */ PhoneWindow(@iContext Context context, Window preservedWindow, ActivityConfigCallback activityConfigCallback)410 public PhoneWindow(@UiContext Context context, Window preservedWindow, 411 ActivityConfigCallback activityConfigCallback) { 412 this(context); 413 // Only main activity windows use decor context, all the other windows depend on whatever 414 // context that was given to them. 415 mUseDecorContext = true; 416 if (preservedWindow != null) { 417 mDecor = (DecorView) preservedWindow.getDecorView(); 418 mElevation = preservedWindow.getElevation(); 419 mLoadElevation = false; 420 mForceDecorInstall = true; 421 setSystemBarAppearance(preservedWindow.getSystemBarAppearance()); 422 // If we're preserving window, carry over the app token from the preserved 423 // window, as we'll be skipping the addView in handleResumeActivity(), and 424 // the token will not be updated as for a new window. 425 getAttributes().token = preservedWindow.getAttributes().token; 426 final ViewRootImpl viewRoot = mDecor.getViewRootImpl(); 427 if (viewRoot != null) { 428 // Clear the old callbacks and attach to the new window. 429 viewRoot.getOnBackInvokedDispatcher().clear(); 430 onViewRootImplSet(viewRoot); 431 } 432 } 433 // Even though the device doesn't support picture-in-picture mode, 434 // an user can force using it through developer options. 435 boolean forceResizable = Settings.Global.getInt(context.getContentResolver(), 436 DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES, 0) != 0; 437 mSupportsPictureInPicture = forceResizable || context.getPackageManager().hasSystemFeature( 438 PackageManager.FEATURE_PICTURE_IN_PICTURE); 439 mActivityConfigCallback = activityConfigCallback; 440 } 441 442 /** 443 * Returns whether the given application is enforced to go edge-to-edge. 444 * 445 * @param info The application to query. 446 * @param local Whether this is called from the process of the given application. 447 * @param windowStyle The style of the window. 448 * @return {@code true} if edge-to-edge is enforced. Otherwise, {@code false}. 449 */ isEdgeToEdgeEnforced(ApplicationInfo info, boolean local, TypedArray windowStyle)450 public static boolean isEdgeToEdgeEnforced(ApplicationInfo info, boolean local, 451 TypedArray windowStyle) { 452 return !windowStyle.getBoolean(R.styleable.Window_windowOptOutEdgeToEdgeEnforcement, false) 453 && (info.targetSdkVersion >= ENFORCE_EDGE_TO_EDGE_SDK_VERSION 454 || (Flags.enforceEdgeToEdge() && (local 455 // Calling this doesn't require a permission. 456 ? CompatChanges.isChangeEnabled(ENFORCE_EDGE_TO_EDGE) 457 // Calling this requires permissions. 458 : info.isChangeEnabled(ENFORCE_EDGE_TO_EDGE)))); 459 } 460 461 @Override setContainer(Window container)462 public final void setContainer(Window container) { 463 super.setContainer(container); 464 } 465 466 @Override requestFeature(int featureId)467 public boolean requestFeature(int featureId) { 468 if (mContentParentExplicitlySet) { 469 throw new AndroidRuntimeException("requestFeature() must be called before adding content"); 470 } 471 final int features = getFeatures(); 472 final int newFeatures = features | (1 << featureId); 473 if ((newFeatures & (1 << FEATURE_CUSTOM_TITLE)) != 0 && 474 (newFeatures & ~CUSTOM_TITLE_COMPATIBLE_FEATURES) != 0) { 475 // Another feature is enabled and the user is trying to enable the custom title feature 476 // or custom title feature is enabled and the user is trying to enable another feature 477 throw new AndroidRuntimeException( 478 "You cannot combine custom titles with other title features"); 479 } 480 if ((features & (1 << FEATURE_NO_TITLE)) != 0 && featureId == FEATURE_ACTION_BAR) { 481 return false; // Ignore. No title dominates. 482 } 483 if ((features & (1 << FEATURE_ACTION_BAR)) != 0 && featureId == FEATURE_NO_TITLE) { 484 // Remove the action bar feature if we have no title. No title dominates. 485 removeFeature(FEATURE_ACTION_BAR); 486 } 487 488 if (featureId == FEATURE_INDETERMINATE_PROGRESS && 489 getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) { 490 throw new AndroidRuntimeException("You cannot use indeterminate progress on a watch."); 491 } 492 return super.requestFeature(featureId); 493 } 494 495 @Override setUiOptions(int uiOptions)496 public void setUiOptions(int uiOptions) { 497 mUiOptions = uiOptions; 498 } 499 500 @Override setUiOptions(int uiOptions, int mask)501 public void setUiOptions(int uiOptions, int mask) { 502 mUiOptions = (mUiOptions & ~mask) | (uiOptions & mask); 503 } 504 505 @Override getTransitionManager()506 public TransitionManager getTransitionManager() { 507 return mTransitionManager; 508 } 509 510 @Override setTransitionManager(TransitionManager tm)511 public void setTransitionManager(TransitionManager tm) { 512 mTransitionManager = tm; 513 } 514 515 @Override getContentScene()516 public Scene getContentScene() { 517 return mContentScene; 518 } 519 520 @Override setContentView(int layoutResID)521 public void setContentView(int layoutResID) { 522 // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window 523 // decor, when theme attributes and the like are crystalized. Do not check the feature 524 // before this happens. 525 if (mContentParent == null) { 526 installDecor(); 527 } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) { 528 mContentParent.removeAllViews(); 529 } 530 531 if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) { 532 final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID, 533 getContext()); 534 transitionTo(newScene); 535 } else { 536 mLayoutInflater.inflate(layoutResID, mContentParent); 537 } 538 mContentParent.requestApplyInsets(); 539 final Callback cb = getCallback(); 540 if (!isDestroyed()) { 541 if (cb != null) { 542 cb.onContentChanged(); 543 } 544 if (mDecorContentParent != null) { 545 mDecorContentParent.notifyContentChanged(); 546 } 547 } 548 mContentParentExplicitlySet = true; 549 } 550 551 @Override setContentView(View view)552 public void setContentView(View view) { 553 setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); 554 } 555 556 @Override setContentView(View view, ViewGroup.LayoutParams params)557 public void setContentView(View view, ViewGroup.LayoutParams params) { 558 // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window 559 // decor, when theme attributes and the like are crystalized. Do not check the feature 560 // before this happens. 561 if (mContentParent == null) { 562 installDecor(); 563 } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) { 564 mContentParent.removeAllViews(); 565 } 566 567 if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) { 568 view.setLayoutParams(params); 569 final Scene newScene = new Scene(mContentParent, view); 570 transitionTo(newScene); 571 } else { 572 mContentParent.addView(view, params); 573 } 574 mContentParent.requestApplyInsets(); 575 final Callback cb = getCallback(); 576 if (!isDestroyed()) { 577 if (cb != null) { 578 cb.onContentChanged(); 579 } 580 if (mDecorContentParent != null) { 581 mDecorContentParent.notifyContentChanged(); 582 } 583 } 584 mContentParentExplicitlySet = true; 585 } 586 587 @Override addContentView(View view, ViewGroup.LayoutParams params)588 public void addContentView(View view, ViewGroup.LayoutParams params) { 589 if (mContentParent == null) { 590 installDecor(); 591 } 592 if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) { 593 // TODO Augment the scenes/transitions API to support this. 594 Log.v(TAG, "addContentView does not support content transitions"); 595 } 596 mContentParent.addView(view, params); 597 mContentParent.requestApplyInsets(); 598 final Callback cb = getCallback(); 599 if (!isDestroyed()) { 600 if (cb != null) { 601 cb.onContentChanged(); 602 } 603 if (mDecorContentParent != null) { 604 mDecorContentParent.notifyContentChanged(); 605 } 606 } 607 } 608 609 @Override clearContentView()610 public void clearContentView() { 611 if (mDecor != null) { 612 mDecor.clearContentView(); 613 } 614 } 615 transitionTo(Scene scene)616 private void transitionTo(Scene scene) { 617 if (mContentScene == null) { 618 scene.enter(); 619 } else { 620 mTransitionManager.transitionTo(scene); 621 } 622 mContentScene = scene; 623 } 624 625 @Override getCurrentFocus()626 public View getCurrentFocus() { 627 return mDecor != null ? mDecor.findFocus() : null; 628 } 629 630 @Override takeSurface(Callback2 callback)631 public void takeSurface(Callback2 callback) { 632 mTakeSurfaceCallback = callback; 633 } 634 takeInputQueue(InputQueue.Callback callback)635 public void takeInputQueue(InputQueue.Callback callback) { 636 mTakeInputQueueCallback = callback; 637 } 638 639 @Override isFloating()640 public boolean isFloating() { 641 return mIsFloating; 642 } 643 isTranslucent()644 public boolean isTranslucent() { 645 return mIsTranslucent; 646 } 647 648 /** 649 * @return Whether the window is currently showing the wallpaper. 650 */ isShowingWallpaper()651 boolean isShowingWallpaper() { 652 return (getAttributes().flags & FLAG_SHOW_WALLPAPER) != 0; 653 } 654 655 /** 656 * Return a LayoutInflater instance that can be used to inflate XML view layout 657 * resources for use in this Window. 658 * 659 * @return LayoutInflater The shared LayoutInflater. 660 */ 661 @Override getLayoutInflater()662 public LayoutInflater getLayoutInflater() { 663 return mLayoutInflater; 664 } 665 666 @Override setTitle(CharSequence title)667 public void setTitle(CharSequence title) { 668 setTitle(title, true); 669 } 670 setTitle(CharSequence title, boolean updateAccessibilityTitle)671 public void setTitle(CharSequence title, boolean updateAccessibilityTitle) { 672 if (mTitleView != null) { 673 mTitleView.setText(title); 674 } else if (mDecorContentParent != null) { 675 mDecorContentParent.setWindowTitle(title); 676 } 677 mTitle = title; 678 if (updateAccessibilityTitle) { 679 WindowManager.LayoutParams params = getAttributes(); 680 if (!TextUtils.equals(title, params.accessibilityTitle)) { 681 params.accessibilityTitle = TextUtils.stringOrSpannedString(title); 682 if (mDecor != null) { 683 // ViewRootImpl will make sure the change propagates to WindowManagerService 684 ViewRootImpl vr = mDecor.getViewRootImpl(); 685 if (vr != null) { 686 vr.onWindowTitleChanged(); 687 } 688 } 689 dispatchWindowAttributesChanged(getAttributes()); 690 } 691 } 692 } 693 694 @Override 695 @Deprecated setTitleColor(int textColor)696 public void setTitleColor(int textColor) { 697 if (mTitleView != null) { 698 mTitleView.setTextColor(textColor); 699 } 700 mTitleColor = textColor; 701 } 702 703 /** 704 * Prepares the panel to either be opened or chorded. This creates the Menu 705 * instance for the panel and populates it via the Activity callbacks. 706 * 707 * @param st The panel state to prepare. 708 * @param event The event that triggered the preparing of the panel. 709 * @return Whether the panel was prepared. If the panel should not be shown, 710 * returns false. 711 */ preparePanel(PanelFeatureState st, KeyEvent event)712 public final boolean preparePanel(PanelFeatureState st, KeyEvent event) { 713 if (isDestroyed()) { 714 return false; 715 } 716 717 // Already prepared (isPrepared will be reset to false later) 718 if (st.isPrepared) { 719 return true; 720 } 721 722 if ((mPreparedPanel != null) && (mPreparedPanel != st)) { 723 // Another Panel is prepared and possibly open, so close it 724 closePanel(mPreparedPanel, false); 725 } 726 727 final Callback cb = getCallback(); 728 729 if (cb != null) { 730 st.createdPanelView = cb.onCreatePanelView(st.featureId); 731 } 732 733 final boolean isActionBarMenu = 734 (st.featureId == FEATURE_OPTIONS_PANEL || st.featureId == FEATURE_ACTION_BAR); 735 736 if (isActionBarMenu && mDecorContentParent != null) { 737 // Enforce ordering guarantees around events so that the action bar never 738 // dispatches menu-related events before the panel is prepared. 739 mDecorContentParent.setMenuPrepared(); 740 } 741 742 if (st.createdPanelView == null) { 743 // Init the panel state's menu--return false if init failed 744 if (st.menu == null || st.refreshMenuContent) { 745 if (st.menu == null) { 746 if (!initializePanelMenu(st) || (st.menu == null)) { 747 return false; 748 } 749 } 750 751 if (isActionBarMenu && mDecorContentParent != null) { 752 if (mActionMenuPresenterCallback == null) { 753 mActionMenuPresenterCallback = new ActionMenuPresenterCallback(); 754 } 755 mDecorContentParent.setMenu(st.menu, mActionMenuPresenterCallback); 756 } 757 758 // Call callback, and return if it doesn't want to display menu. 759 760 // Creating the panel menu will involve a lot of manipulation; 761 // don't dispatch change events to presenters until we're done. 762 st.menu.stopDispatchingItemsChanged(); 763 if ((cb == null) || !cb.onCreatePanelMenu(st.featureId, st.menu)) { 764 // Ditch the menu created above 765 st.setMenu(null); 766 767 if (isActionBarMenu && mDecorContentParent != null) { 768 // Don't show it in the action bar either 769 mDecorContentParent.setMenu(null, mActionMenuPresenterCallback); 770 } 771 772 return false; 773 } 774 775 st.refreshMenuContent = false; 776 } 777 778 // Callback and return if the callback does not want to show the menu 779 780 // Preparing the panel menu can involve a lot of manipulation; 781 // don't dispatch change events to presenters until we're done. 782 st.menu.stopDispatchingItemsChanged(); 783 784 // Restore action view state before we prepare. This gives apps 785 // an opportunity to override frozen/restored state in onPrepare. 786 if (st.frozenActionViewState != null) { 787 st.menu.restoreActionViewStates(st.frozenActionViewState); 788 st.frozenActionViewState = null; 789 } 790 791 if (!cb.onPreparePanel(st.featureId, st.createdPanelView, st.menu)) { 792 if (isActionBarMenu && mDecorContentParent != null) { 793 // The app didn't want to show the menu for now but it still exists. 794 // Clear it out of the action bar. 795 mDecorContentParent.setMenu(null, mActionMenuPresenterCallback); 796 } 797 st.menu.startDispatchingItemsChanged(); 798 return false; 799 } 800 801 // Set the proper keymap 802 KeyCharacterMap kmap = KeyCharacterMap.load( 803 event != null ? event.getDeviceId() : KeyCharacterMap.VIRTUAL_KEYBOARD); 804 st.qwertyMode = kmap.getKeyboardType() != KeyCharacterMap.NUMERIC; 805 st.menu.setQwertyMode(st.qwertyMode); 806 st.menu.startDispatchingItemsChanged(); 807 } 808 809 // Set other state 810 st.isPrepared = true; 811 st.isHandled = false; 812 mPreparedPanel = st; 813 814 return true; 815 } 816 817 @Override onConfigurationChanged(Configuration newConfig)818 public void onConfigurationChanged(Configuration newConfig) { 819 // Action bars handle their own menu state 820 if (mDecorContentParent == null) { 821 PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false); 822 if ((st != null) && (st.menu != null)) { 823 if (st.isOpen) { 824 // Freeze state 825 final Bundle state = new Bundle(); 826 if (st.iconMenuPresenter != null) { 827 st.iconMenuPresenter.saveHierarchyState(state); 828 } 829 if (st.listMenuPresenter != null) { 830 st.listMenuPresenter.saveHierarchyState(state); 831 } 832 833 // Remove the menu views since they need to be recreated 834 // according to the new configuration 835 clearMenuViews(st); 836 837 // Re-open the same menu 838 reopenMenu(false); 839 840 // Restore state 841 if (st.iconMenuPresenter != null) { 842 st.iconMenuPresenter.restoreHierarchyState(state); 843 } 844 if (st.listMenuPresenter != null) { 845 st.listMenuPresenter.restoreHierarchyState(state); 846 } 847 848 } else { 849 // Clear menu views so on next menu opening, it will use 850 // the proper layout 851 clearMenuViews(st); 852 } 853 } 854 } 855 } 856 857 @Override onMultiWindowModeChanged()858 public void onMultiWindowModeChanged() { 859 if (mDecor != null) { 860 mDecor.onConfigurationChanged(getContext().getResources().getConfiguration()); 861 } 862 } 863 864 @Override onPictureInPictureModeChanged(boolean isInPictureInPictureMode)865 public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) { 866 if (mDecor != null) { 867 mDecor.updatePictureInPictureOutlineProvider(isInPictureInPictureMode); 868 } 869 } 870 clearMenuViews(PanelFeatureState st)871 private static void clearMenuViews(PanelFeatureState st) { 872 // This can be called on config changes, so we should make sure 873 // the views will be reconstructed based on the new orientation, etc. 874 875 // Allow the callback to create a new panel view 876 st.createdPanelView = null; 877 878 // Causes the decor view to be recreated 879 st.refreshDecorView = true; 880 881 st.clearMenuPresenters(); 882 } 883 884 @Override openPanel(int featureId, KeyEvent event)885 public final void openPanel(int featureId, KeyEvent event) { 886 if (featureId == FEATURE_OPTIONS_PANEL && mDecorContentParent != null && 887 mDecorContentParent.canShowOverflowMenu() && 888 !ViewConfiguration.get(getContext()).hasPermanentMenuKey()) { 889 mDecorContentParent.showOverflowMenu(); 890 } else { 891 openPanel(getPanelState(featureId, true), event); 892 } 893 } 894 openPanel(final PanelFeatureState st, KeyEvent event)895 private void openPanel(final PanelFeatureState st, KeyEvent event) { 896 // System.out.println("Open panel: isOpen=" + st.isOpen); 897 898 // Already open, return 899 if (st.isOpen || isDestroyed()) { 900 return; 901 } 902 903 // Don't open an options panel for honeycomb apps on xlarge devices. 904 // (The app should be using an action bar for menu items.) 905 if (st.featureId == FEATURE_OPTIONS_PANEL) { 906 Context context = getContext(); 907 Configuration config = context.getResources().getConfiguration(); 908 boolean isXLarge = (config.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) == 909 Configuration.SCREENLAYOUT_SIZE_XLARGE; 910 boolean isHoneycombApp = context.getApplicationInfo().targetSdkVersion >= 911 android.os.Build.VERSION_CODES.HONEYCOMB; 912 913 if (isXLarge && isHoneycombApp) { 914 return; 915 } 916 } 917 918 Callback cb = getCallback(); 919 if ((cb != null) && (!cb.onMenuOpened(st.featureId, st.menu))) { 920 // Callback doesn't want the menu to open, reset any state 921 closePanel(st, true); 922 return; 923 } 924 925 final WindowManager wm = getWindowManager(); 926 if (wm == null) { 927 return; 928 } 929 930 // Prepare panel (should have been done before, but just in case) 931 if (!preparePanel(st, event)) { 932 return; 933 } 934 935 int width = WRAP_CONTENT; 936 if (st.decorView == null || st.refreshDecorView) { 937 if (st.decorView == null) { 938 // Initialize the panel decor, this will populate st.decorView 939 if (!initializePanelDecor(st) || (st.decorView == null)) 940 return; 941 } else if (st.refreshDecorView && (st.decorView.getChildCount() > 0)) { 942 // Decor needs refreshing, so remove its views 943 st.decorView.removeAllViews(); 944 } 945 946 // This will populate st.shownPanelView 947 if (!initializePanelContent(st) || !st.hasPanelItems()) { 948 return; 949 } 950 951 ViewGroup.LayoutParams lp = st.shownPanelView.getLayoutParams(); 952 if (lp == null) { 953 lp = new ViewGroup.LayoutParams(WRAP_CONTENT, WRAP_CONTENT); 954 } 955 956 int backgroundResId; 957 if (lp.width == ViewGroup.LayoutParams.MATCH_PARENT) { 958 // If the contents is fill parent for the width, set the 959 // corresponding background 960 backgroundResId = st.fullBackground; 961 width = MATCH_PARENT; 962 } else { 963 // Otherwise, set the normal panel background 964 backgroundResId = st.background; 965 } 966 st.decorView.setWindowBackground(getContext().getDrawable( 967 backgroundResId)); 968 969 ViewParent shownPanelParent = st.shownPanelView.getParent(); 970 if (shownPanelParent != null && shownPanelParent instanceof ViewGroup) { 971 ((ViewGroup) shownPanelParent).removeView(st.shownPanelView); 972 } 973 st.decorView.addView(st.shownPanelView, lp); 974 975 /* 976 * Give focus to the view, if it or one of its children does not 977 * already have it. 978 */ 979 if (!st.shownPanelView.hasFocus()) { 980 st.shownPanelView.requestFocus(); 981 } 982 } else if (!st.isInListMode()) { 983 width = MATCH_PARENT; 984 } else if (st.createdPanelView != null) { 985 // If we already had a panel view, carry width=MATCH_PARENT through 986 // as we did above when it was created. 987 ViewGroup.LayoutParams lp = st.createdPanelView.getLayoutParams(); 988 if (lp != null && lp.width == ViewGroup.LayoutParams.MATCH_PARENT) { 989 width = MATCH_PARENT; 990 } 991 } 992 993 if (!st.hasPanelItems()) { 994 // Ensure that |st.decorView| has its actual content. Otherwise, an empty window can be 995 // created and cause ANR. 996 return; 997 } 998 999 st.isHandled = false; 1000 1001 WindowManager.LayoutParams lp = new WindowManager.LayoutParams( 1002 width, WRAP_CONTENT, 1003 st.x, st.y, WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG, 1004 WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM 1005 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, 1006 st.decorView.mDefaultOpacity); 1007 1008 if (st.isCompact) { 1009 lp.gravity = getOptionsPanelGravity(); 1010 sRotationWatcher.addWindow(this); 1011 } else { 1012 lp.gravity = st.gravity; 1013 } 1014 1015 lp.windowAnimations = st.windowAnimations; 1016 1017 wm.addView(st.decorView, lp); 1018 st.isOpen = true; 1019 // Log.v(TAG, "Adding main menu to window manager."); 1020 } 1021 1022 @Override closePanel(int featureId)1023 public final void closePanel(int featureId) { 1024 if (featureId == FEATURE_OPTIONS_PANEL && mDecorContentParent != null && 1025 mDecorContentParent.canShowOverflowMenu() && 1026 !ViewConfiguration.get(getContext()).hasPermanentMenuKey()) { 1027 mDecorContentParent.hideOverflowMenu(); 1028 } else if (featureId == FEATURE_CONTEXT_MENU) { 1029 closeContextMenu(); 1030 } else { 1031 closePanel(getPanelState(featureId, true), true); 1032 } 1033 } 1034 1035 /** 1036 * Closes the given panel. 1037 * 1038 * @param st The panel to be closed. 1039 * @param doCallback Whether to notify the callback that the panel was 1040 * closed. If the panel is in the process of re-opening or 1041 * opening another panel (e.g., menu opening a sub menu), the 1042 * callback should not happen and this variable should be false. 1043 * In addition, this method internally will only perform the 1044 * callback if the panel is open. 1045 */ closePanel(PanelFeatureState st, boolean doCallback)1046 public final void closePanel(PanelFeatureState st, boolean doCallback) { 1047 // System.out.println("Close panel: isOpen=" + st.isOpen); 1048 if (doCallback && st.featureId == FEATURE_OPTIONS_PANEL && 1049 mDecorContentParent != null && mDecorContentParent.isOverflowMenuShowing()) { 1050 checkCloseActionMenu(st.menu); 1051 return; 1052 } 1053 1054 final ViewManager wm = getWindowManager(); 1055 if ((wm != null) && st.isOpen) { 1056 if (st.decorView != null) { 1057 wm.removeView(st.decorView); 1058 // Log.v(TAG, "Removing main menu from window manager."); 1059 if (st.isCompact) { 1060 sRotationWatcher.removeWindow(this); 1061 } 1062 } 1063 1064 if (doCallback) { 1065 callOnPanelClosed(st.featureId, st, null); 1066 } 1067 } 1068 1069 st.isPrepared = false; 1070 st.isHandled = false; 1071 st.isOpen = false; 1072 1073 // This view is no longer shown, so null it out 1074 st.shownPanelView = null; 1075 1076 if (st.isInExpandedMode) { 1077 // Next time the menu opens, it should not be in expanded mode, so 1078 // force a refresh of the decor 1079 st.refreshDecorView = true; 1080 st.isInExpandedMode = false; 1081 } 1082 1083 if (mPreparedPanel == st) { 1084 mPreparedPanel = null; 1085 mPanelChordingKey = 0; 1086 } 1087 } 1088 checkCloseActionMenu(Menu menu)1089 void checkCloseActionMenu(Menu menu) { 1090 if (mClosingActionMenu) { 1091 return; 1092 } 1093 1094 mClosingActionMenu = true; 1095 mDecorContentParent.dismissPopups(); 1096 Callback cb = getCallback(); 1097 if (cb != null && !isDestroyed()) { 1098 cb.onPanelClosed(FEATURE_ACTION_BAR, menu); 1099 } 1100 mClosingActionMenu = false; 1101 } 1102 1103 @Override togglePanel(int featureId, KeyEvent event)1104 public final void togglePanel(int featureId, KeyEvent event) { 1105 PanelFeatureState st = getPanelState(featureId, true); 1106 if (st.isOpen) { 1107 closePanel(st, true); 1108 } else { 1109 openPanel(st, event); 1110 } 1111 } 1112 1113 @Override invalidatePanelMenu(int featureId)1114 public void invalidatePanelMenu(int featureId) { 1115 mInvalidatePanelMenuFeatures |= 1 << featureId; 1116 1117 if (!mInvalidatePanelMenuPosted && mDecor != null) { 1118 mDecor.postOnAnimation(mInvalidatePanelMenuRunnable); 1119 mInvalidatePanelMenuPosted = true; 1120 } 1121 } 1122 doPendingInvalidatePanelMenu()1123 void doPendingInvalidatePanelMenu() { 1124 if (mInvalidatePanelMenuPosted) { 1125 mDecor.removeCallbacks(mInvalidatePanelMenuRunnable); 1126 mInvalidatePanelMenuRunnable.run(); 1127 } 1128 } 1129 doInvalidatePanelMenu(int featureId)1130 void doInvalidatePanelMenu(int featureId) { 1131 PanelFeatureState st = getPanelState(featureId, false); 1132 if (st == null) { 1133 return; 1134 } 1135 Bundle savedActionViewStates = null; 1136 if (st.menu != null) { 1137 savedActionViewStates = new Bundle(); 1138 st.menu.saveActionViewStates(savedActionViewStates); 1139 if (savedActionViewStates.size() > 0) { 1140 st.frozenActionViewState = savedActionViewStates; 1141 } 1142 // This will be started again when the panel is prepared. 1143 st.menu.stopDispatchingItemsChanged(); 1144 st.menu.clear(); 1145 } 1146 st.refreshMenuContent = true; 1147 st.refreshDecorView = true; 1148 1149 // Prepare the options panel if we have an action bar 1150 if ((featureId == FEATURE_ACTION_BAR || featureId == FEATURE_OPTIONS_PANEL) 1151 && mDecorContentParent != null) { 1152 st = getPanelState(Window.FEATURE_OPTIONS_PANEL, false); 1153 if (st != null) { 1154 st.isPrepared = false; 1155 preparePanel(st, null); 1156 } 1157 } 1158 } 1159 1160 /** 1161 * Called when the panel key is pushed down. 1162 * @param featureId The feature ID of the relevant panel (defaults to FEATURE_OPTIONS_PANEL}. 1163 * @param event The key event. 1164 * @return Whether the key was handled. 1165 */ onKeyDownPanel(int featureId, KeyEvent event)1166 public final boolean onKeyDownPanel(int featureId, KeyEvent event) { 1167 final int keyCode = event.getKeyCode(); 1168 1169 if (event.getRepeatCount() == 0) { 1170 // The panel key was pushed, so set the chording key 1171 mPanelChordingKey = keyCode; 1172 1173 PanelFeatureState st = getPanelState(featureId, false); 1174 if (st != null && !st.isOpen) { 1175 return preparePanel(st, event); 1176 } 1177 } 1178 1179 return false; 1180 } 1181 1182 /** 1183 * Called when the panel key is released. 1184 * @param featureId The feature ID of the relevant panel (defaults to FEATURE_OPTIONS_PANEL}. 1185 * @param event The key event. 1186 */ onKeyUpPanel(int featureId, KeyEvent event)1187 public final void onKeyUpPanel(int featureId, KeyEvent event) { 1188 // The panel key was released, so clear the chording key 1189 if (mPanelChordingKey != 0) { 1190 mPanelChordingKey = 0; 1191 1192 final PanelFeatureState st = getPanelState(featureId, false); 1193 1194 if (event.isCanceled() || (mDecor != null && mDecor.mPrimaryActionMode != null) || 1195 (st == null)) { 1196 return; 1197 } 1198 1199 boolean playSoundEffect = false; 1200 if (featureId == FEATURE_OPTIONS_PANEL && mDecorContentParent != null && 1201 mDecorContentParent.canShowOverflowMenu() && 1202 !ViewConfiguration.get(getContext()).hasPermanentMenuKey()) { 1203 if (!mDecorContentParent.isOverflowMenuShowing()) { 1204 if (!isDestroyed() && preparePanel(st, event)) { 1205 playSoundEffect = mDecorContentParent.showOverflowMenu(); 1206 } 1207 } else { 1208 playSoundEffect = mDecorContentParent.hideOverflowMenu(); 1209 } 1210 } else { 1211 if (st.isOpen || st.isHandled) { 1212 1213 // Play the sound effect if the user closed an open menu (and not if 1214 // they just released a menu shortcut) 1215 playSoundEffect = st.isOpen; 1216 1217 // Close menu 1218 closePanel(st, true); 1219 1220 } else if (st.isPrepared) { 1221 boolean show = true; 1222 if (st.refreshMenuContent) { 1223 // Something may have invalidated the menu since we prepared it. 1224 // Re-prepare it to refresh. 1225 st.isPrepared = false; 1226 show = preparePanel(st, event); 1227 } 1228 1229 if (show) { 1230 // Write 'menu opened' to event log 1231 EventLog.writeEvent(50001, 0); 1232 1233 // Show menu 1234 openPanel(st, event); 1235 1236 playSoundEffect = true; 1237 } 1238 } 1239 } 1240 1241 if (playSoundEffect) { 1242 AudioManager audioManager = (AudioManager) getContext().getSystemService( 1243 Context.AUDIO_SERVICE); 1244 if (audioManager != null) { 1245 audioManager.playSoundEffect(AudioManager.FX_KEY_CLICK); 1246 } else { 1247 Log.w(TAG, "Couldn't get audio manager"); 1248 } 1249 } 1250 } 1251 } 1252 1253 @Override closeAllPanels()1254 public final void closeAllPanels() { 1255 final ViewManager wm = getWindowManager(); 1256 if (wm == null) { 1257 return; 1258 } 1259 1260 final PanelFeatureState[] panels = mPanels; 1261 final int N = panels != null ? panels.length : 0; 1262 for (int i = 0; i < N; i++) { 1263 final PanelFeatureState panel = panels[i]; 1264 if (panel != null) { 1265 closePanel(panel, true); 1266 } 1267 } 1268 1269 closeContextMenu(); 1270 } 1271 1272 /** 1273 * Closes the context menu. This notifies the menu logic of the close, along 1274 * with dismissing it from the UI. 1275 */ closeContextMenu()1276 private synchronized void closeContextMenu() { 1277 if (mContextMenu != null) { 1278 mContextMenu.close(); 1279 dismissContextMenu(); 1280 } 1281 } 1282 1283 /** 1284 * Dismisses just the context menu UI. To close the context menu, use 1285 * {@link #closeContextMenu()}. 1286 */ dismissContextMenu()1287 private synchronized void dismissContextMenu() { 1288 mContextMenu = null; 1289 1290 if (mContextMenuHelper != null) { 1291 mContextMenuHelper.dismiss(); 1292 mContextMenuHelper = null; 1293 } 1294 } 1295 1296 @Override performPanelShortcut(int featureId, int keyCode, KeyEvent event, int flags)1297 public boolean performPanelShortcut(int featureId, int keyCode, KeyEvent event, int flags) { 1298 return performPanelShortcut(getPanelState(featureId, false), keyCode, event, flags); 1299 } 1300 performPanelShortcut(PanelFeatureState st, int keyCode, KeyEvent event, int flags)1301 boolean performPanelShortcut(PanelFeatureState st, int keyCode, KeyEvent event, 1302 int flags) { 1303 if (event.isSystem() || (st == null)) { 1304 return false; 1305 } 1306 1307 boolean handled = false; 1308 1309 // Only try to perform menu shortcuts if preparePanel returned true (possible false 1310 // return value from application not wanting to show the menu). 1311 if ((st.isPrepared || preparePanel(st, event)) && st.menu != null) { 1312 // The menu is prepared now, perform the shortcut on it 1313 handled = st.menu.performShortcut(keyCode, event, flags); 1314 } 1315 1316 if (handled) { 1317 // Mark as handled 1318 st.isHandled = true; 1319 1320 // Only close down the menu if we don't have an action bar keeping it open. 1321 if ((flags & Menu.FLAG_PERFORM_NO_CLOSE) == 0 && mDecorContentParent == null) { 1322 closePanel(st, true); 1323 } 1324 } 1325 1326 return handled; 1327 } 1328 1329 @Override performPanelIdentifierAction(int featureId, int id, int flags)1330 public boolean performPanelIdentifierAction(int featureId, int id, int flags) { 1331 1332 PanelFeatureState st = getPanelState(featureId, true); 1333 if (!preparePanel(st, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MENU))) { 1334 return false; 1335 } 1336 if (st.menu == null) { 1337 return false; 1338 } 1339 1340 boolean res = st.menu.performIdentifierAction(id, flags); 1341 1342 // Only close down the menu if we don't have an action bar keeping it open. 1343 if (mDecorContentParent == null) { 1344 closePanel(st, true); 1345 } 1346 1347 return res; 1348 } 1349 findMenuPanel(Menu menu)1350 public PanelFeatureState findMenuPanel(Menu menu) { 1351 final PanelFeatureState[] panels = mPanels; 1352 final int N = panels != null ? panels.length : 0; 1353 for (int i = 0; i < N; i++) { 1354 final PanelFeatureState panel = panels[i]; 1355 if (panel != null && panel.menu == menu) { 1356 return panel; 1357 } 1358 } 1359 return null; 1360 } 1361 onMenuItemSelected(MenuBuilder menu, MenuItem item)1362 public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) { 1363 final Callback cb = getCallback(); 1364 if (cb != null && !isDestroyed()) { 1365 final PanelFeatureState panel = findMenuPanel(menu.getRootMenu()); 1366 if (panel != null) { 1367 return cb.onMenuItemSelected(panel.featureId, item); 1368 } 1369 } 1370 return false; 1371 } 1372 onMenuModeChange(MenuBuilder menu)1373 public void onMenuModeChange(MenuBuilder menu) { 1374 reopenMenu(true); 1375 } 1376 reopenMenu(boolean toggleMenuMode)1377 private void reopenMenu(boolean toggleMenuMode) { 1378 if (mDecorContentParent != null && mDecorContentParent.canShowOverflowMenu() && 1379 (!ViewConfiguration.get(getContext()).hasPermanentMenuKey() || 1380 mDecorContentParent.isOverflowMenuShowPending())) { 1381 final Callback cb = getCallback(); 1382 if (!mDecorContentParent.isOverflowMenuShowing() || !toggleMenuMode) { 1383 if (cb != null && !isDestroyed()) { 1384 // If we have a menu invalidation pending, do it now. 1385 if (mInvalidatePanelMenuPosted && 1386 (mInvalidatePanelMenuFeatures & (1 << FEATURE_OPTIONS_PANEL)) != 0) { 1387 mDecor.removeCallbacks(mInvalidatePanelMenuRunnable); 1388 mInvalidatePanelMenuRunnable.run(); 1389 } 1390 1391 final PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false); 1392 1393 // If we don't have a menu or we're waiting for a full content refresh, 1394 // forget it. This is a lingering event that no longer matters. 1395 if (st != null && st.menu != null && !st.refreshMenuContent && 1396 cb.onPreparePanel(FEATURE_OPTIONS_PANEL, st.createdPanelView, st.menu)) { 1397 cb.onMenuOpened(FEATURE_ACTION_BAR, st.menu); 1398 mDecorContentParent.showOverflowMenu(); 1399 } 1400 } 1401 } else { 1402 mDecorContentParent.hideOverflowMenu(); 1403 final PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false); 1404 if (st != null && cb != null && !isDestroyed()) { 1405 cb.onPanelClosed(FEATURE_ACTION_BAR, st.menu); 1406 } 1407 } 1408 return; 1409 } 1410 1411 PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false); 1412 1413 if (st == null) { 1414 return; 1415 } 1416 1417 // Save the future expanded mode state since closePanel will reset it 1418 boolean newExpandedMode = toggleMenuMode ? !st.isInExpandedMode : st.isInExpandedMode; 1419 1420 st.refreshDecorView = true; 1421 closePanel(st, false); 1422 1423 // Set the expanded mode state 1424 st.isInExpandedMode = newExpandedMode; 1425 1426 openPanel(st, null); 1427 } 1428 1429 /** 1430 * Initializes the menu associated with the given panel feature state. You 1431 * must at the very least set PanelFeatureState.menu to the Menu to be 1432 * associated with the given panel state. The default implementation creates 1433 * a new menu for the panel state. 1434 * 1435 * @param st The panel whose menu is being initialized. 1436 * @return Whether the initialization was successful. 1437 */ initializePanelMenu(final PanelFeatureState st)1438 protected boolean initializePanelMenu(final PanelFeatureState st) { 1439 Context context = getContext(); 1440 1441 // If we have an action bar, initialize the menu with the right theme. 1442 if ((st.featureId == FEATURE_OPTIONS_PANEL || st.featureId == FEATURE_ACTION_BAR) && 1443 mDecorContentParent != null) { 1444 final TypedValue outValue = new TypedValue(); 1445 final Theme baseTheme = context.getTheme(); 1446 baseTheme.resolveAttribute(R.attr.actionBarTheme, outValue, true); 1447 1448 Theme widgetTheme = null; 1449 if (outValue.resourceId != 0) { 1450 widgetTheme = context.getResources().newTheme(); 1451 widgetTheme.setTo(baseTheme); 1452 widgetTheme.applyStyle(outValue.resourceId, true); 1453 widgetTheme.resolveAttribute( 1454 R.attr.actionBarWidgetTheme, outValue, true); 1455 } else { 1456 baseTheme.resolveAttribute( 1457 R.attr.actionBarWidgetTheme, outValue, true); 1458 } 1459 1460 if (outValue.resourceId != 0) { 1461 if (widgetTheme == null) { 1462 widgetTheme = context.getResources().newTheme(); 1463 widgetTheme.setTo(baseTheme); 1464 } 1465 widgetTheme.applyStyle(outValue.resourceId, true); 1466 } 1467 1468 if (widgetTheme != null) { 1469 context = new ContextThemeWrapper(context, 0); 1470 context.getTheme().setTo(widgetTheme); 1471 } 1472 } 1473 1474 final MenuBuilder menu = new MenuBuilder(context); 1475 menu.setCallback(this); 1476 st.setMenu(menu); 1477 1478 return true; 1479 } 1480 1481 /** 1482 * Perform initial setup of a panel. This should at the very least set the 1483 * style information in the PanelFeatureState and must set 1484 * PanelFeatureState.decor to the panel's window decor view. 1485 * 1486 * @param st The panel being initialized. 1487 */ initializePanelDecor(PanelFeatureState st)1488 protected boolean initializePanelDecor(PanelFeatureState st) { 1489 st.decorView = generateDecor(st.featureId); 1490 st.gravity = Gravity.CENTER | Gravity.BOTTOM; 1491 st.setStyle(getContext()); 1492 TypedArray a = getContext().obtainStyledAttributes(null, 1493 R.styleable.Window, 0, st.listPresenterTheme); 1494 final float elevation = a.getDimension(R.styleable.Window_windowElevation, 0); 1495 if (elevation != 0) { 1496 st.decorView.setElevation(elevation); 1497 } 1498 a.recycle(); 1499 1500 return true; 1501 } 1502 1503 /** 1504 * Determine the gravity value for the options panel. This can 1505 * differ in compact mode. 1506 * 1507 * @return gravity value to use for the panel window 1508 */ getOptionsPanelGravity()1509 private int getOptionsPanelGravity() { 1510 try { 1511 return WindowManagerHolder.sWindowManager.getPreferredOptionsPanelGravity( 1512 getContext().getDisplayId()); 1513 } catch (RemoteException ex) { 1514 Log.e(TAG, "Couldn't getOptionsPanelGravity; using default", ex); 1515 return Gravity.CENTER | Gravity.BOTTOM; 1516 } 1517 } 1518 onOptionsPanelRotationChanged()1519 void onOptionsPanelRotationChanged() { 1520 final PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false); 1521 if (st == null) return; 1522 1523 final WindowManager.LayoutParams lp = st.decorView != null ? 1524 (WindowManager.LayoutParams) st.decorView.getLayoutParams() : null; 1525 if (lp != null) { 1526 lp.gravity = getOptionsPanelGravity(); 1527 final ViewManager wm = getWindowManager(); 1528 if (wm != null) { 1529 wm.updateViewLayout(st.decorView, lp); 1530 } 1531 } 1532 } 1533 1534 /** 1535 * Initializes the panel associated with the panel feature state. You must 1536 * at the very least set PanelFeatureState.panel to the View implementing 1537 * its contents. The default implementation gets the panel from the menu. 1538 * 1539 * @param st The panel state being initialized. 1540 * @return Whether the initialization was successful. 1541 */ initializePanelContent(PanelFeatureState st)1542 protected boolean initializePanelContent(PanelFeatureState st) { 1543 if (st.createdPanelView != null) { 1544 st.shownPanelView = st.createdPanelView; 1545 return true; 1546 } 1547 1548 if (st.menu == null) { 1549 return false; 1550 } 1551 1552 if (mPanelMenuPresenterCallback == null) { 1553 mPanelMenuPresenterCallback = new PanelMenuPresenterCallback(); 1554 } 1555 1556 MenuView menuView = st.isInListMode() 1557 ? st.getListMenuView(getContext(), mPanelMenuPresenterCallback) 1558 : st.getIconMenuView(getContext(), mPanelMenuPresenterCallback); 1559 1560 st.shownPanelView = (View) menuView; 1561 1562 if (st.shownPanelView != null) { 1563 // Use the menu View's default animations if it has any 1564 final int defaultAnimations = menuView.getWindowAnimations(); 1565 if (defaultAnimations != 0) { 1566 st.windowAnimations = defaultAnimations; 1567 } 1568 return true; 1569 } else { 1570 return false; 1571 } 1572 } 1573 1574 @Override performContextMenuIdentifierAction(int id, int flags)1575 public boolean performContextMenuIdentifierAction(int id, int flags) { 1576 return (mContextMenu != null) ? mContextMenu.performIdentifierAction(id, flags) : false; 1577 } 1578 1579 @Override setElevation(float elevation)1580 public final void setElevation(float elevation) { 1581 mElevation = elevation; 1582 final WindowManager.LayoutParams attrs = getAttributes(); 1583 if (mDecor != null) { 1584 mDecor.setElevation(elevation); 1585 attrs.setSurfaceInsets(mDecor, true /*manual*/, false /*preservePrevious*/); 1586 } 1587 dispatchWindowAttributesChanged(attrs); 1588 } 1589 1590 @Override getElevation()1591 public float getElevation() { 1592 return mElevation; 1593 } 1594 1595 @Override setClipToOutline(boolean clipToOutline)1596 public final void setClipToOutline(boolean clipToOutline) { 1597 mClipToOutline = clipToOutline; 1598 if (mDecor != null) { 1599 mDecor.setClipToOutline(clipToOutline); 1600 } 1601 } 1602 1603 @Override setBackgroundDrawable(Drawable drawable)1604 public final void setBackgroundDrawable(Drawable drawable) { 1605 if (drawable != mBackgroundDrawable) { 1606 mBackgroundDrawable = drawable; 1607 if (mDecor != null) { 1608 mDecor.startChanging(); 1609 mDecor.setWindowBackground(drawable); 1610 if (mBackgroundFallbackDrawable != null) { 1611 mDecor.setBackgroundFallback(drawable != null ? null : 1612 mBackgroundFallbackDrawable); 1613 } 1614 mDecor.finishChanging(); 1615 } 1616 } 1617 } 1618 1619 @Override setBackgroundBlurRadius(int blurRadius)1620 public final void setBackgroundBlurRadius(int blurRadius) { 1621 super.setBackgroundBlurRadius(blurRadius); 1622 if (CrossWindowBlurListeners.CROSS_WINDOW_BLUR_SUPPORTED) { 1623 if (mBackgroundBlurRadius != Math.max(blurRadius, 0)) { 1624 mBackgroundBlurRadius = Math.max(blurRadius, 0); 1625 mDecor.setBackgroundBlurRadius(mBackgroundBlurRadius); 1626 } 1627 } 1628 } 1629 1630 @Override setFeatureDrawableResource(int featureId, int resId)1631 public final void setFeatureDrawableResource(int featureId, int resId) { 1632 if (resId != 0) { 1633 DrawableFeatureState st = getDrawableState(featureId, true); 1634 if (st.resid != resId) { 1635 st.resid = resId; 1636 st.uri = null; 1637 st.local = getContext().getDrawable(resId); 1638 updateDrawable(featureId, st, false); 1639 } 1640 } else { 1641 setFeatureDrawable(featureId, null); 1642 } 1643 } 1644 1645 @Override setFeatureDrawableUri(int featureId, Uri uri)1646 public final void setFeatureDrawableUri(int featureId, Uri uri) { 1647 if (uri != null) { 1648 DrawableFeatureState st = getDrawableState(featureId, true); 1649 if (st.uri == null || !st.uri.equals(uri)) { 1650 st.resid = 0; 1651 st.uri = uri; 1652 st.local = loadImageURI(uri); 1653 updateDrawable(featureId, st, false); 1654 } 1655 } else { 1656 setFeatureDrawable(featureId, null); 1657 } 1658 } 1659 1660 @Override setFeatureDrawable(int featureId, Drawable drawable)1661 public final void setFeatureDrawable(int featureId, Drawable drawable) { 1662 DrawableFeatureState st = getDrawableState(featureId, true); 1663 st.resid = 0; 1664 st.uri = null; 1665 if (st.local != drawable) { 1666 st.local = drawable; 1667 updateDrawable(featureId, st, false); 1668 } 1669 } 1670 1671 @Override setFeatureDrawableAlpha(int featureId, int alpha)1672 public void setFeatureDrawableAlpha(int featureId, int alpha) { 1673 DrawableFeatureState st = getDrawableState(featureId, true); 1674 if (st.alpha != alpha) { 1675 st.alpha = alpha; 1676 updateDrawable(featureId, st, false); 1677 } 1678 } 1679 setFeatureDefaultDrawable(int featureId, Drawable drawable)1680 protected final void setFeatureDefaultDrawable(int featureId, Drawable drawable) { 1681 DrawableFeatureState st = getDrawableState(featureId, true); 1682 if (st.def != drawable) { 1683 st.def = drawable; 1684 updateDrawable(featureId, st, false); 1685 } 1686 } 1687 1688 @Override setFeatureInt(int featureId, int value)1689 public final void setFeatureInt(int featureId, int value) { 1690 // XXX Should do more management (as with drawable features) to 1691 // deal with interactions between multiple window policies. 1692 updateInt(featureId, value, false); 1693 } 1694 1695 /** 1696 * Update the state of a drawable feature. This should be called, for every 1697 * drawable feature supported, as part of onActive(), to make sure that the 1698 * contents of a containing window is properly updated. 1699 * 1700 * @see #onActive 1701 * @param featureId The desired drawable feature to change. 1702 * @param fromActive Always true when called from onActive(). 1703 */ updateDrawable(int featureId, boolean fromActive)1704 protected final void updateDrawable(int featureId, boolean fromActive) { 1705 final DrawableFeatureState st = getDrawableState(featureId, false); 1706 if (st != null) { 1707 updateDrawable(featureId, st, fromActive); 1708 } 1709 } 1710 1711 /** 1712 * Called when a Drawable feature changes, for the window to update its 1713 * graphics. 1714 * 1715 * @param featureId The feature being changed. 1716 * @param drawable The new Drawable to show, or null if none. 1717 * @param alpha The new alpha blending of the Drawable. 1718 */ onDrawableChanged(int featureId, Drawable drawable, int alpha)1719 protected void onDrawableChanged(int featureId, Drawable drawable, int alpha) { 1720 ImageView view; 1721 if (featureId == FEATURE_LEFT_ICON) { 1722 view = getLeftIconView(); 1723 } else if (featureId == FEATURE_RIGHT_ICON) { 1724 view = getRightIconView(); 1725 } else { 1726 return; 1727 } 1728 1729 if (drawable != null) { 1730 drawable.setAlpha(alpha); 1731 view.setImageDrawable(drawable); 1732 view.setVisibility(View.VISIBLE); 1733 } else { 1734 view.setVisibility(View.GONE); 1735 } 1736 } 1737 1738 /** 1739 * Called when an int feature changes, for the window to update its 1740 * graphics. 1741 * 1742 * @param featureId The feature being changed. 1743 * @param value The new integer value. 1744 */ onIntChanged(int featureId, int value)1745 protected void onIntChanged(int featureId, int value) { 1746 if (featureId == FEATURE_PROGRESS || featureId == FEATURE_INDETERMINATE_PROGRESS) { 1747 updateProgressBars(value); 1748 } else if (featureId == FEATURE_CUSTOM_TITLE) { 1749 FrameLayout titleContainer = findViewById(R.id.title_container); 1750 if (titleContainer != null) { 1751 mLayoutInflater.inflate(value, titleContainer); 1752 } 1753 } 1754 } 1755 1756 /** 1757 * Updates the progress bars that are shown in the title bar. 1758 * 1759 * @param value Can be one of {@link Window#PROGRESS_VISIBILITY_ON}, 1760 * {@link Window#PROGRESS_VISIBILITY_OFF}, 1761 * {@link Window#PROGRESS_INDETERMINATE_ON}, 1762 * {@link Window#PROGRESS_INDETERMINATE_OFF}, or a value 1763 * starting at {@link Window#PROGRESS_START} through 1764 * {@link Window#PROGRESS_END} for setting the default 1765 * progress (if {@link Window#PROGRESS_END} is given, 1766 * the progress bar widgets in the title will be hidden after an 1767 * animation), a value between 1768 * {@link Window#PROGRESS_SECONDARY_START} - 1769 * {@link Window#PROGRESS_SECONDARY_END} for the 1770 * secondary progress (if 1771 * {@link Window#PROGRESS_SECONDARY_END} is given, the 1772 * progress bar widgets will still be shown with the secondary 1773 * progress bar will be completely filled in.) 1774 */ updateProgressBars(int value)1775 private void updateProgressBars(int value) { 1776 ProgressBar circularProgressBar = getCircularProgressBar(true); 1777 ProgressBar horizontalProgressBar = getHorizontalProgressBar(true); 1778 1779 final int features = getLocalFeatures(); 1780 if (value == PROGRESS_VISIBILITY_ON) { 1781 if ((features & (1 << FEATURE_PROGRESS)) != 0) { 1782 if (horizontalProgressBar != null) { 1783 int level = horizontalProgressBar.getProgress(); 1784 int visibility = (horizontalProgressBar.isIndeterminate() || level < 10000) ? 1785 View.VISIBLE : View.INVISIBLE; 1786 horizontalProgressBar.setVisibility(visibility); 1787 } else { 1788 Log.e(TAG, "Horizontal progress bar not located in current window decor"); 1789 } 1790 } 1791 if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) { 1792 if (circularProgressBar != null) { 1793 circularProgressBar.setVisibility(View.VISIBLE); 1794 } else { 1795 Log.e(TAG, "Circular progress bar not located in current window decor"); 1796 } 1797 } 1798 } else if (value == PROGRESS_VISIBILITY_OFF) { 1799 if ((features & (1 << FEATURE_PROGRESS)) != 0) { 1800 if (horizontalProgressBar != null) { 1801 horizontalProgressBar.setVisibility(View.GONE); 1802 } else { 1803 Log.e(TAG, "Horizontal progress bar not located in current window decor"); 1804 } 1805 } 1806 if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) { 1807 if (circularProgressBar != null) { 1808 circularProgressBar.setVisibility(View.GONE); 1809 } else { 1810 Log.e(TAG, "Circular progress bar not located in current window decor"); 1811 } 1812 } 1813 } else if (value == PROGRESS_INDETERMINATE_ON) { 1814 if (horizontalProgressBar != null) { 1815 horizontalProgressBar.setIndeterminate(true); 1816 } else { 1817 Log.e(TAG, "Horizontal progress bar not located in current window decor"); 1818 } 1819 } else if (value == PROGRESS_INDETERMINATE_OFF) { 1820 if (horizontalProgressBar != null) { 1821 horizontalProgressBar.setIndeterminate(false); 1822 } else { 1823 Log.e(TAG, "Horizontal progress bar not located in current window decor"); 1824 } 1825 } else if (PROGRESS_START <= value && value <= PROGRESS_END) { 1826 // We want to set the progress value before testing for visibility 1827 // so that when the progress bar becomes visible again, it has the 1828 // correct level. 1829 if (horizontalProgressBar != null) { 1830 horizontalProgressBar.setProgress(value - PROGRESS_START); 1831 } else { 1832 Log.e(TAG, "Horizontal progress bar not located in current window decor"); 1833 } 1834 1835 if (value < PROGRESS_END) { 1836 showProgressBars(horizontalProgressBar, circularProgressBar); 1837 } else { 1838 hideProgressBars(horizontalProgressBar, circularProgressBar); 1839 } 1840 } else if (PROGRESS_SECONDARY_START <= value && value <= PROGRESS_SECONDARY_END) { 1841 if (horizontalProgressBar != null) { 1842 horizontalProgressBar.setSecondaryProgress(value - PROGRESS_SECONDARY_START); 1843 } else { 1844 Log.e(TAG, "Horizontal progress bar not located in current window decor"); 1845 } 1846 1847 showProgressBars(horizontalProgressBar, circularProgressBar); 1848 } 1849 1850 } 1851 showProgressBars(ProgressBar horizontalProgressBar, ProgressBar spinnyProgressBar)1852 private void showProgressBars(ProgressBar horizontalProgressBar, ProgressBar spinnyProgressBar) { 1853 final int features = getLocalFeatures(); 1854 if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0 && 1855 spinnyProgressBar != null && spinnyProgressBar.getVisibility() == View.INVISIBLE) { 1856 spinnyProgressBar.setVisibility(View.VISIBLE); 1857 } 1858 // Only show the progress bars if the primary progress is not complete 1859 if ((features & (1 << FEATURE_PROGRESS)) != 0 && horizontalProgressBar != null && 1860 horizontalProgressBar.getProgress() < 10000) { 1861 horizontalProgressBar.setVisibility(View.VISIBLE); 1862 } 1863 } 1864 hideProgressBars(ProgressBar horizontalProgressBar, ProgressBar spinnyProgressBar)1865 private void hideProgressBars(ProgressBar horizontalProgressBar, ProgressBar spinnyProgressBar) { 1866 final int features = getLocalFeatures(); 1867 Animation anim = AnimationUtils.loadAnimation(getContext(), R.anim.fade_out); 1868 anim.setDuration(1000); 1869 if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0 && 1870 spinnyProgressBar != null && 1871 spinnyProgressBar.getVisibility() == View.VISIBLE) { 1872 spinnyProgressBar.startAnimation(anim); 1873 spinnyProgressBar.setVisibility(View.INVISIBLE); 1874 } 1875 if ((features & (1 << FEATURE_PROGRESS)) != 0 && horizontalProgressBar != null && 1876 horizontalProgressBar.getVisibility() == View.VISIBLE) { 1877 horizontalProgressBar.startAnimation(anim); 1878 horizontalProgressBar.setVisibility(View.INVISIBLE); 1879 } 1880 } 1881 1882 @Override setIcon(int resId)1883 public void setIcon(int resId) { 1884 mIconRes = resId; 1885 mResourcesSetFlags |= FLAG_RESOURCE_SET_ICON; 1886 mResourcesSetFlags &= ~FLAG_RESOURCE_SET_ICON_FALLBACK; 1887 if (mDecorContentParent != null) { 1888 mDecorContentParent.setIcon(resId); 1889 } 1890 } 1891 1892 @Override setDefaultIcon(int resId)1893 public void setDefaultIcon(int resId) { 1894 if ((mResourcesSetFlags & FLAG_RESOURCE_SET_ICON) != 0) { 1895 return; 1896 } 1897 mIconRes = resId; 1898 if (mDecorContentParent != null && (!mDecorContentParent.hasIcon() || 1899 (mResourcesSetFlags & FLAG_RESOURCE_SET_ICON_FALLBACK) != 0)) { 1900 if (resId != 0) { 1901 mDecorContentParent.setIcon(resId); 1902 mResourcesSetFlags &= ~FLAG_RESOURCE_SET_ICON_FALLBACK; 1903 } else { 1904 mDecorContentParent.setIcon( 1905 getContext().getPackageManager().getDefaultActivityIcon()); 1906 mResourcesSetFlags |= FLAG_RESOURCE_SET_ICON_FALLBACK; 1907 } 1908 } 1909 } 1910 1911 @Override setLogo(int resId)1912 public void setLogo(int resId) { 1913 mLogoRes = resId; 1914 mResourcesSetFlags |= FLAG_RESOURCE_SET_LOGO; 1915 if (mDecorContentParent != null) { 1916 mDecorContentParent.setLogo(resId); 1917 } 1918 } 1919 1920 @Override setDefaultLogo(int resId)1921 public void setDefaultLogo(int resId) { 1922 if ((mResourcesSetFlags & FLAG_RESOURCE_SET_LOGO) != 0) { 1923 return; 1924 } 1925 mLogoRes = resId; 1926 if (mDecorContentParent != null && !mDecorContentParent.hasLogo()) { 1927 mDecorContentParent.setLogo(resId); 1928 } 1929 } 1930 1931 @Override setLocalFocus(boolean hasFocus, boolean inTouchMode)1932 public void setLocalFocus(boolean hasFocus, boolean inTouchMode) { 1933 ViewRootImpl viewRoot = getViewRootImpl(); 1934 viewRoot.windowFocusChanged(hasFocus); 1935 viewRoot.touchModeChanged(inTouchMode); 1936 } 1937 1938 @Override injectInputEvent(InputEvent event)1939 public void injectInputEvent(InputEvent event) { 1940 getViewRootImpl().dispatchInputEvent(event); 1941 } 1942 getViewRootImpl()1943 private ViewRootImpl getViewRootImpl() { 1944 ViewRootImpl viewRootImpl = getViewRootImplOrNull(); 1945 if (viewRootImpl != null) { 1946 return viewRootImpl; 1947 } 1948 throw new IllegalStateException("view not added"); 1949 } 1950 getViewRootImplOrNull()1951 private ViewRootImpl getViewRootImplOrNull() { 1952 if (mDecor == null) { 1953 return null; 1954 } 1955 return mDecor.getViewRootImpl(); 1956 } 1957 1958 /** 1959 * Request that key events come to this activity. Use this if your activity 1960 * has no views with focus, but the activity still wants a chance to process 1961 * key events. 1962 */ 1963 @Override takeKeyEvents(boolean get)1964 public void takeKeyEvents(boolean get) { 1965 mDecor.setFocusable(get); 1966 } 1967 1968 @Override superDispatchKeyEvent(KeyEvent event)1969 public boolean superDispatchKeyEvent(KeyEvent event) { 1970 return mDecor.superDispatchKeyEvent(event); 1971 } 1972 1973 @Override superDispatchKeyShortcutEvent(KeyEvent event)1974 public boolean superDispatchKeyShortcutEvent(KeyEvent event) { 1975 return mDecor.superDispatchKeyShortcutEvent(event); 1976 } 1977 1978 @Override superDispatchTouchEvent(MotionEvent event)1979 public boolean superDispatchTouchEvent(MotionEvent event) { 1980 return mDecor.superDispatchTouchEvent(event); 1981 } 1982 1983 @Override superDispatchTrackballEvent(MotionEvent event)1984 public boolean superDispatchTrackballEvent(MotionEvent event) { 1985 return mDecor.superDispatchTrackballEvent(event); 1986 } 1987 1988 @Override superDispatchGenericMotionEvent(MotionEvent event)1989 public boolean superDispatchGenericMotionEvent(MotionEvent event) { 1990 return mDecor.superDispatchGenericMotionEvent(event); 1991 } 1992 1993 /** 1994 * A key was pressed down and not handled by anything else in the window. 1995 * 1996 * @see #onKeyUp 1997 * @see android.view.KeyEvent 1998 */ onKeyDown(int featureId, int keyCode, KeyEvent event)1999 protected boolean onKeyDown(int featureId, int keyCode, KeyEvent event) { 2000 /* **************************************************************************** 2001 * HOW TO DECIDE WHERE YOUR KEY HANDLING GOES. 2002 * 2003 * If your key handling must happen before the app gets a crack at the event, 2004 * it goes in PhoneWindowManager. 2005 * 2006 * If your key handling should happen in all windows, and does not depend on 2007 * the state of the current application, other than that the current 2008 * application can override the behavior by handling the event itself, it 2009 * should go in PhoneFallbackEventHandler. 2010 * 2011 * Only if your handling depends on the window, and the fact that it has 2012 * a DecorView, should it go here. 2013 * ****************************************************************************/ 2014 2015 final KeyEvent.DispatcherState dispatcher = 2016 mDecor != null ? mDecor.getKeyDispatcherState() : null; 2017 //Log.i(TAG, "Key down: repeat=" + event.getRepeatCount() 2018 // + " flags=0x" + Integer.toHexString(event.getFlags())); 2019 2020 switch (keyCode) { 2021 case KeyEvent.KEYCODE_VOLUME_UP: 2022 case KeyEvent.KEYCODE_VOLUME_DOWN: 2023 case KeyEvent.KEYCODE_VOLUME_MUTE: { 2024 // If we have a session and no active phone call send it the volume command, 2025 // otherwise use the suggested stream. 2026 if (mMediaController != null && !isActivePhoneCallOngoing()) { 2027 getMediaSessionManager().dispatchVolumeKeyEventToSessionAsSystemService(event, 2028 mMediaController.getSessionToken()); 2029 } else { 2030 getMediaSessionManager().dispatchVolumeKeyEventAsSystemService(event, 2031 mVolumeControlStreamType); 2032 } 2033 return true; 2034 } 2035 // These are all the recognized media key codes in 2036 // KeyEvent.isMediaSessionKey() 2037 case KeyEvent.KEYCODE_MEDIA_PLAY: 2038 case KeyEvent.KEYCODE_MEDIA_PAUSE: 2039 case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: 2040 case KeyEvent.KEYCODE_MUTE: 2041 case KeyEvent.KEYCODE_HEADSETHOOK: 2042 case KeyEvent.KEYCODE_MEDIA_STOP: 2043 case KeyEvent.KEYCODE_MEDIA_NEXT: 2044 case KeyEvent.KEYCODE_MEDIA_PREVIOUS: 2045 case KeyEvent.KEYCODE_MEDIA_REWIND: 2046 case KeyEvent.KEYCODE_MEDIA_RECORD: 2047 case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: { 2048 if (mMediaController != null) { 2049 if (getMediaSessionManager().dispatchMediaKeyEventToSessionAsSystemService( 2050 event, mMediaController.getSessionToken())) { 2051 return true; 2052 } 2053 } 2054 return false; 2055 } 2056 2057 case KeyEvent.KEYCODE_MENU: { 2058 onKeyDownPanel((featureId < 0) ? FEATURE_OPTIONS_PANEL : featureId, event); 2059 return true; 2060 } 2061 2062 case KeyEvent.KEYCODE_BACK: { 2063 if (event.getRepeatCount() > 0) break; 2064 if (featureId < 0) break; 2065 // Currently don't do anything with long press. 2066 if (dispatcher != null) { 2067 dispatcher.startTracking(event, this); 2068 } 2069 return true; 2070 } 2071 2072 } 2073 2074 return false; 2075 } 2076 isActivePhoneCallOngoing()2077 private boolean isActivePhoneCallOngoing() { 2078 return mAudioMode == AudioManager.MODE_IN_CALL 2079 || mAudioMode == AudioManager.MODE_IN_COMMUNICATION; 2080 } 2081 getKeyguardManager()2082 private KeyguardManager getKeyguardManager() { 2083 if (mKeyguardManager == null) { 2084 mKeyguardManager = (KeyguardManager) getContext().getSystemService( 2085 Context.KEYGUARD_SERVICE); 2086 } 2087 return mKeyguardManager; 2088 } 2089 getAudioManager()2090 AudioManager getAudioManager() { 2091 if (mAudioManager == null) { 2092 mAudioManager = (AudioManager)getContext().getSystemService(Context.AUDIO_SERVICE); 2093 } 2094 return mAudioManager; 2095 } 2096 getMediaSessionManager()2097 private MediaSessionManager getMediaSessionManager() { 2098 if (mMediaSessionManager == null) { 2099 mMediaSessionManager = (MediaSessionManager) getContext().getSystemService( 2100 Context.MEDIA_SESSION_SERVICE); 2101 } 2102 return mMediaSessionManager; 2103 } 2104 2105 /** 2106 * A key was released and not handled by anything else in the window. 2107 * 2108 * @see #onKeyDown 2109 * @see android.view.KeyEvent 2110 */ onKeyUp(int featureId, int keyCode, KeyEvent event)2111 protected boolean onKeyUp(int featureId, int keyCode, KeyEvent event) { 2112 final KeyEvent.DispatcherState dispatcher = 2113 mDecor != null ? mDecor.getKeyDispatcherState() : null; 2114 if (dispatcher != null) { 2115 dispatcher.handleUpEvent(event); 2116 } 2117 //Log.i(TAG, "Key up: repeat=" + event.getRepeatCount() 2118 // + " flags=0x" + Integer.toHexString(event.getFlags())); 2119 2120 switch (keyCode) { 2121 case KeyEvent.KEYCODE_VOLUME_UP: 2122 case KeyEvent.KEYCODE_VOLUME_DOWN: { 2123 // If we have a session send it the volume command, otherwise 2124 // use the suggested stream. 2125 if (mMediaController != null) { 2126 getMediaSessionManager().dispatchVolumeKeyEventToSessionAsSystemService(event, 2127 mMediaController.getSessionToken()); 2128 } else { 2129 getMediaSessionManager().dispatchVolumeKeyEventAsSystemService( 2130 event, mVolumeControlStreamType); 2131 } 2132 return true; 2133 } 2134 case KeyEvent.KEYCODE_VOLUME_MUTE: { 2135 // Similar code is in PhoneFallbackEventHandler in case the window 2136 // doesn't have one of these. In this case, we execute it here and 2137 // eat the event instead, because we have mVolumeControlStreamType 2138 // and they don't. 2139 getMediaSessionManager().dispatchVolumeKeyEventAsSystemService( 2140 event, AudioManager.USE_DEFAULT_STREAM_TYPE); 2141 return true; 2142 } 2143 // These are all the recognized media key codes in 2144 // KeyEvent.isMediaSessionKey() 2145 case KeyEvent.KEYCODE_MEDIA_PLAY: 2146 case KeyEvent.KEYCODE_MEDIA_PAUSE: 2147 case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: 2148 case KeyEvent.KEYCODE_MUTE: 2149 case KeyEvent.KEYCODE_HEADSETHOOK: 2150 case KeyEvent.KEYCODE_MEDIA_STOP: 2151 case KeyEvent.KEYCODE_MEDIA_NEXT: 2152 case KeyEvent.KEYCODE_MEDIA_PREVIOUS: 2153 case KeyEvent.KEYCODE_MEDIA_REWIND: 2154 case KeyEvent.KEYCODE_MEDIA_RECORD: 2155 case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: { 2156 if (mMediaController != null) { 2157 if (getMediaSessionManager().dispatchMediaKeyEventToSessionAsSystemService( 2158 event, mMediaController.getSessionToken())) { 2159 return true; 2160 } 2161 } 2162 return false; 2163 } 2164 2165 case KeyEvent.KEYCODE_MENU: { 2166 onKeyUpPanel(featureId < 0 ? FEATURE_OPTIONS_PANEL : featureId, 2167 event); 2168 return true; 2169 } 2170 2171 case KeyEvent.KEYCODE_BACK: { 2172 if (featureId < 0) break; 2173 if (event.isTracking() && !event.isCanceled()) { 2174 if (featureId == FEATURE_OPTIONS_PANEL) { 2175 PanelFeatureState st = getPanelState(featureId, false); 2176 if (st != null && st.isInExpandedMode) { 2177 // If the user is in an expanded menu and hits back, it 2178 // should go back to the icon menu 2179 reopenMenu(true); 2180 return true; 2181 } 2182 } 2183 closePanel(featureId); 2184 return true; 2185 } 2186 break; 2187 } 2188 2189 case KeyEvent.KEYCODE_SEARCH: { 2190 /* 2191 * Do this in onKeyUp since the Search key is also used for 2192 * chording quick launch shortcuts. 2193 */ 2194 if (isNotInstantAppAndKeyguardRestricted()) { 2195 break; 2196 } 2197 if ((getContext().getResources().getConfiguration().uiMode 2198 & Configuration.UI_MODE_TYPE_MASK) == Configuration.UI_MODE_TYPE_WATCH) { 2199 break; 2200 } 2201 if (event.isTracking() && !event.isCanceled()) { 2202 launchDefaultSearch(event); 2203 } 2204 return true; 2205 } 2206 2207 case KeyEvent.KEYCODE_WINDOW: { 2208 if (mSupportsPictureInPicture && !event.isCanceled()) { 2209 getWindowControllerCallback().enterPictureInPictureModeIfPossible(); 2210 } 2211 return true; 2212 } 2213 } 2214 2215 return false; 2216 } 2217 2218 private boolean isNotInstantAppAndKeyguardRestricted() { 2219 return !getContext().getPackageManager().isInstantApp() 2220 && getKeyguardManager().inKeyguardRestrictedInputMode(); 2221 } 2222 2223 @Override 2224 protected void onActive() { 2225 } 2226 2227 @Override 2228 public final @NonNull View getDecorView() { 2229 if (mDecor == null || mForceDecorInstall) { 2230 installDecor(); 2231 } 2232 return mDecor; 2233 } 2234 2235 @Override 2236 public final View peekDecorView() { 2237 return mDecor; 2238 } 2239 2240 /** Notify when decor view is attached to window and {@link ViewRootImpl} is available. */ 2241 void onViewRootImplSet(ViewRootImpl viewRoot) { 2242 viewRoot.setActivityConfigCallback(mActivityConfigCallback); 2243 viewRoot.getOnBackInvokedDispatcher().updateContext(getContext()); 2244 mProxyOnBackInvokedDispatcher.setActualDispatcher(viewRoot.getOnBackInvokedDispatcher()); 2245 applyDecorFitsSystemWindows(); 2246 } 2247 2248 static private final String FOCUSED_ID_TAG = "android:focusedViewId"; 2249 static private final String VIEWS_TAG = "android:views"; 2250 static private final String PANELS_TAG = "android:Panels"; 2251 static private final String ACTION_BAR_TAG = "android:ActionBar"; 2252 2253 /** {@inheritDoc} */ 2254 @Override 2255 public Bundle saveHierarchyState() { 2256 Bundle outState = new Bundle(); 2257 if (mContentParent == null) { 2258 return outState; 2259 } 2260 2261 SparseArray<Parcelable> states = new SparseArray<Parcelable>(); 2262 mContentParent.saveHierarchyState(states); 2263 outState.putSparseParcelableArray(VIEWS_TAG, states); 2264 2265 // Save the focused view ID. 2266 final View focusedView = mContentParent.findFocus(); 2267 if (focusedView != null && focusedView.getId() != View.NO_ID) { 2268 outState.putInt(FOCUSED_ID_TAG, focusedView.getId()); 2269 } 2270 2271 // save the panels 2272 SparseArray<Parcelable> panelStates = new SparseArray<Parcelable>(); 2273 savePanelState(panelStates); 2274 if (panelStates.size() > 0) { 2275 outState.putSparseParcelableArray(PANELS_TAG, panelStates); 2276 } 2277 2278 if (mDecorContentParent != null) { 2279 SparseArray<Parcelable> actionBarStates = new SparseArray<Parcelable>(); 2280 mDecorContentParent.saveToolbarHierarchyState(actionBarStates); 2281 outState.putSparseParcelableArray(ACTION_BAR_TAG, actionBarStates); 2282 } 2283 2284 return outState; 2285 } 2286 2287 /** {@inheritDoc} */ 2288 @Override 2289 public void restoreHierarchyState(Bundle savedInstanceState) { 2290 if (mContentParent == null) { 2291 return; 2292 } 2293 2294 SparseArray<Parcelable> savedStates 2295 = savedInstanceState.getSparseParcelableArray(VIEWS_TAG); 2296 if (savedStates != null) { 2297 mContentParent.restoreHierarchyState(savedStates); 2298 } 2299 2300 // restore the focused view 2301 int focusedViewId = savedInstanceState.getInt(FOCUSED_ID_TAG, View.NO_ID); 2302 if (focusedViewId != View.NO_ID) { 2303 View needsFocus = mContentParent.findViewById(focusedViewId); 2304 if (needsFocus != null) { 2305 needsFocus.requestFocus(); 2306 } else { 2307 Log.w(TAG, 2308 "Previously focused view reported id " + focusedViewId 2309 + " during save, but can't be found during restore."); 2310 } 2311 } 2312 2313 // Restore the panels. 2314 SparseArray<Parcelable> panelStates = savedInstanceState.getSparseParcelableArray(PANELS_TAG); 2315 if (panelStates != null) { 2316 restorePanelState(panelStates); 2317 } 2318 2319 if (mDecorContentParent != null) { 2320 SparseArray<Parcelable> actionBarStates = 2321 savedInstanceState.getSparseParcelableArray(ACTION_BAR_TAG); 2322 if (actionBarStates != null) { 2323 doPendingInvalidatePanelMenu(); 2324 mDecorContentParent.restoreToolbarHierarchyState(actionBarStates); 2325 } else { 2326 Log.w(TAG, "Missing saved instance states for action bar views! " + 2327 "State will not be restored."); 2328 } 2329 } 2330 } 2331 2332 /** 2333 * Invoked when the panels should freeze their state. 2334 * 2335 * @param icicles Save state into this. This is usually indexed by the 2336 * featureId. This will be given to {@link #restorePanelState} in the 2337 * future. 2338 */ 2339 private void savePanelState(SparseArray<Parcelable> icicles) { 2340 PanelFeatureState[] panels = mPanels; 2341 if (panels == null) { 2342 return; 2343 } 2344 2345 for (int curFeatureId = panels.length - 1; curFeatureId >= 0; curFeatureId--) { 2346 if (panels[curFeatureId] != null) { 2347 icicles.put(curFeatureId, panels[curFeatureId].onSaveInstanceState()); 2348 } 2349 } 2350 } 2351 2352 /** 2353 * Invoked when the panels should thaw their state from a previously frozen state. 2354 * 2355 * @param icicles The state saved by {@link #savePanelState} that needs to be thawed. 2356 */ restorePanelState(SparseArray<Parcelable> icicles)2357 private void restorePanelState(SparseArray<Parcelable> icicles) { 2358 PanelFeatureState st; 2359 int curFeatureId; 2360 for (int i = icicles.size() - 1; i >= 0; i--) { 2361 curFeatureId = icicles.keyAt(i); 2362 st = getPanelState(curFeatureId, false /* required */); 2363 if (st == null) { 2364 // The panel must not have been required, and is currently not around, skip it 2365 continue; 2366 } 2367 2368 st.onRestoreInstanceState(icicles.get(curFeatureId)); 2369 invalidatePanelMenu(curFeatureId); 2370 } 2371 2372 /* 2373 * Implementation note: call openPanelsAfterRestore later to actually open the 2374 * restored panels. 2375 */ 2376 } 2377 2378 /** 2379 * Opens the panels that have had their state restored. This should be 2380 * called sometime after {@link #restorePanelState} when it is safe to add 2381 * to the window manager. 2382 */ openPanelsAfterRestore()2383 void openPanelsAfterRestore() { 2384 PanelFeatureState[] panels = mPanels; 2385 2386 if (panels == null) { 2387 return; 2388 } 2389 2390 PanelFeatureState st; 2391 for (int i = panels.length - 1; i >= 0; i--) { 2392 st = panels[i]; 2393 // We restore the panel if it was last open; we skip it if it 2394 // now is open, to avoid a race condition if the user immediately 2395 // opens it when we are resuming. 2396 if (st != null) { 2397 st.applyFrozenState(); 2398 if (!st.isOpen && st.wasLastOpen) { 2399 st.isInExpandedMode = st.wasLastExpanded; 2400 openPanel(st, null); 2401 } 2402 } 2403 } 2404 } 2405 2406 @Override onDestroy()2407 protected void onDestroy() { 2408 if (mOnModeChangedListener != null) { 2409 getAudioManager().removeOnModeChangedListener(mOnModeChangedListener); 2410 mOnModeChangedListener = null; 2411 } 2412 } 2413 2414 private class PanelMenuPresenterCallback implements MenuPresenter.Callback { 2415 @Override onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing)2416 public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { 2417 final Menu parentMenu = menu.getRootMenu(); 2418 final boolean isSubMenu = parentMenu != menu; 2419 final PanelFeatureState panel = findMenuPanel(isSubMenu ? parentMenu : menu); 2420 if (panel != null) { 2421 if (isSubMenu) { 2422 callOnPanelClosed(panel.featureId, panel, parentMenu); 2423 closePanel(panel, true); 2424 } else { 2425 // Close the panel and only do the callback if the menu is being 2426 // closed completely, not if opening a sub menu 2427 closePanel(panel, allMenusAreClosing); 2428 } 2429 } 2430 } 2431 2432 @Override onOpenSubMenu(MenuBuilder subMenu)2433 public boolean onOpenSubMenu(MenuBuilder subMenu) { 2434 if (subMenu == null && hasFeature(FEATURE_ACTION_BAR)) { 2435 Callback cb = getCallback(); 2436 if (cb != null && !isDestroyed()) { 2437 cb.onMenuOpened(FEATURE_ACTION_BAR, subMenu); 2438 } 2439 } 2440 2441 return true; 2442 } 2443 } 2444 2445 private final class ActionMenuPresenterCallback implements MenuPresenter.Callback { 2446 @Override onOpenSubMenu(MenuBuilder subMenu)2447 public boolean onOpenSubMenu(MenuBuilder subMenu) { 2448 Callback cb = getCallback(); 2449 if (cb != null) { 2450 cb.onMenuOpened(FEATURE_ACTION_BAR, subMenu); 2451 return true; 2452 } 2453 return false; 2454 } 2455 2456 @Override onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing)2457 public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { 2458 checkCloseActionMenu(menu); 2459 } 2460 } 2461 generateDecor(int featureId)2462 protected DecorView generateDecor(int featureId) { 2463 // System process doesn't have application context and in that case we need to directly use 2464 // the context we have. Otherwise we want the application context, so we don't cling to the 2465 // activity. 2466 Context context; 2467 if (mUseDecorContext) { 2468 Context applicationContext = getContext().getApplicationContext(); 2469 if (applicationContext == null) { 2470 context = getContext(); 2471 } else { 2472 context = new DecorContext(applicationContext, this); 2473 if (mTheme != -1) { 2474 context.setTheme(mTheme); 2475 } 2476 } 2477 } else { 2478 context = getContext(); 2479 } 2480 return new DecorView(context, featureId, this, getAttributes()); 2481 } 2482 generateLayout(DecorView decor)2483 protected ViewGroup generateLayout(DecorView decor) { 2484 // Apply data from current theme. 2485 2486 TypedArray a = getWindowStyle(); 2487 WindowManager.LayoutParams params = getAttributes(); 2488 2489 if (false) { 2490 System.out.println("From style:"); 2491 String s = "Attrs:"; 2492 for (int i = 0; i < R.styleable.Window.length; i++) { 2493 s = s + " " + Integer.toHexString(R.styleable.Window[i]) + "=" 2494 + a.getString(i); 2495 } 2496 System.out.println(s); 2497 } 2498 2499 mEdgeToEdgeEnforced = isEdgeToEdgeEnforced( 2500 getContext().getApplicationInfo(), true /* local */, a); 2501 if (mEdgeToEdgeEnforced) { 2502 getAttributes().privateFlags |= PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED; 2503 mDecorFitsSystemWindows = false; 2504 mStatusBarColor = Color.TRANSPARENT; 2505 mNavigationBarDividerColor = Color.TRANSPARENT; 2506 // mNavigationBarColor is not reset here because it might be used to draw the scrim. 2507 } 2508 if (CompatChanges.isChangeEnabled(OVERRIDE_LAYOUT_IN_DISPLAY_CUTOUT_MODE) 2509 && !a.getBoolean(R.styleable.Window_windowOptOutEdgeToEdgeEnforcement, 2510 false /* defValue */)) { 2511 getAttributes().privateFlags |= PRIVATE_FLAG_OVERRIDE_LAYOUT_IN_DISPLAY_CUTOUT_MODE; 2512 } 2513 2514 mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false); 2515 int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR) 2516 & (~getForcedWindowFlags()); 2517 if (mIsFloating && !mAllowFloatingWindowsFillScreen) { 2518 setLayout(WRAP_CONTENT, WRAP_CONTENT); 2519 setFlags(0, flagsToUpdate); 2520 } else { 2521 setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate); 2522 params.setFitInsetsSides(0); 2523 params.setFitInsetsTypes(0); 2524 } 2525 2526 if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) { 2527 requestFeature(FEATURE_NO_TITLE); 2528 } else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) { 2529 // Don't allow an action bar if there is no title. 2530 requestFeature(FEATURE_ACTION_BAR); 2531 } 2532 2533 if (a.getBoolean(R.styleable.Window_windowActionBarOverlay, false)) { 2534 requestFeature(FEATURE_ACTION_BAR_OVERLAY); 2535 } 2536 2537 if (a.getBoolean(R.styleable.Window_windowActionModeOverlay, false)) { 2538 requestFeature(FEATURE_ACTION_MODE_OVERLAY); 2539 } 2540 2541 if (a.getBoolean(R.styleable.Window_windowFullscreen, false)) { 2542 setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags())); 2543 } 2544 2545 if (a.getBoolean(R.styleable.Window_windowTranslucentStatus, 2546 false)) { 2547 setFlags(FLAG_TRANSLUCENT_STATUS, FLAG_TRANSLUCENT_STATUS 2548 & (~getForcedWindowFlags())); 2549 } 2550 2551 if (a.getBoolean(R.styleable.Window_windowTranslucentNavigation, 2552 false)) { 2553 setFlags(FLAG_TRANSLUCENT_NAVIGATION, FLAG_TRANSLUCENT_NAVIGATION 2554 & (~getForcedWindowFlags())); 2555 } 2556 2557 if (a.getBoolean(R.styleable.Window_windowShowWallpaper, false)) { 2558 setFlags(FLAG_SHOW_WALLPAPER, FLAG_SHOW_WALLPAPER&(~getForcedWindowFlags())); 2559 } 2560 2561 if (a.getBoolean(R.styleable.Window_windowEnableSplitTouch, 2562 getContext().getApplicationInfo().targetSdkVersion 2563 >= android.os.Build.VERSION_CODES.HONEYCOMB)) { 2564 setFlags(FLAG_SPLIT_TOUCH, FLAG_SPLIT_TOUCH&(~getForcedWindowFlags())); 2565 } 2566 2567 a.getValue(R.styleable.Window_windowMinWidthMajor, mMinWidthMajor); 2568 a.getValue(R.styleable.Window_windowMinWidthMinor, mMinWidthMinor); 2569 if (DEBUG) Log.d(TAG, "Min width minor: " + mMinWidthMinor.coerceToString() 2570 + ", major: " + mMinWidthMajor.coerceToString()); 2571 if (a.hasValue(R.styleable.Window_windowFixedWidthMajor)) { 2572 if (mFixedWidthMajor == null) mFixedWidthMajor = new TypedValue(); 2573 a.getValue(R.styleable.Window_windowFixedWidthMajor, 2574 mFixedWidthMajor); 2575 } 2576 if (a.hasValue(R.styleable.Window_windowFixedWidthMinor)) { 2577 if (mFixedWidthMinor == null) mFixedWidthMinor = new TypedValue(); 2578 a.getValue(R.styleable.Window_windowFixedWidthMinor, 2579 mFixedWidthMinor); 2580 } 2581 if (a.hasValue(R.styleable.Window_windowFixedHeightMajor)) { 2582 if (mFixedHeightMajor == null) mFixedHeightMajor = new TypedValue(); 2583 a.getValue(R.styleable.Window_windowFixedHeightMajor, 2584 mFixedHeightMajor); 2585 } 2586 if (a.hasValue(R.styleable.Window_windowFixedHeightMinor)) { 2587 if (mFixedHeightMinor == null) mFixedHeightMinor = new TypedValue(); 2588 a.getValue(R.styleable.Window_windowFixedHeightMinor, 2589 mFixedHeightMinor); 2590 } 2591 if (a.getBoolean(R.styleable.Window_windowContentTransitions, false)) { 2592 requestFeature(FEATURE_CONTENT_TRANSITIONS); 2593 } 2594 if (a.getBoolean(R.styleable.Window_windowActivityTransitions, false)) { 2595 requestFeature(FEATURE_ACTIVITY_TRANSITIONS); 2596 } 2597 if (a.hasValue(R.styleable.Window_windowIsFrameRatePowerSavingsBalanced)) { 2598 if (sToolkitSetFrameRateReadOnlyFlagValue) { 2599 setFrameRatePowerSavingsBalanced( 2600 a.getBoolean(R.styleable.Window_windowIsFrameRatePowerSavingsBalanced, 2601 true)); 2602 } 2603 } 2604 2605 mIsTranslucent = a.getBoolean(R.styleable.Window_windowIsTranslucent, false); 2606 2607 final Context context = getContext(); 2608 final int targetSdk = context.getApplicationInfo().targetSdkVersion; 2609 final boolean targetPreL = targetSdk < android.os.Build.VERSION_CODES.LOLLIPOP; 2610 final boolean targetPreQ = targetSdk < Build.VERSION_CODES.Q; 2611 2612 if (!mForcedStatusBarColor && !mEdgeToEdgeEnforced) { 2613 mStatusBarColor = a.getColor(R.styleable.Window_statusBarColor, Color.BLACK); 2614 } 2615 if (!mForcedNavigationBarColor) { 2616 final int navBarCompatibleColor = context.getColor(R.color.navigation_bar_compatible); 2617 final int navBarDefaultColor = context.getColor(R.color.navigation_bar_default); 2618 final int navBarColor = a.getColor(R.styleable.Window_navigationBarColor, 2619 navBarDefaultColor); 2620 final boolean navigationBarColorSpecified = navBarColor != navBarDefaultColor; 2621 2622 mNavigationBarColor = 2623 !navigationBarColorSpecified 2624 && !mEdgeToEdgeEnforced 2625 && !context.getResources().getBoolean( 2626 R.bool.config_navBarDefaultTransparent) 2627 ? navBarCompatibleColor 2628 : navBarColor; 2629 2630 mNavigationBarColorSpecified |= navigationBarColorSpecified; 2631 2632 if (!mEdgeToEdgeEnforced) { 2633 mNavigationBarDividerColor = a.getColor( 2634 R.styleable.Window_navigationBarDividerColor, Color.TRANSPARENT); 2635 } 2636 } 2637 if (!targetPreQ) { 2638 mEnsureStatusBarContrastWhenTransparent = a.getBoolean( 2639 R.styleable.Window_enforceStatusBarContrast, false); 2640 mEnsureNavigationBarContrastWhenTransparent = a.getBoolean( 2641 R.styleable.Window_enforceNavigationBarContrast, true); 2642 } 2643 2644 // Non-floating windows on high end devices must put up decor beneath the system bars and 2645 // therefore must know about visibility changes of those. 2646 if (!mIsFloating) { 2647 if (!targetPreL && a.getBoolean( 2648 R.styleable.Window_windowDrawsSystemBarBackgrounds, 2649 false)) { 2650 setFlags(FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, 2651 FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS & ~getForcedWindowFlags()); 2652 } 2653 if (mDecor.mForceWindowDrawsBarBackgrounds) { 2654 params.privateFlags |= PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS; 2655 } 2656 params.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION; 2657 } 2658 if (a.getBoolean( 2659 R.styleable.Window_windowNoMoveAnimation, 2660 false)) { 2661 params.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION; 2662 } 2663 2664 final boolean lightStatus = a.getBoolean(R.styleable.Window_windowLightStatusBar, false); 2665 final boolean lightNav = a.getBoolean(R.styleable.Window_windowLightNavigationBar, false); 2666 2667 // Here still sets the light bar flags via setSystemUiVisibility (even it is deprecated) to 2668 // make the light bar state be able to be read from the legacy method. 2669 decor.setSystemUiVisibility( 2670 (decor.getSystemUiVisibility() 2671 & ~(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR 2672 | View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR)) 2673 | (lightStatus ? View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR : 0) 2674 | (lightNav ? View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR : 0)); 2675 2676 decor.getWindowInsetsController().setSystemBarsAppearanceFromResource( 2677 (lightStatus ? APPEARANCE_LIGHT_STATUS_BARS : 0) 2678 | (lightNav ? APPEARANCE_LIGHT_NAVIGATION_BARS : 0), 2679 APPEARANCE_LIGHT_STATUS_BARS | APPEARANCE_LIGHT_NAVIGATION_BARS); 2680 2681 if (a.hasValue(R.styleable.Window_windowLayoutInDisplayCutoutMode)) { 2682 int mode = a.getInt(R.styleable.Window_windowLayoutInDisplayCutoutMode, -1); 2683 if (mode < LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT 2684 || mode > LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS) { 2685 throw new UnsupportedOperationException("Unknown windowLayoutInDisplayCutoutMode: " 2686 + a.getString(R.styleable.Window_windowLayoutInDisplayCutoutMode)); 2687 } 2688 params.layoutInDisplayCutoutMode = mode; 2689 } 2690 2691 if (mAlwaysReadCloseOnTouchAttr || getContext().getApplicationInfo().targetSdkVersion 2692 >= android.os.Build.VERSION_CODES.HONEYCOMB) { 2693 if (a.getBoolean( 2694 R.styleable.Window_windowCloseOnTouchOutside, 2695 false)) { 2696 setCloseOnTouchOutsideIfNotSet(true); 2697 } 2698 } 2699 2700 if (!hasSoftInputMode()) { 2701 params.softInputMode = a.getInt( 2702 R.styleable.Window_windowSoftInputMode, 2703 params.softInputMode); 2704 } 2705 2706 if (a.getBoolean(R.styleable.Window_backgroundDimEnabled, 2707 mIsFloating)) { 2708 /* All dialogs should have the window dimmed */ 2709 if ((getForcedWindowFlags()&WindowManager.LayoutParams.FLAG_DIM_BEHIND) == 0) { 2710 params.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND; 2711 } 2712 if (!haveDimAmount()) { 2713 params.dimAmount = a.getFloat( 2714 android.R.styleable.Window_backgroundDimAmount, 0.5f); 2715 } 2716 } 2717 2718 if (a.getBoolean(R.styleable.Window_windowBlurBehindEnabled, false)) { 2719 if ((getForcedWindowFlags() & WindowManager.LayoutParams.FLAG_BLUR_BEHIND) == 0) { 2720 params.flags |= WindowManager.LayoutParams.FLAG_BLUR_BEHIND; 2721 } 2722 2723 params.setBlurBehindRadius(a.getDimensionPixelSize( 2724 android.R.styleable.Window_windowBlurBehindRadius, 0)); 2725 } 2726 2727 setBackgroundBlurRadius(a.getDimensionPixelSize( 2728 R.styleable.Window_windowBackgroundBlurRadius, 0)); 2729 2730 2731 if (params.windowAnimations == 0) { 2732 params.windowAnimations = a.getResourceId( 2733 R.styleable.Window_windowAnimationStyle, 0); 2734 } 2735 2736 // The rest are only done if this window is not embedded; otherwise, 2737 // the values are inherited from our container. 2738 if (getContainer() == null) { 2739 if (mBackgroundDrawable == null) { 2740 2741 if (mFrameResource == 0) { 2742 mFrameResource = a.getResourceId(R.styleable.Window_windowFrame, 0); 2743 } 2744 2745 if (a.hasValue(R.styleable.Window_windowBackground)) { 2746 mBackgroundDrawable = a.getDrawable(R.styleable.Window_windowBackground); 2747 } 2748 } 2749 if (a.hasValue(R.styleable.Window_windowBackgroundFallback)) { 2750 mBackgroundFallbackDrawable = 2751 a.getDrawable(R.styleable.Window_windowBackgroundFallback); 2752 } 2753 if (mLoadElevation) { 2754 mElevation = a.getDimension(R.styleable.Window_windowElevation, 0); 2755 } 2756 mClipToOutline = a.getBoolean(R.styleable.Window_windowClipToOutline, false); 2757 mTextColor = a.getColor(R.styleable.Window_textColor, Color.TRANSPARENT); 2758 } 2759 2760 // Inflate the window decor. 2761 2762 int layoutResource; 2763 int features = getLocalFeatures(); 2764 // System.out.println("Features: 0x" + Integer.toHexString(features)); 2765 if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) { 2766 if (mIsFloating) { 2767 TypedValue res = new TypedValue(); 2768 getContext().getTheme().resolveAttribute( 2769 R.attr.dialogTitleIconsDecorLayout, res, true); 2770 layoutResource = res.resourceId; 2771 } else { 2772 layoutResource = R.layout.screen_title_icons; 2773 } 2774 // XXX Remove this once action bar supports these features. 2775 removeFeature(FEATURE_ACTION_BAR); 2776 // System.out.println("Title Icons!"); 2777 } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0 2778 && (features & (1 << FEATURE_ACTION_BAR)) == 0) { 2779 // Special case for a window with only a progress bar (and title). 2780 // XXX Need to have a no-title version of embedded windows. 2781 layoutResource = R.layout.screen_progress; 2782 // System.out.println("Progress!"); 2783 } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) { 2784 // Special case for a window with a custom title. 2785 // If the window is floating, we need a dialog layout 2786 if (mIsFloating) { 2787 TypedValue res = new TypedValue(); 2788 getContext().getTheme().resolveAttribute( 2789 R.attr.dialogCustomTitleDecorLayout, res, true); 2790 layoutResource = res.resourceId; 2791 } else { 2792 layoutResource = R.layout.screen_custom_title; 2793 } 2794 // XXX Remove this once action bar supports these features. 2795 removeFeature(FEATURE_ACTION_BAR); 2796 } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) { 2797 // If no other features and not embedded, only need a title. 2798 // If the window is floating, we need a dialog layout 2799 if (mIsFloating) { 2800 TypedValue res = new TypedValue(); 2801 getContext().getTheme().resolveAttribute( 2802 R.attr.dialogTitleDecorLayout, res, true); 2803 layoutResource = res.resourceId; 2804 } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) { 2805 layoutResource = a.getResourceId( 2806 R.styleable.Window_windowActionBarFullscreenDecorLayout, 2807 R.layout.screen_action_bar); 2808 } else { 2809 layoutResource = R.layout.screen_title; 2810 } 2811 // System.out.println("Title!"); 2812 } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) { 2813 layoutResource = R.layout.screen_simple_overlay_action_mode; 2814 } else { 2815 // Embedded, so no decoration is needed. 2816 layoutResource = R.layout.screen_simple; 2817 // System.out.println("Simple!"); 2818 } 2819 2820 mDecor.startChanging(); 2821 mDecor.onResourcesLoaded(mLayoutInflater, layoutResource); 2822 2823 ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); 2824 if (contentParent == null) { 2825 throw new RuntimeException("Window couldn't find content container view"); 2826 } 2827 2828 if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) { 2829 ProgressBar progress = getCircularProgressBar(false); 2830 if (progress != null) { 2831 progress.setIndeterminate(true); 2832 } 2833 } 2834 2835 // Remaining setup -- of background and title -- that only applies 2836 // to top-level windows. 2837 if (getContainer() == null) { 2838 mDecor.setWindowBackground(mBackgroundDrawable); 2839 2840 final Drawable frame; 2841 if (mFrameResource != 0) { 2842 frame = getContext().getDrawable(mFrameResource); 2843 } else { 2844 frame = null; 2845 } 2846 mDecor.setWindowFrame(frame); 2847 2848 mDecor.setElevation(mElevation); 2849 mDecor.setClipToOutline(mClipToOutline); 2850 2851 if (mTitle != null) { 2852 setTitle(mTitle); 2853 } 2854 2855 if (mTitleColor == 0) { 2856 mTitleColor = mTextColor; 2857 } 2858 setTitleColor(mTitleColor); 2859 } 2860 2861 mDecor.finishChanging(); 2862 2863 return contentParent; 2864 } 2865 2866 /** @hide */ 2867 public void alwaysReadCloseOnTouchAttr() { 2868 mAlwaysReadCloseOnTouchAttr = true; 2869 } 2870 2871 private void installDecor() { 2872 mForceDecorInstall = false; 2873 if (mDecor == null) { 2874 mDecor = generateDecor(-1); 2875 mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); 2876 mDecor.setIsRootNamespace(true); 2877 if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) { 2878 mDecor.postOnAnimation(mInvalidatePanelMenuRunnable); 2879 } 2880 } else { 2881 mDecor.setWindow(this); 2882 } 2883 if (mContentParent == null) { 2884 mContentParent = generateLayout(mDecor); 2885 2886 // Set up decor part of UI to ignore fitsSystemWindows if appropriate. 2887 mDecor.makeFrameworkOptionalFitsSystemWindows(); 2888 2889 final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById( 2890 R.id.decor_content_parent); 2891 2892 if (decorContentParent != null) { 2893 mDecorContentParent = decorContentParent; 2894 mDecorContentParent.setWindowCallback(getCallback()); 2895 if (mDecorContentParent.getTitle() == null) { 2896 mDecorContentParent.setWindowTitle(mTitle); 2897 } 2898 2899 final int localFeatures = getLocalFeatures(); 2900 for (int i = 0; i < FEATURE_MAX; i++) { 2901 if ((localFeatures & (1 << i)) != 0) { 2902 mDecorContentParent.initFeature(i); 2903 } 2904 } 2905 2906 mDecorContentParent.setUiOptions(mUiOptions); 2907 2908 if ((mResourcesSetFlags & FLAG_RESOURCE_SET_ICON) != 0 || 2909 (mIconRes != 0 && !mDecorContentParent.hasIcon())) { 2910 mDecorContentParent.setIcon(mIconRes); 2911 } else if ((mResourcesSetFlags & FLAG_RESOURCE_SET_ICON) == 0 && 2912 mIconRes == 0 && !mDecorContentParent.hasIcon()) { 2913 mDecorContentParent.setIcon( 2914 getContext().getPackageManager().getDefaultActivityIcon()); 2915 mResourcesSetFlags |= FLAG_RESOURCE_SET_ICON_FALLBACK; 2916 } 2917 if ((mResourcesSetFlags & FLAG_RESOURCE_SET_LOGO) != 0 || 2918 (mLogoRes != 0 && !mDecorContentParent.hasLogo())) { 2919 mDecorContentParent.setLogo(mLogoRes); 2920 } 2921 2922 // Invalidate if the panel menu hasn't been created before this. 2923 // Panel menu invalidation is deferred avoiding application onCreateOptionsMenu 2924 // being called in the middle of onCreate or similar. 2925 // A pending invalidation will typically be resolved before the posted message 2926 // would run normally in order to satisfy instance state restoration. 2927 PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false); 2928 if (!isDestroyed() && (st == null || st.menu == null) && !mIsStartingWindow) { 2929 invalidatePanelMenu(FEATURE_ACTION_BAR); 2930 } 2931 } else { 2932 mTitleView = findViewById(R.id.title); 2933 if (mTitleView != null) { 2934 if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) { 2935 final View titleContainer = findViewById(R.id.title_container); 2936 if (titleContainer != null) { 2937 titleContainer.setVisibility(View.GONE); 2938 } else { 2939 mTitleView.setVisibility(View.GONE); 2940 } 2941 mContentParent.setForeground(null); 2942 } else { 2943 mTitleView.setText(mTitle); 2944 } 2945 } 2946 } 2947 2948 if (mDecor.getBackground() == null && mBackgroundFallbackDrawable != null) { 2949 mDecor.setBackgroundFallback(mBackgroundFallbackDrawable); 2950 } 2951 2952 // Only inflate or create a new TransitionManager if the caller hasn't 2953 // already set a custom one. 2954 if (hasFeature(FEATURE_ACTIVITY_TRANSITIONS)) { 2955 if (mTransitionManager == null) { 2956 final int transitionRes = getWindowStyle().getResourceId( 2957 R.styleable.Window_windowContentTransitionManager, 2958 0); 2959 if (transitionRes != 0) { 2960 final TransitionInflater inflater = TransitionInflater.from(getContext()); 2961 mTransitionManager = inflater.inflateTransitionManager(transitionRes, 2962 mContentParent); 2963 } else { 2964 mTransitionManager = new TransitionManager(); 2965 } 2966 } 2967 2968 mEnterTransition = getTransition(mEnterTransition, null, 2969 R.styleable.Window_windowEnterTransition); 2970 mReturnTransition = getTransition(mReturnTransition, USE_DEFAULT_TRANSITION, 2971 R.styleable.Window_windowReturnTransition); 2972 mExitTransition = getTransition(mExitTransition, null, 2973 R.styleable.Window_windowExitTransition); 2974 mReenterTransition = getTransition(mReenterTransition, USE_DEFAULT_TRANSITION, 2975 R.styleable.Window_windowReenterTransition); 2976 mSharedElementEnterTransition = getTransition(mSharedElementEnterTransition, null, 2977 R.styleable.Window_windowSharedElementEnterTransition); 2978 mSharedElementReturnTransition = getTransition(mSharedElementReturnTransition, 2979 USE_DEFAULT_TRANSITION, 2980 R.styleable.Window_windowSharedElementReturnTransition); 2981 mSharedElementExitTransition = getTransition(mSharedElementExitTransition, null, 2982 R.styleable.Window_windowSharedElementExitTransition); 2983 mSharedElementReenterTransition = getTransition(mSharedElementReenterTransition, 2984 USE_DEFAULT_TRANSITION, 2985 R.styleable.Window_windowSharedElementReenterTransition); 2986 if (mAllowEnterTransitionOverlap == null) { 2987 mAllowEnterTransitionOverlap = getWindowStyle().getBoolean( 2988 R.styleable.Window_windowAllowEnterTransitionOverlap, true); 2989 } 2990 if (mAllowReturnTransitionOverlap == null) { 2991 mAllowReturnTransitionOverlap = getWindowStyle().getBoolean( 2992 R.styleable.Window_windowAllowReturnTransitionOverlap, true); 2993 } 2994 if (mBackgroundFadeDurationMillis < 0) { 2995 mBackgroundFadeDurationMillis = getWindowStyle().getInteger( 2996 R.styleable.Window_windowTransitionBackgroundFadeDuration, 2997 DEFAULT_BACKGROUND_FADE_DURATION_MS); 2998 } 2999 if (mSharedElementsUseOverlay == null) { 3000 mSharedElementsUseOverlay = getWindowStyle().getBoolean( 3001 R.styleable.Window_windowSharedElementsUseOverlay, true); 3002 } 3003 } 3004 } 3005 } 3006 3007 private Transition getTransition(Transition currentValue, Transition defaultValue, int id) { 3008 if (currentValue != defaultValue) { 3009 return currentValue; 3010 } 3011 int transitionId = getWindowStyle().getResourceId(id, -1); 3012 Transition transition = defaultValue; 3013 if (transitionId != -1 && transitionId != R.transition.no_transition) { 3014 TransitionInflater inflater = TransitionInflater.from(getContext()); 3015 transition = inflater.inflateTransition(transitionId); 3016 if (transition instanceof TransitionSet && 3017 ((TransitionSet)transition).getTransitionCount() == 0) { 3018 transition = null; 3019 } 3020 } 3021 return transition; 3022 } 3023 3024 private Drawable loadImageURI(Uri uri) { 3025 try { 3026 return Drawable.createFromStream( 3027 getContext().getContentResolver().openInputStream(uri), null); 3028 } catch (Exception e) { 3029 Log.w(TAG, "Unable to open content: " + uri); 3030 } 3031 return null; 3032 } 3033 3034 private DrawableFeatureState getDrawableState(int featureId, boolean required) { 3035 if ((getFeatures() & (1 << featureId)) == 0) { 3036 if (!required) { 3037 return null; 3038 } 3039 throw new RuntimeException("The feature has not been requested"); 3040 } 3041 3042 DrawableFeatureState[] ar; 3043 if ((ar = mDrawables) == null || ar.length <= featureId) { 3044 DrawableFeatureState[] nar = new DrawableFeatureState[featureId + 1]; 3045 if (ar != null) { 3046 System.arraycopy(ar, 0, nar, 0, ar.length); 3047 } 3048 mDrawables = ar = nar; 3049 } 3050 3051 DrawableFeatureState st = ar[featureId]; 3052 if (st == null) { 3053 ar[featureId] = st = new DrawableFeatureState(featureId); 3054 } 3055 return st; 3056 } 3057 3058 /** 3059 * Gets a panel's state based on its feature ID. 3060 * 3061 * @param featureId The feature ID of the panel. 3062 * @param required Whether the panel is required (if it is required and it 3063 * isn't in our features, this throws an exception). 3064 * @return The panel state. 3065 */ 3066 PanelFeatureState getPanelState(int featureId, boolean required) { 3067 return getPanelState(featureId, required, null); 3068 } 3069 3070 /** 3071 * Gets a panel's state based on its feature ID. 3072 * 3073 * @param featureId The feature ID of the panel. 3074 * @param required Whether the panel is required (if it is required and it 3075 * isn't in our features, this throws an exception). 3076 * @param convertPanelState Optional: If the panel state does not exist, use 3077 * this as the panel state. 3078 * @return The panel state. 3079 */ 3080 private PanelFeatureState getPanelState(int featureId, boolean required, 3081 PanelFeatureState convertPanelState) { 3082 if ((getFeatures() & (1 << featureId)) == 0) { 3083 if (!required) { 3084 return null; 3085 } 3086 throw new RuntimeException("The feature has not been requested"); 3087 } 3088 3089 PanelFeatureState[] ar; 3090 if ((ar = mPanels) == null || ar.length <= featureId) { 3091 PanelFeatureState[] nar = new PanelFeatureState[featureId + 1]; 3092 if (ar != null) { 3093 System.arraycopy(ar, 0, nar, 0, ar.length); 3094 } 3095 mPanels = ar = nar; 3096 } 3097 3098 PanelFeatureState st = ar[featureId]; 3099 if (st == null) { 3100 ar[featureId] = st = (convertPanelState != null) 3101 ? convertPanelState 3102 : new PanelFeatureState(featureId); 3103 } 3104 return st; 3105 } 3106 3107 @Override 3108 public final void setChildDrawable(int featureId, Drawable drawable) { 3109 DrawableFeatureState st = getDrawableState(featureId, true); 3110 st.child = drawable; 3111 updateDrawable(featureId, st, false); 3112 } 3113 3114 @Override 3115 public final void setChildInt(int featureId, int value) { 3116 updateInt(featureId, value, false); 3117 } 3118 3119 @Override 3120 public boolean isShortcutKey(int keyCode, KeyEvent event) { 3121 PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false); 3122 return st != null && st.menu != null && st.menu.isShortcutKey(keyCode, event); 3123 } 3124 3125 private void updateDrawable(int featureId, DrawableFeatureState st, boolean fromResume) { 3126 // Do nothing if the decor is not yet installed... an update will 3127 // need to be forced when we eventually become active. 3128 if (mContentParent == null) { 3129 return; 3130 } 3131 3132 final int featureMask = 1 << featureId; 3133 3134 if ((getFeatures() & featureMask) == 0 && !fromResume) { 3135 return; 3136 } 3137 3138 Drawable drawable = null; 3139 if (st != null) { 3140 drawable = st.child; 3141 if (drawable == null) 3142 drawable = st.local; 3143 if (drawable == null) 3144 drawable = st.def; 3145 } 3146 if ((getLocalFeatures() & featureMask) == 0) { 3147 if (getContainer() != null) { 3148 if (isActive() || fromResume) { 3149 getContainer().setChildDrawable(featureId, drawable); 3150 } 3151 } 3152 } else if (st != null && (st.cur != drawable || st.curAlpha != st.alpha)) { 3153 // System.out.println("Drawable changed: old=" + st.cur 3154 // + ", new=" + drawable); 3155 st.cur = drawable; 3156 st.curAlpha = st.alpha; 3157 onDrawableChanged(featureId, drawable, st.alpha); 3158 } 3159 } 3160 3161 private void updateInt(int featureId, int value, boolean fromResume) { 3162 3163 // Do nothing if the decor is not yet installed... an update will 3164 // need to be forced when we eventually become active. 3165 if (mContentParent == null) { 3166 return; 3167 } 3168 3169 final int featureMask = 1 << featureId; 3170 3171 if ((getFeatures() & featureMask) == 0 && !fromResume) { 3172 return; 3173 } 3174 3175 if ((getLocalFeatures() & featureMask) == 0) { 3176 if (getContainer() != null) { 3177 getContainer().setChildInt(featureId, value); 3178 } 3179 } else { 3180 onIntChanged(featureId, value); 3181 } 3182 } 3183 3184 private ImageView getLeftIconView() { 3185 if (mLeftIconView != null) { 3186 return mLeftIconView; 3187 } 3188 if (mContentParent == null) { 3189 installDecor(); 3190 } 3191 return (mLeftIconView = (ImageView)findViewById(R.id.left_icon)); 3192 } 3193 3194 @Override 3195 protected void dispatchWindowAttributesChanged(WindowManager.LayoutParams attrs) { 3196 super.dispatchWindowAttributesChanged(attrs); 3197 if (mDecor != null) { 3198 mDecor.updateColorViews(null /* insets */, true /* animate */); 3199 } 3200 } 3201 3202 private ProgressBar getCircularProgressBar(boolean shouldInstallDecor) { 3203 if (mCircularProgressBar != null) { 3204 return mCircularProgressBar; 3205 } 3206 if (mContentParent == null && shouldInstallDecor) { 3207 installDecor(); 3208 } 3209 mCircularProgressBar = findViewById(R.id.progress_circular); 3210 if (mCircularProgressBar != null) { 3211 mCircularProgressBar.setVisibility(View.INVISIBLE); 3212 } 3213 return mCircularProgressBar; 3214 } 3215 3216 private ProgressBar getHorizontalProgressBar(boolean shouldInstallDecor) { 3217 if (mHorizontalProgressBar != null) { 3218 return mHorizontalProgressBar; 3219 } 3220 if (mContentParent == null && shouldInstallDecor) { 3221 installDecor(); 3222 } 3223 mHorizontalProgressBar = findViewById(R.id.progress_horizontal); 3224 if (mHorizontalProgressBar != null) { 3225 mHorizontalProgressBar.setVisibility(View.INVISIBLE); 3226 } 3227 return mHorizontalProgressBar; 3228 } 3229 3230 private ImageView getRightIconView() { 3231 if (mRightIconView != null) { 3232 return mRightIconView; 3233 } 3234 if (mContentParent == null) { 3235 installDecor(); 3236 } 3237 return (mRightIconView = (ImageView)findViewById(R.id.right_icon)); 3238 } 3239 3240 /** 3241 * Helper method for calling the {@link Callback#onPanelClosed(int, Menu)} 3242 * callback. This method will grab whatever extra state is needed for the 3243 * callback that isn't given in the parameters. If the panel is not open, 3244 * this will not perform the callback. 3245 * 3246 * @param featureId Feature ID of the panel that was closed. Must be given. 3247 * @param panel Panel that was closed. Optional but useful if there is no 3248 * menu given. 3249 * @param menu The menu that was closed. Optional, but give if you have. 3250 */ 3251 private void callOnPanelClosed(int featureId, PanelFeatureState panel, Menu menu) { 3252 final Callback cb = getCallback(); 3253 if (cb == null) 3254 return; 3255 3256 // Try to get a menu 3257 if (menu == null) { 3258 // Need a panel to grab the menu, so try to get that 3259 if (panel == null) { 3260 if ((featureId >= 0) && (featureId < mPanels.length)) { 3261 panel = mPanels[featureId]; 3262 } 3263 } 3264 3265 if (panel != null) { 3266 // menu still may be null, which is okay--we tried our best 3267 menu = panel.menu; 3268 } 3269 } 3270 3271 // If the panel is not open, do not callback 3272 if ((panel != null) && (!panel.isOpen)) 3273 return; 3274 3275 if (!isDestroyed()) { 3276 cb.onPanelClosed(featureId, menu); 3277 } 3278 } 3279 3280 /** 3281 * Check if Setup or Post-Setup update is completed on TV 3282 * @return true if completed 3283 */ 3284 private boolean isTvUserSetupComplete() { 3285 boolean isTvSetupComplete = Settings.Secure.getInt(getContext().getContentResolver(), 3286 Settings.Secure.USER_SETUP_COMPLETE, 0) != 0; 3287 isTvSetupComplete &= Settings.Secure.getInt(getContext().getContentResolver(), 3288 Settings.Secure.TV_USER_SETUP_COMPLETE, 0) != 0; 3289 return isTvSetupComplete; 3290 } 3291 3292 /** 3293 * Helper method for adding launch-search to most applications. Opens the 3294 * search window using default settings. 3295 * 3296 * @return true if search window opened 3297 */ 3298 private boolean launchDefaultSearch(KeyEvent event) { 3299 if (getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK) 3300 && !isTvUserSetupComplete()) { 3301 // If we are in Setup or Post-Setup update mode on TV, consume the search key 3302 return false; 3303 } 3304 boolean result; 3305 final Callback cb = getCallback(); 3306 if (cb == null || isDestroyed()) { 3307 result = false; 3308 } else { 3309 int deviceId = event.getDeviceId(); 3310 SearchEvent searchEvent = null; 3311 if (deviceId != 0) { 3312 searchEvent = new SearchEvent(InputDevice.getDevice(deviceId)); 3313 } 3314 try { 3315 result = cb.onSearchRequested(searchEvent); 3316 } catch (AbstractMethodError e) { 3317 Log.e(TAG, "WindowCallback " + cb.getClass().getName() + " does not implement" 3318 + " method onSearchRequested(SearchEvent); fa", e); 3319 result = cb.onSearchRequested(); 3320 } 3321 } 3322 if (!result && (getContext().getResources().getConfiguration().uiMode 3323 & Configuration.UI_MODE_TYPE_MASK) == Configuration.UI_MODE_TYPE_TELEVISION) { 3324 // On TVs, if the app doesn't implement search, we want to launch assist. 3325 Bundle args = new Bundle(); 3326 args.putInt(Intent.EXTRA_ASSIST_INPUT_DEVICE_ID, event.getDeviceId()); 3327 args.putLong(Intent.EXTRA_TIME, event.getEventTime()); 3328 ((SearchManager) getContext().getSystemService(Context.SEARCH_SERVICE)) 3329 .launchAssist(args); 3330 return true; 3331 } 3332 return result; 3333 } 3334 3335 @Override 3336 public void setVolumeControlStream(int streamType) { 3337 mVolumeControlStreamType = streamType; 3338 } 3339 3340 @Override 3341 public int getVolumeControlStream() { 3342 return mVolumeControlStreamType; 3343 } 3344 3345 @Override 3346 public void setMediaController(MediaController controller) { 3347 mMediaController = controller; 3348 if (controller != null && mOnModeChangedListener == null) { 3349 mAudioMode = getAudioManager().getMode(); 3350 mOnModeChangedListener = mode -> mAudioMode = mode; 3351 getAudioManager().addOnModeChangedListener(getContext().getMainExecutor(), 3352 mOnModeChangedListener); 3353 } else if (mOnModeChangedListener != null) { 3354 getAudioManager().removeOnModeChangedListener(mOnModeChangedListener); 3355 mOnModeChangedListener = null; 3356 } 3357 } 3358 3359 @Override 3360 public MediaController getMediaController() { 3361 return mMediaController; 3362 } 3363 3364 @Override 3365 public void setEnterTransition(Transition enterTransition) { 3366 mEnterTransition = enterTransition; 3367 } 3368 3369 @Override 3370 public void setReturnTransition(Transition transition) { 3371 mReturnTransition = transition; 3372 } 3373 3374 @Override 3375 public void setExitTransition(Transition exitTransition) { 3376 mExitTransition = exitTransition; 3377 } 3378 3379 @Override 3380 public void setReenterTransition(Transition transition) { 3381 mReenterTransition = transition; 3382 } 3383 3384 @Override 3385 public void setSharedElementEnterTransition(Transition sharedElementEnterTransition) { 3386 mSharedElementEnterTransition = sharedElementEnterTransition; 3387 } 3388 3389 @Override 3390 public void setSharedElementReturnTransition(Transition transition) { 3391 mSharedElementReturnTransition = transition; 3392 } 3393 3394 @Override 3395 public void setSharedElementExitTransition(Transition sharedElementExitTransition) { 3396 mSharedElementExitTransition = sharedElementExitTransition; 3397 } 3398 3399 @Override 3400 public void setSharedElementReenterTransition(Transition transition) { 3401 mSharedElementReenterTransition = transition; 3402 } 3403 3404 @Override 3405 public Transition getEnterTransition() { 3406 return mEnterTransition; 3407 } 3408 3409 @Override 3410 public Transition getReturnTransition() { 3411 return mReturnTransition == USE_DEFAULT_TRANSITION ? getEnterTransition() 3412 : mReturnTransition; 3413 } 3414 3415 @Override 3416 public Transition getExitTransition() { 3417 return mExitTransition; 3418 } 3419 3420 @Override 3421 public Transition getReenterTransition() { 3422 return mReenterTransition == USE_DEFAULT_TRANSITION ? getExitTransition() 3423 : mReenterTransition; 3424 } 3425 3426 @Override 3427 public Transition getSharedElementEnterTransition() { 3428 return mSharedElementEnterTransition; 3429 } 3430 3431 @Override 3432 public Transition getSharedElementReturnTransition() { 3433 return mSharedElementReturnTransition == USE_DEFAULT_TRANSITION 3434 ? getSharedElementEnterTransition() : mSharedElementReturnTransition; 3435 } 3436 3437 @Override 3438 public Transition getSharedElementExitTransition() { 3439 return mSharedElementExitTransition; 3440 } 3441 3442 @Override 3443 public Transition getSharedElementReenterTransition() { 3444 return mSharedElementReenterTransition == USE_DEFAULT_TRANSITION 3445 ? getSharedElementExitTransition() : mSharedElementReenterTransition; 3446 } 3447 3448 @Override 3449 public void setAllowEnterTransitionOverlap(boolean allow) { 3450 mAllowEnterTransitionOverlap = allow; 3451 } 3452 3453 @Override 3454 public boolean getAllowEnterTransitionOverlap() { 3455 return (mAllowEnterTransitionOverlap == null) ? true : mAllowEnterTransitionOverlap; 3456 } 3457 3458 @Override 3459 public void setAllowReturnTransitionOverlap(boolean allowExitTransitionOverlap) { 3460 mAllowReturnTransitionOverlap = allowExitTransitionOverlap; 3461 } 3462 3463 @Override 3464 public boolean getAllowReturnTransitionOverlap() { 3465 return (mAllowReturnTransitionOverlap == null) ? true : mAllowReturnTransitionOverlap; 3466 } 3467 3468 @Override 3469 public long getTransitionBackgroundFadeDuration() { 3470 return (mBackgroundFadeDurationMillis < 0) ? DEFAULT_BACKGROUND_FADE_DURATION_MS 3471 : mBackgroundFadeDurationMillis; 3472 } 3473 3474 @Override 3475 public void setTransitionBackgroundFadeDuration(long fadeDurationMillis) { 3476 if (fadeDurationMillis < 0) { 3477 throw new IllegalArgumentException("negative durations are not allowed"); 3478 } 3479 mBackgroundFadeDurationMillis = fadeDurationMillis; 3480 } 3481 3482 @Override 3483 public void setSharedElementsUseOverlay(boolean sharedElementsUseOverlay) { 3484 mSharedElementsUseOverlay = sharedElementsUseOverlay; 3485 } 3486 3487 @Override 3488 public boolean getSharedElementsUseOverlay() { 3489 return (mSharedElementsUseOverlay == null) ? true : mSharedElementsUseOverlay; 3490 } 3491 3492 private static final class DrawableFeatureState { 3493 DrawableFeatureState(int _featureId) { 3494 featureId = _featureId; 3495 } 3496 3497 final int featureId; 3498 3499 int resid; 3500 3501 Uri uri; 3502 3503 Drawable local; 3504 3505 Drawable child; 3506 3507 Drawable def; 3508 3509 Drawable cur; 3510 3511 int alpha = 255; 3512 3513 int curAlpha = 255; 3514 } 3515 3516 static final class PanelFeatureState { 3517 3518 /** Feature ID for this panel. */ 3519 int featureId; 3520 3521 // Information pulled from the style for this panel. 3522 3523 int background; 3524 3525 /** The background when the panel spans the entire available width. */ 3526 int fullBackground; 3527 3528 int gravity; 3529 3530 int x; 3531 3532 int y; 3533 3534 int windowAnimations; 3535 3536 /** Dynamic state of the panel. */ 3537 DecorView decorView; 3538 3539 /** The panel that was returned by onCreatePanelView(). */ 3540 View createdPanelView; 3541 3542 /** The panel that we are actually showing. */ 3543 View shownPanelView; 3544 3545 /** Use {@link #setMenu} to set this. */ 3546 MenuBuilder menu; 3547 3548 IconMenuPresenter iconMenuPresenter; 3549 ListMenuPresenter listMenuPresenter; 3550 3551 /** true if this menu will show in single-list compact mode */ 3552 boolean isCompact; 3553 3554 /** Theme resource ID for list elements of the panel menu */ 3555 int listPresenterTheme; 3556 3557 /** 3558 * Whether the panel has been prepared (see 3559 * {@link PhoneWindow#preparePanel}). 3560 */ 3561 boolean isPrepared; 3562 3563 /** 3564 * Whether an item's action has been performed. This happens in obvious 3565 * scenarios (user clicks on menu item), but can also happen with 3566 * chording menu+(shortcut key). 3567 */ 3568 boolean isHandled; 3569 3570 boolean isOpen; 3571 3572 /** 3573 * True if the menu is in expanded mode, false if the menu is in icon 3574 * mode 3575 */ 3576 boolean isInExpandedMode; 3577 3578 public boolean qwertyMode; 3579 3580 boolean refreshDecorView; 3581 3582 boolean refreshMenuContent; 3583 3584 boolean wasLastOpen; 3585 3586 boolean wasLastExpanded; 3587 3588 /** 3589 * Contains the state of the menu when told to freeze. 3590 */ 3591 Bundle frozenMenuState; 3592 3593 /** 3594 * Contains the state of associated action views when told to freeze. 3595 * These are saved across invalidations. 3596 */ 3597 Bundle frozenActionViewState; 3598 3599 PanelFeatureState(int featureId) { 3600 this.featureId = featureId; 3601 3602 refreshDecorView = false; 3603 } 3604 3605 public boolean isInListMode() { 3606 return isInExpandedMode || isCompact; 3607 } 3608 3609 public boolean hasPanelItems() { 3610 if (shownPanelView == null) return false; 3611 if (createdPanelView != null) return true; 3612 3613 if (isCompact || isInExpandedMode) { 3614 return listMenuPresenter.getAdapter().getCount() > 0; 3615 } else { 3616 return ((ViewGroup) shownPanelView).getChildCount() > 0; 3617 } 3618 } 3619 3620 /** 3621 * Unregister and free attached MenuPresenters. They will be recreated as needed. 3622 */ 3623 public void clearMenuPresenters() { 3624 if (menu != null) { 3625 menu.removeMenuPresenter(iconMenuPresenter); 3626 menu.removeMenuPresenter(listMenuPresenter); 3627 } 3628 iconMenuPresenter = null; 3629 listMenuPresenter = null; 3630 } 3631 3632 void setStyle(Context context) { 3633 TypedArray a = context.obtainStyledAttributes(R.styleable.Theme); 3634 background = a.getResourceId( 3635 R.styleable.Theme_panelBackground, 0); 3636 fullBackground = a.getResourceId( 3637 R.styleable.Theme_panelFullBackground, 0); 3638 windowAnimations = a.getResourceId( 3639 R.styleable.Theme_windowAnimationStyle, 0); 3640 isCompact = a.getBoolean( 3641 R.styleable.Theme_panelMenuIsCompact, false); 3642 listPresenterTheme = a.getResourceId( 3643 R.styleable.Theme_panelMenuListTheme, 3644 R.style.Theme_ExpandedMenu); 3645 a.recycle(); 3646 } 3647 3648 void setMenu(MenuBuilder menu) { 3649 if (menu == this.menu) return; 3650 3651 if (this.menu != null) { 3652 this.menu.removeMenuPresenter(iconMenuPresenter); 3653 this.menu.removeMenuPresenter(listMenuPresenter); 3654 } 3655 this.menu = menu; 3656 if (menu != null) { 3657 if (iconMenuPresenter != null) menu.addMenuPresenter(iconMenuPresenter); 3658 if (listMenuPresenter != null) menu.addMenuPresenter(listMenuPresenter); 3659 } 3660 } 3661 3662 MenuView getListMenuView(Context context, MenuPresenter.Callback cb) { 3663 if (menu == null) return null; 3664 3665 if (!isCompact) { 3666 getIconMenuView(context, cb); // Need this initialized to know where our offset goes 3667 } 3668 3669 if (listMenuPresenter == null) { 3670 listMenuPresenter = new ListMenuPresenter( 3671 R.layout.list_menu_item_layout, listPresenterTheme); 3672 listMenuPresenter.setCallback(cb); 3673 listMenuPresenter.setId(R.id.list_menu_presenter); 3674 menu.addMenuPresenter(listMenuPresenter); 3675 } 3676 3677 if (iconMenuPresenter != null) { 3678 listMenuPresenter.setItemIndexOffset( 3679 iconMenuPresenter.getNumActualItemsShown()); 3680 } 3681 MenuView result = listMenuPresenter.getMenuView(decorView); 3682 3683 return result; 3684 } 3685 3686 MenuView getIconMenuView(Context context, MenuPresenter.Callback cb) { 3687 if (menu == null) return null; 3688 3689 if (iconMenuPresenter == null) { 3690 iconMenuPresenter = new IconMenuPresenter(context); 3691 iconMenuPresenter.setCallback(cb); 3692 iconMenuPresenter.setId(R.id.icon_menu_presenter); 3693 menu.addMenuPresenter(iconMenuPresenter); 3694 } 3695 3696 MenuView result = iconMenuPresenter.getMenuView(decorView); 3697 3698 return result; 3699 } 3700 3701 Parcelable onSaveInstanceState() { 3702 SavedState savedState = new SavedState(); 3703 savedState.featureId = featureId; 3704 savedState.isOpen = isOpen; 3705 savedState.isInExpandedMode = isInExpandedMode; 3706 3707 if (menu != null) { 3708 savedState.menuState = new Bundle(); 3709 menu.savePresenterStates(savedState.menuState); 3710 } 3711 3712 return savedState; 3713 } 3714 3715 void onRestoreInstanceState(Parcelable state) { 3716 SavedState savedState = (SavedState) state; 3717 featureId = savedState.featureId; 3718 wasLastOpen = savedState.isOpen; 3719 wasLastExpanded = savedState.isInExpandedMode; 3720 frozenMenuState = savedState.menuState; 3721 3722 /* 3723 * A LocalActivityManager keeps the same instance of this class around. 3724 * The first time the menu is being shown after restoring, the 3725 * Activity.onCreateOptionsMenu should be called. But, if it is the 3726 * same instance then menu != null and we won't call that method. 3727 * We clear any cached views here. The caller should invalidatePanelMenu. 3728 */ 3729 createdPanelView = null; 3730 shownPanelView = null; 3731 decorView = null; 3732 } 3733 3734 void applyFrozenState() { 3735 if (menu != null && frozenMenuState != null) { 3736 menu.restorePresenterStates(frozenMenuState); 3737 frozenMenuState = null; 3738 } 3739 } 3740 3741 private static class SavedState implements Parcelable { 3742 int featureId; 3743 boolean isOpen; 3744 boolean isInExpandedMode; 3745 Bundle menuState; 3746 3747 public int describeContents() { 3748 return 0; 3749 } 3750 3751 public void writeToParcel(Parcel dest, int flags) { 3752 dest.writeInt(featureId); 3753 dest.writeInt(isOpen ? 1 : 0); 3754 dest.writeInt(isInExpandedMode ? 1 : 0); 3755 3756 if (isOpen) { 3757 dest.writeBundle(menuState); 3758 } 3759 } 3760 3761 private static SavedState readFromParcel(Parcel source) { 3762 SavedState savedState = new SavedState(); 3763 savedState.featureId = source.readInt(); 3764 savedState.isOpen = source.readInt() == 1; 3765 savedState.isInExpandedMode = source.readInt() == 1; 3766 3767 if (savedState.isOpen) { 3768 savedState.menuState = source.readBundle(); 3769 } 3770 3771 return savedState; 3772 } 3773 3774 public static final Parcelable.Creator<SavedState> CREATOR 3775 = new Parcelable.Creator<SavedState>() { 3776 public SavedState createFromParcel(Parcel in) { 3777 return readFromParcel(in); 3778 } 3779 3780 public SavedState[] newArray(int size) { 3781 return new SavedState[size]; 3782 } 3783 }; 3784 } 3785 3786 } 3787 3788 static class RotationWatcher extends Stub { 3789 private Handler mHandler; 3790 private final Runnable mRotationChanged = new Runnable() { 3791 public void run() { 3792 dispatchRotationChanged(); 3793 } 3794 }; 3795 private final ArrayList<WeakReference<PhoneWindow>> mWindows = 3796 new ArrayList<WeakReference<PhoneWindow>>(); 3797 private boolean mIsWatching; 3798 3799 @Override 3800 public void onRotationChanged(int rotation) throws RemoteException { 3801 mHandler.post(mRotationChanged); 3802 } 3803 3804 public void addWindow(PhoneWindow phoneWindow) { 3805 synchronized (mWindows) { 3806 if (!mIsWatching) { 3807 try { 3808 WindowManagerHolder.sWindowManager.watchRotation(this, 3809 phoneWindow.getContext().getDisplayId()); 3810 mHandler = new Handler(); 3811 mIsWatching = true; 3812 } catch (RemoteException ex) { 3813 Log.e(TAG, "Couldn't start watching for device rotation", ex); 3814 } 3815 } 3816 mWindows.add(new WeakReference<PhoneWindow>(phoneWindow)); 3817 } 3818 } 3819 3820 public void removeWindow(PhoneWindow phoneWindow) { 3821 synchronized (mWindows) { 3822 int i = 0; 3823 while (i < mWindows.size()) { 3824 final WeakReference<PhoneWindow> ref = mWindows.get(i); 3825 final PhoneWindow win = ref.get(); 3826 if (win == null || win == phoneWindow) { 3827 mWindows.remove(i); 3828 } else { 3829 i++; 3830 } 3831 } 3832 } 3833 } 3834 3835 void dispatchRotationChanged() { 3836 synchronized (mWindows) { 3837 int i = 0; 3838 while (i < mWindows.size()) { 3839 final WeakReference<PhoneWindow> ref = mWindows.get(i); 3840 final PhoneWindow win = ref.get(); 3841 if (win != null) { 3842 win.onOptionsPanelRotationChanged(); 3843 i++; 3844 } else { 3845 mWindows.remove(i); 3846 } 3847 } 3848 } 3849 } 3850 } 3851 3852 /** 3853 * Simple implementation of MenuBuilder.Callback that: 3854 * <li> Opens a submenu when selected. 3855 * <li> Calls back to the callback's onMenuItemSelected when an item is 3856 * selected. 3857 */ 3858 public static final class PhoneWindowMenuCallback 3859 implements MenuBuilder.Callback, MenuPresenter.Callback { 3860 private static final int FEATURE_ID = FEATURE_CONTEXT_MENU; 3861 3862 private final PhoneWindow mWindow; 3863 3864 private MenuDialogHelper mSubMenuHelper; 3865 3866 private boolean mShowDialogForSubmenu; 3867 3868 public PhoneWindowMenuCallback(PhoneWindow window) { 3869 mWindow = window; 3870 } 3871 3872 @Override 3873 public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { 3874 if (menu.getRootMenu() != menu) { 3875 onCloseSubMenu(menu); 3876 } 3877 3878 if (allMenusAreClosing) { 3879 final Callback callback = mWindow.getCallback(); 3880 if (callback != null && !mWindow.isDestroyed()) { 3881 callback.onPanelClosed(FEATURE_ID, menu); 3882 } 3883 3884 if (menu == mWindow.mContextMenu) { 3885 mWindow.dismissContextMenu(); 3886 } 3887 3888 // Dismiss the submenu, if it is showing 3889 if (mSubMenuHelper != null) { 3890 mSubMenuHelper.dismiss(); 3891 mSubMenuHelper = null; 3892 } 3893 } 3894 } 3895 3896 private void onCloseSubMenu(MenuBuilder menu) { 3897 final Callback callback = mWindow.getCallback(); 3898 if (callback != null && !mWindow.isDestroyed()) { 3899 callback.onPanelClosed(FEATURE_ID, menu.getRootMenu()); 3900 } 3901 } 3902 3903 @Override 3904 public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) { 3905 final Callback callback = mWindow.getCallback(); 3906 return callback != null && !mWindow.isDestroyed() 3907 && callback.onMenuItemSelected(FEATURE_ID, item); 3908 } 3909 3910 @Override 3911 public void onMenuModeChange(MenuBuilder menu) { 3912 } 3913 3914 @Override 3915 public boolean onOpenSubMenu(MenuBuilder subMenu) { 3916 if (subMenu == null) { 3917 return false; 3918 } 3919 3920 // Set a simple callback for the submenu 3921 subMenu.setCallback(this); 3922 3923 if (mShowDialogForSubmenu) { 3924 // The window manager will give us a valid window token 3925 mSubMenuHelper = new MenuDialogHelper(subMenu); 3926 mSubMenuHelper.show(null); 3927 return true; 3928 } 3929 3930 return false; 3931 } 3932 3933 public void setShowDialogForSubmenu(boolean enabled) { 3934 mShowDialogForSubmenu = enabled; 3935 } 3936 } 3937 3938 int getLocalFeaturesPrivate() { 3939 return super.getLocalFeatures(); 3940 } 3941 3942 protected void setDefaultWindowFormat(int format) { 3943 super.setDefaultWindowFormat(format); 3944 } 3945 3946 void sendCloseSystemWindows() { 3947 sendCloseSystemWindows(getContext(), null); 3948 } 3949 3950 void sendCloseSystemWindows(String reason) { 3951 sendCloseSystemWindows(getContext(), reason); 3952 } 3953 3954 public static void sendCloseSystemWindows(Context context, String reason) { 3955 if (ActivityManager.isSystemReady()) { 3956 try { 3957 ActivityManager.getService().closeSystemDialogs(reason); 3958 } catch (RemoteException e) { 3959 } 3960 } 3961 } 3962 3963 @Override 3964 public int getStatusBarColor() { 3965 return mStatusBarColor; 3966 } 3967 3968 @Override 3969 public void setStatusBarColor(int color) { 3970 if (mEdgeToEdgeEnforced) { 3971 return; 3972 } 3973 if (mStatusBarColor == color && mForcedStatusBarColor) { 3974 return; 3975 } 3976 mStatusBarColor = color; 3977 mForcedStatusBarColor = true; 3978 if (mDecor != null) { 3979 mDecor.updateColorViews(null, false /* animate */); 3980 } 3981 final WindowControllerCallback callback = getWindowControllerCallback(); 3982 if (callback != null) { 3983 getWindowControllerCallback().updateStatusBarColor(color); 3984 } 3985 } 3986 3987 @Override 3988 public int getNavigationBarColor() { 3989 if (mEdgeToEdgeEnforced) { 3990 return Color.TRANSPARENT; 3991 } 3992 return mNavigationBarColor; 3993 } 3994 3995 @Override 3996 public void setNavigationBarColor(int color) { 3997 if (mNavigationBarColor == color && mForcedNavigationBarColor) { 3998 return; 3999 } 4000 mNavigationBarColor = color; 4001 mForcedNavigationBarColor = true; 4002 mNavigationBarColorSpecified = true; 4003 if (mDecor != null) { 4004 mDecor.getWindowInsetsController().setSystemBarsAppearance( 4005 0, APPEARANCE_FORCE_LIGHT_NAVIGATION_BARS); 4006 mDecor.updateColorViews(null, false /* animate */); 4007 } 4008 if (mEdgeToEdgeEnforced) { 4009 return; 4010 } 4011 final WindowControllerCallback callback = getWindowControllerCallback(); 4012 if (callback != null) { 4013 getWindowControllerCallback().updateNavigationBarColor(color); 4014 } 4015 } 4016 4017 @Override 4018 public void setNavigationBarDividerColor(int navigationBarDividerColor) { 4019 if (mEdgeToEdgeEnforced) { 4020 return; 4021 } 4022 mNavigationBarDividerColor = navigationBarDividerColor; 4023 if (mDecor != null) { 4024 mDecor.updateColorViews(null, false /* animate */); 4025 } 4026 } 4027 4028 @Override 4029 public int getNavigationBarDividerColor() { 4030 return mNavigationBarDividerColor; 4031 } 4032 4033 @Override 4034 public void setStatusBarContrastEnforced(boolean ensureContrast) { 4035 mEnsureStatusBarContrastWhenTransparent = ensureContrast; 4036 if (mDecor != null) { 4037 mDecor.updateColorViews(null, false /* animate */); 4038 } 4039 } 4040 4041 @Override 4042 public boolean isStatusBarContrastEnforced() { 4043 return mEnsureStatusBarContrastWhenTransparent; 4044 } 4045 4046 @Override 4047 public void setNavigationBarContrastEnforced(boolean enforceContrast) { 4048 mEnsureNavigationBarContrastWhenTransparent = enforceContrast; 4049 if (mDecor != null) { 4050 mDecor.updateColorViews(null, false /* animate */); 4051 } 4052 } 4053 4054 @Override 4055 public boolean isNavigationBarContrastEnforced() { 4056 return mEnsureNavigationBarContrastWhenTransparent; 4057 } 4058 4059 public void setIsStartingWindow(boolean isStartingWindow) { 4060 mIsStartingWindow = isStartingWindow; 4061 } 4062 4063 @Override 4064 public void setTheme(int resid) { 4065 mTheme = resid; 4066 if (mDecor != null) { 4067 Context context = mDecor.getContext(); 4068 if (context instanceof DecorContext) { 4069 context.setTheme(resid); 4070 } 4071 } 4072 } 4073 4074 @Override 4075 public void setResizingCaptionDrawable(Drawable drawable) {} 4076 4077 @Override 4078 public void setDecorCaptionShade(int decorCaptionShade) { 4079 // TODO(b/328668781): Make proper treatment to this public API per the outcome of the bug. 4080 } 4081 4082 @Override 4083 public void setAttributes(WindowManager.LayoutParams params) { 4084 super.setAttributes(params); 4085 if (mDecor != null) { 4086 mDecor.updateLogTag(params); 4087 } 4088 } 4089 4090 @Override 4091 public WindowInsetsController getInsetsController() { 4092 return mDecor.getWindowInsetsController(); 4093 } 4094 4095 @Override 4096 public void setSystemGestureExclusionRects(@NonNull List<Rect> rects) { 4097 getViewRootImpl().setRootSystemGestureExclusionRects(rects); 4098 } 4099 4100 @Override 4101 @NonNull 4102 public List<Rect> getSystemGestureExclusionRects() { 4103 return getViewRootImpl().getRootSystemGestureExclusionRects(); 4104 } 4105 4106 @Override 4107 public void setDecorFitsSystemWindows(boolean decorFitsSystemWindows) { 4108 if (mEdgeToEdgeEnforced) { 4109 return; 4110 } 4111 mDecorFitsSystemWindows = decorFitsSystemWindows; 4112 applyDecorFitsSystemWindows(); 4113 } 4114 4115 @Override 4116 public boolean decorFitsSystemWindows() { 4117 return mDecorFitsSystemWindows; 4118 } 4119 4120 private void applyDecorFitsSystemWindows() { 4121 ViewRootImpl impl = getViewRootImplOrNull(); 4122 if (impl != null) { 4123 impl.setOnContentApplyWindowInsetsListener(mDecorFitsSystemWindows 4124 ? sDefaultContentInsetsApplier 4125 : null); 4126 } 4127 } 4128 4129 /** 4130 * System request to begin scroll capture. 4131 * 4132 * @param listener to receive the response 4133 * @hide 4134 */ 4135 @Override 4136 public void requestScrollCapture(IScrollCaptureResponseListener listener) { 4137 getViewRootImpl().dispatchScrollCaptureRequest(listener); 4138 } 4139 4140 /** 4141 * Registers a handler providing scrolling capture support for window content. 4142 * 4143 * @param callback the callback to add 4144 */ 4145 @Override 4146 public void registerScrollCaptureCallback(@NonNull ScrollCaptureCallback callback) { 4147 getViewRootImpl().addScrollCaptureCallback(callback); 4148 } 4149 4150 /** 4151 * Unregisters the given {@link ScrollCaptureCallback}. 4152 * 4153 * @param callback the callback to remove 4154 */ 4155 @Override 4156 public void unregisterScrollCaptureCallback(@NonNull ScrollCaptureCallback callback) { 4157 getViewRootImpl().removeScrollCaptureCallback(callback); 4158 } 4159 4160 @Override 4161 @Nullable 4162 public View getStatusBarBackgroundView() { 4163 return mDecor != null ? mDecor.getStatusBarBackgroundView() : null; 4164 } 4165 4166 @Override 4167 @Nullable 4168 public View getNavigationBarBackgroundView() { 4169 return mDecor != null ? mDecor.getNavigationBarBackgroundView() : null; 4170 } 4171 4172 @Override 4173 public AttachedSurfaceControl getRootSurfaceControl() { 4174 return getViewRootImplOrNull(); 4175 } 4176 4177 @NonNull 4178 @Override 4179 public OnBackInvokedDispatcher getOnBackInvokedDispatcher() { 4180 return mProxyOnBackInvokedDispatcher; 4181 } 4182 } 4183