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