1 /* 2 * Copyright (C) 2013 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 androidx.appcompat.app; 18 19 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; 20 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; 21 import static android.view.Window.FEATURE_OPTIONS_PANEL; 22 23 import android.app.Activity; 24 import android.app.Dialog; 25 import android.app.UiModeManager; 26 import android.content.BroadcastReceiver; 27 import android.content.ComponentName; 28 import android.content.Context; 29 import android.content.Intent; 30 import android.content.IntentFilter; 31 import android.content.pm.ActivityInfo; 32 import android.content.pm.PackageManager; 33 import android.content.res.Configuration; 34 import android.content.res.Resources; 35 import android.content.res.TypedArray; 36 import android.graphics.PixelFormat; 37 import android.graphics.Rect; 38 import android.graphics.drawable.Drawable; 39 import android.media.AudioManager; 40 import android.os.Build; 41 import android.os.Bundle; 42 import android.os.Parcel; 43 import android.os.Parcelable; 44 import android.text.TextUtils; 45 import android.util.AndroidRuntimeException; 46 import android.util.AttributeSet; 47 import android.util.DisplayMetrics; 48 import android.util.Log; 49 import android.util.TypedValue; 50 import android.view.Gravity; 51 import android.view.KeyCharacterMap; 52 import android.view.KeyEvent; 53 import android.view.KeyboardShortcutGroup; 54 import android.view.LayoutInflater; 55 import android.view.Menu; 56 import android.view.MenuInflater; 57 import android.view.MenuItem; 58 import android.view.MotionEvent; 59 import android.view.View; 60 import android.view.ViewConfiguration; 61 import android.view.ViewGroup; 62 import android.view.ViewParent; 63 import android.view.Window; 64 import android.view.WindowManager; 65 import android.view.accessibility.AccessibilityEvent; 66 import android.widget.FrameLayout; 67 import android.widget.PopupWindow; 68 import android.widget.TextView; 69 70 import androidx.annotation.IdRes; 71 import androidx.annotation.NonNull; 72 import androidx.annotation.Nullable; 73 import androidx.annotation.RequiresApi; 74 import androidx.annotation.VisibleForTesting; 75 import androidx.appcompat.R; 76 import androidx.appcompat.content.res.AppCompatResources; 77 import androidx.appcompat.view.ActionMode; 78 import androidx.appcompat.view.ContextThemeWrapper; 79 import androidx.appcompat.view.StandaloneActionMode; 80 import androidx.appcompat.view.SupportActionModeWrapper; 81 import androidx.appcompat.view.SupportMenuInflater; 82 import androidx.appcompat.view.WindowCallbackWrapper; 83 import androidx.appcompat.view.menu.ListMenuPresenter; 84 import androidx.appcompat.view.menu.MenuBuilder; 85 import androidx.appcompat.view.menu.MenuPresenter; 86 import androidx.appcompat.view.menu.MenuView; 87 import androidx.appcompat.widget.ActionBarContextView; 88 import androidx.appcompat.widget.AppCompatDrawableManager; 89 import androidx.appcompat.widget.ContentFrameLayout; 90 import androidx.appcompat.widget.DecorContentParent; 91 import androidx.appcompat.widget.FitWindowsViewGroup; 92 import androidx.appcompat.widget.TintTypedArray; 93 import androidx.appcompat.widget.Toolbar; 94 import androidx.appcompat.widget.VectorEnabledTintResources; 95 import androidx.appcompat.widget.ViewStubCompat; 96 import androidx.appcompat.widget.ViewUtils; 97 import androidx.core.app.NavUtils; 98 import androidx.core.view.LayoutInflaterCompat; 99 import androidx.core.view.OnApplyWindowInsetsListener; 100 import androidx.core.view.ViewCompat; 101 import androidx.core.view.ViewPropertyAnimatorCompat; 102 import androidx.core.view.ViewPropertyAnimatorListenerAdapter; 103 import androidx.core.view.WindowCompat; 104 import androidx.core.view.WindowInsetsCompat; 105 import androidx.core.widget.PopupWindowCompat; 106 107 import org.xmlpull.v1.XmlPullParser; 108 109 import java.util.List; 110 111 class AppCompatDelegateImpl extends AppCompatDelegate 112 implements MenuBuilder.Callback, LayoutInflater.Factory2 { 113 114 private static final boolean DEBUG = false; 115 private static final boolean IS_PRE_LOLLIPOP = Build.VERSION.SDK_INT < 21; 116 private static final String KEY_LOCAL_NIGHT_MODE = "appcompat:local_night_mode"; 117 118 private static final int[] sWindowBackgroundStyleable = {android.R.attr.windowBackground}; 119 120 private static boolean sInstalledExceptionHandler; 121 122 static final String EXCEPTION_HANDLER_MESSAGE_SUFFIX= ". If the resource you are" 123 + " trying to use is a vector resource, you may be referencing it in an unsupported" 124 + " way. See AppCompatDelegate.setCompatVectorFromResourcesEnabled() for more info."; 125 126 static { 127 if (IS_PRE_LOLLIPOP && !sInstalledExceptionHandler) { 128 final Thread.UncaughtExceptionHandler defHandler 129 = Thread.getDefaultUncaughtExceptionHandler(); 130 131 Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { 132 @Override 133 public void uncaughtException(Thread thread, final Throwable thowable) { 134 if (shouldWrapException(thowable)) { 135 // Now wrap the throwable, but append some extra information to the message 136 final Throwable wrapped = new Resources.NotFoundException( 137 thowable.getMessage() + EXCEPTION_HANDLER_MESSAGE_SUFFIX); 138 wrapped.initCause(thowable.getCause()); 139 wrapped.setStackTrace(thowable.getStackTrace()); 140 defHandler.uncaughtException(thread, wrapped); 141 } else { 142 defHandler.uncaughtException(thread, thowable); 143 } 144 } 145 146 private boolean shouldWrapException(Throwable throwable) { 147 if (throwable instanceof Resources.NotFoundException) { 148 final String message = throwable.getMessage(); 149 return message != null && (message.contains("drawable") 150 || message.contains("Drawable")); 151 } 152 return false; 153 } 154 }); 155 156 sInstalledExceptionHandler = true; 157 } 158 } 159 160 final Context mContext; 161 final Window mWindow; 162 final Window.Callback mOriginalWindowCallback; 163 final Window.Callback mAppCompatWindowCallback; 164 final AppCompatCallback mAppCompatCallback; 165 166 ActionBar mActionBar; 167 MenuInflater mMenuInflater; 168 169 private CharSequence mTitle; 170 171 private DecorContentParent mDecorContentParent; 172 private ActionMenuPresenterCallback mActionMenuPresenterCallback; 173 private PanelMenuPresenterCallback mPanelMenuPresenterCallback; 174 175 ActionMode mActionMode; 176 ActionBarContextView mActionModeView; 177 PopupWindow mActionModePopup; 178 Runnable mShowActionModePopup; 179 ViewPropertyAnimatorCompat mFadeAnim = null; 180 181 private boolean mHandleNativeActionModes = true; // defaults to true 182 183 // true if we have installed a window sub-decor layout. 184 private boolean mSubDecorInstalled; 185 private ViewGroup mSubDecor; 186 187 private TextView mTitleView; 188 private View mStatusGuard; 189 190 // Used to keep track of Progress Bar Window features 191 private boolean mFeatureProgress, mFeatureIndeterminateProgress; 192 193 // true if this activity has an action bar. 194 boolean mHasActionBar; 195 // true if this activity's action bar overlays other activity content. 196 boolean mOverlayActionBar; 197 // true if this any action modes should overlay the activity content 198 boolean mOverlayActionMode; 199 // true if this activity is floating (e.g. Dialog) 200 boolean mIsFloating; 201 // true if this activity has no title 202 boolean mWindowNoTitle; 203 204 // Used for emulating PanelFeatureState 205 private boolean mClosingActionMenu; 206 private PanelFeatureState[] mPanels; 207 private PanelFeatureState mPreparedPanel; 208 209 private boolean mLongPressBackDown; 210 211 private boolean mIsDestroyed; 212 213 @NightMode 214 private int mLocalNightMode = MODE_NIGHT_UNSPECIFIED; 215 private boolean mApplyDayNightCalled; 216 217 private AutoNightModeManager mAutoNightModeManager; 218 219 boolean mInvalidatePanelMenuPosted; 220 int mInvalidatePanelMenuFeatures; 221 private final Runnable mInvalidatePanelMenuRunnable = new Runnable() { 222 @Override 223 public void run() { 224 if ((mInvalidatePanelMenuFeatures & 1 << FEATURE_OPTIONS_PANEL) != 0) { 225 doInvalidatePanelMenu(FEATURE_OPTIONS_PANEL); 226 } 227 if ((mInvalidatePanelMenuFeatures & 1 << FEATURE_SUPPORT_ACTION_BAR) != 0) { 228 doInvalidatePanelMenu(FEATURE_SUPPORT_ACTION_BAR); 229 } 230 mInvalidatePanelMenuPosted = false; 231 mInvalidatePanelMenuFeatures = 0; 232 } 233 }; 234 235 private boolean mEnableDefaultActionBarUp; 236 237 private Rect mTempRect1; 238 private Rect mTempRect2; 239 240 private AppCompatViewInflater mAppCompatViewInflater; 241 242 AppCompatDelegateImpl(Context context, Window window, AppCompatCallback callback) { 243 mContext = context; 244 mWindow = window; 245 mAppCompatCallback = callback; 246 247 mOriginalWindowCallback = mWindow.getCallback(); 248 if (mOriginalWindowCallback instanceof AppCompatWindowCallback) { 249 throw new IllegalStateException( 250 "AppCompat has already installed itself into the Window"); 251 } 252 mAppCompatWindowCallback = new AppCompatWindowCallback(mOriginalWindowCallback); 253 // Now install the new callback 254 mWindow.setCallback(mAppCompatWindowCallback); 255 256 final TintTypedArray a = TintTypedArray.obtainStyledAttributes( 257 context, null, sWindowBackgroundStyleable); 258 final Drawable winBg = a.getDrawableIfKnown(0); 259 if (winBg != null) { 260 mWindow.setBackgroundDrawable(winBg); 261 } 262 a.recycle(); 263 } 264 265 @Override 266 public void onCreate(Bundle savedInstanceState) { 267 if (mOriginalWindowCallback instanceof Activity) { 268 if (NavUtils.getParentActivityName((Activity) mOriginalWindowCallback) != null) { 269 // Peek at the Action Bar and update it if it already exists 270 ActionBar ab = peekSupportActionBar(); 271 if (ab == null) { 272 mEnableDefaultActionBarUp = true; 273 } else { 274 ab.setDefaultDisplayHomeAsUpEnabled(true); 275 } 276 } 277 } 278 279 if (savedInstanceState != null && mLocalNightMode == MODE_NIGHT_UNSPECIFIED) { 280 // If we have a icicle and we haven't had a local night mode set yet, try and read 281 // it from the icicle 282 mLocalNightMode = savedInstanceState.getInt(KEY_LOCAL_NIGHT_MODE, 283 MODE_NIGHT_UNSPECIFIED); 284 } 285 } 286 287 @Override 288 public void onPostCreate(Bundle savedInstanceState) { 289 // Make sure that the sub decor is installed 290 ensureSubDecor(); 291 } 292 293 @Override 294 public ActionBar getSupportActionBar() { 295 // The Action Bar should be lazily created as hasActionBar 296 // could change after onCreate 297 initWindowDecorActionBar(); 298 return mActionBar; 299 } 300 301 final ActionBar peekSupportActionBar() { 302 return mActionBar; 303 } 304 305 final Window.Callback getWindowCallback() { 306 return mWindow.getCallback(); 307 } 308 309 private void initWindowDecorActionBar() { 310 ensureSubDecor(); 311 312 if (!mHasActionBar || mActionBar != null) { 313 return; 314 } 315 316 if (mOriginalWindowCallback instanceof Activity) { 317 mActionBar = new WindowDecorActionBar((Activity) mOriginalWindowCallback, 318 mOverlayActionBar); 319 } else if (mOriginalWindowCallback instanceof Dialog) { 320 mActionBar = new WindowDecorActionBar((Dialog) mOriginalWindowCallback); 321 } 322 if (mActionBar != null) { 323 mActionBar.setDefaultDisplayHomeAsUpEnabled(mEnableDefaultActionBarUp); 324 } 325 } 326 327 @Override 328 public void setSupportActionBar(Toolbar toolbar) { 329 if (!(mOriginalWindowCallback instanceof Activity)) { 330 // Only Activities support custom Action Bars 331 return; 332 } 333 334 final ActionBar ab = getSupportActionBar(); 335 if (ab instanceof WindowDecorActionBar) { 336 throw new IllegalStateException("This Activity already has an action bar supplied " + 337 "by the window decor. Do not request Window.FEATURE_SUPPORT_ACTION_BAR and set " + 338 "windowActionBar to false in your theme to use a Toolbar instead."); 339 } 340 341 // If we reach here then we're setting a new action bar 342 // First clear out the MenuInflater to make sure that it is valid for the new Action Bar 343 mMenuInflater = null; 344 345 // If we have an action bar currently, destroy it 346 if (ab != null) { 347 ab.onDestroy(); 348 } 349 350 if (toolbar != null) { 351 final ToolbarActionBar tbab = new ToolbarActionBar(toolbar, 352 ((Activity) mOriginalWindowCallback).getTitle(), mAppCompatWindowCallback); 353 mActionBar = tbab; 354 mWindow.setCallback(tbab.getWrappedWindowCallback()); 355 } else { 356 mActionBar = null; 357 // Re-set the original window callback since we may have already set a Toolbar wrapper 358 mWindow.setCallback(mAppCompatWindowCallback); 359 } 360 361 invalidateOptionsMenu(); 362 } 363 364 final Context getActionBarThemedContext() { 365 Context context = null; 366 367 // If we have an action bar, let it return a themed context 368 ActionBar ab = getSupportActionBar(); 369 if (ab != null) { 370 context = ab.getThemedContext(); 371 } 372 373 if (context == null) { 374 context = mContext; 375 } 376 return context; 377 } 378 379 @Override 380 public MenuInflater getMenuInflater() { 381 // Make sure that action views can get an appropriate theme. 382 if (mMenuInflater == null) { 383 initWindowDecorActionBar(); 384 mMenuInflater = new SupportMenuInflater( 385 mActionBar != null ? mActionBar.getThemedContext() : mContext); 386 } 387 return mMenuInflater; 388 } 389 390 @SuppressWarnings("TypeParameterUnusedInFormals") 391 @Nullable 392 @Override 393 public <T extends View> T findViewById(@IdRes int id) { 394 ensureSubDecor(); 395 return (T) mWindow.findViewById(id); 396 } 397 398 @Override 399 public void onConfigurationChanged(Configuration newConfig) { 400 // If this is called before sub-decor is installed, ActionBar will not 401 // be properly initialized. 402 if (mHasActionBar && mSubDecorInstalled) { 403 // Note: The action bar will need to access 404 // view changes from superclass. 405 ActionBar ab = getSupportActionBar(); 406 if (ab != null) { 407 ab.onConfigurationChanged(newConfig); 408 } 409 } 410 411 // Make sure that the DrawableManager knows about the new config 412 AppCompatDrawableManager.get().onConfigurationChanged(mContext); 413 414 // Re-apply Day/Night to the new configuration 415 applyDayNight(); 416 } 417 418 @Override 419 public void onStart() { 420 // This will apply day/night if the time has changed, it will also call through to 421 // setupAutoNightModeIfNeeded() 422 applyDayNight(); 423 } 424 425 @Override 426 public void onStop() { 427 ActionBar ab = getSupportActionBar(); 428 if (ab != null) { 429 ab.setShowHideAnimationEnabled(false); 430 } 431 432 // Make sure we clean up any receivers setup for AUTO mode 433 if (mAutoNightModeManager != null) { 434 mAutoNightModeManager.cleanup(); 435 } 436 } 437 438 @Override 439 public void onPostResume() { 440 ActionBar ab = getSupportActionBar(); 441 if (ab != null) { 442 ab.setShowHideAnimationEnabled(true); 443 } 444 } 445 446 @Override 447 public void setContentView(View v) { 448 ensureSubDecor(); 449 ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content); 450 contentParent.removeAllViews(); 451 contentParent.addView(v); 452 mOriginalWindowCallback.onContentChanged(); 453 } 454 455 @Override 456 public void setContentView(int resId) { 457 ensureSubDecor(); 458 ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content); 459 contentParent.removeAllViews(); 460 LayoutInflater.from(mContext).inflate(resId, contentParent); 461 mOriginalWindowCallback.onContentChanged(); 462 } 463 464 @Override 465 public void setContentView(View v, ViewGroup.LayoutParams lp) { 466 ensureSubDecor(); 467 ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content); 468 contentParent.removeAllViews(); 469 contentParent.addView(v, lp); 470 mOriginalWindowCallback.onContentChanged(); 471 } 472 473 @Override 474 public void addContentView(View v, ViewGroup.LayoutParams lp) { 475 ensureSubDecor(); 476 ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content); 477 contentParent.addView(v, lp); 478 mOriginalWindowCallback.onContentChanged(); 479 } 480 481 @Override 482 public void onSaveInstanceState(Bundle outState) { 483 if (mLocalNightMode != MODE_NIGHT_UNSPECIFIED) { 484 // If we have a local night mode set, save it 485 outState.putInt(KEY_LOCAL_NIGHT_MODE, mLocalNightMode); 486 } 487 } 488 489 @Override 490 public void onDestroy() { 491 if (mInvalidatePanelMenuPosted) { 492 mWindow.getDecorView().removeCallbacks(mInvalidatePanelMenuRunnable); 493 } 494 495 mIsDestroyed = true; 496 497 if (mActionBar != null) { 498 mActionBar.onDestroy(); 499 } 500 501 // Make sure we clean up any receivers setup for AUTO mode 502 if (mAutoNightModeManager != null) { 503 mAutoNightModeManager.cleanup(); 504 } 505 } 506 507 private void ensureSubDecor() { 508 if (!mSubDecorInstalled) { 509 mSubDecor = createSubDecor(); 510 511 // If a title was set before we installed the decor, propagate it now 512 CharSequence title = getTitle(); 513 if (!TextUtils.isEmpty(title)) { 514 if (mDecorContentParent != null) { 515 mDecorContentParent.setWindowTitle(title); 516 } else if (peekSupportActionBar() != null) { 517 peekSupportActionBar().setWindowTitle(title); 518 } else if (mTitleView != null) { 519 mTitleView.setText(title); 520 } 521 } 522 523 applyFixedSizeWindow(); 524 525 onSubDecorInstalled(mSubDecor); 526 527 mSubDecorInstalled = true; 528 529 // Invalidate if the panel menu hasn't been created before this. 530 // Panel menu invalidation is deferred avoiding application onCreateOptionsMenu 531 // being called in the middle of onCreate or similar. 532 // A pending invalidation will typically be resolved before the posted message 533 // would run normally in order to satisfy instance state restoration. 534 PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false); 535 if (!mIsDestroyed && (st == null || st.menu == null)) { 536 invalidatePanelMenu(FEATURE_SUPPORT_ACTION_BAR); 537 } 538 } 539 } 540 541 private ViewGroup createSubDecor() { 542 TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme); 543 544 if (!a.hasValue(R.styleable.AppCompatTheme_windowActionBar)) { 545 a.recycle(); 546 throw new IllegalStateException( 547 "You need to use a Theme.AppCompat theme (or descendant) with this activity."); 548 } 549 550 if (a.getBoolean(R.styleable.AppCompatTheme_windowNoTitle, false)) { 551 requestWindowFeature(Window.FEATURE_NO_TITLE); 552 } else if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBar, false)) { 553 // Don't allow an action bar if there is no title. 554 requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR); 555 } 556 if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBarOverlay, false)) { 557 requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR_OVERLAY); 558 } 559 if (a.getBoolean(R.styleable.AppCompatTheme_windowActionModeOverlay, false)) { 560 requestWindowFeature(FEATURE_ACTION_MODE_OVERLAY); 561 } 562 mIsFloating = a.getBoolean(R.styleable.AppCompatTheme_android_windowIsFloating, false); 563 a.recycle(); 564 565 // Now let's make sure that the Window has installed its decor by retrieving it 566 mWindow.getDecorView(); 567 568 final LayoutInflater inflater = LayoutInflater.from(mContext); 569 ViewGroup subDecor = null; 570 571 572 if (!mWindowNoTitle) { 573 if (mIsFloating) { 574 // If we're floating, inflate the dialog title decor 575 subDecor = (ViewGroup) inflater.inflate( 576 R.layout.abc_dialog_title_material, null); 577 578 // Floating windows can never have an action bar, reset the flags 579 mHasActionBar = mOverlayActionBar = false; 580 } else if (mHasActionBar) { 581 /** 582 * This needs some explanation. As we can not use the android:theme attribute 583 * pre-L, we emulate it by manually creating a LayoutInflater using a 584 * ContextThemeWrapper pointing to actionBarTheme. 585 */ 586 TypedValue outValue = new TypedValue(); 587 mContext.getTheme().resolveAttribute(R.attr.actionBarTheme, outValue, true); 588 589 Context themedContext; 590 if (outValue.resourceId != 0) { 591 themedContext = new ContextThemeWrapper(mContext, outValue.resourceId); 592 } else { 593 themedContext = mContext; 594 } 595 596 // Now inflate the view using the themed context and set it as the content view 597 subDecor = (ViewGroup) LayoutInflater.from(themedContext) 598 .inflate(R.layout.abc_screen_toolbar, null); 599 600 mDecorContentParent = (DecorContentParent) subDecor 601 .findViewById(R.id.decor_content_parent); 602 mDecorContentParent.setWindowCallback(getWindowCallback()); 603 604 /** 605 * Propagate features to DecorContentParent 606 */ 607 if (mOverlayActionBar) { 608 mDecorContentParent.initFeature(FEATURE_SUPPORT_ACTION_BAR_OVERLAY); 609 } 610 if (mFeatureProgress) { 611 mDecorContentParent.initFeature(Window.FEATURE_PROGRESS); 612 } 613 if (mFeatureIndeterminateProgress) { 614 mDecorContentParent.initFeature(Window.FEATURE_INDETERMINATE_PROGRESS); 615 } 616 } 617 } else { 618 if (mOverlayActionMode) { 619 subDecor = (ViewGroup) inflater.inflate( 620 R.layout.abc_screen_simple_overlay_action_mode, null); 621 } else { 622 subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null); 623 } 624 625 if (Build.VERSION.SDK_INT >= 21) { 626 // If we're running on L or above, we can rely on ViewCompat's 627 // setOnApplyWindowInsetsListener 628 ViewCompat.setOnApplyWindowInsetsListener(subDecor, 629 new OnApplyWindowInsetsListener() { 630 @Override 631 public WindowInsetsCompat onApplyWindowInsets(View v, 632 WindowInsetsCompat insets) { 633 final int top = insets.getSystemWindowInsetTop(); 634 final int newTop = updateStatusGuard(top); 635 636 if (top != newTop) { 637 insets = insets.replaceSystemWindowInsets( 638 insets.getSystemWindowInsetLeft(), 639 newTop, 640 insets.getSystemWindowInsetRight(), 641 insets.getSystemWindowInsetBottom()); 642 } 643 644 // Now apply the insets on our view 645 return ViewCompat.onApplyWindowInsets(v, insets); 646 } 647 }); 648 } else { 649 // Else, we need to use our own FitWindowsViewGroup handling 650 ((FitWindowsViewGroup) subDecor).setOnFitSystemWindowsListener( 651 new FitWindowsViewGroup.OnFitSystemWindowsListener() { 652 @Override 653 public void onFitSystemWindows(Rect insets) { 654 insets.top = updateStatusGuard(insets.top); 655 } 656 }); 657 } 658 } 659 660 if (subDecor == null) { 661 throw new IllegalArgumentException( 662 "AppCompat does not support the current theme features: { " 663 + "windowActionBar: " + mHasActionBar 664 + ", windowActionBarOverlay: "+ mOverlayActionBar 665 + ", android:windowIsFloating: " + mIsFloating 666 + ", windowActionModeOverlay: " + mOverlayActionMode 667 + ", windowNoTitle: " + mWindowNoTitle 668 + " }"); 669 } 670 671 if (mDecorContentParent == null) { 672 mTitleView = (TextView) subDecor.findViewById(R.id.title); 673 } 674 675 // Make the decor optionally fit system windows, like the window's decor 676 ViewUtils.makeOptionalFitsSystemWindows(subDecor); 677 678 final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById( 679 R.id.action_bar_activity_content); 680 681 final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content); 682 if (windowContentView != null) { 683 // There might be Views already added to the Window's content view so we need to 684 // migrate them to our content view 685 while (windowContentView.getChildCount() > 0) { 686 final View child = windowContentView.getChildAt(0); 687 windowContentView.removeViewAt(0); 688 contentView.addView(child); 689 } 690 691 // Change our content FrameLayout to use the android.R.id.content id. 692 // Useful for fragments. 693 windowContentView.setId(View.NO_ID); 694 contentView.setId(android.R.id.content); 695 696 // The decorContent may have a foreground drawable set (windowContentOverlay). 697 // Remove this as we handle it ourselves 698 if (windowContentView instanceof FrameLayout) { 699 ((FrameLayout) windowContentView).setForeground(null); 700 } 701 } 702 703 // Now set the Window's content view with the decor 704 mWindow.setContentView(subDecor); 705 706 contentView.setAttachListener(new ContentFrameLayout.OnAttachListener() { 707 @Override 708 public void onAttachedFromWindow() {} 709 710 @Override 711 public void onDetachedFromWindow() { 712 dismissPopups(); 713 } 714 }); 715 716 return subDecor; 717 } 718 719 void onSubDecorInstalled(ViewGroup subDecor) {} 720 721 private void applyFixedSizeWindow() { 722 ContentFrameLayout cfl = (ContentFrameLayout) mSubDecor.findViewById(android.R.id.content); 723 724 // This is a bit weird. In the framework, the window sizing attributes control 725 // the decor view's size, meaning that any padding is inset for the min/max widths below. 726 // We don't control measurement at that level, so we need to workaround it by making sure 727 // that the decor view's padding is taken into account. 728 final View windowDecor = mWindow.getDecorView(); 729 cfl.setDecorPadding(windowDecor.getPaddingLeft(), 730 windowDecor.getPaddingTop(), windowDecor.getPaddingRight(), 731 windowDecor.getPaddingBottom()); 732 733 TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme); 734 a.getValue(R.styleable.AppCompatTheme_windowMinWidthMajor, cfl.getMinWidthMajor()); 735 a.getValue(R.styleable.AppCompatTheme_windowMinWidthMinor, cfl.getMinWidthMinor()); 736 737 if (a.hasValue(R.styleable.AppCompatTheme_windowFixedWidthMajor)) { 738 a.getValue(R.styleable.AppCompatTheme_windowFixedWidthMajor, 739 cfl.getFixedWidthMajor()); 740 } 741 if (a.hasValue(R.styleable.AppCompatTheme_windowFixedWidthMinor)) { 742 a.getValue(R.styleable.AppCompatTheme_windowFixedWidthMinor, 743 cfl.getFixedWidthMinor()); 744 } 745 if (a.hasValue(R.styleable.AppCompatTheme_windowFixedHeightMajor)) { 746 a.getValue(R.styleable.AppCompatTheme_windowFixedHeightMajor, 747 cfl.getFixedHeightMajor()); 748 } 749 if (a.hasValue(R.styleable.AppCompatTheme_windowFixedHeightMinor)) { 750 a.getValue(R.styleable.AppCompatTheme_windowFixedHeightMinor, 751 cfl.getFixedHeightMinor()); 752 } 753 a.recycle(); 754 755 cfl.requestLayout(); 756 } 757 758 @Override 759 public boolean requestWindowFeature(int featureId) { 760 featureId = sanitizeWindowFeatureId(featureId); 761 762 if (mWindowNoTitle && featureId == FEATURE_SUPPORT_ACTION_BAR) { 763 return false; // Ignore. No title dominates. 764 } 765 if (mHasActionBar && featureId == Window.FEATURE_NO_TITLE) { 766 // Remove the action bar feature if we have no title. No title dominates. 767 mHasActionBar = false; 768 } 769 770 switch (featureId) { 771 case FEATURE_SUPPORT_ACTION_BAR: 772 throwFeatureRequestIfSubDecorInstalled(); 773 mHasActionBar = true; 774 return true; 775 case FEATURE_SUPPORT_ACTION_BAR_OVERLAY: 776 throwFeatureRequestIfSubDecorInstalled(); 777 mOverlayActionBar = true; 778 return true; 779 case FEATURE_ACTION_MODE_OVERLAY: 780 throwFeatureRequestIfSubDecorInstalled(); 781 mOverlayActionMode = true; 782 return true; 783 case Window.FEATURE_PROGRESS: 784 throwFeatureRequestIfSubDecorInstalled(); 785 mFeatureProgress = true; 786 return true; 787 case Window.FEATURE_INDETERMINATE_PROGRESS: 788 throwFeatureRequestIfSubDecorInstalled(); 789 mFeatureIndeterminateProgress = true; 790 return true; 791 case Window.FEATURE_NO_TITLE: 792 throwFeatureRequestIfSubDecorInstalled(); 793 mWindowNoTitle = true; 794 return true; 795 } 796 797 return mWindow.requestFeature(featureId); 798 } 799 800 @Override 801 public boolean hasWindowFeature(int featureId) { 802 boolean result = false; 803 switch (sanitizeWindowFeatureId(featureId)) { 804 case FEATURE_SUPPORT_ACTION_BAR: 805 result = mHasActionBar; 806 break; 807 case FEATURE_SUPPORT_ACTION_BAR_OVERLAY: 808 result = mOverlayActionBar; 809 break; 810 case FEATURE_ACTION_MODE_OVERLAY: 811 result = mOverlayActionMode; 812 break; 813 case Window.FEATURE_PROGRESS: 814 result = mFeatureProgress; 815 break; 816 case Window.FEATURE_INDETERMINATE_PROGRESS: 817 result = mFeatureIndeterminateProgress; 818 break; 819 case Window.FEATURE_NO_TITLE: 820 result = mWindowNoTitle; 821 break; 822 } 823 return result || mWindow.hasFeature(featureId); 824 } 825 826 @Override 827 public final void setTitle(CharSequence title) { 828 mTitle = title; 829 830 if (mDecorContentParent != null) { 831 mDecorContentParent.setWindowTitle(title); 832 } else if (peekSupportActionBar() != null) { 833 peekSupportActionBar().setWindowTitle(title); 834 } else if (mTitleView != null) { 835 mTitleView.setText(title); 836 } 837 } 838 839 final CharSequence getTitle() { 840 // If the original window callback is an Activity, we'll use its title 841 if (mOriginalWindowCallback instanceof Activity) { 842 return ((Activity) mOriginalWindowCallback).getTitle(); 843 } 844 // Else, we'll return the title we have recorded ourselves 845 return mTitle; 846 } 847 848 void onPanelClosed(final int featureId) { 849 if (featureId == FEATURE_SUPPORT_ACTION_BAR) { 850 ActionBar ab = getSupportActionBar(); 851 if (ab != null) { 852 ab.dispatchMenuVisibilityChanged(false); 853 } 854 } else if (featureId == FEATURE_OPTIONS_PANEL) { 855 // Make sure that the options panel is closed. This is mainly used when we're using a 856 // ToolbarActionBar 857 PanelFeatureState st = getPanelState(featureId, true); 858 if (st.isOpen) { 859 closePanel(st, false); 860 } 861 } 862 } 863 864 void onMenuOpened(final int featureId) { 865 if (featureId == FEATURE_SUPPORT_ACTION_BAR) { 866 ActionBar ab = getSupportActionBar(); 867 if (ab != null) { 868 ab.dispatchMenuVisibilityChanged(true); 869 } 870 } 871 } 872 873 @Override 874 public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) { 875 final Window.Callback cb = getWindowCallback(); 876 if (cb != null && !mIsDestroyed) { 877 final PanelFeatureState panel = findMenuPanel(menu.getRootMenu()); 878 if (panel != null) { 879 return cb.onMenuItemSelected(panel.featureId, item); 880 } 881 } 882 return false; 883 } 884 885 @Override 886 public void onMenuModeChange(MenuBuilder menu) { 887 reopenMenu(menu, true); 888 } 889 890 @Override 891 public ActionMode startSupportActionMode(@NonNull final ActionMode.Callback callback) { 892 if (callback == null) { 893 throw new IllegalArgumentException("ActionMode callback can not be null."); 894 } 895 896 if (mActionMode != null) { 897 mActionMode.finish(); 898 } 899 900 final ActionMode.Callback wrappedCallback = new ActionModeCallbackWrapperV9(callback); 901 902 ActionBar ab = getSupportActionBar(); 903 if (ab != null) { 904 mActionMode = ab.startActionMode(wrappedCallback); 905 if (mActionMode != null && mAppCompatCallback != null) { 906 mAppCompatCallback.onSupportActionModeStarted(mActionMode); 907 } 908 } 909 910 if (mActionMode == null) { 911 // If the action bar didn't provide an action mode, start the emulated window one 912 mActionMode = startSupportActionModeFromWindow(wrappedCallback); 913 } 914 915 return mActionMode; 916 } 917 918 @Override 919 public void invalidateOptionsMenu() { 920 final ActionBar ab = getSupportActionBar(); 921 if (ab != null && ab.invalidateOptionsMenu()) return; 922 923 invalidatePanelMenu(FEATURE_OPTIONS_PANEL); 924 } 925 926 ActionMode startSupportActionModeFromWindow(@NonNull ActionMode.Callback callback) { 927 endOnGoingFadeAnimation(); 928 if (mActionMode != null) { 929 mActionMode.finish(); 930 } 931 932 if (!(callback instanceof ActionModeCallbackWrapperV9)) { 933 // If the callback hasn't been wrapped yet, wrap it 934 callback = new ActionModeCallbackWrapperV9(callback); 935 } 936 937 ActionMode mode = null; 938 if (mAppCompatCallback != null && !mIsDestroyed) { 939 try { 940 mode = mAppCompatCallback.onWindowStartingSupportActionMode(callback); 941 } catch (AbstractMethodError ame) { 942 // Older apps might not implement this callback method. 943 } 944 } 945 946 if (mode != null) { 947 mActionMode = mode; 948 } else { 949 if (mActionModeView == null) { 950 if (mIsFloating) { 951 // Use the action bar theme. 952 final TypedValue outValue = new TypedValue(); 953 final Resources.Theme baseTheme = mContext.getTheme(); 954 baseTheme.resolveAttribute(R.attr.actionBarTheme, outValue, true); 955 956 final Context actionBarContext; 957 if (outValue.resourceId != 0) { 958 final Resources.Theme actionBarTheme = mContext.getResources().newTheme(); 959 actionBarTheme.setTo(baseTheme); 960 actionBarTheme.applyStyle(outValue.resourceId, true); 961 962 actionBarContext = new ContextThemeWrapper(mContext, 0); 963 actionBarContext.getTheme().setTo(actionBarTheme); 964 } else { 965 actionBarContext = mContext; 966 } 967 968 mActionModeView = new ActionBarContextView(actionBarContext); 969 mActionModePopup = new PopupWindow(actionBarContext, null, 970 R.attr.actionModePopupWindowStyle); 971 PopupWindowCompat.setWindowLayoutType(mActionModePopup, 972 WindowManager.LayoutParams.TYPE_APPLICATION); 973 mActionModePopup.setContentView(mActionModeView); 974 mActionModePopup.setWidth(ViewGroup.LayoutParams.MATCH_PARENT); 975 976 actionBarContext.getTheme().resolveAttribute( 977 R.attr.actionBarSize, outValue, true); 978 final int height = TypedValue.complexToDimensionPixelSize(outValue.data, 979 actionBarContext.getResources().getDisplayMetrics()); 980 mActionModeView.setContentHeight(height); 981 mActionModePopup.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT); 982 mShowActionModePopup = new Runnable() { 983 @Override 984 public void run() { 985 mActionModePopup.showAtLocation( 986 mActionModeView, 987 Gravity.TOP | Gravity.FILL_HORIZONTAL, 0, 0); 988 endOnGoingFadeAnimation(); 989 990 if (shouldAnimateActionModeView()) { 991 mActionModeView.setAlpha(0f); 992 mFadeAnim = ViewCompat.animate(mActionModeView).alpha(1f); 993 mFadeAnim.setListener(new ViewPropertyAnimatorListenerAdapter() { 994 @Override 995 public void onAnimationStart(View view) { 996 mActionModeView.setVisibility(View.VISIBLE); 997 } 998 999 @Override 1000 public void onAnimationEnd(View view) { 1001 mActionModeView.setAlpha(1f); 1002 mFadeAnim.setListener(null); 1003 mFadeAnim = null; 1004 } 1005 }); 1006 } else { 1007 mActionModeView.setAlpha(1f); 1008 mActionModeView.setVisibility(View.VISIBLE); 1009 } 1010 } 1011 }; 1012 } else { 1013 ViewStubCompat stub = (ViewStubCompat) mSubDecor 1014 .findViewById(R.id.action_mode_bar_stub); 1015 if (stub != null) { 1016 // Set the layout inflater so that it is inflated with the action bar's context 1017 stub.setLayoutInflater(LayoutInflater.from(getActionBarThemedContext())); 1018 mActionModeView = (ActionBarContextView) stub.inflate(); 1019 } 1020 } 1021 } 1022 1023 if (mActionModeView != null) { 1024 endOnGoingFadeAnimation(); 1025 mActionModeView.killMode(); 1026 mode = new StandaloneActionMode(mActionModeView.getContext(), mActionModeView, 1027 callback, mActionModePopup == null); 1028 if (callback.onCreateActionMode(mode, mode.getMenu())) { 1029 mode.invalidate(); 1030 mActionModeView.initForMode(mode); 1031 mActionMode = mode; 1032 1033 if (shouldAnimateActionModeView()) { 1034 mActionModeView.setAlpha(0f); 1035 mFadeAnim = ViewCompat.animate(mActionModeView).alpha(1f); 1036 mFadeAnim.setListener(new ViewPropertyAnimatorListenerAdapter() { 1037 @Override 1038 public void onAnimationStart(View view) { 1039 mActionModeView.setVisibility(View.VISIBLE); 1040 mActionModeView.sendAccessibilityEvent( 1041 AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); 1042 if (mActionModeView.getParent() instanceof View) { 1043 ViewCompat.requestApplyInsets((View) mActionModeView.getParent()); 1044 } 1045 } 1046 1047 @Override 1048 public void onAnimationEnd(View view) { 1049 mActionModeView.setAlpha(1f); 1050 mFadeAnim.setListener(null); 1051 mFadeAnim = null; 1052 } 1053 }); 1054 } else { 1055 mActionModeView.setAlpha(1f); 1056 mActionModeView.setVisibility(View.VISIBLE); 1057 mActionModeView.sendAccessibilityEvent( 1058 AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); 1059 if (mActionModeView.getParent() instanceof View) { 1060 ViewCompat.requestApplyInsets((View) mActionModeView.getParent()); 1061 } 1062 } 1063 1064 if (mActionModePopup != null) { 1065 mWindow.getDecorView().post(mShowActionModePopup); 1066 } 1067 } else { 1068 mActionMode = null; 1069 } 1070 } 1071 } 1072 if (mActionMode != null && mAppCompatCallback != null) { 1073 mAppCompatCallback.onSupportActionModeStarted(mActionMode); 1074 } 1075 return mActionMode; 1076 } 1077 1078 final boolean shouldAnimateActionModeView() { 1079 // We only to animate the action mode in if the sub decor has already been laid out. 1080 // If it hasn't been laid out, it hasn't been drawn to screen yet. 1081 return mSubDecorInstalled && mSubDecor != null && ViewCompat.isLaidOut(mSubDecor); 1082 } 1083 1084 @Override 1085 public void setHandleNativeActionModesEnabled(boolean enabled) { 1086 mHandleNativeActionModes = enabled; 1087 } 1088 1089 @Override 1090 public boolean isHandleNativeActionModesEnabled() { 1091 return mHandleNativeActionModes; 1092 } 1093 1094 void endOnGoingFadeAnimation() { 1095 if (mFadeAnim != null) { 1096 mFadeAnim.cancel(); 1097 } 1098 } 1099 1100 boolean onBackPressed() { 1101 // Back cancels action modes first. 1102 if (mActionMode != null) { 1103 mActionMode.finish(); 1104 return true; 1105 } 1106 1107 // Next collapse any expanded action views. 1108 ActionBar ab = getSupportActionBar(); 1109 if (ab != null && ab.collapseActionView()) { 1110 return true; 1111 } 1112 1113 // Let the call through... 1114 return false; 1115 } 1116 1117 boolean onKeyShortcut(int keyCode, KeyEvent ev) { 1118 // Let the Action Bar have a chance at handling the shortcut 1119 ActionBar ab = getSupportActionBar(); 1120 if (ab != null && ab.onKeyShortcut(keyCode, ev)) { 1121 return true; 1122 } 1123 1124 // If the panel is already prepared, then perform the shortcut using it. 1125 boolean handled; 1126 if (mPreparedPanel != null) { 1127 handled = performPanelShortcut(mPreparedPanel, ev.getKeyCode(), ev, 1128 Menu.FLAG_PERFORM_NO_CLOSE); 1129 if (handled) { 1130 if (mPreparedPanel != null) { 1131 mPreparedPanel.isHandled = true; 1132 } 1133 return true; 1134 } 1135 } 1136 1137 // If the panel is not prepared, then we may be trying to handle a shortcut key 1138 // combination such as Control+C. Temporarily prepare the panel then mark it 1139 // unprepared again when finished to ensure that the panel will again be prepared 1140 // the next time it is shown for real. 1141 if (mPreparedPanel == null) { 1142 PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, true); 1143 preparePanel(st, ev); 1144 handled = performPanelShortcut(st, ev.getKeyCode(), ev, Menu.FLAG_PERFORM_NO_CLOSE); 1145 st.isPrepared = false; 1146 if (handled) { 1147 return true; 1148 } 1149 } 1150 return false; 1151 } 1152 1153 boolean dispatchKeyEvent(KeyEvent event) { 1154 if (event.getKeyCode() == KeyEvent.KEYCODE_MENU) { 1155 // If this is a MENU event, let the Activity have a go. 1156 if (mOriginalWindowCallback.dispatchKeyEvent(event)) { 1157 return true; 1158 } 1159 } 1160 1161 final int keyCode = event.getKeyCode(); 1162 final int action = event.getAction(); 1163 final boolean isDown = action == KeyEvent.ACTION_DOWN; 1164 1165 return isDown ? onKeyDown(keyCode, event) : onKeyUp(keyCode, event); 1166 } 1167 1168 boolean onKeyUp(int keyCode, KeyEvent event) { 1169 switch (keyCode) { 1170 case KeyEvent.KEYCODE_MENU: 1171 onKeyUpPanel(Window.FEATURE_OPTIONS_PANEL, event); 1172 return true; 1173 case KeyEvent.KEYCODE_BACK: 1174 final boolean wasLongPressBackDown = mLongPressBackDown; 1175 mLongPressBackDown = false; 1176 1177 PanelFeatureState st = getPanelState(Window.FEATURE_OPTIONS_PANEL, false); 1178 if (st != null && st.isOpen) { 1179 if (!wasLongPressBackDown) { 1180 // Certain devices allow opening the options menu via a long press of the 1181 // back button. We should only close the open options menu if it wasn't 1182 // opened via a long press gesture. 1183 closePanel(st, true); 1184 } 1185 return true; 1186 } 1187 if (onBackPressed()) { 1188 return true; 1189 } 1190 break; 1191 } 1192 return false; 1193 } 1194 1195 boolean onKeyDown(int keyCode, KeyEvent event) { 1196 switch (keyCode) { 1197 case KeyEvent.KEYCODE_MENU: 1198 onKeyDownPanel(Window.FEATURE_OPTIONS_PANEL, event); 1199 // We need to return true here and not let it bubble up to the Window. 1200 // For empty menus, PhoneWindow's KEYCODE_BACK handling will steals all events, 1201 // not allowing the Activity to call onBackPressed(). 1202 return true; 1203 case KeyEvent.KEYCODE_BACK: 1204 // Certain devices allow opening the options menu via a long press of the back 1205 // button. We keep a record of whether the last event is from a long press. 1206 mLongPressBackDown = (event.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0; 1207 break; 1208 } 1209 return false; 1210 } 1211 1212 @Override 1213 public View createView(View parent, final String name, @NonNull Context context, 1214 @NonNull AttributeSet attrs) { 1215 if (mAppCompatViewInflater == null) { 1216 TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme); 1217 String viewInflaterClassName = 1218 a.getString(R.styleable.AppCompatTheme_viewInflaterClass); 1219 if ((viewInflaterClassName == null) 1220 || AppCompatViewInflater.class.getName().equals(viewInflaterClassName)) { 1221 // Either default class name or set explicitly to null. In both cases 1222 // create the base inflater (no reflection) 1223 mAppCompatViewInflater = new AppCompatViewInflater(); 1224 } else { 1225 try { 1226 Class viewInflaterClass = Class.forName(viewInflaterClassName); 1227 mAppCompatViewInflater = 1228 (AppCompatViewInflater) viewInflaterClass.getDeclaredConstructor() 1229 .newInstance(); 1230 } catch (Throwable t) { 1231 Log.i(TAG, "Failed to instantiate custom view inflater " 1232 + viewInflaterClassName + ". Falling back to default.", t); 1233 mAppCompatViewInflater = new AppCompatViewInflater(); 1234 } 1235 } 1236 } 1237 1238 boolean inheritContext = false; 1239 if (IS_PRE_LOLLIPOP) { 1240 inheritContext = (attrs instanceof XmlPullParser) 1241 // If we have a XmlPullParser, we can detect where we are in the layout 1242 ? ((XmlPullParser) attrs).getDepth() > 1 1243 // Otherwise we have to use the old heuristic 1244 : shouldInheritContext((ViewParent) parent); 1245 } 1246 1247 return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext, 1248 IS_PRE_LOLLIPOP, /* Only read android:theme pre-L (L+ handles this anyway) */ 1249 true, /* Read read app:theme as a fallback at all times for legacy reasons */ 1250 VectorEnabledTintResources.shouldBeUsed() /* Only tint wrap the context if enabled */ 1251 ); 1252 } 1253 1254 private boolean shouldInheritContext(ViewParent parent) { 1255 if (parent == null) { 1256 // The initial parent is null so just return false 1257 return false; 1258 } 1259 final View windowDecor = mWindow.getDecorView(); 1260 while (true) { 1261 if (parent == null) { 1262 // Bingo. We've hit a view which has a null parent before being terminated from 1263 // the loop. This is (most probably) because it's the root view in an inflation 1264 // call, therefore we should inherit. This works as the inflated layout is only 1265 // added to the hierarchy at the end of the inflate() call. 1266 return true; 1267 } else if (parent == windowDecor || !(parent instanceof View) 1268 || ViewCompat.isAttachedToWindow((View) parent)) { 1269 // We have either hit the window's decor view, a parent which isn't a View 1270 // (i.e. ViewRootImpl), or an attached view, so we know that the original parent 1271 // is currently added to the view hierarchy. This means that it has not be 1272 // inflated in the current inflate() call and we should not inherit the context. 1273 return false; 1274 } 1275 parent = parent.getParent(); 1276 } 1277 } 1278 1279 @Override 1280 public void installViewFactory() { 1281 LayoutInflater layoutInflater = LayoutInflater.from(mContext); 1282 if (layoutInflater.getFactory() == null) { 1283 LayoutInflaterCompat.setFactory2(layoutInflater, this); 1284 } else { 1285 if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImpl)) { 1286 Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed" 1287 + " so we can not install AppCompat's"); 1288 } 1289 } 1290 } 1291 1292 /** 1293 * From {@link LayoutInflater.Factory2}. 1294 */ 1295 @Override 1296 public final View onCreateView(View parent, String name, Context context, AttributeSet attrs) { 1297 return createView(parent, name, context, attrs); 1298 } 1299 1300 /** 1301 * From {@link LayoutInflater.Factory2}. 1302 */ 1303 @Override 1304 public View onCreateView(String name, Context context, AttributeSet attrs) { 1305 return onCreateView(null, name, context, attrs); 1306 } 1307 1308 private void openPanel(final PanelFeatureState st, KeyEvent event) { 1309 // Already open, return 1310 if (st.isOpen || mIsDestroyed) { 1311 return; 1312 } 1313 1314 // Don't open an options panel on xlarge devices. 1315 // (The app should be using an action bar for menu items.) 1316 if (st.featureId == FEATURE_OPTIONS_PANEL) { 1317 Configuration config = mContext.getResources().getConfiguration(); 1318 boolean isXLarge = (config.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) 1319 == Configuration.SCREENLAYOUT_SIZE_XLARGE; 1320 if (isXLarge) { 1321 return; 1322 } 1323 } 1324 1325 Window.Callback cb = getWindowCallback(); 1326 if ((cb != null) && (!cb.onMenuOpened(st.featureId, st.menu))) { 1327 // Callback doesn't want the menu to open, reset any state 1328 closePanel(st, true); 1329 return; 1330 } 1331 1332 final WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); 1333 if (wm == null) { 1334 return; 1335 } 1336 1337 // Prepare panel (should have been done before, but just in case) 1338 if (!preparePanel(st, event)) { 1339 return; 1340 } 1341 1342 int width = WRAP_CONTENT; 1343 if (st.decorView == null || st.refreshDecorView) { 1344 if (st.decorView == null) { 1345 // Initialize the panel decor, this will populate st.decorView 1346 if (!initializePanelDecor(st) || (st.decorView == null)) 1347 return; 1348 } else if (st.refreshDecorView && (st.decorView.getChildCount() > 0)) { 1349 // Decor needs refreshing, so remove its views 1350 st.decorView.removeAllViews(); 1351 } 1352 1353 // This will populate st.shownPanelView 1354 if (!initializePanelContent(st) || !st.hasPanelItems()) { 1355 return; 1356 } 1357 1358 ViewGroup.LayoutParams lp = st.shownPanelView.getLayoutParams(); 1359 if (lp == null) { 1360 lp = new ViewGroup.LayoutParams(WRAP_CONTENT, WRAP_CONTENT); 1361 } 1362 1363 int backgroundResId = st.background; 1364 st.decorView.setBackgroundResource(backgroundResId); 1365 1366 ViewParent shownPanelParent = st.shownPanelView.getParent(); 1367 if (shownPanelParent != null && shownPanelParent instanceof ViewGroup) { 1368 ((ViewGroup) shownPanelParent).removeView(st.shownPanelView); 1369 } 1370 st.decorView.addView(st.shownPanelView, lp); 1371 1372 /* 1373 * Give focus to the view, if it or one of its children does not 1374 * already have it. 1375 */ 1376 if (!st.shownPanelView.hasFocus()) { 1377 st.shownPanelView.requestFocus(); 1378 } 1379 } else if (st.createdPanelView != null) { 1380 // If we already had a panel view, carry width=MATCH_PARENT through 1381 // as we did above when it was created. 1382 ViewGroup.LayoutParams lp = st.createdPanelView.getLayoutParams(); 1383 if (lp != null && lp.width == ViewGroup.LayoutParams.MATCH_PARENT) { 1384 width = MATCH_PARENT; 1385 } 1386 } 1387 1388 st.isHandled = false; 1389 1390 WindowManager.LayoutParams lp = new WindowManager.LayoutParams( 1391 width, WRAP_CONTENT, 1392 st.x, st.y, WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL, 1393 WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM 1394 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, 1395 PixelFormat.TRANSLUCENT); 1396 1397 lp.gravity = st.gravity; 1398 lp.windowAnimations = st.windowAnimations; 1399 1400 wm.addView(st.decorView, lp); 1401 st.isOpen = true; 1402 } 1403 1404 private boolean initializePanelDecor(PanelFeatureState st) { 1405 st.setStyle(getActionBarThemedContext()); 1406 st.decorView = new ListMenuDecorView(st.listPresenterContext); 1407 st.gravity = Gravity.CENTER | Gravity.BOTTOM; 1408 return true; 1409 } 1410 1411 private void reopenMenu(MenuBuilder menu, boolean toggleMenuMode) { 1412 if (mDecorContentParent != null && mDecorContentParent.canShowOverflowMenu() 1413 && (!ViewConfiguration.get(mContext).hasPermanentMenuKey() 1414 || mDecorContentParent.isOverflowMenuShowPending())) { 1415 1416 final Window.Callback cb = getWindowCallback(); 1417 1418 if (!mDecorContentParent.isOverflowMenuShowing() || !toggleMenuMode) { 1419 if (cb != null && !mIsDestroyed) { 1420 // If we have a menu invalidation pending, do it now. 1421 if (mInvalidatePanelMenuPosted && 1422 (mInvalidatePanelMenuFeatures & (1 << FEATURE_OPTIONS_PANEL)) != 0) { 1423 mWindow.getDecorView().removeCallbacks(mInvalidatePanelMenuRunnable); 1424 mInvalidatePanelMenuRunnable.run(); 1425 } 1426 1427 final PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, true); 1428 1429 // If we don't have a menu or we're waiting for a full content refresh, 1430 // forget it. This is a lingering event that no longer matters. 1431 if (st.menu != null && !st.refreshMenuContent && 1432 cb.onPreparePanel(FEATURE_OPTIONS_PANEL, st.createdPanelView, st.menu)) { 1433 cb.onMenuOpened(FEATURE_SUPPORT_ACTION_BAR, st.menu); 1434 mDecorContentParent.showOverflowMenu(); 1435 } 1436 } 1437 } else { 1438 mDecorContentParent.hideOverflowMenu(); 1439 if (!mIsDestroyed) { 1440 final PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, true); 1441 cb.onPanelClosed(FEATURE_SUPPORT_ACTION_BAR, st.menu); 1442 } 1443 } 1444 return; 1445 } 1446 1447 PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, true); 1448 1449 st.refreshDecorView = true; 1450 closePanel(st, false); 1451 1452 openPanel(st, null); 1453 } 1454 1455 private boolean initializePanelMenu(final PanelFeatureState st) { 1456 Context context = mContext; 1457 1458 // If we have an action bar, initialize the menu with the right theme. 1459 if ((st.featureId == FEATURE_OPTIONS_PANEL || st.featureId == FEATURE_SUPPORT_ACTION_BAR) && 1460 mDecorContentParent != null) { 1461 final TypedValue outValue = new TypedValue(); 1462 final Resources.Theme baseTheme = context.getTheme(); 1463 baseTheme.resolveAttribute(R.attr.actionBarTheme, outValue, true); 1464 1465 Resources.Theme widgetTheme = null; 1466 if (outValue.resourceId != 0) { 1467 widgetTheme = context.getResources().newTheme(); 1468 widgetTheme.setTo(baseTheme); 1469 widgetTheme.applyStyle(outValue.resourceId, true); 1470 widgetTheme.resolveAttribute( 1471 R.attr.actionBarWidgetTheme, outValue, true); 1472 } else { 1473 baseTheme.resolveAttribute( 1474 R.attr.actionBarWidgetTheme, outValue, true); 1475 } 1476 1477 if (outValue.resourceId != 0) { 1478 if (widgetTheme == null) { 1479 widgetTheme = context.getResources().newTheme(); 1480 widgetTheme.setTo(baseTheme); 1481 } 1482 widgetTheme.applyStyle(outValue.resourceId, true); 1483 } 1484 1485 if (widgetTheme != null) { 1486 context = new ContextThemeWrapper(context, 0); 1487 context.getTheme().setTo(widgetTheme); 1488 } 1489 } 1490 1491 final MenuBuilder menu = new MenuBuilder(context); 1492 menu.setCallback(this); 1493 st.setMenu(menu); 1494 1495 return true; 1496 } 1497 1498 private boolean initializePanelContent(PanelFeatureState st) { 1499 if (st.createdPanelView != null) { 1500 st.shownPanelView = st.createdPanelView; 1501 return true; 1502 } 1503 1504 if (st.menu == null) { 1505 return false; 1506 } 1507 1508 if (mPanelMenuPresenterCallback == null) { 1509 mPanelMenuPresenterCallback = new PanelMenuPresenterCallback(); 1510 } 1511 1512 MenuView menuView = st.getListMenuView(mPanelMenuPresenterCallback); 1513 1514 st.shownPanelView = (View) menuView; 1515 1516 return st.shownPanelView != null; 1517 } 1518 1519 private boolean preparePanel(PanelFeatureState st, KeyEvent event) { 1520 if (mIsDestroyed) { 1521 return false; 1522 } 1523 1524 // Already prepared (isPrepared will be reset to false later) 1525 if (st.isPrepared) { 1526 return true; 1527 } 1528 1529 if ((mPreparedPanel != null) && (mPreparedPanel != st)) { 1530 // Another Panel is prepared and possibly open, so close it 1531 closePanel(mPreparedPanel, false); 1532 } 1533 1534 final Window.Callback cb = getWindowCallback(); 1535 1536 if (cb != null) { 1537 st.createdPanelView = cb.onCreatePanelView(st.featureId); 1538 } 1539 1540 final boolean isActionBarMenu = 1541 (st.featureId == FEATURE_OPTIONS_PANEL || st.featureId == FEATURE_SUPPORT_ACTION_BAR); 1542 1543 if (isActionBarMenu && mDecorContentParent != null) { 1544 // Enforce ordering guarantees around events so that the action bar never 1545 // dispatches menu-related events before the panel is prepared. 1546 mDecorContentParent.setMenuPrepared(); 1547 } 1548 1549 if (st.createdPanelView == null && 1550 (!isActionBarMenu || !(peekSupportActionBar() instanceof ToolbarActionBar))) { 1551 // Since ToolbarActionBar handles the list options menu itself, we only want to 1552 // init this menu panel if we're not using a TAB. 1553 if (st.menu == null || st.refreshMenuContent) { 1554 if (st.menu == null) { 1555 if (!initializePanelMenu(st) || (st.menu == null)) { 1556 return false; 1557 } 1558 } 1559 1560 if (isActionBarMenu && mDecorContentParent != null) { 1561 if (mActionMenuPresenterCallback == null) { 1562 mActionMenuPresenterCallback = new ActionMenuPresenterCallback(); 1563 } 1564 mDecorContentParent.setMenu(st.menu, mActionMenuPresenterCallback); 1565 } 1566 1567 // Creating the panel menu will involve a lot of manipulation; 1568 // don't dispatch change events to presenters until we're done. 1569 st.menu.stopDispatchingItemsChanged(); 1570 if (!cb.onCreatePanelMenu(st.featureId, st.menu)) { 1571 // Ditch the menu created above 1572 st.setMenu(null); 1573 1574 if (isActionBarMenu && mDecorContentParent != null) { 1575 // Don't show it in the action bar either 1576 mDecorContentParent.setMenu(null, mActionMenuPresenterCallback); 1577 } 1578 1579 return false; 1580 } 1581 1582 st.refreshMenuContent = false; 1583 } 1584 1585 // Preparing the panel menu can involve a lot of manipulation; 1586 // don't dispatch change events to presenters until we're done. 1587 st.menu.stopDispatchingItemsChanged(); 1588 1589 // Restore action view state before we prepare. This gives apps 1590 // an opportunity to override frozen/restored state in onPrepare. 1591 if (st.frozenActionViewState != null) { 1592 st.menu.restoreActionViewStates(st.frozenActionViewState); 1593 st.frozenActionViewState = null; 1594 } 1595 1596 // Callback and return if the callback does not want to show the menu 1597 if (!cb.onPreparePanel(FEATURE_OPTIONS_PANEL, st.createdPanelView, st.menu)) { 1598 if (isActionBarMenu && mDecorContentParent != null) { 1599 // The app didn't want to show the menu for now but it still exists. 1600 // Clear it out of the action bar. 1601 mDecorContentParent.setMenu(null, mActionMenuPresenterCallback); 1602 } 1603 st.menu.startDispatchingItemsChanged(); 1604 return false; 1605 } 1606 1607 // Set the proper keymap 1608 KeyCharacterMap kmap = KeyCharacterMap.load( 1609 event != null ? event.getDeviceId() : KeyCharacterMap.VIRTUAL_KEYBOARD); 1610 st.qwertyMode = kmap.getKeyboardType() != KeyCharacterMap.NUMERIC; 1611 st.menu.setQwertyMode(st.qwertyMode); 1612 st.menu.startDispatchingItemsChanged(); 1613 } 1614 1615 // Set other state 1616 st.isPrepared = true; 1617 st.isHandled = false; 1618 mPreparedPanel = st; 1619 1620 return true; 1621 } 1622 1623 void checkCloseActionMenu(MenuBuilder menu) { 1624 if (mClosingActionMenu) { 1625 return; 1626 } 1627 1628 mClosingActionMenu = true; 1629 mDecorContentParent.dismissPopups(); 1630 Window.Callback cb = getWindowCallback(); 1631 if (cb != null && !mIsDestroyed) { 1632 cb.onPanelClosed(FEATURE_SUPPORT_ACTION_BAR, menu); 1633 } 1634 mClosingActionMenu = false; 1635 } 1636 1637 void closePanel(int featureId) { 1638 closePanel(getPanelState(featureId, true), true); 1639 } 1640 1641 void closePanel(PanelFeatureState st, boolean doCallback) { 1642 if (doCallback && st.featureId == FEATURE_OPTIONS_PANEL && 1643 mDecorContentParent != null && mDecorContentParent.isOverflowMenuShowing()) { 1644 checkCloseActionMenu(st.menu); 1645 return; 1646 } 1647 1648 final WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); 1649 if (wm != null && st.isOpen && st.decorView != null) { 1650 wm.removeView(st.decorView); 1651 1652 if (doCallback) { 1653 callOnPanelClosed(st.featureId, st, null); 1654 } 1655 } 1656 1657 st.isPrepared = false; 1658 st.isHandled = false; 1659 st.isOpen = false; 1660 1661 // This view is no longer shown, so null it out 1662 st.shownPanelView = null; 1663 1664 // Next time the menu opens, it should not be in expanded mode, so 1665 // force a refresh of the decor 1666 st.refreshDecorView = true; 1667 1668 if (mPreparedPanel == st) { 1669 mPreparedPanel = null; 1670 } 1671 } 1672 1673 private boolean onKeyDownPanel(int featureId, KeyEvent event) { 1674 if (event.getRepeatCount() == 0) { 1675 PanelFeatureState st = getPanelState(featureId, true); 1676 if (!st.isOpen) { 1677 return preparePanel(st, event); 1678 } 1679 } 1680 1681 return false; 1682 } 1683 1684 private boolean onKeyUpPanel(int featureId, KeyEvent event) { 1685 if (mActionMode != null) { 1686 return false; 1687 } 1688 1689 boolean handled = false; 1690 final PanelFeatureState st = getPanelState(featureId, true); 1691 if (featureId == FEATURE_OPTIONS_PANEL && mDecorContentParent != null && 1692 mDecorContentParent.canShowOverflowMenu() && 1693 !ViewConfiguration.get(mContext).hasPermanentMenuKey()) { 1694 if (!mDecorContentParent.isOverflowMenuShowing()) { 1695 if (!mIsDestroyed && preparePanel(st, event)) { 1696 handled = mDecorContentParent.showOverflowMenu(); 1697 } 1698 } else { 1699 handled = mDecorContentParent.hideOverflowMenu(); 1700 } 1701 } else { 1702 if (st.isOpen || st.isHandled) { 1703 // Play the sound effect if the user closed an open menu (and not if 1704 // they just released a menu shortcut) 1705 handled = st.isOpen; 1706 // Close menu 1707 closePanel(st, true); 1708 } else if (st.isPrepared) { 1709 boolean show = true; 1710 if (st.refreshMenuContent) { 1711 // Something may have invalidated the menu since we prepared it. 1712 // Re-prepare it to refresh. 1713 st.isPrepared = false; 1714 show = preparePanel(st, event); 1715 } 1716 1717 if (show) { 1718 // Show menu 1719 openPanel(st, event); 1720 handled = true; 1721 } 1722 } 1723 } 1724 1725 if (handled) { 1726 AudioManager audioManager = (AudioManager) mContext.getSystemService( 1727 Context.AUDIO_SERVICE); 1728 if (audioManager != null) { 1729 audioManager.playSoundEffect(AudioManager.FX_KEY_CLICK); 1730 } else { 1731 Log.w(TAG, "Couldn't get audio manager"); 1732 } 1733 } 1734 return handled; 1735 } 1736 1737 void callOnPanelClosed(int featureId, PanelFeatureState panel, Menu menu) { 1738 // Try to get a menu 1739 if (menu == null) { 1740 // Need a panel to grab the menu, so try to get that 1741 if (panel == null) { 1742 if ((featureId >= 0) && (featureId < mPanels.length)) { 1743 panel = mPanels[featureId]; 1744 } 1745 } 1746 1747 if (panel != null) { 1748 // menu still may be null, which is okay--we tried our best 1749 menu = panel.menu; 1750 } 1751 } 1752 1753 // If the panel is not open, do not callback 1754 if ((panel != null) && (!panel.isOpen)) 1755 return; 1756 1757 if (!mIsDestroyed) { 1758 // We need to be careful which callback we dispatch the call to. We can not dispatch 1759 // this to the Window's callback since that will call back into this method and cause a 1760 // crash. Instead we need to dispatch down to the original Activity/Dialog/etc. 1761 mOriginalWindowCallback.onPanelClosed(featureId, menu); 1762 } 1763 } 1764 1765 PanelFeatureState findMenuPanel(Menu menu) { 1766 final PanelFeatureState[] panels = mPanels; 1767 final int N = panels != null ? panels.length : 0; 1768 for (int i = 0; i < N; i++) { 1769 final PanelFeatureState panel = panels[i]; 1770 if (panel != null && panel.menu == menu) { 1771 return panel; 1772 } 1773 } 1774 return null; 1775 } 1776 1777 protected PanelFeatureState getPanelState(int featureId, boolean required) { 1778 PanelFeatureState[] ar; 1779 if ((ar = mPanels) == null || ar.length <= featureId) { 1780 PanelFeatureState[] nar = new PanelFeatureState[featureId + 1]; 1781 if (ar != null) { 1782 System.arraycopy(ar, 0, nar, 0, ar.length); 1783 } 1784 mPanels = ar = nar; 1785 } 1786 1787 PanelFeatureState st = ar[featureId]; 1788 if (st == null) { 1789 ar[featureId] = st = new PanelFeatureState(featureId); 1790 } 1791 return st; 1792 } 1793 1794 private boolean performPanelShortcut(PanelFeatureState st, int keyCode, KeyEvent event, 1795 int flags) { 1796 if (event.isSystem()) { 1797 return false; 1798 } 1799 1800 boolean handled = false; 1801 1802 // Only try to perform menu shortcuts if preparePanel returned true (possible false 1803 // return value from application not wanting to show the menu). 1804 if ((st.isPrepared || preparePanel(st, event)) && st.menu != null) { 1805 // The menu is prepared now, perform the shortcut on it 1806 handled = st.menu.performShortcut(keyCode, event, flags); 1807 } 1808 1809 if (handled) { 1810 // Only close down the menu if we don't have an action bar keeping it open. 1811 if ((flags & Menu.FLAG_PERFORM_NO_CLOSE) == 0 && mDecorContentParent == null) { 1812 closePanel(st, true); 1813 } 1814 } 1815 1816 return handled; 1817 } 1818 1819 private void invalidatePanelMenu(int featureId) { 1820 mInvalidatePanelMenuFeatures |= 1 << featureId; 1821 1822 if (!mInvalidatePanelMenuPosted) { 1823 ViewCompat.postOnAnimation(mWindow.getDecorView(), mInvalidatePanelMenuRunnable); 1824 mInvalidatePanelMenuPosted = true; 1825 } 1826 } 1827 1828 void doInvalidatePanelMenu(int featureId) { 1829 PanelFeatureState st = getPanelState(featureId, true); 1830 Bundle savedActionViewStates = null; 1831 if (st.menu != null) { 1832 savedActionViewStates = new Bundle(); 1833 st.menu.saveActionViewStates(savedActionViewStates); 1834 if (savedActionViewStates.size() > 0) { 1835 st.frozenActionViewState = savedActionViewStates; 1836 } 1837 // This will be started again when the panel is prepared. 1838 st.menu.stopDispatchingItemsChanged(); 1839 st.menu.clear(); 1840 } 1841 st.refreshMenuContent = true; 1842 st.refreshDecorView = true; 1843 1844 // Prepare the options panel if we have an action bar 1845 if ((featureId == FEATURE_SUPPORT_ACTION_BAR || featureId == FEATURE_OPTIONS_PANEL) 1846 && mDecorContentParent != null) { 1847 st = getPanelState(Window.FEATURE_OPTIONS_PANEL, false); 1848 if (st != null) { 1849 st.isPrepared = false; 1850 preparePanel(st, null); 1851 } 1852 } 1853 } 1854 1855 /** 1856 * Updates the status bar guard 1857 * 1858 * @param insetTop the current top system window inset 1859 * @return the new top system window inset 1860 */ 1861 int updateStatusGuard(int insetTop) { 1862 boolean showStatusGuard = false; 1863 // Show the status guard when the non-overlay contextual action bar is showing 1864 if (mActionModeView != null) { 1865 if (mActionModeView.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) { 1866 ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) 1867 mActionModeView.getLayoutParams(); 1868 boolean mlpChanged = false; 1869 1870 if (mActionModeView.isShown()) { 1871 if (mTempRect1 == null) { 1872 mTempRect1 = new Rect(); 1873 mTempRect2 = new Rect(); 1874 } 1875 final Rect insets = mTempRect1; 1876 final Rect localInsets = mTempRect2; 1877 insets.set(0, insetTop, 0, 0); 1878 1879 ViewUtils.computeFitSystemWindows(mSubDecor, insets, localInsets); 1880 final int newMargin = localInsets.top == 0 ? insetTop : 0; 1881 if (mlp.topMargin != newMargin) { 1882 mlpChanged = true; 1883 mlp.topMargin = insetTop; 1884 1885 if (mStatusGuard == null) { 1886 mStatusGuard = new View(mContext); 1887 mStatusGuard.setBackgroundColor(mContext.getResources() 1888 .getColor(R.color.abc_input_method_navigation_guard)); 1889 mSubDecor.addView(mStatusGuard, -1, 1890 new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 1891 insetTop)); 1892 } else { 1893 ViewGroup.LayoutParams lp = mStatusGuard.getLayoutParams(); 1894 if (lp.height != insetTop) { 1895 lp.height = insetTop; 1896 mStatusGuard.setLayoutParams(lp); 1897 } 1898 } 1899 } 1900 1901 // The action mode's theme may differ from the app, so 1902 // always show the status guard above it. 1903 showStatusGuard = mStatusGuard != null; 1904 1905 // We only need to consume the insets if the action 1906 // mode is overlaid on the app content (e.g. it's 1907 // sitting in a FrameLayout, see 1908 // screen_simple_overlay_action_mode.xml). 1909 if (!mOverlayActionMode && showStatusGuard) { 1910 insetTop = 0; 1911 } 1912 } else { 1913 // reset top margin 1914 if (mlp.topMargin != 0) { 1915 mlpChanged = true; 1916 mlp.topMargin = 0; 1917 } 1918 } 1919 if (mlpChanged) { 1920 mActionModeView.setLayoutParams(mlp); 1921 } 1922 } 1923 } 1924 if (mStatusGuard != null) { 1925 mStatusGuard.setVisibility(showStatusGuard ? View.VISIBLE : View.GONE); 1926 } 1927 1928 return insetTop; 1929 } 1930 1931 private void throwFeatureRequestIfSubDecorInstalled() { 1932 if (mSubDecorInstalled) { 1933 throw new AndroidRuntimeException( 1934 "Window feature must be requested before adding content"); 1935 } 1936 } 1937 1938 private int sanitizeWindowFeatureId(int featureId) { 1939 if (featureId == WindowCompat.FEATURE_ACTION_BAR) { 1940 Log.i(TAG, "You should now use the AppCompatDelegate.FEATURE_SUPPORT_ACTION_BAR" 1941 + " id when requesting this feature."); 1942 return FEATURE_SUPPORT_ACTION_BAR; 1943 } else if (featureId == WindowCompat.FEATURE_ACTION_BAR_OVERLAY) { 1944 Log.i(TAG, "You should now use the AppCompatDelegate.FEATURE_SUPPORT_ACTION_BAR_OVERLAY" 1945 + " id when requesting this feature."); 1946 return FEATURE_SUPPORT_ACTION_BAR_OVERLAY; 1947 } 1948 // Else we'll just return the original id 1949 return featureId; 1950 } 1951 1952 ViewGroup getSubDecor() { 1953 return mSubDecor; 1954 } 1955 1956 void dismissPopups() { 1957 if (mDecorContentParent != null) { 1958 mDecorContentParent.dismissPopups(); 1959 } 1960 1961 if (mActionModePopup != null) { 1962 mWindow.getDecorView().removeCallbacks(mShowActionModePopup); 1963 if (mActionModePopup.isShowing()) { 1964 try { 1965 mActionModePopup.dismiss(); 1966 } catch (IllegalArgumentException e) { 1967 // Pre-v18, there are times when the Window will remove the popup before us. 1968 // In these cases we need to swallow the resulting exception. 1969 } 1970 } 1971 mActionModePopup = null; 1972 } 1973 endOnGoingFadeAnimation(); 1974 1975 PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false); 1976 if (st != null && st.menu != null) { 1977 st.menu.close(); 1978 } 1979 } 1980 1981 @Override 1982 public boolean applyDayNight() { 1983 boolean applied = false; 1984 1985 @NightMode final int nightMode = getNightMode(); 1986 @ApplyableNightMode final int modeToApply = mapNightMode(nightMode); 1987 if (modeToApply != MODE_NIGHT_FOLLOW_SYSTEM) { 1988 applied = updateForNightMode(modeToApply); 1989 } 1990 1991 if (nightMode == MODE_NIGHT_AUTO) { 1992 // If we're already been started, we may need to setup auto mode again 1993 ensureAutoNightModeManager(); 1994 mAutoNightModeManager.setup(); 1995 } 1996 1997 mApplyDayNightCalled = true; 1998 return applied; 1999 } 2000 2001 @Override 2002 public void setLocalNightMode(@NightMode final int mode) { 2003 switch (mode) { 2004 case MODE_NIGHT_AUTO: 2005 case MODE_NIGHT_NO: 2006 case MODE_NIGHT_YES: 2007 case MODE_NIGHT_FOLLOW_SYSTEM: 2008 if (mLocalNightMode != mode) { 2009 mLocalNightMode = mode; 2010 if (mApplyDayNightCalled) { 2011 // If we've already applied day night, re-apply since we won't be 2012 // called again 2013 applyDayNight(); 2014 } 2015 } 2016 break; 2017 default: 2018 Log.i(TAG, "setLocalNightMode() called with an unknown mode"); 2019 break; 2020 } 2021 } 2022 2023 @ApplyableNightMode 2024 int mapNightMode(@NightMode final int mode) { 2025 switch (mode) { 2026 case MODE_NIGHT_AUTO: 2027 if (Build.VERSION.SDK_INT >= 23) { 2028 UiModeManager uiModeManager = mContext.getSystemService(UiModeManager.class); 2029 if (uiModeManager.getNightMode() == UiModeManager.MODE_NIGHT_AUTO) { 2030 // If we're set to AUTO and the system's auto night mode is already enabled, 2031 // we'll just let the system handle it by returning FOLLOW_SYSTEM 2032 return MODE_NIGHT_FOLLOW_SYSTEM; 2033 } 2034 } 2035 ensureAutoNightModeManager(); 2036 return mAutoNightModeManager.getApplyableNightMode(); 2037 case MODE_NIGHT_UNSPECIFIED: 2038 // If we don't have a mode specified, just let the system handle it 2039 return MODE_NIGHT_FOLLOW_SYSTEM; 2040 default: 2041 return mode; 2042 } 2043 } 2044 2045 @NightMode 2046 private int getNightMode() { 2047 return mLocalNightMode != MODE_NIGHT_UNSPECIFIED ? mLocalNightMode : getDefaultNightMode(); 2048 } 2049 2050 /** 2051 * Updates the {@link Resources} configuration {@code uiMode} with the 2052 * chosen {@code UI_MODE_NIGHT} value. 2053 */ 2054 private boolean updateForNightMode(@ApplyableNightMode final int mode) { 2055 final Resources res = mContext.getResources(); 2056 final Configuration conf = res.getConfiguration(); 2057 final int currentNightMode = conf.uiMode & Configuration.UI_MODE_NIGHT_MASK; 2058 2059 final int newNightMode = (mode == MODE_NIGHT_YES) 2060 ? Configuration.UI_MODE_NIGHT_YES 2061 : Configuration.UI_MODE_NIGHT_NO; 2062 2063 if (currentNightMode != newNightMode) { 2064 if (shouldRecreateOnNightModeChange()) { 2065 if (DEBUG) { 2066 Log.d(TAG, "applyNightMode() | Night mode changed, recreating Activity"); 2067 } 2068 // If we've already been created, we need to recreate the Activity for the 2069 // mode to be applied 2070 final Activity activity = (Activity) mContext; 2071 activity.recreate(); 2072 } else { 2073 if (DEBUG) { 2074 Log.d(TAG, "applyNightMode() | Night mode changed, updating configuration"); 2075 } 2076 final Configuration config = new Configuration(conf); 2077 final DisplayMetrics metrics = res.getDisplayMetrics(); 2078 2079 // Update the UI Mode to reflect the new night mode 2080 config.uiMode = newNightMode | (config.uiMode & ~Configuration.UI_MODE_NIGHT_MASK); 2081 res.updateConfiguration(config, metrics); 2082 2083 // We may need to flush the Resources' drawable cache due to framework bugs. 2084 if (!(Build.VERSION.SDK_INT >= 26)) { 2085 ResourcesFlusher.flush(res); 2086 } 2087 } 2088 return true; 2089 } else { 2090 if (DEBUG) { 2091 Log.d(TAG, "applyNightMode() | Skipping. Night mode has not changed: " + mode); 2092 } 2093 } 2094 return false; 2095 } 2096 2097 private void ensureAutoNightModeManager() { 2098 if (mAutoNightModeManager == null) { 2099 mAutoNightModeManager = new AutoNightModeManager(TwilightManager.getInstance(mContext)); 2100 } 2101 } 2102 2103 @VisibleForTesting 2104 final AutoNightModeManager getAutoNightModeManager() { 2105 ensureAutoNightModeManager(); 2106 return mAutoNightModeManager; 2107 } 2108 2109 private boolean shouldRecreateOnNightModeChange() { 2110 if (mApplyDayNightCalled && mContext instanceof Activity) { 2111 // If we've already applyDayNight() (via setTheme), we need to check if the 2112 // Activity has configChanges set to handle uiMode changes 2113 final PackageManager pm = mContext.getPackageManager(); 2114 try { 2115 final ActivityInfo info = pm.getActivityInfo( 2116 new ComponentName(mContext, mContext.getClass()), 0); 2117 // We should return true (to recreate) if configChanges does not want to 2118 // handle uiMode 2119 return (info.configChanges & ActivityInfo.CONFIG_UI_MODE) == 0; 2120 } catch (PackageManager.NameNotFoundException e) { 2121 // This shouldn't happen but let's not crash because of it, we'll just log and 2122 // return true (since most apps will do that anyway) 2123 Log.d(TAG, "Exception while getting ActivityInfo", e); 2124 return true; 2125 } 2126 } 2127 return false; 2128 } 2129 2130 /** 2131 * Clears out internal reference when the action mode is destroyed. 2132 */ 2133 class ActionModeCallbackWrapperV9 implements ActionMode.Callback { 2134 private ActionMode.Callback mWrapped; 2135 2136 public ActionModeCallbackWrapperV9(ActionMode.Callback wrapped) { 2137 mWrapped = wrapped; 2138 } 2139 2140 @Override 2141 public boolean onCreateActionMode(ActionMode mode, Menu menu) { 2142 return mWrapped.onCreateActionMode(mode, menu); 2143 } 2144 2145 @Override 2146 public boolean onPrepareActionMode(ActionMode mode, Menu menu) { 2147 return mWrapped.onPrepareActionMode(mode, menu); 2148 } 2149 2150 @Override 2151 public boolean onActionItemClicked(ActionMode mode, MenuItem item) { 2152 return mWrapped.onActionItemClicked(mode, item); 2153 } 2154 2155 @Override 2156 public void onDestroyActionMode(ActionMode mode) { 2157 mWrapped.onDestroyActionMode(mode); 2158 if (mActionModePopup != null) { 2159 mWindow.getDecorView().removeCallbacks(mShowActionModePopup); 2160 } 2161 2162 if (mActionModeView != null) { 2163 endOnGoingFadeAnimation(); 2164 mFadeAnim = ViewCompat.animate(mActionModeView).alpha(0f); 2165 mFadeAnim.setListener(new ViewPropertyAnimatorListenerAdapter() { 2166 @Override 2167 public void onAnimationEnd(View view) { 2168 mActionModeView.setVisibility(View.GONE); 2169 if (mActionModePopup != null) { 2170 mActionModePopup.dismiss(); 2171 } else if (mActionModeView.getParent() instanceof View) { 2172 ViewCompat.requestApplyInsets((View) mActionModeView.getParent()); 2173 } 2174 mActionModeView.removeAllViews(); 2175 mFadeAnim.setListener(null); 2176 mFadeAnim = null; 2177 } 2178 }); 2179 } 2180 if (mAppCompatCallback != null) { 2181 mAppCompatCallback.onSupportActionModeFinished(mActionMode); 2182 } 2183 mActionMode = null; 2184 } 2185 } 2186 2187 private final class PanelMenuPresenterCallback implements MenuPresenter.Callback { 2188 PanelMenuPresenterCallback() { 2189 } 2190 2191 @Override 2192 public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { 2193 final Menu parentMenu = menu.getRootMenu(); 2194 final boolean isSubMenu = parentMenu != menu; 2195 final PanelFeatureState panel = findMenuPanel(isSubMenu ? parentMenu : menu); 2196 if (panel != null) { 2197 if (isSubMenu) { 2198 callOnPanelClosed(panel.featureId, panel, parentMenu); 2199 closePanel(panel, true); 2200 } else { 2201 // Close the panel and only do the callback if the menu is being 2202 // closed completely, not if opening a sub menu 2203 closePanel(panel, allMenusAreClosing); 2204 } 2205 } 2206 } 2207 2208 @Override 2209 public boolean onOpenSubMenu(MenuBuilder subMenu) { 2210 if (subMenu == null && mHasActionBar) { 2211 Window.Callback cb = getWindowCallback(); 2212 if (cb != null && !mIsDestroyed) { 2213 cb.onMenuOpened(FEATURE_SUPPORT_ACTION_BAR, subMenu); 2214 } 2215 } 2216 return true; 2217 } 2218 } 2219 2220 private final class ActionMenuPresenterCallback implements MenuPresenter.Callback { 2221 ActionMenuPresenterCallback() { 2222 } 2223 2224 @Override 2225 public boolean onOpenSubMenu(MenuBuilder subMenu) { 2226 Window.Callback cb = getWindowCallback(); 2227 if (cb != null) { 2228 cb.onMenuOpened(FEATURE_SUPPORT_ACTION_BAR, subMenu); 2229 } 2230 return true; 2231 } 2232 2233 @Override 2234 public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { 2235 checkCloseActionMenu(menu); 2236 } 2237 } 2238 2239 protected static final class PanelFeatureState { 2240 2241 /** Feature ID for this panel. */ 2242 int featureId; 2243 2244 int background; 2245 2246 int gravity; 2247 2248 int x; 2249 2250 int y; 2251 2252 int windowAnimations; 2253 2254 /** Dynamic state of the panel. */ 2255 ViewGroup decorView; 2256 2257 /** The panel that we are actually showing. */ 2258 View shownPanelView; 2259 2260 /** The panel that was returned by onCreatePanelView(). */ 2261 View createdPanelView; 2262 2263 /** Use {@link #setMenu} to set this. */ 2264 MenuBuilder menu; 2265 2266 ListMenuPresenter listMenuPresenter; 2267 2268 Context listPresenterContext; 2269 2270 /** 2271 * Whether the panel has been prepared (see 2272 * {@link #preparePanel}). 2273 */ 2274 boolean isPrepared; 2275 2276 /** 2277 * Whether an item's action has been performed. This happens in obvious 2278 * scenarios (user clicks on menu item), but can also happen with 2279 * chording menu+(shortcut key). 2280 */ 2281 boolean isHandled; 2282 2283 boolean isOpen; 2284 2285 public boolean qwertyMode; 2286 2287 boolean refreshDecorView; 2288 2289 boolean refreshMenuContent; 2290 2291 boolean wasLastOpen; 2292 2293 /** 2294 * Contains the state of the menu when told to freeze. 2295 */ 2296 Bundle frozenMenuState; 2297 2298 /** 2299 * Contains the state of associated action views when told to freeze. 2300 * These are saved across invalidations. 2301 */ 2302 Bundle frozenActionViewState; 2303 2304 PanelFeatureState(int featureId) { 2305 this.featureId = featureId; 2306 2307 refreshDecorView = false; 2308 } 2309 2310 public boolean hasPanelItems() { 2311 if (shownPanelView == null) return false; 2312 if (createdPanelView != null) return true; 2313 2314 return listMenuPresenter.getAdapter().getCount() > 0; 2315 } 2316 2317 /** 2318 * Unregister and free attached MenuPresenters. They will be recreated as needed. 2319 */ 2320 public void clearMenuPresenters() { 2321 if (menu != null) { 2322 menu.removeMenuPresenter(listMenuPresenter); 2323 } 2324 listMenuPresenter = null; 2325 } 2326 2327 void setStyle(Context context) { 2328 final TypedValue outValue = new TypedValue(); 2329 final Resources.Theme widgetTheme = context.getResources().newTheme(); 2330 widgetTheme.setTo(context.getTheme()); 2331 2332 // First apply the actionBarPopupTheme 2333 widgetTheme.resolveAttribute(R.attr.actionBarPopupTheme, outValue, true); 2334 if (outValue.resourceId != 0) { 2335 widgetTheme.applyStyle(outValue.resourceId, true); 2336 } 2337 2338 // Now apply the panelMenuListTheme 2339 widgetTheme.resolveAttribute(R.attr.panelMenuListTheme, outValue, true); 2340 if (outValue.resourceId != 0) { 2341 widgetTheme.applyStyle(outValue.resourceId, true); 2342 } else { 2343 widgetTheme.applyStyle(R.style.Theme_AppCompat_CompactMenu, true); 2344 } 2345 2346 context = new ContextThemeWrapper(context, 0); 2347 context.getTheme().setTo(widgetTheme); 2348 2349 listPresenterContext = context; 2350 2351 TypedArray a = context.obtainStyledAttributes(R.styleable.AppCompatTheme); 2352 background = a.getResourceId( 2353 R.styleable.AppCompatTheme_panelBackground, 0); 2354 windowAnimations = a.getResourceId( 2355 R.styleable.AppCompatTheme_android_windowAnimationStyle, 0); 2356 a.recycle(); 2357 } 2358 2359 void setMenu(MenuBuilder menu) { 2360 if (menu == this.menu) return; 2361 2362 if (this.menu != null) { 2363 this.menu.removeMenuPresenter(listMenuPresenter); 2364 } 2365 this.menu = menu; 2366 if (menu != null) { 2367 if (listMenuPresenter != null) menu.addMenuPresenter(listMenuPresenter); 2368 } 2369 } 2370 2371 MenuView getListMenuView(MenuPresenter.Callback cb) { 2372 if (menu == null) return null; 2373 2374 if (listMenuPresenter == null) { 2375 listMenuPresenter = new ListMenuPresenter(listPresenterContext, 2376 R.layout.abc_list_menu_item_layout); 2377 listMenuPresenter.setCallback(cb); 2378 menu.addMenuPresenter(listMenuPresenter); 2379 } 2380 2381 MenuView result = listMenuPresenter.getMenuView(decorView); 2382 2383 return result; 2384 } 2385 2386 Parcelable onSaveInstanceState() { 2387 SavedState savedState = new SavedState(); 2388 savedState.featureId = featureId; 2389 savedState.isOpen = isOpen; 2390 2391 if (menu != null) { 2392 savedState.menuState = new Bundle(); 2393 menu.savePresenterStates(savedState.menuState); 2394 } 2395 2396 return savedState; 2397 } 2398 2399 void onRestoreInstanceState(Parcelable state) { 2400 SavedState savedState = (SavedState) state; 2401 featureId = savedState.featureId; 2402 wasLastOpen = savedState.isOpen; 2403 frozenMenuState = savedState.menuState; 2404 2405 shownPanelView = null; 2406 decorView = null; 2407 } 2408 2409 void applyFrozenState() { 2410 if (menu != null && frozenMenuState != null) { 2411 menu.restorePresenterStates(frozenMenuState); 2412 frozenMenuState = null; 2413 } 2414 } 2415 2416 private static class SavedState implements Parcelable { 2417 int featureId; 2418 boolean isOpen; 2419 Bundle menuState; 2420 2421 SavedState() { 2422 } 2423 2424 @Override 2425 public int describeContents() { 2426 return 0; 2427 } 2428 2429 @Override 2430 public void writeToParcel(Parcel dest, int flags) { 2431 dest.writeInt(featureId); 2432 dest.writeInt(isOpen ? 1 : 0); 2433 2434 if (isOpen) { 2435 dest.writeBundle(menuState); 2436 } 2437 } 2438 2439 static SavedState readFromParcel(Parcel source, ClassLoader loader) { 2440 SavedState savedState = new SavedState(); 2441 savedState.featureId = source.readInt(); 2442 savedState.isOpen = source.readInt() == 1; 2443 2444 if (savedState.isOpen) { 2445 savedState.menuState = source.readBundle(loader); 2446 } 2447 2448 return savedState; 2449 } 2450 2451 public static final Creator<SavedState> CREATOR = new ClassLoaderCreator<SavedState>() { 2452 @Override 2453 public SavedState createFromParcel(Parcel in, ClassLoader loader) { 2454 return readFromParcel(in, loader); 2455 } 2456 2457 @Override 2458 public SavedState createFromParcel(Parcel in) { 2459 return readFromParcel(in, null); 2460 } 2461 2462 @Override 2463 public SavedState[] newArray(int size) { 2464 return new SavedState[size]; 2465 } 2466 }; 2467 } 2468 } 2469 2470 private class ListMenuDecorView extends ContentFrameLayout { 2471 public ListMenuDecorView(Context context) { 2472 super(context); 2473 } 2474 2475 @Override 2476 public boolean dispatchKeyEvent(KeyEvent event) { 2477 return AppCompatDelegateImpl.this.dispatchKeyEvent(event) 2478 || super.dispatchKeyEvent(event); 2479 } 2480 2481 @Override 2482 public boolean onInterceptTouchEvent(MotionEvent event) { 2483 int action = event.getAction(); 2484 if (action == MotionEvent.ACTION_DOWN) { 2485 int x = (int) event.getX(); 2486 int y = (int) event.getY(); 2487 if (isOutOfBounds(x, y)) { 2488 closePanel(Window.FEATURE_OPTIONS_PANEL); 2489 return true; 2490 } 2491 } 2492 return super.onInterceptTouchEvent(event); 2493 } 2494 2495 @Override 2496 public void setBackgroundResource(int resid) { 2497 setBackgroundDrawable(AppCompatResources.getDrawable(getContext(), resid)); 2498 } 2499 2500 private boolean isOutOfBounds(int x, int y) { 2501 return x < -5 || y < -5 || x > (getWidth() + 5) || y > (getHeight() + 5); 2502 } 2503 } 2504 2505 2506 class AppCompatWindowCallback extends WindowCallbackWrapper { 2507 AppCompatWindowCallback(Window.Callback callback) { 2508 super(callback); 2509 } 2510 2511 @Override 2512 public boolean dispatchKeyEvent(KeyEvent event) { 2513 return AppCompatDelegateImpl.this.dispatchKeyEvent(event) 2514 || super.dispatchKeyEvent(event); 2515 } 2516 2517 @Override 2518 public boolean dispatchKeyShortcutEvent(KeyEvent event) { 2519 return super.dispatchKeyShortcutEvent(event) 2520 || AppCompatDelegateImpl.this.onKeyShortcut(event.getKeyCode(), event); 2521 } 2522 2523 @Override 2524 public boolean onCreatePanelMenu(int featureId, Menu menu) { 2525 if (featureId == Window.FEATURE_OPTIONS_PANEL && !(menu instanceof MenuBuilder)) { 2526 // If this is an options menu but it's not an AppCompat menu, we eat the event 2527 // and return false 2528 return false; 2529 } 2530 return super.onCreatePanelMenu(featureId, menu); 2531 } 2532 2533 @Override 2534 public void onContentChanged() { 2535 // We purposely do not propagate this call as this is called when we install 2536 // our sub-decor rather than the user's content 2537 } 2538 2539 @Override 2540 public boolean onPreparePanel(int featureId, View view, Menu menu) { 2541 final MenuBuilder mb = menu instanceof MenuBuilder ? (MenuBuilder) menu : null; 2542 2543 if (featureId == Window.FEATURE_OPTIONS_PANEL && mb == null) { 2544 // If this is an options menu but it's not an AppCompat menu, we eat the event 2545 // and return false 2546 return false; 2547 } 2548 2549 // On ICS and below devices, onPreparePanel calls menu.hasVisibleItems() to determine 2550 // if a panel is prepared. This interferes with any initially invisible items, which 2551 // are later made visible. We workaround it by making hasVisibleItems() always 2552 // return true during the onPreparePanel call. 2553 if (mb != null) { 2554 mb.setOverrideVisibleItems(true); 2555 } 2556 2557 final boolean handled = super.onPreparePanel(featureId, view, menu); 2558 2559 if (mb != null) { 2560 mb.setOverrideVisibleItems(false); 2561 } 2562 2563 return handled; 2564 } 2565 2566 @Override 2567 public boolean onMenuOpened(int featureId, Menu menu) { 2568 super.onMenuOpened(featureId, menu); 2569 AppCompatDelegateImpl.this.onMenuOpened(featureId); 2570 return true; 2571 } 2572 2573 @Override 2574 public void onPanelClosed(int featureId, Menu menu) { 2575 super.onPanelClosed(featureId, menu); 2576 AppCompatDelegateImpl.this.onPanelClosed(featureId); 2577 } 2578 2579 @Override 2580 public android.view.ActionMode onWindowStartingActionMode( 2581 android.view.ActionMode.Callback callback) { 2582 if (Build.VERSION.SDK_INT >= 23) { 2583 // No-op on API 23+ 2584 return null; 2585 } 2586 // We wrap in a support action mode on v14+ if enabled 2587 if (isHandleNativeActionModesEnabled()) { 2588 return startAsSupportActionMode(callback); 2589 } 2590 // Else, let the call fall through to the wrapped callback 2591 return super.onWindowStartingActionMode(callback); 2592 } 2593 2594 /** 2595 * Wrap the framework {@link android.view.ActionMode.Callback} in a support action mode and 2596 * let AppCompat display it. 2597 */ 2598 final android.view.ActionMode startAsSupportActionMode( 2599 android.view.ActionMode.Callback callback) { 2600 // Wrap the callback as a v7 ActionMode.Callback 2601 final SupportActionModeWrapper.CallbackWrapper callbackWrapper = 2602 new SupportActionModeWrapper.CallbackWrapper(mContext, callback); 2603 2604 // Try and start a support action mode using the wrapped callback 2605 final androidx.appcompat.view.ActionMode supportActionMode = 2606 startSupportActionMode(callbackWrapper); 2607 2608 if (supportActionMode != null) { 2609 // If we received a support action mode, wrap and return it 2610 return callbackWrapper.getActionModeWrapper(supportActionMode); 2611 } 2612 return null; 2613 } 2614 2615 @Override 2616 @RequiresApi(23) 2617 public android.view.ActionMode onWindowStartingActionMode( 2618 android.view.ActionMode.Callback callback, int type) { 2619 if (isHandleNativeActionModesEnabled()) { 2620 switch (type) { 2621 case android.view.ActionMode.TYPE_PRIMARY: 2622 // We only take over if the type is TYPE_PRIMARY 2623 return startAsSupportActionMode(callback); 2624 } 2625 } 2626 // Else, let the call fall through to the wrapped callback 2627 return super.onWindowStartingActionMode(callback, type); 2628 } 2629 2630 @Override 2631 @RequiresApi(24) 2632 public void onProvideKeyboardShortcuts( 2633 List<KeyboardShortcutGroup> data, Menu menu, int deviceId) { 2634 final PanelFeatureState panel = getPanelState(Window.FEATURE_OPTIONS_PANEL, true); 2635 if (panel != null && panel.menu != null) { 2636 // The menu provided is one created by PhoneWindow which we don't actually use. 2637 // Instead we'll pass through our own... 2638 super.onProvideKeyboardShortcuts(data, panel.menu, deviceId); 2639 } else { 2640 // If we don't have a menu, jump pass through the original instead 2641 super.onProvideKeyboardShortcuts(data, menu, deviceId); 2642 } 2643 } 2644 } 2645 2646 @VisibleForTesting 2647 final class AutoNightModeManager { 2648 private TwilightManager mTwilightManager; 2649 private boolean mIsNight; 2650 2651 private BroadcastReceiver mAutoTimeChangeReceiver; 2652 private IntentFilter mAutoTimeChangeReceiverFilter; 2653 2654 AutoNightModeManager(@NonNull TwilightManager twilightManager) { 2655 mTwilightManager = twilightManager; 2656 mIsNight = twilightManager.isNight(); 2657 } 2658 2659 @ApplyableNightMode 2660 int getApplyableNightMode() { 2661 mIsNight = mTwilightManager.isNight(); 2662 return mIsNight ? MODE_NIGHT_YES : MODE_NIGHT_NO; 2663 } 2664 2665 void dispatchTimeChanged() { 2666 final boolean isNight = mTwilightManager.isNight(); 2667 if (isNight != mIsNight) { 2668 mIsNight = isNight; 2669 applyDayNight(); 2670 } 2671 } 2672 2673 void setup() { 2674 cleanup(); 2675 2676 // If we're set to AUTO, we register a receiver to be notified on time changes. The 2677 // system only sends the tick out every minute, but that's enough fidelity for our use 2678 // case 2679 if (mAutoTimeChangeReceiver == null) { 2680 mAutoTimeChangeReceiver = new BroadcastReceiver() { 2681 @Override 2682 public void onReceive(Context context, Intent intent) { 2683 if (DEBUG) { 2684 Log.d("AutoTimeChangeReceiver", "onReceive | Intent: " + intent); 2685 } 2686 dispatchTimeChanged(); 2687 } 2688 }; 2689 } 2690 if (mAutoTimeChangeReceiverFilter == null) { 2691 mAutoTimeChangeReceiverFilter = new IntentFilter(); 2692 mAutoTimeChangeReceiverFilter.addAction(Intent.ACTION_TIME_CHANGED); 2693 mAutoTimeChangeReceiverFilter.addAction(Intent.ACTION_TIMEZONE_CHANGED); 2694 mAutoTimeChangeReceiverFilter.addAction(Intent.ACTION_TIME_TICK); 2695 } 2696 mContext.registerReceiver(mAutoTimeChangeReceiver, mAutoTimeChangeReceiverFilter); 2697 } 2698 2699 void cleanup() { 2700 if (mAutoTimeChangeReceiver != null) { 2701 mContext.unregisterReceiver(mAutoTimeChangeReceiver); 2702 mAutoTimeChangeReceiver = null; 2703 } 2704 } 2705 } 2706 2707 @Override 2708 public final ActionBarDrawerToggle.Delegate getDrawerToggleDelegate() { 2709 return new ActionBarDrawableToggleImpl(); 2710 } 2711 2712 private class ActionBarDrawableToggleImpl implements ActionBarDrawerToggle.Delegate { 2713 ActionBarDrawableToggleImpl() { 2714 } 2715 2716 @Override 2717 public Drawable getThemeUpIndicator() { 2718 final TintTypedArray a = TintTypedArray.obtainStyledAttributes( 2719 getActionBarThemedContext(), null, new int[]{ R.attr.homeAsUpIndicator }); 2720 final Drawable result = a.getDrawable(0); 2721 a.recycle(); 2722 return result; 2723 } 2724 2725 @Override 2726 public Context getActionBarThemedContext() { 2727 return AppCompatDelegateImpl.this.getActionBarThemedContext(); 2728 } 2729 2730 @Override 2731 public boolean isNavigationVisible() { 2732 final ActionBar ab = getSupportActionBar(); 2733 return ab != null && (ab.getDisplayOptions() & ActionBar.DISPLAY_HOME_AS_UP) != 0; 2734 } 2735 2736 @Override 2737 public void setActionBarUpIndicator(Drawable upDrawable, int contentDescRes) { 2738 ActionBar ab = getSupportActionBar(); 2739 if (ab != null) { 2740 ab.setHomeAsUpIndicator(upDrawable); 2741 ab.setHomeActionContentDescription(contentDescRes); 2742 } 2743 } 2744 2745 @Override 2746 public void setActionBarDescription(int contentDescRes) { 2747 ActionBar ab = getSupportActionBar(); 2748 if (ab != null) { 2749 ab.setHomeActionContentDescription(contentDescRes); 2750 } 2751 } 2752 } 2753 } 2754