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