blob: b1812125535343023b1db253870df7cb325b0b26 [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 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.view.DisplayCutout;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
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.statusbar.policy.DarkIconDispatcher;
import com.android.systemui.statusbar.policy.DarkIconDispatcher.DarkReceiver;
import com.android.systemui.util.leak.RotationUtils;
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;
StatusBar mBar;
boolean mIsFullyOpenedPanel = false;
private final PhoneStatusBarTransitions mBarTransitions;
private ScrimController mScrimController;
private float mMinFraction;
private float mPanelFraction;
private Runnable mHideExpandedRunnable = new Runnable() {
@Override
public void run() {
if (mPanelFraction == 0.0f) {
mBar.makeExpandedInvisible();
}
}
};
private DarkReceiver mBattery;
private int mLastOrientation;
@Nullable
private View mCutoutSpace;
@Nullable
private DisplayCutout mDisplayCutout;
public PhoneStatusBarView(Context context, AttributeSet attrs) {
super(context, attrs);
mBarTransitions = new PhoneStatusBarTransitions(this);
}
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);
}
@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)) {
postUpdateLayoutForCutout();
}
}
@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)) {
postUpdateLayoutForCutout();
}
}
/**
*
* @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 (mDisplayCutout == null) {
DisplayCutout cutout = getRootWindowInsets().getDisplayCutout();
if (cutout != null) {
changed = true;
mDisplayCutout = cutout;
}
}
return changed;
}
@Override
public boolean panelEnabled() {
return mBar.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);
mPanelFraction = frac;
updateScrimFraction();
}
private void updateScrimFraction() {
float scrimFraction = mPanelFraction;
if (mMinFraction < 1.0f) {
scrimFraction = Math.max((mPanelFraction - mMinFraction) / (1.0f - mMinFraction),
0);
}
mScrimController.setPanelExpansion(scrimFraction);
}
public void onDensityOrFontScaleChanged() {
ViewGroup.LayoutParams layoutParams = getLayoutParams();
layoutParams.height = getResources().getDimensionPixelSize(
R.dimen.status_bar_height);
setLayoutParams(layoutParams);
}
private void updateLayoutForCutout() {
updateCutoutLocation();
updateSafeInsets();
}
private void postUpdateLayoutForCutout() {
Runnable r = new Runnable() {
@Override
public void run() {
updateLayoutForCutout();
}
};
// Let the cutout emulation draw first
postDelayed(r, 0);
}
private void updateCutoutLocation() {
// Not all layouts have a cutout (e.g., Car)
if (mCutoutSpace == null) {
return;
}
if (mDisplayCutout == null || mDisplayCutout.isEmpty()
|| mLastOrientation != ORIENTATION_PORTRAIT) {
mCutoutSpace.setVisibility(View.GONE);
return;
}
mCutoutSpace.setVisibility(View.VISIBLE);
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mCutoutSpace.getLayoutParams();
lp.width = mDisplayCutout.getBoundingRect().width();
lp.height = mDisplayCutout.getBoundingRect().height();
}
private void updateSafeInsets() {
// 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()) {
lp.leftMargin = 0;
lp.rightMargin = 0;
return;
}
int leftMargin = 0;
int rightMargin = 0;
switch (RotationUtils.getRotation(getContext())) {
/*
* Landscape: <-|
* Seascape: |->
*/
case RotationUtils.ROTATION_LANDSCAPE:
leftMargin = getDisplayCutoutHeight();
break;
case RotationUtils.ROTATION_SEASCAPE:
rightMargin = getDisplayCutoutHeight();
break;
default:
break;
}
lp.leftMargin = leftMargin;
lp.rightMargin = rightMargin;
}
//TODO: Find a better way
private int getDisplayCutoutHeight() {
if (mDisplayCutout == null || mDisplayCutout.isEmpty()) {
return 0;
}
Rect r = mDisplayCutout.getBoundingRect();
return r.bottom - r.top;
}
}