blob: 2383c3e2199eda7c2abf2ade7b3f89593f53909a [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package com.android.systemui.statusbar.phone.fragment;
import static android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS;
import static android.app.StatusBarManager.DISABLE_CLOCK;
import static android.app.StatusBarManager.DISABLE_NOTIFICATION_ICONS;
import static android.app.StatusBarManager.DISABLE_ONGOING_CALL_CHIP;
import static android.app.StatusBarManager.DISABLE_SYSTEM_INFO;
import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.ANIMATING_IN;
import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.ANIMATING_OUT;
import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.IDLE;
import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.SHOWING_PERSISTENT_DOT;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Fragment;
import android.database.ContentObserver;
import android.os.Bundle;
import android.os.Parcelable;
import android.provider.Settings;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewStub;
import android.widget.LinearLayout;
import androidx.annotation.VisibleForTesting;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.DisableFlagsLogger.DisableState;
import com.android.systemui.statusbar.OperatorNameView;
import com.android.systemui.statusbar.OperatorNameViewController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.connectivity.IconState;
import com.android.systemui.statusbar.connectivity.NetworkController;
import com.android.systemui.statusbar.connectivity.SignalCallback;
import com.android.systemui.statusbar.events.SystemStatusAnimationCallback;
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
import com.android.systemui.statusbar.phone.NotificationPanelViewController;
import com.android.systemui.statusbar.phone.PhoneStatusBarView;
import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.StatusBarIconController.DarkIconManager;
import com.android.systemui.statusbar.phone.StatusBarLocationPublisher;
import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent;
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallListener;
import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
import com.android.systemui.statusbar.policy.EncryptionHelper;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.settings.SecureSettings;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Executor;
import javax.inject.Inject;
/**
* Contains the collapsed status bar and handles hiding/showing based on disable flags
* and keyguard state. Also manages lifecycle to make sure the views it contains are being
* updated by the StatusBarIconController and DarkIconManager while it is attached.
*/
public class CollapsedStatusBarFragment extends Fragment implements CommandQueue.Callbacks,
StatusBarStateController.StateListener,
SystemStatusAnimationCallback {
public static final String TAG = "CollapsedStatusBarFragment";
private static final String EXTRA_PANEL_STATE = "panel_state";
public static final String STATUS_BAR_ICON_MANAGER_TAG = "status_bar_icon_manager";
public static final int FADE_IN_DURATION = 320;
public static final int FADE_IN_DELAY = 50;
private StatusBarFragmentComponent mStatusBarFragmentComponent;
private PhoneStatusBarView mStatusBar;
private final StatusBarStateController mStatusBarStateController;
private final KeyguardStateController mKeyguardStateController;
private final NotificationPanelViewController mNotificationPanelViewController;
private final NetworkController mNetworkController;
private LinearLayout mSystemIconArea;
private View mClockView;
private View mOngoingCallChip;
private View mNotificationIconAreaInner;
private View mCenteredIconArea;
private int mDisabled1;
private int mDisabled2;
private DarkIconManager mDarkIconManager;
private final StatusBarFragmentComponent.Factory mStatusBarFragmentComponentFactory;
private final CommandQueue mCommandQueue;
private final CollapsedStatusBarFragmentLogger mCollapsedStatusBarFragmentLogger;
private final OperatorNameViewController.Factory mOperatorNameViewControllerFactory;
private final OngoingCallController mOngoingCallController;
private final SystemStatusAnimationScheduler mAnimationScheduler;
private final StatusBarLocationPublisher mLocationPublisher;
private final FeatureFlags mFeatureFlags;
private final NotificationIconAreaController mNotificationIconAreaController;
private final PanelExpansionStateManager mPanelExpansionStateManager;
private final StatusBarIconController mStatusBarIconController;
private final StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
private final SecureSettings mSecureSettings;
private final Executor mMainExecutor;
private List<String> mBlockedIcons = new ArrayList<>();
private SignalCallback mSignalCallback = new SignalCallback() {
@Override
public void setIsAirplaneMode(@NonNull IconState icon) {
mCommandQueue.recomputeDisableFlags(getContext().getDisplayId(), true /* animate */);
}
};
private final OngoingCallListener mOngoingCallListener = new OngoingCallListener() {
@Override
public void onOngoingCallStateChanged(boolean animate) {
disable(getContext().getDisplayId(), mDisabled1, mDisabled2, animate);
}
};
private OperatorNameViewController mOperatorNameViewController;
@Inject
public CollapsedStatusBarFragment(
StatusBarFragmentComponent.Factory statusBarFragmentComponentFactory,
OngoingCallController ongoingCallController,
SystemStatusAnimationScheduler animationScheduler,
StatusBarLocationPublisher locationPublisher,
NotificationIconAreaController notificationIconAreaController,
PanelExpansionStateManager panelExpansionStateManager,
FeatureFlags featureFlags,
StatusBarIconController statusBarIconController,
StatusBarHideIconsForBouncerManager statusBarHideIconsForBouncerManager,
KeyguardStateController keyguardStateController,
NotificationPanelViewController notificationPanelViewController,
NetworkController networkController,
StatusBarStateController statusBarStateController,
CommandQueue commandQueue,
CollapsedStatusBarFragmentLogger collapsedStatusBarFragmentLogger,
OperatorNameViewController.Factory operatorNameViewControllerFactory,
SecureSettings secureSettings,
@Main Executor mainExecutor
) {
mStatusBarFragmentComponentFactory = statusBarFragmentComponentFactory;
mOngoingCallController = ongoingCallController;
mAnimationScheduler = animationScheduler;
mLocationPublisher = locationPublisher;
mNotificationIconAreaController = notificationIconAreaController;
mPanelExpansionStateManager = panelExpansionStateManager;
mFeatureFlags = featureFlags;
mStatusBarIconController = statusBarIconController;
mStatusBarHideIconsForBouncerManager = statusBarHideIconsForBouncerManager;
mKeyguardStateController = keyguardStateController;
mNotificationPanelViewController = notificationPanelViewController;
mNetworkController = networkController;
mStatusBarStateController = statusBarStateController;
mCommandQueue = commandQueue;
mCollapsedStatusBarFragmentLogger = collapsedStatusBarFragmentLogger;
mOperatorNameViewControllerFactory = operatorNameViewControllerFactory;
mSecureSettings = secureSettings;
mMainExecutor = mainExecutor;
}
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.status_bar, container, false);
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mStatusBarFragmentComponent = mStatusBarFragmentComponentFactory.create(this);
mStatusBarFragmentComponent.init();
mStatusBar = (PhoneStatusBarView) view;
View contents = mStatusBar.findViewById(R.id.status_bar_contents);
contents.addOnLayoutChangeListener(mStatusBarLayoutListener);
updateStatusBarLocation(contents.getLeft(), contents.getRight());
if (savedInstanceState != null && savedInstanceState.containsKey(EXTRA_PANEL_STATE)) {
mStatusBar.restoreHierarchyState(
savedInstanceState.getSparseParcelableArray(EXTRA_PANEL_STATE));
}
mDarkIconManager = new DarkIconManager(view.findViewById(R.id.statusIcons), mFeatureFlags);
mDarkIconManager.setShouldLog(true);
updateBlockedIcons();
mStatusBarIconController.addIconGroup(mDarkIconManager);
mSystemIconArea = mStatusBar.findViewById(R.id.system_icon_area);
mClockView = mStatusBar.findViewById(R.id.clock);
mOngoingCallChip = mStatusBar.findViewById(R.id.ongoing_call_chip);
showSystemIconArea(false);
showClock(false);
initEmergencyCryptkeeperText();
initOperatorName();
initNotificationIconArea();
mAnimationScheduler.addCallback(this);
}
@VisibleForTesting
void updateBlockedIcons() {
mBlockedIcons.clear();
// Reload the blocklist from res
List<String> blockList = Arrays.asList(getResources().getStringArray(
R.array.config_collapsed_statusbar_icon_blocklist));
String vibrateIconSlot = getString(com.android.internal.R.string.status_bar_volume);
boolean showVibrateIcon =
mSecureSettings.getInt(Settings.Secure.STATUS_BAR_SHOW_VIBRATE_ICON, 0) == 0;
// Filter out vibrate icon from the blocklist if the setting is on
for (int i = 0; i < blockList.size(); i++) {
if (blockList.get(i).equals(vibrateIconSlot)) {
if (showVibrateIcon) {
mBlockedIcons.add(blockList.get(i));
}
} else {
mBlockedIcons.add(blockList.get(i));
}
}
mMainExecutor.execute(() -> mDarkIconManager.setBlockList(mBlockedIcons));
}
@VisibleForTesting
List<String> getBlockedIcons() {
return mBlockedIcons;
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
SparseArray<Parcelable> states = new SparseArray<>();
mStatusBar.saveHierarchyState(states);
outState.putSparseParcelableArray(EXTRA_PANEL_STATE, states);
}
@Override
public void onResume() {
super.onResume();
mCommandQueue.addCallback(this);
mStatusBarStateController.addCallback(this);
initOngoingCallChip();
mSecureSettings.registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.STATUS_BAR_SHOW_VIBRATE_ICON),
false,
mVolumeSettingObserver);
}
@Override
public void onPause() {
super.onPause();
mCommandQueue.removeCallback(this);
mStatusBarStateController.removeCallback(this);
mOngoingCallController.removeCallback(mOngoingCallListener);
mSecureSettings.unregisterContentObserver(mVolumeSettingObserver);
}
@Override
public void onDestroyView() {
super.onDestroyView();
mStatusBarIconController.removeIconGroup(mDarkIconManager);
mAnimationScheduler.removeCallback(this);
if (mNetworkController.hasEmergencyCryptKeeperText()) {
mNetworkController.removeCallback(mSignalCallback);
}
}
/** Initializes views related to the notification icon area. */
public void initNotificationIconArea() {
ViewGroup notificationIconArea = mStatusBar.findViewById(R.id.notification_icon_area);
mNotificationIconAreaInner =
mNotificationIconAreaController.getNotificationInnerAreaView();
if (mNotificationIconAreaInner.getParent() != null) {
((ViewGroup) mNotificationIconAreaInner.getParent())
.removeView(mNotificationIconAreaInner);
}
notificationIconArea.addView(mNotificationIconAreaInner);
ViewGroup statusBarCenteredIconArea = mStatusBar.findViewById(R.id.centered_icon_area);
mCenteredIconArea = mNotificationIconAreaController.getCenteredNotificationAreaView();
if (mCenteredIconArea.getParent() != null) {
((ViewGroup) mCenteredIconArea.getParent())
.removeView(mCenteredIconArea);
}
statusBarCenteredIconArea.addView(mCenteredIconArea);
// #disable should have already been called, so use the disable values to set visibility.
updateNotificationIconAreaAndCallChip(mDisabled1, false);
}
/**
* Returns the dagger component for this fragment.
*
* TODO(b/205609837): Eventually, the dagger component should encapsulate all status bar
* fragment functionality and we won't need to expose it here anymore.
*/
@Nullable
public StatusBarFragmentComponent getStatusBarFragmentComponent() {
return mStatusBarFragmentComponent;
}
@Override
public void disable(int displayId, int state1, int state2, boolean animate) {
if (displayId != getContext().getDisplayId()) {
return;
}
int state1BeforeAdjustment = state1;
state1 = adjustDisableFlags(state1);
mCollapsedStatusBarFragmentLogger.logDisableFlagChange(
/* new= */ new DisableState(state1BeforeAdjustment, state2),
/* newAfterLocalModification= */ new DisableState(state1, state2));
final int old1 = mDisabled1;
final int diff1 = state1 ^ old1;
final int old2 = mDisabled2;
final int diff2 = state2 ^ old2;
mDisabled1 = state1;
mDisabled2 = state2;
if ((diff1 & DISABLE_SYSTEM_INFO) != 0 || ((diff2 & DISABLE2_SYSTEM_ICONS) != 0)) {
if ((state1 & DISABLE_SYSTEM_INFO) != 0 || ((state2 & DISABLE2_SYSTEM_ICONS) != 0)) {
hideSystemIconArea(animate);
hideOperatorName(animate);
} else {
showSystemIconArea(animate);
showOperatorName(animate);
}
}
// The ongoing call chip and notification icon visibilities are intertwined, so update both
// if either change.
if (((diff1 & DISABLE_ONGOING_CALL_CHIP) != 0)
|| ((diff1 & DISABLE_NOTIFICATION_ICONS) != 0)) {
updateNotificationIconAreaAndCallChip(state1, animate);
}
// The clock may have already been hidden, but we might want to shift its
// visibility to GONE from INVISIBLE or vice versa
if ((diff1 & DISABLE_CLOCK) != 0 || mClockView.getVisibility() != clockHiddenMode()) {
if ((state1 & DISABLE_CLOCK) != 0) {
hideClock(animate);
} else {
showClock(animate);
}
}
}
protected int adjustDisableFlags(int state) {
boolean headsUpVisible =
mStatusBarFragmentComponent.getHeadsUpAppearanceController().shouldBeVisible();
if (headsUpVisible) {
state |= DISABLE_CLOCK;
}
if (!mKeyguardStateController.isLaunchTransitionFadingAway()
&& !mKeyguardStateController.isKeyguardFadingAway()
&& shouldHideNotificationIcons()
&& !(mStatusBarStateController.getState() == StatusBarState.KEYGUARD
&& headsUpVisible)) {
state |= DISABLE_NOTIFICATION_ICONS;
state |= DISABLE_SYSTEM_INFO;
state |= DISABLE_CLOCK;
}
if (mNetworkController != null && EncryptionHelper.IS_DATA_ENCRYPTED) {
if (mNetworkController.hasEmergencyCryptKeeperText()) {
state |= DISABLE_NOTIFICATION_ICONS;
}
if (!mNetworkController.isRadioOn()) {
state |= DISABLE_SYSTEM_INFO;
}
}
// The shelf will be hidden when dozing with a custom clock, we must show notification
// icons in this occasion.
if (mStatusBarStateController.isDozing()
&& mNotificationPanelViewController.hasCustomClock()) {
state |= DISABLE_CLOCK | DISABLE_SYSTEM_INFO;
}
if (mOngoingCallController.hasOngoingCall()) {
state &= ~DISABLE_ONGOING_CALL_CHIP;
} else {
state |= DISABLE_ONGOING_CALL_CHIP;
}
return state;
}
/**
* Updates the visibility of the notification icon area and ongoing call chip based on disabled1
* state.
*/
private void updateNotificationIconAreaAndCallChip(int state1, boolean animate) {
boolean disableNotifications = (state1 & DISABLE_NOTIFICATION_ICONS) != 0;
boolean hasOngoingCall = (state1 & DISABLE_ONGOING_CALL_CHIP) == 0;
// Hide notifications if the disable flag is set or we have an ongoing call.
if (disableNotifications || hasOngoingCall) {
hideNotificationIconArea(animate);
} else {
showNotificationIconArea(animate);
}
// Show the ongoing call chip only if there is an ongoing call *and* notification icons
// are allowed. (The ongoing call chip occupies the same area as the notification icons,
// so if the icons are disabled then the call chip should be, too.)
boolean showOngoingCallChip = hasOngoingCall && !disableNotifications;
if (showOngoingCallChip) {
showOngoingCallChip(animate);
} else {
hideOngoingCallChip(animate);
}
mOngoingCallController.notifyChipVisibilityChanged(showOngoingCallChip);
}
private boolean shouldHideNotificationIcons() {
if (!mPanelExpansionStateManager.isClosed()
&& mNotificationPanelViewController.hideStatusBarIconsWhenExpanded()) {
return true;
}
return mStatusBarHideIconsForBouncerManager.getShouldHideStatusBarIconsForBouncer();
}
private void hideSystemIconArea(boolean animate) {
animateHide(mSystemIconArea, animate);
}
private void showSystemIconArea(boolean animate) {
// Only show the system icon area if we are not currently animating
int state = mAnimationScheduler.getAnimationState();
if (state == IDLE || state == SHOWING_PERSISTENT_DOT) {
animateShow(mSystemIconArea, animate);
}
}
private void hideClock(boolean animate) {
animateHiddenState(mClockView, clockHiddenMode(), animate);
}
private void showClock(boolean animate) {
animateShow(mClockView, animate);
}
/** Hides the ongoing call chip. */
public void hideOngoingCallChip(boolean animate) {
animateHiddenState(mOngoingCallChip, View.GONE, animate);
}
/** Displays the ongoing call chip. */
public void showOngoingCallChip(boolean animate) {
animateShow(mOngoingCallChip, animate);
}
/**
* If panel is expanded/expanding it usually means QS shade is opening, so
* don't set the clock GONE otherwise it'll mess up the animation.
*/
private int clockHiddenMode() {
if (!mPanelExpansionStateManager.isClosed() && !mKeyguardStateController.isShowing()
&& !mStatusBarStateController.isDozing()) {
return View.INVISIBLE;
}
return View.GONE;
}
public void hideNotificationIconArea(boolean animate) {
animateHide(mNotificationIconAreaInner, animate);
animateHide(mCenteredIconArea, animate);
}
public void showNotificationIconArea(boolean animate) {
animateShow(mNotificationIconAreaInner, animate);
animateShow(mCenteredIconArea, animate);
}
public void hideOperatorName(boolean animate) {
if (mOperatorNameViewController != null) {
animateHide(mOperatorNameViewController.getView(), animate);
}
}
public void showOperatorName(boolean animate) {
if (mOperatorNameViewController != null) {
animateShow(mOperatorNameViewController.getView(), animate);
}
}
/**
* Animate a view to INVISIBLE or GONE
*/
private void animateHiddenState(final View v, int state, boolean animate) {
v.animate().cancel();
if (!animate) {
v.setAlpha(0f);
v.setVisibility(state);
return;
}
v.animate()
.alpha(0f)
.setDuration(160)
.setStartDelay(0)
.setInterpolator(Interpolators.ALPHA_OUT)
.withEndAction(() -> v.setVisibility(state));
}
/**
* Hides a view.
*/
private void animateHide(final View v, boolean animate) {
animateHiddenState(v, View.INVISIBLE, animate);
}
/**
* Shows a view, and synchronizes the animation with Keyguard exit animations, if applicable.
*/
private void animateShow(View v, boolean animate) {
v.animate().cancel();
v.setVisibility(View.VISIBLE);
if (!animate) {
v.setAlpha(1f);
return;
}
v.animate()
.alpha(1f)
.setDuration(FADE_IN_DURATION)
.setInterpolator(Interpolators.ALPHA_IN)
.setStartDelay(FADE_IN_DELAY)
// We need to clean up any pending end action from animateHide if we call
// both hide and show in the same frame before the animation actually gets started.
// cancel() doesn't really remove the end action.
.withEndAction(null);
// Synchronize the motion with the Keyguard fading if necessary.
if (mKeyguardStateController.isKeyguardFadingAway()) {
v.animate()
.setDuration(mKeyguardStateController.getKeyguardFadingAwayDuration())
.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN)
.setStartDelay(mKeyguardStateController.getKeyguardFadingAwayDelay())
.start();
}
}
private void initEmergencyCryptkeeperText() {
View emergencyViewStub = mStatusBar.findViewById(R.id.emergency_cryptkeeper_text);
if (mNetworkController.hasEmergencyCryptKeeperText()) {
if (emergencyViewStub != null) {
((ViewStub) emergencyViewStub).inflate();
}
mNetworkController.addCallback(mSignalCallback);
} else if (emergencyViewStub != null) {
ViewGroup parent = (ViewGroup) emergencyViewStub.getParent();
parent.removeView(emergencyViewStub);
}
}
private void initOperatorName() {
if (getResources().getBoolean(R.bool.config_showOperatorNameInStatusBar)) {
ViewStub stub = mStatusBar.findViewById(R.id.operator_name);
mOperatorNameViewController =
mOperatorNameViewControllerFactory.create((OperatorNameView) stub.inflate());
mOperatorNameViewController.init();
}
}
private void initOngoingCallChip() {
mOngoingCallController.addCallback(mOngoingCallListener);
mOngoingCallController.setChipView(mOngoingCallChip);
}
@Override
public void onStateChanged(int newState) { }
@Override
public void onDozingChanged(boolean isDozing) {
disable(getContext().getDisplayId(), mDisabled1, mDisabled2, false /* animate */);
}
@Override
public void onSystemChromeAnimationStart() {
if (mAnimationScheduler.getAnimationState() == ANIMATING_OUT
&& !isSystemIconAreaDisabled()) {
mSystemIconArea.setVisibility(View.VISIBLE);
mSystemIconArea.setAlpha(0f);
}
}
@Override
public void onSystemChromeAnimationEnd() {
// Make sure the system icons are out of the way
if (mAnimationScheduler.getAnimationState() == ANIMATING_IN) {
mSystemIconArea.setVisibility(View.INVISIBLE);
mSystemIconArea.setAlpha(0f);
} else {
if (isSystemIconAreaDisabled()) {
// don't unhide
return;
}
mSystemIconArea.setAlpha(1f);
mSystemIconArea.setVisibility(View.VISIBLE);
}
}
@Override
public void onSystemChromeAnimationUpdate(@NotNull ValueAnimator animator) {
mSystemIconArea.setAlpha((float) animator.getAnimatedValue());
}
private boolean isSystemIconAreaDisabled() {
return (mDisabled1 & DISABLE_SYSTEM_INFO) != 0 || (mDisabled2 & DISABLE2_SYSTEM_ICONS) != 0;
}
private void updateStatusBarLocation(int left, int right) {
int leftMargin = left - mStatusBar.getLeft();
int rightMargin = mStatusBar.getRight() - right;
mLocationPublisher.updateStatusBarMargin(leftMargin, rightMargin);
}
private final ContentObserver mVolumeSettingObserver = new ContentObserver(null) {
@Override
public void onChange(boolean selfChange) {
updateBlockedIcons();
}
};
// Listen for view end changes of PhoneStatusBarView and publish that to the privacy dot
private View.OnLayoutChangeListener mStatusBarLayoutListener =
(view, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
if (left != oldLeft || right != oldRight) {
updateStatusBarLocation(left, right);
}
};
}