blob: 68eba50984d66dac216d6537a94427fc0fb197cf [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 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 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;
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;
}
}