| /* |
| * 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; |
| |
| 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.Nullable; |
| import android.app.Fragment; |
| import android.os.Bundle; |
| import android.os.Parcelable; |
| 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 com.android.systemui.R; |
| import com.android.systemui.animation.Interpolators; |
| import com.android.systemui.plugins.statusbar.StatusBarStateController; |
| import com.android.systemui.statusbar.CommandQueue; |
| import com.android.systemui.statusbar.FeatureFlags; |
| import com.android.systemui.statusbar.StatusBarState; |
| import com.android.systemui.statusbar.events.SystemStatusAnimationCallback; |
| import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; |
| import com.android.systemui.statusbar.phone.StatusBarIconController.DarkIconManager; |
| import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController; |
| import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallListener; |
| import com.android.systemui.statusbar.policy.EncryptionHelper; |
| import com.android.systemui.statusbar.policy.KeyguardStateController; |
| import com.android.systemui.statusbar.policy.NetworkController; |
| import com.android.systemui.statusbar.policy.NetworkController.SignalCallback; |
| |
| import org.jetbrains.annotations.NotNull; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| 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 PhoneStatusBarView mStatusBar; |
| private final StatusBarStateController mStatusBarStateController; |
| private final KeyguardStateController mKeyguardStateController; |
| 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 final StatusBar mStatusBarComponent; |
| private DarkIconManager mDarkIconManager; |
| private View mOperatorNameFrame; |
| private final CommandQueue mCommandQueue; |
| private final OngoingCallController mOngoingCallController; |
| private final SystemStatusAnimationScheduler mAnimationScheduler; |
| private final StatusBarLocationPublisher mLocationPublisher; |
| private final FeatureFlags mFeatureFlags; |
| private final NotificationIconAreaController mNotificationIconAreaController; |
| private final StatusBarIconController mStatusBarIconController; |
| |
| private List<String> mBlockedIcons = new ArrayList<>(); |
| |
| private SignalCallback mSignalCallback = new SignalCallback() { |
| @Override |
| public void setIsAirplaneMode(NetworkController.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); |
| } |
| }; |
| |
| @Inject |
| public CollapsedStatusBarFragment( |
| OngoingCallController ongoingCallController, |
| SystemStatusAnimationScheduler animationScheduler, |
| StatusBarLocationPublisher locationPublisher, |
| NotificationIconAreaController notificationIconAreaController, |
| FeatureFlags featureFlags, |
| StatusBarIconController statusBarIconController, |
| KeyguardStateController keyguardStateController, |
| NetworkController networkController, |
| StatusBarStateController statusBarStateController, |
| StatusBar statusBarComponent, |
| CommandQueue commandQueue |
| ) { |
| mOngoingCallController = ongoingCallController; |
| mAnimationScheduler = animationScheduler; |
| mLocationPublisher = locationPublisher; |
| mNotificationIconAreaController = notificationIconAreaController; |
| mFeatureFlags = featureFlags; |
| mStatusBarIconController = statusBarIconController; |
| mKeyguardStateController = keyguardStateController; |
| mNetworkController = networkController; |
| mStatusBarStateController = statusBarStateController; |
| mStatusBarComponent = statusBarComponent; |
| mCommandQueue = commandQueue; |
| } |
| |
| @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); |
| 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); |
| mBlockedIcons.add(getString(com.android.internal.R.string.status_bar_volume)); |
| mBlockedIcons.add(getString(com.android.internal.R.string.status_bar_alarm_clock)); |
| mBlockedIcons.add(getString(com.android.internal.R.string.status_bar_call_strength)); |
| mDarkIconManager.setBlockList(mBlockedIcons); |
| 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); |
| } |
| |
| @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(); |
| } |
| |
| @Override |
| public void onPause() { |
| super.onPause(); |
| mCommandQueue.removeCallback(this); |
| mStatusBarStateController.removeCallback(this); |
| mOngoingCallController.removeCallback(mOngoingCallListener); |
| } |
| |
| @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); |
| } |
| |
| @Override |
| public void disable(int displayId, int state1, int state2, boolean animate) { |
| if (displayId != getContext().getDisplayId()) { |
| return; |
| } |
| state1 = adjustDisableFlags(state1); |
| 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 = mStatusBarComponent.headsUpShouldBeVisible(); |
| 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() |
| && mStatusBarComponent.getPanelController().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 (!mStatusBar.isClosed() && mStatusBarComponent.hideStatusBarIconsWhenExpanded()) { |
| return true; |
| } |
| if (mStatusBarComponent.hideStatusBarIconsForBouncer()) { |
| return true; |
| } |
| return false; |
| } |
| |
| 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 (!mStatusBar.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 (mOperatorNameFrame != null) { |
| animateHide(mOperatorNameFrame, animate); |
| } |
| } |
| |
| public void showOperatorName(boolean animate) { |
| if (mOperatorNameFrame != null) { |
| animateShow(mOperatorNameFrame, 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); |
| mOperatorNameFrame = stub.inflate(); |
| } |
| } |
| |
| 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); |
| } |
| |
| // 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); |
| } |
| }; |
| } |