1 /*
2  * Copyright (C) 2022 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 android.inputmethodservice;
18 
19 import static android.inputmethodservice.InputMethodService.ENABLE_HIDE_IME_CAPTION_BAR;
20 import static android.view.WindowInsets.Type.captionBar;
21 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
22 
23 import android.animation.ValueAnimator;
24 import android.annotation.FloatRange;
25 import android.annotation.NonNull;
26 import android.annotation.Nullable;
27 import android.app.StatusBarManager;
28 import android.graphics.Color;
29 import android.graphics.Insets;
30 import android.graphics.Rect;
31 import android.graphics.Region;
32 import android.inputmethodservice.navigationbar.NavigationBarFrame;
33 import android.inputmethodservice.navigationbar.NavigationBarView;
34 import android.view.Gravity;
35 import android.view.LayoutInflater;
36 import android.view.View;
37 import android.view.ViewGroup;
38 import android.view.ViewParent;
39 import android.view.ViewTreeObserver;
40 import android.view.Window;
41 import android.view.WindowInsets;
42 import android.view.WindowInsetsController.Appearance;
43 import android.view.animation.Interpolator;
44 import android.view.animation.PathInterpolator;
45 import android.widget.FrameLayout;
46 
47 import com.android.internal.inputmethod.InputMethodNavButtonFlags;
48 
49 import java.util.Objects;
50 
51 /**
52  * This class hides details behind {@link InputMethodService#canImeRenderGesturalNavButtons()} from
53  * {@link InputMethodService}.
54  *
55  * <p>All the package-private methods are no-op when
56  * {@link InputMethodService#canImeRenderGesturalNavButtons()} returns {@code false}.</p>
57  */
58 final class NavigationBarController {
59 
60     private interface Callback {
updateTouchableInsets(@onNull InputMethodService.Insets originalInsets, @NonNull ViewTreeObserver.InternalInsetsInfo dest)61         default void updateTouchableInsets(@NonNull InputMethodService.Insets originalInsets,
62                 @NonNull ViewTreeObserver.InternalInsetsInfo dest) {
63         }
64 
onSoftInputWindowCreated(@onNull SoftInputWindow softInputWindow)65         default void onSoftInputWindowCreated(@NonNull SoftInputWindow softInputWindow) {
66         }
67 
onViewInitialized()68         default void onViewInitialized() {
69         }
70 
onWindowShown()71         default void onWindowShown() {
72         }
73 
onDestroy()74         default void onDestroy() {
75         }
76 
onNavButtonFlagsChanged(@nputMethodNavButtonFlags int navButtonFlags)77         default void onNavButtonFlagsChanged(@InputMethodNavButtonFlags int navButtonFlags) {
78         }
79 
toDebugString()80         default String toDebugString() {
81             return "No-op implementation";
82         }
83 
84         Callback NOOP = new Callback() {
85         };
86     }
87 
88     private final Callback mImpl;
89 
NavigationBarController(@onNull InputMethodService inputMethodService)90     NavigationBarController(@NonNull InputMethodService inputMethodService) {
91         mImpl = InputMethodService.canImeRenderGesturalNavButtons()
92                 ? new Impl(inputMethodService) : Callback.NOOP;
93     }
94 
updateTouchableInsets(@onNull InputMethodService.Insets originalInsets, @NonNull ViewTreeObserver.InternalInsetsInfo dest)95     void updateTouchableInsets(@NonNull InputMethodService.Insets originalInsets,
96             @NonNull ViewTreeObserver.InternalInsetsInfo dest) {
97         mImpl.updateTouchableInsets(originalInsets, dest);
98     }
99 
onSoftInputWindowCreated(@onNull SoftInputWindow softInputWindow)100     void onSoftInputWindowCreated(@NonNull SoftInputWindow softInputWindow) {
101         mImpl.onSoftInputWindowCreated(softInputWindow);
102     }
103 
onViewInitialized()104     void onViewInitialized() {
105         mImpl.onViewInitialized();
106     }
107 
onWindowShown()108     void onWindowShown() {
109         mImpl.onWindowShown();
110     }
111 
onDestroy()112     void onDestroy() {
113         mImpl.onDestroy();
114     }
115 
onNavButtonFlagsChanged(@nputMethodNavButtonFlags int navButtonFlags)116     void onNavButtonFlagsChanged(@InputMethodNavButtonFlags int navButtonFlags) {
117         mImpl.onNavButtonFlagsChanged(navButtonFlags);
118     }
119 
toDebugString()120     String toDebugString() {
121         return mImpl.toDebugString();
122     }
123 
124     private static final class Impl implements Callback, Window.DecorCallback {
125         private static final int DEFAULT_COLOR_ADAPT_TRANSITION_TIME = 1700;
126 
127         // Copied from com.android.systemui.animation.Interpolators#LEGACY_DECELERATE
128         private static final Interpolator LEGACY_DECELERATE =
129                 new PathInterpolator(0f, 0f, 0.2f, 1f);
130 
131         @NonNull
132         private final InputMethodService mService;
133 
134         private boolean mDestroyed = false;
135 
136         private boolean mImeDrawsImeNavBar;
137 
138         @Nullable
139         private NavigationBarFrame mNavigationBarFrame;
140         @Nullable
141         Insets mLastInsets;
142 
143         private boolean mShouldShowImeSwitcherWhenImeIsShown;
144 
145         @Appearance
146         private int mAppearance;
147 
148         @FloatRange(from = 0.0f, to = 1.0f)
149         private float mDarkIntensity;
150 
151         @Nullable
152         private ValueAnimator mTintAnimator;
153 
154         private boolean mDrawLegacyNavigationBarBackground;
155 
156         private final Rect mTempRect = new Rect();
157         private final int[] mTempPos = new int[2];
158 
Impl(@onNull InputMethodService inputMethodService)159         Impl(@NonNull InputMethodService inputMethodService) {
160             mService = inputMethodService;
161         }
162 
163         @Nullable
getSystemInsets()164         private Insets getSystemInsets() {
165             if (mService.mWindow == null) {
166                 return null;
167             }
168             final View decorView = mService.mWindow.getWindow().getDecorView();
169             if (decorView == null) {
170                 return null;
171             }
172             final WindowInsets windowInsets = decorView.getRootWindowInsets();
173             if (windowInsets == null) {
174                 return null;
175             }
176             final Insets stableBarInsets =
177                     windowInsets.getInsetsIgnoringVisibility(WindowInsets.Type.systemBars());
178             return Insets.min(windowInsets.getInsets(WindowInsets.Type.systemBars()
179                     | WindowInsets.Type.displayCutout()), stableBarInsets);
180         }
181 
installNavigationBarFrameIfNecessary()182         private void installNavigationBarFrameIfNecessary() {
183             if (!mImeDrawsImeNavBar) {
184                 return;
185             }
186             if (mNavigationBarFrame != null) {
187                 return;
188             }
189             final View rawDecorView = mService.mWindow.getWindow().getDecorView();
190             if (!(rawDecorView instanceof ViewGroup)) {
191                 return;
192             }
193             final ViewGroup decorView = (ViewGroup) rawDecorView;
194             mNavigationBarFrame = decorView.findViewByPredicate(
195                     NavigationBarFrame.class::isInstance);
196             final Insets systemInsets = getSystemInsets();
197             if (mNavigationBarFrame == null) {
198                 mNavigationBarFrame = new NavigationBarFrame(mService);
199                 LayoutInflater.from(mService).inflate(
200                         com.android.internal.R.layout.input_method_navigation_bar,
201                         mNavigationBarFrame);
202                 if (systemInsets != null) {
203                     decorView.addView(mNavigationBarFrame, new FrameLayout.LayoutParams(
204                             ViewGroup.LayoutParams.MATCH_PARENT,
205                             systemInsets.bottom, Gravity.BOTTOM));
206                     mLastInsets = systemInsets;
207                 } else {
208                     decorView.addView(mNavigationBarFrame);
209                 }
210                 final NavigationBarView navigationBarView = mNavigationBarFrame.findViewByPredicate(
211                         NavigationBarView.class::isInstance);
212                 if (navigationBarView != null) {
213                     // TODO(b/213337792): Support InputMethodService#setBackDisposition().
214                     // TODO(b/213337792): Set NAVIGATION_HINT_IME_SHOWN only when necessary.
215                     final int hints = StatusBarManager.NAVIGATION_HINT_BACK_ALT
216                             | (mShouldShowImeSwitcherWhenImeIsShown
217                                     ? StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_SHOWN
218                                     : 0);
219                     navigationBarView.setNavigationIconHints(hints);
220                 }
221             } else {
222                 mNavigationBarFrame.setLayoutParams(new FrameLayout.LayoutParams(
223                         ViewGroup.LayoutParams.MATCH_PARENT, systemInsets.bottom, Gravity.BOTTOM));
224                 mLastInsets = systemInsets;
225             }
226 
227             if (mDrawLegacyNavigationBarBackground) {
228                 mNavigationBarFrame.setBackgroundColor(Color.BLACK);
229             } else {
230                 mNavigationBarFrame.setBackground(null);
231             }
232 
233             setIconTintInternal(calculateTargetDarkIntensity(mAppearance,
234                     mDrawLegacyNavigationBarBackground));
235 
236             if (ENABLE_HIDE_IME_CAPTION_BAR) {
237                 mNavigationBarFrame.setOnApplyWindowInsetsListener((view, insets) -> {
238                     if (mNavigationBarFrame != null) {
239                         boolean visible = insets.isVisible(captionBar());
240                         mNavigationBarFrame.setVisibility(visible ? View.VISIBLE : View.GONE);
241                     }
242                     return view.onApplyWindowInsets(insets);
243                 });
244             }
245         }
246 
uninstallNavigationBarFrameIfNecessary()247         private void uninstallNavigationBarFrameIfNecessary() {
248             if (mNavigationBarFrame == null) {
249                 return;
250             }
251             final ViewParent parent = mNavigationBarFrame.getParent();
252             if (parent instanceof ViewGroup) {
253                 ((ViewGroup) parent).removeView(mNavigationBarFrame);
254             }
255             if (ENABLE_HIDE_IME_CAPTION_BAR) {
256                 mNavigationBarFrame.setOnApplyWindowInsetsListener(null);
257             }
258             mNavigationBarFrame = null;
259         }
260 
261         @Override
updateTouchableInsets(@onNull InputMethodService.Insets originalInsets, @NonNull ViewTreeObserver.InternalInsetsInfo dest)262         public void updateTouchableInsets(@NonNull InputMethodService.Insets originalInsets,
263                 @NonNull ViewTreeObserver.InternalInsetsInfo dest) {
264             if (!mImeDrawsImeNavBar || mNavigationBarFrame == null) {
265                 return;
266             }
267 
268             final Insets systemInsets = getSystemInsets();
269             if (systemInsets != null) {
270                 final Window window = mService.mWindow.getWindow();
271                 final View decor = window.getDecorView();
272 
273                 // If the extract view is shown, everything is touchable, so no need to update
274                 // touchable insets, but we still update normal insets below.
275                 if (!mService.isExtractViewShown()) {
276                     Region touchableRegion = null;
277                     final View inputFrame = mService.mInputFrame;
278                     switch (originalInsets.touchableInsets) {
279                         case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME:
280                             if (inputFrame.getVisibility() == View.VISIBLE) {
281                                 inputFrame.getLocationInWindow(mTempPos);
282                                 mTempRect.set(mTempPos[0], mTempPos[1],
283                                         mTempPos[0] + inputFrame.getWidth(),
284                                         mTempPos[1] + inputFrame.getHeight());
285                                 touchableRegion = new Region(mTempRect);
286                             }
287                             break;
288                         case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT:
289                             if (inputFrame.getVisibility() == View.VISIBLE) {
290                                 inputFrame.getLocationInWindow(mTempPos);
291                                 mTempRect.set(mTempPos[0], originalInsets.contentTopInsets,
292                                         mTempPos[0] + inputFrame.getWidth(),
293                                         mTempPos[1] + inputFrame.getHeight());
294                                 touchableRegion = new Region(mTempRect);
295                             }
296                             break;
297                         case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE:
298                             if (inputFrame.getVisibility() == View.VISIBLE) {
299                                 inputFrame.getLocationInWindow(mTempPos);
300                                 mTempRect.set(mTempPos[0], originalInsets.visibleTopInsets,
301                                         mTempPos[0] + inputFrame.getWidth(),
302                                         mTempPos[1] + inputFrame.getHeight());
303                                 touchableRegion = new Region(mTempRect);
304                             }
305                             break;
306                         case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION:
307                             touchableRegion = new Region();
308                             touchableRegion.set(originalInsets.touchableRegion);
309                             break;
310                     }
311                     // Hereafter "mTempRect" means a navigation bar rect.
312                     mTempRect.set(decor.getLeft(), decor.getBottom() - systemInsets.bottom,
313                             decor.getRight(), decor.getBottom());
314                     if (touchableRegion == null) {
315                         touchableRegion = new Region(mTempRect);
316                     } else {
317                         touchableRegion.union(mTempRect);
318                     }
319 
320                     dest.touchableRegion.set(touchableRegion);
321                     dest.setTouchableInsets(
322                             ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
323                 }
324 
325                 // TODO(b/215443343): See if we can use View#OnLayoutChangeListener().
326                 // TODO(b/215443343): See if we can replace DecorView#mNavigationColorViewState.view
327                 boolean zOrderChanged = false;
328                 if (decor instanceof ViewGroup) {
329                     ViewGroup decorGroup = (ViewGroup) decor;
330                     final View navbarBackgroundView = window.getNavigationBarBackgroundView();
331                     zOrderChanged = navbarBackgroundView != null
332                             && decorGroup.indexOfChild(navbarBackgroundView)
333                             > decorGroup.indexOfChild(mNavigationBarFrame);
334                 }
335                 final boolean insetChanged = !Objects.equals(systemInsets, mLastInsets);
336                 if (zOrderChanged || insetChanged) {
337                     scheduleRelayout();
338                 }
339             }
340         }
341 
scheduleRelayout()342         private void scheduleRelayout() {
343             // Capture the current frame object in case the object is replaced or cleared later.
344             final NavigationBarFrame frame = mNavigationBarFrame;
345             frame.post(() -> {
346                 if (mDestroyed) {
347                     return;
348                 }
349                 if (!frame.isAttachedToWindow()) {
350                     return;
351                 }
352                 final Window window = mService.mWindow.getWindow();
353                 if (window == null) {
354                     return;
355                 }
356                 final View decor = window.peekDecorView();
357                 if (decor == null) {
358                     return;
359                 }
360                 if (!(decor instanceof ViewGroup)) {
361                     return;
362                 }
363                 final ViewGroup decorGroup = (ViewGroup) decor;
364                 final Insets currentSystemInsets = getSystemInsets();
365                 if (!Objects.equals(currentSystemInsets, mLastInsets)) {
366                     frame.setLayoutParams(new FrameLayout.LayoutParams(
367                             ViewGroup.LayoutParams.MATCH_PARENT,
368                             currentSystemInsets.bottom, Gravity.BOTTOM));
369                     mLastInsets = currentSystemInsets;
370                 }
371                 final View navbarBackgroundView =
372                         window.getNavigationBarBackgroundView();
373                 if (navbarBackgroundView != null
374                         && decorGroup.indexOfChild(navbarBackgroundView)
375                         > decorGroup.indexOfChild(frame)) {
376                     decorGroup.bringChildToFront(frame);
377                 }
378             });
379         }
380 
381         @Override
onSoftInputWindowCreated(@onNull SoftInputWindow softInputWindow)382         public void onSoftInputWindowCreated(@NonNull SoftInputWindow softInputWindow) {
383             final Window window = softInputWindow.getWindow();
384             mAppearance = window.getSystemBarAppearance();
385             window.setDecorCallback(this);
386         }
387 
388         @Override
onViewInitialized()389         public void onViewInitialized() {
390             if (mDestroyed) {
391                 return;
392             }
393             installNavigationBarFrameIfNecessary();
394         }
395 
396         @Override
onDestroy()397         public void onDestroy() {
398             if (mDestroyed) {
399                 return;
400             }
401             if (mTintAnimator != null) {
402                 mTintAnimator.cancel();
403                 mTintAnimator = null;
404             }
405             mDestroyed = true;
406         }
407 
408         @Override
onWindowShown()409         public void onWindowShown() {
410             if (mDestroyed || !mImeDrawsImeNavBar || mNavigationBarFrame == null) {
411                 return;
412             }
413             final Insets systemInsets = getSystemInsets();
414             if (systemInsets != null) {
415                 if (!Objects.equals(systemInsets, mLastInsets)) {
416                     mNavigationBarFrame.setLayoutParams(new NavigationBarFrame.LayoutParams(
417                             ViewGroup.LayoutParams.MATCH_PARENT,
418                             systemInsets.bottom, Gravity.BOTTOM));
419                     mLastInsets = systemInsets;
420                 }
421                 final Window window = mService.mWindow.getWindow();
422                 View rawDecorView = window.getDecorView();
423                 if (rawDecorView instanceof ViewGroup) {
424                     final ViewGroup decor = (ViewGroup) rawDecorView;
425                     final View navbarBackgroundView = window.getNavigationBarBackgroundView();
426                     if (navbarBackgroundView != null
427                             && decor.indexOfChild(navbarBackgroundView)
428                             > decor.indexOfChild(mNavigationBarFrame)) {
429                         decor.bringChildToFront(mNavigationBarFrame);
430                     }
431                 }
432                 if (!ENABLE_HIDE_IME_CAPTION_BAR) {
433                     mNavigationBarFrame.setVisibility(View.VISIBLE);
434                 }
435             }
436         }
437 
438         @Override
onNavButtonFlagsChanged(@nputMethodNavButtonFlags int navButtonFlags)439         public void onNavButtonFlagsChanged(@InputMethodNavButtonFlags int navButtonFlags) {
440             if (mDestroyed) {
441                 return;
442             }
443 
444             final boolean imeDrawsImeNavBar =
445                     (navButtonFlags & InputMethodNavButtonFlags.IME_DRAWS_IME_NAV_BAR) != 0;
446             final boolean shouldShowImeSwitcherWhenImeIsShown =
447                     (navButtonFlags & InputMethodNavButtonFlags.SHOW_IME_SWITCHER_WHEN_IME_IS_SHOWN)
448                     != 0;
449 
450             mImeDrawsImeNavBar = imeDrawsImeNavBar;
451             final boolean prevShouldShowImeSwitcherWhenImeIsShown =
452                     mShouldShowImeSwitcherWhenImeIsShown;
453             mShouldShowImeSwitcherWhenImeIsShown = shouldShowImeSwitcherWhenImeIsShown;
454 
455             if (ENABLE_HIDE_IME_CAPTION_BAR) {
456                 mService.mWindow.getWindow().getDecorView().getWindowInsetsController()
457                         .setImeCaptionBarInsetsHeight(getImeCaptionBarHeight());
458             }
459 
460             if (imeDrawsImeNavBar) {
461                 installNavigationBarFrameIfNecessary();
462                 if (mNavigationBarFrame == null) {
463                     return;
464                 }
465                 if (mShouldShowImeSwitcherWhenImeIsShown
466                         == prevShouldShowImeSwitcherWhenImeIsShown) {
467                     return;
468                 }
469                 final NavigationBarView navigationBarView = mNavigationBarFrame.findViewByPredicate(
470                         NavigationBarView.class::isInstance);
471                 if (navigationBarView == null) {
472                     return;
473                 }
474                 final int hints = StatusBarManager.NAVIGATION_HINT_BACK_ALT
475                         | (shouldShowImeSwitcherWhenImeIsShown
476                                 ? StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_SHOWN : 0);
477                 navigationBarView.setNavigationIconHints(hints);
478             } else {
479                 uninstallNavigationBarFrameIfNecessary();
480             }
481         }
482 
483         @Override
onSystemBarAppearanceChanged(@ppearance int appearance)484         public void onSystemBarAppearanceChanged(@Appearance int appearance) {
485             if (mDestroyed) {
486                 return;
487             }
488 
489             mAppearance = appearance;
490 
491             if (mNavigationBarFrame == null) {
492                 return;
493             }
494 
495             final float targetDarkIntensity = calculateTargetDarkIntensity(mAppearance,
496                     mDrawLegacyNavigationBarBackground);
497 
498             if (mTintAnimator != null) {
499                 mTintAnimator.cancel();
500             }
501             mTintAnimator = ValueAnimator.ofFloat(mDarkIntensity, targetDarkIntensity);
502             mTintAnimator.addUpdateListener(
503                     animation -> setIconTintInternal((Float) animation.getAnimatedValue()));
504             mTintAnimator.setDuration(DEFAULT_COLOR_ADAPT_TRANSITION_TIME);
505             mTintAnimator.setStartDelay(0);
506             mTintAnimator.setInterpolator(LEGACY_DECELERATE);
507             mTintAnimator.start();
508         }
509 
setIconTintInternal(float darkIntensity)510         private void setIconTintInternal(float darkIntensity) {
511             mDarkIntensity = darkIntensity;
512             if (mNavigationBarFrame == null) {
513                 return;
514             }
515             final NavigationBarView navigationBarView =
516                     mNavigationBarFrame.findViewByPredicate(NavigationBarView.class::isInstance);
517             if (navigationBarView == null) {
518                 return;
519             }
520             navigationBarView.setDarkIntensity(darkIntensity);
521         }
522 
523         @FloatRange(from = 0.0f, to = 1.0f)
calculateTargetDarkIntensity(@ppearance int appearance, boolean drawLegacyNavigationBarBackground)524         private static float calculateTargetDarkIntensity(@Appearance int appearance,
525                 boolean drawLegacyNavigationBarBackground) {
526             final boolean lightNavBar = !drawLegacyNavigationBarBackground
527                     && (appearance & APPEARANCE_LIGHT_NAVIGATION_BARS) != 0;
528             return lightNavBar ? 1.0f : 0.0f;
529         }
530 
531         @Override
onDrawLegacyNavigationBarBackgroundChanged( boolean drawLegacyNavigationBarBackground)532         public boolean onDrawLegacyNavigationBarBackgroundChanged(
533                 boolean drawLegacyNavigationBarBackground) {
534             if (mDestroyed) {
535                 return false;
536             }
537 
538             if (drawLegacyNavigationBarBackground != mDrawLegacyNavigationBarBackground) {
539                 mDrawLegacyNavigationBarBackground = drawLegacyNavigationBarBackground;
540                 if (mNavigationBarFrame != null) {
541                     if (mDrawLegacyNavigationBarBackground) {
542                         mNavigationBarFrame.setBackgroundColor(Color.BLACK);
543                     } else {
544                         mNavigationBarFrame.setBackground(null);
545                     }
546                     scheduleRelayout();
547                 }
548                 onSystemBarAppearanceChanged(mAppearance);
549             }
550             return drawLegacyNavigationBarBackground;
551         }
552 
553         /**
554          * Returns the height of the IME caption bar if this should be shown, or {@code 0} instead.
555          */
getImeCaptionBarHeight()556         private int getImeCaptionBarHeight() {
557             return mImeDrawsImeNavBar
558                     ? mService.getResources().getDimensionPixelSize(
559                             com.android.internal.R.dimen.navigation_bar_frame_height)
560                     : 0;
561         }
562 
563         @Override
toDebugString()564         public String toDebugString() {
565             return "{mImeDrawsImeNavBar=" + mImeDrawsImeNavBar
566                     + " mNavigationBarFrame=" + mNavigationBarFrame
567                     + " mShouldShowImeSwitcherWhenImeIsShown="
568                     + mShouldShowImeSwitcherWhenImeIsShown
569                     + " mAppearance=0x" + Integer.toHexString(mAppearance)
570                     + " mDarkIntensity=" + mDarkIntensity
571                     + " mDrawLegacyNavigationBarBackground=" + mDrawLegacyNavigationBarBackground
572                     + "}";
573         }
574     }
575 }
576