blob: 977a307a3d9571ae6e1f1c1ec9bb447e39076e38 [file] [log] [blame]
/*
* 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 com.android.systemui.ScreenDecorations.DisplayCutoutView.boundsFromDirection;
import static java.lang.Float.isNaN;
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.EventLog;
import android.util.Pair;
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.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.util.leak.RotationUtils;
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 final CommandQueue mCommandQueue;
StatusBar mBar;
boolean mIsFullyOpenedPanel = false;
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 mRotationOrientation = -1;
@Nullable
private View mCenterIconSpace;
@Nullable
private View mCutoutSpace;
@Nullable
private DisplayCutout mDisplayCutout;
private int mStatusBarHeight;
/**
* Draw this many pixels into the left/right side of the cutout to optimally use the space
*/
private int mCutoutSideNudge = 0;
private boolean mHeadsUpVisible;
private int mRoundedCornerPadding = 0;
public PhoneStatusBarView(Context context, AttributeSet attrs) {
super(context, attrs);
mCommandQueue = Dependency.get(CommandQueue.class);
}
public void setBar(StatusBar bar) {
mBar = bar;
}
public void setScrimController(ScrimController scrimController) {
mScrimController = scrimController;
}
@Override
public void onFinishInflate() {
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()) {
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()) {
updateLayoutForCutout();
requestLayout();
}
}
@Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
if (updateOrientationAndCutout()) {
updateLayoutForCutout();
requestLayout();
}
return super.onApplyWindowInsets(insets);
}
/**
* @return boolean indicating if we need to update the cutout location / margins
*/
private boolean updateOrientationAndCutout() {
boolean changed = false;
int newRotation = RotationUtils.getExactRotation(mContext);
if (newRotation != mRotationOrientation) {
changed = true;
mRotationOrientation = newRotation;
}
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.getView().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 (isNaN(minFraction)) {
throw new IllegalArgumentException("minFraction cannot be NaN");
}
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().onStatusBarPanelStateChanged();
}
}
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);
mRoundedCornerPadding = getResources().getDimensionPixelSize(
R.dimen.rounded_corner_content_padding);
updateStatusBarHeight();
}
private void updateStatusBarHeight() {
final int waterfallTopInset =
mDisplayCutout == null ? 0 : mDisplayCutout.getWaterfallInsets().top;
ViewGroup.LayoutParams layoutParams = getLayoutParams();
mStatusBarHeight = getResources().getDimensionPixelSize(R.dimen.status_bar_height);
layoutParams.height = mStatusBarHeight - waterfallTopInset;
setLayoutParams(layoutParams);
}
private void updateLayoutForCutout() {
updateStatusBarHeight();
updateCutoutLocation(StatusBarWindowView.cornerCutoutMargins(mDisplayCutout, getDisplay()));
updateSafeInsets(StatusBarWindowView.statusBarCornerCutoutMargins(mDisplayCutout,
getDisplay(), mRotationOrientation, mStatusBarHeight));
}
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() || 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.
Pair<Integer, Integer> padding =
StatusBarWindowView.paddingNeededForCutoutAndRoundedCorner(
mDisplayCutout, cornerCutoutMargins, mRoundedCornerPadding);
setPadding(padding.first, getPaddingTop(), padding.second, getPaddingBottom());
}
public void setHeadsUpVisible(boolean headsUpVisible) {
mHeadsUpVisible = headsUpVisible;
updateVisibility();
}
@Override
protected boolean shouldPanelBeVisible() {
return mHeadsUpVisible || super.shouldPanelBeVisible();
}
}