| /* |
| * Copyright (C) 2008 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.content.res.Configuration.ORIENTATION_PORTRAIT; |
| |
| import static com.android.systemui.ScreenDecorations.DisplayCutoutView.boundsFromDirection; |
| import static com.android.systemui.SysUiServiceProvider.getComponent; |
| |
| import android.annotation.Nullable; |
| import android.content.Context; |
| import android.content.res.Configuration; |
| import android.graphics.Point; |
| import android.graphics.Rect; |
| import android.util.AttributeSet; |
| import android.util.EventLog; |
| import android.util.Pair; |
| import android.view.Display; |
| import android.view.DisplayCutout; |
| import android.view.Gravity; |
| import android.view.MotionEvent; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.view.WindowInsets; |
| import android.view.accessibility.AccessibilityEvent; |
| import android.widget.FrameLayout; |
| import android.widget.LinearLayout; |
| |
| import com.android.systemui.Dependency; |
| import com.android.systemui.EventLogTags; |
| import com.android.systemui.R; |
| import com.android.systemui.plugins.DarkIconDispatcher; |
| import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver; |
| import com.android.systemui.statusbar.CommandQueue; |
| import com.android.systemui.statusbar.policy.HeadsUpManager; |
| |
| import java.util.Objects; |
| |
| public class PhoneStatusBarView extends PanelBar { |
| private static final String TAG = "PhoneStatusBarView"; |
| private static final boolean DEBUG = StatusBar.DEBUG; |
| private static final boolean DEBUG_GESTURES = false; |
| private static final int NO_VALUE = Integer.MIN_VALUE; |
| private final CommandQueue mCommandQueue; |
| |
| StatusBar mBar; |
| |
| boolean mIsFullyOpenedPanel = false; |
| private final PhoneStatusBarTransitions mBarTransitions; |
| private ScrimController mScrimController; |
| private float mMinFraction; |
| private Runnable mHideExpandedRunnable = new Runnable() { |
| @Override |
| public void run() { |
| if (mPanelFraction == 0.0f) { |
| mBar.makeExpandedInvisible(); |
| } |
| } |
| }; |
| private DarkReceiver mBattery; |
| private int mLastOrientation; |
| @Nullable |
| private View mCenterIconSpace; |
| @Nullable |
| private View mCutoutSpace; |
| @Nullable |
| private DisplayCutout mDisplayCutout; |
| /** |
| * Draw this many pixels into the left/right side of the cutout to optimally use the space |
| */ |
| private int mCutoutSideNudge = 0; |
| private boolean mHeadsUpVisible; |
| |
| public PhoneStatusBarView(Context context, AttributeSet attrs) { |
| super(context, attrs); |
| |
| mBarTransitions = new PhoneStatusBarTransitions(this); |
| mCommandQueue = getComponent(context, CommandQueue.class); |
| } |
| |
| public BarTransitions getBarTransitions() { |
| return mBarTransitions; |
| } |
| |
| public void setBar(StatusBar bar) { |
| mBar = bar; |
| } |
| |
| public void setScrimController(ScrimController scrimController) { |
| mScrimController = scrimController; |
| } |
| |
| @Override |
| public void onFinishInflate() { |
| mBarTransitions.init(); |
| mBattery = findViewById(R.id.battery); |
| mCutoutSpace = findViewById(R.id.cutout_space_view); |
| mCenterIconSpace = findViewById(R.id.centered_icon_area); |
| |
| updateResources(); |
| } |
| |
| @Override |
| protected void onAttachedToWindow() { |
| super.onAttachedToWindow(); |
| // Always have Battery meters in the status bar observe the dark/light modes. |
| Dependency.get(DarkIconDispatcher.class).addDarkReceiver(mBattery); |
| if (updateOrientationAndCutout(getResources().getConfiguration().orientation)) { |
| updateLayoutForCutout(); |
| } |
| } |
| |
| @Override |
| protected void onDetachedFromWindow() { |
| super.onDetachedFromWindow(); |
| Dependency.get(DarkIconDispatcher.class).removeDarkReceiver(mBattery); |
| mDisplayCutout = null; |
| } |
| |
| @Override |
| protected void onConfigurationChanged(Configuration newConfig) { |
| super.onConfigurationChanged(newConfig); |
| |
| // May trigger cutout space layout-ing |
| if (updateOrientationAndCutout(newConfig.orientation)) { |
| updateLayoutForCutout(); |
| requestLayout(); |
| } |
| } |
| |
| @Override |
| public WindowInsets onApplyWindowInsets(WindowInsets insets) { |
| if (updateOrientationAndCutout(mLastOrientation)) { |
| updateLayoutForCutout(); |
| requestLayout(); |
| } |
| return super.onApplyWindowInsets(insets); |
| } |
| |
| /** |
| * |
| * @param newOrientation may pass NO_VALUE for no change |
| * @return boolean indicating if we need to update the cutout location / margins |
| */ |
| private boolean updateOrientationAndCutout(int newOrientation) { |
| boolean changed = false; |
| if (newOrientation != NO_VALUE) { |
| if (mLastOrientation != newOrientation) { |
| changed = true; |
| mLastOrientation = newOrientation; |
| } |
| } |
| |
| if (!Objects.equals(getRootWindowInsets().getDisplayCutout(), mDisplayCutout)) { |
| changed = true; |
| mDisplayCutout = getRootWindowInsets().getDisplayCutout(); |
| } |
| |
| return changed; |
| } |
| |
| @Override |
| public boolean panelEnabled() { |
| return mCommandQueue.panelsEnabled(); |
| } |
| |
| @Override |
| public boolean onRequestSendAccessibilityEventInternal(View child, AccessibilityEvent event) { |
| if (super.onRequestSendAccessibilityEventInternal(child, event)) { |
| // The status bar is very small so augment the view that the user is touching |
| // with the content of the status bar a whole. This way an accessibility service |
| // may announce the current item as well as the entire content if appropriate. |
| AccessibilityEvent record = AccessibilityEvent.obtain(); |
| onInitializeAccessibilityEvent(record); |
| dispatchPopulateAccessibilityEvent(record); |
| event.appendRecord(record); |
| return true; |
| } |
| return false; |
| } |
| |
| @Override |
| public void onPanelPeeked() { |
| super.onPanelPeeked(); |
| mBar.makeExpandedVisible(false); |
| } |
| |
| @Override |
| public void onPanelCollapsed() { |
| super.onPanelCollapsed(); |
| // Close the status bar in the next frame so we can show the end of the animation. |
| post(mHideExpandedRunnable); |
| mIsFullyOpenedPanel = false; |
| } |
| |
| public void removePendingHideExpandedRunnables() { |
| removeCallbacks(mHideExpandedRunnable); |
| } |
| |
| @Override |
| public void onPanelFullyOpened() { |
| super.onPanelFullyOpened(); |
| if (!mIsFullyOpenedPanel) { |
| mPanel.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); |
| } |
| mIsFullyOpenedPanel = true; |
| } |
| |
| @Override |
| public boolean onTouchEvent(MotionEvent event) { |
| boolean barConsumedEvent = mBar.interceptTouchEvent(event); |
| |
| if (DEBUG_GESTURES) { |
| if (event.getActionMasked() != MotionEvent.ACTION_MOVE) { |
| EventLog.writeEvent(EventLogTags.SYSUI_PANELBAR_TOUCH, |
| event.getActionMasked(), (int) event.getX(), (int) event.getY(), |
| barConsumedEvent ? 1 : 0); |
| } |
| } |
| |
| return barConsumedEvent || super.onTouchEvent(event); |
| } |
| |
| @Override |
| public void onTrackingStarted() { |
| super.onTrackingStarted(); |
| mBar.onTrackingStarted(); |
| mScrimController.onTrackingStarted(); |
| removePendingHideExpandedRunnables(); |
| } |
| |
| @Override |
| public void onClosingFinished() { |
| super.onClosingFinished(); |
| mBar.onClosingFinished(); |
| } |
| |
| @Override |
| public void onTrackingStopped(boolean expand) { |
| super.onTrackingStopped(expand); |
| mBar.onTrackingStopped(expand); |
| } |
| |
| @Override |
| public void onExpandingFinished() { |
| super.onExpandingFinished(); |
| mScrimController.onExpandingFinished(); |
| } |
| |
| @Override |
| public boolean onInterceptTouchEvent(MotionEvent event) { |
| return mBar.interceptTouchEvent(event) || super.onInterceptTouchEvent(event); |
| } |
| |
| @Override |
| public void panelScrimMinFractionChanged(float minFraction) { |
| if (mMinFraction != minFraction) { |
| mMinFraction = minFraction; |
| updateScrimFraction(); |
| } |
| } |
| |
| @Override |
| public void panelExpansionChanged(float frac, boolean expanded) { |
| super.panelExpansionChanged(frac, expanded); |
| updateScrimFraction(); |
| if ((frac == 0 || frac == 1) && mBar.getNavigationBarView() != null) { |
| mBar.getNavigationBarView().onPanelExpandedChange(); |
| } |
| } |
| |
| private void updateScrimFraction() { |
| float scrimFraction = mPanelFraction; |
| if (mMinFraction < 1.0f) { |
| scrimFraction = Math.max((mPanelFraction - mMinFraction) / (1.0f - mMinFraction), |
| 0); |
| } |
| mScrimController.setPanelExpansion(scrimFraction); |
| } |
| |
| public void updateResources() { |
| mCutoutSideNudge = getResources().getDimensionPixelSize( |
| R.dimen.display_cutout_margin_consumption); |
| |
| ViewGroup.LayoutParams layoutParams = getLayoutParams(); |
| layoutParams.height = getResources().getDimensionPixelSize( |
| R.dimen.status_bar_height); |
| setLayoutParams(layoutParams); |
| } |
| |
| private void updateLayoutForCutout() { |
| Pair<Integer, Integer> cornerCutoutMargins = cornerCutoutMargins(mDisplayCutout, |
| getDisplay()); |
| updateCutoutLocation(cornerCutoutMargins); |
| updateSafeInsets(cornerCutoutMargins); |
| } |
| |
| private void updateCutoutLocation(Pair<Integer, Integer> cornerCutoutMargins) { |
| // Not all layouts have a cutout (e.g., Car) |
| if (mCutoutSpace == null) { |
| return; |
| } |
| |
| if (mDisplayCutout == null || mDisplayCutout.isEmpty() |
| || mLastOrientation != ORIENTATION_PORTRAIT || cornerCutoutMargins != null) { |
| mCenterIconSpace.setVisibility(View.VISIBLE); |
| mCutoutSpace.setVisibility(View.GONE); |
| return; |
| } |
| |
| mCenterIconSpace.setVisibility(View.GONE); |
| mCutoutSpace.setVisibility(View.VISIBLE); |
| LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mCutoutSpace.getLayoutParams(); |
| |
| Rect bounds = new Rect(); |
| boundsFromDirection(mDisplayCutout, Gravity.TOP, bounds); |
| |
| bounds.left = bounds.left + mCutoutSideNudge; |
| bounds.right = bounds.right - mCutoutSideNudge; |
| lp.width = bounds.width(); |
| lp.height = bounds.height(); |
| } |
| |
| private void updateSafeInsets(Pair<Integer, Integer> cornerCutoutMargins) { |
| // Depending on our rotation, we may have to work around a cutout in the middle of the view, |
| // or letterboxing from the right or left sides. |
| |
| FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams(); |
| if (mDisplayCutout == null || mDisplayCutout.isEmpty() |
| || mLastOrientation != ORIENTATION_PORTRAIT || cornerCutoutMargins == null) { |
| lp.leftMargin = 0; |
| lp.rightMargin = 0; |
| return; |
| } |
| |
| lp.leftMargin = Math.max(lp.leftMargin, cornerCutoutMargins.first); |
| lp.rightMargin = Math.max(lp.rightMargin, cornerCutoutMargins.second); |
| |
| // If we're already inset enough (e.g. on the status bar side), we can have 0 margin |
| WindowInsets insets = getRootWindowInsets(); |
| int leftInset = insets.getSystemWindowInsetLeft(); |
| int rightInset = insets.getSystemWindowInsetRight(); |
| if (lp.leftMargin <= leftInset) { |
| lp.leftMargin = 0; |
| } |
| if (lp.rightMargin <= rightInset) { |
| lp.rightMargin = 0; |
| } |
| } |
| |
| public static Pair<Integer, Integer> cornerCutoutMargins(DisplayCutout cutout, |
| Display display) { |
| if (cutout == null) { |
| return null; |
| } |
| Point size = new Point(); |
| display.getRealSize(size); |
| |
| Rect bounds = new Rect(); |
| boundsFromDirection(cutout, Gravity.TOP, bounds); |
| |
| if (bounds.left <= 0) { |
| return new Pair<>(bounds.right, 0); |
| } |
| if (bounds.right >= size.x) { |
| return new Pair<>(0, size.x - bounds.left); |
| } |
| return null; |
| } |
| |
| public void setHeadsUpVisible(boolean headsUpVisible) { |
| mHeadsUpVisible = headsUpVisible; |
| updateVisibility(); |
| } |
| |
| @Override |
| protected boolean shouldPanelBeVisible() { |
| return mHeadsUpVisible || super.shouldPanelBeVisible(); |
| } |
| } |