Initial support for camera cutout in CollapsedStatusBar
- Display a space view exactly covering where the display cutout is - Custom layout for system_icons because this view needs to now layout right-to-left, and hide icons that don't fit. Similar to notification icon container but in the other direction. Still needs dots and to limit the # of icons - When in landscape/seascape, the cutout space disappears and instead the status bar insets itself by the same amount that the window is letterboxed - Moved battery percent back to the right of the battery because the time is no longer on that side Test: adb shell cmd overlay enable com.android.internal.display.cutout.emulation && adb shell stop && adb shell start # to start emulation Bug: 63772836 Change-Id: I8071bfb4983a9d9306df1487cdac956494e80c28
This commit is contained in:
@@ -25,6 +25,6 @@
|
||||
android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Clock"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:gravity="center_vertical|start"
|
||||
android:paddingEnd="@dimen/battery_level_padding_start"
|
||||
android:paddingStart="@dimen/battery_level_padding_start"
|
||||
android:importantForAccessibility="no"
|
||||
/>
|
||||
|
||||
@@ -54,30 +54,47 @@
|
||||
android:layout_height="match_parent"
|
||||
android:layout="@layout/operator_name" />
|
||||
|
||||
<com.android.systemui.statusbar.policy.Clock
|
||||
android:id="@+id/clock"
|
||||
android:textAppearance="@style/TextAppearance.StatusBar.Clock"
|
||||
android:layout_width="wrap_content"
|
||||
<LinearLayout
|
||||
android:layout_height="match_parent"
|
||||
android:singleLine="true"
|
||||
android:paddingStart="@dimen/status_bar_left_clock_starting_padding"
|
||||
android:paddingEnd="@dimen/status_bar_left_clock_end_padding"
|
||||
android:gravity="center_vertical|start"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
>
|
||||
<com.android.systemui.statusbar.policy.Clock
|
||||
android:id="@+id/clock"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:textAppearance="@style/TextAppearance.StatusBar.Clock"
|
||||
android:singleLine="true"
|
||||
android:paddingStart="@dimen/status_bar_left_clock_starting_padding"
|
||||
android:paddingEnd="@dimen/status_bar_left_clock_end_padding"
|
||||
android:gravity="center_vertical|start"
|
||||
/>
|
||||
|
||||
<!-- The alpha of this area is controlled from both PhoneStatusBarTransitions and
|
||||
PhoneStatusBar (DISABLE_NOTIFICATION_ICONS). -->
|
||||
<com.android.systemui.statusbar.AlphaOptimizedFrameLayout
|
||||
android:id="@+id/notification_icon_area"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:orientation="horizontal" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Space should cover the notch (if it exists) and let other views lay out around it -->
|
||||
<android.widget.Space
|
||||
android:id="@+id/cutout_space_view"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center_horizontal|center_vertical"
|
||||
/>
|
||||
|
||||
<!-- The alpha of this area is controlled from both PhoneStatusBarTransitions and
|
||||
PhoneStatusBar (DISABLE_NOTIFICATION_ICONS). -->
|
||||
<com.android.systemui.statusbar.AlphaOptimizedFrameLayout
|
||||
android:id="@+id/notification_icon_area"
|
||||
android:layout_width="0dip"
|
||||
<com.android.keyguard.AlphaOptimizedLinearLayout android:id="@+id/system_icon_area"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:orientation="horizontal" />
|
||||
|
||||
<com.android.keyguard.AlphaOptimizedLinearLayout android:id="@+id/system_icon_area"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical|end"
|
||||
>
|
||||
|
||||
<include layout="@layout/system_icons" />
|
||||
|
||||
@@ -16,12 +16,13 @@
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/system_icons"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<com.android.keyguard.AlphaOptimizedLinearLayout android:id="@+id/statusIcons"
|
||||
android:layout_width="wrap_content"
|
||||
<com.android.systemui.statusbar.phone.StatusIconContainer android:id="@+id/statusIcons"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"/>
|
||||
|
||||
@@ -93,5 +93,8 @@
|
||||
<item type="id" name="action_snooze_long"/>
|
||||
<item type="id" name="action_snooze_longer"/>
|
||||
<item type="id" name="action_snooze_assistant_suggestion_1"/>
|
||||
|
||||
<!-- For StatusBarIconContainer to tag its icon views -->
|
||||
<item type="id" name="status_bar_view_state_tag" />
|
||||
</resources>
|
||||
|
||||
|
||||
@@ -224,7 +224,6 @@ public class BatteryMeterView extends LinearLayout implements
|
||||
if (mTextColor != 0) mBatteryPercentView.setTextColor(mTextColor);
|
||||
updatePercentText();
|
||||
addView(mBatteryPercentView,
|
||||
0,
|
||||
new ViewGroup.LayoutParams(
|
||||
LayoutParams.WRAP_CONTENT,
|
||||
LayoutParams.MATCH_PARENT));
|
||||
|
||||
@@ -16,26 +16,34 @@
|
||||
|
||||
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 com.android.systemui.BatteryMeterView;
|
||||
import com.android.systemui.DejankUtils;
|
||||
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;
|
||||
|
||||
@@ -53,6 +61,10 @@ public class PhoneStatusBarView extends PanelBar {
|
||||
}
|
||||
};
|
||||
private DarkReceiver mBattery;
|
||||
private int mLastOrientation;
|
||||
private View mCutoutSpace;
|
||||
@Nullable
|
||||
private DisplayCutout mDisplayCutout;
|
||||
|
||||
public PhoneStatusBarView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
@@ -76,6 +88,7 @@ public class PhoneStatusBarView extends PanelBar {
|
||||
public void onFinishInflate() {
|
||||
mBarTransitions.init();
|
||||
mBattery = findViewById(R.id.battery);
|
||||
mCutoutSpace = findViewById(R.id.cutout_space_view);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -83,12 +96,51 @@ public class PhoneStatusBarView extends PanelBar {
|
||||
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
|
||||
@@ -214,4 +266,75 @@ public class PhoneStatusBarView extends PanelBar {
|
||||
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() {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ public interface StatusBarIconController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Version of ViewGroup that observers state from the DarkIconDispatcher.
|
||||
* Version of ViewGroup that observes state from the DarkIconDispatcher.
|
||||
*/
|
||||
public static class DarkIconManager extends IconManager {
|
||||
private final DarkIconDispatcher mDarkIconDispatcher;
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A container for Status bar system icons. Limits the number of system icons and handles overflow
|
||||
* similar to NotificationIconController. Can be used to layout nested StatusIconContainers
|
||||
*
|
||||
* Children are expected to be of type StatusBarIconView.
|
||||
*/
|
||||
package com.android.systemui.statusbar.phone;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import android.view.View;
|
||||
import com.android.keyguard.AlphaOptimizedLinearLayout;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.statusbar.StatusBarIconView;
|
||||
import com.android.systemui.statusbar.stack.ViewState;
|
||||
|
||||
public class StatusIconContainer extends AlphaOptimizedLinearLayout {
|
||||
|
||||
private static final String TAG = "StatusIconContainer";
|
||||
private static final int MAX_ICONS = 5;
|
||||
private static final int MAX_DOTS = 3;
|
||||
|
||||
public StatusIconContainer(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
||||
float midY = getHeight() / 2.0f;
|
||||
|
||||
// Layout all child views so that we can move them around later
|
||||
for (int i = 0; i < getChildCount(); i++) {
|
||||
View child = getChildAt(i);
|
||||
int width = child.getMeasuredWidth();
|
||||
int height = child.getMeasuredHeight();
|
||||
int top = (int) (midY - height / 2.0f);
|
||||
child.layout(0, top, width, top + height);
|
||||
}
|
||||
|
||||
resetViewStates();
|
||||
calculateIconTranslations();
|
||||
applyIconStates();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
final int count = getChildCount();
|
||||
// Measure all children so that they report the correct width
|
||||
for (int i = 0; i < count; i++) {
|
||||
measureChild(getChildAt(i), widthMeasureSpec, heightMeasureSpec);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewAdded(View child) {
|
||||
super.onViewAdded(child);
|
||||
ViewState vs = new ViewState();
|
||||
child.setTag(R.id.status_bar_view_state_tag, vs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewRemoved(View child) {
|
||||
super.onViewRemoved(child);
|
||||
child.setTag(R.id.status_bar_view_state_tag, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Layout is happening from end -> start
|
||||
*/
|
||||
private void calculateIconTranslations() {
|
||||
float translationX = getWidth();
|
||||
float contentStart = getPaddingStart();
|
||||
int childCount = getChildCount();
|
||||
// Underflow === don't show content until that index
|
||||
int firstUnderflowIndex = -1;
|
||||
android.util.Log.d(TAG, "calculateIconTransitions: start=" + translationX);
|
||||
|
||||
//TODO: Dots
|
||||
for (int i = childCount - 1; i >= 0; i--) {
|
||||
View child = getChildAt(i);
|
||||
if (!(child instanceof StatusBarIconView)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ViewState childState = getViewStateFromChild(child);
|
||||
if (childState == null ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Rely on StatusBarIcon for truth about visibility
|
||||
if (!((StatusBarIconView) child).getStatusBarIcon().visible) {
|
||||
childState.hidden = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
childState.xTranslation = translationX - child.getWidth();
|
||||
|
||||
if (childState.xTranslation < contentStart) {
|
||||
if (firstUnderflowIndex == -1) {
|
||||
firstUnderflowIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
translationX -= child.getWidth();
|
||||
}
|
||||
|
||||
if (firstUnderflowIndex != -1) {
|
||||
for (int i = 0; i <= firstUnderflowIndex; i++) {
|
||||
View child = getChildAt(i);
|
||||
ViewState vs = getViewStateFromChild(child);
|
||||
if (vs != null) {
|
||||
vs.hidden = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void applyIconStates() {
|
||||
for (int i = 0; i < getChildCount(); i++) {
|
||||
View child = getChildAt(i);
|
||||
ViewState vs = getViewStateFromChild(child);
|
||||
if (vs != null) {
|
||||
vs.applyToView(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void resetViewStates() {
|
||||
for (int i = 0; i < getChildCount(); i++) {
|
||||
View child = getChildAt(i);
|
||||
ViewState vs = getViewStateFromChild(child);
|
||||
if (vs == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
vs.initFrom(child);
|
||||
vs.alpha = 1.0f;
|
||||
if (child instanceof StatusBarIconView) {
|
||||
vs.hidden = !((StatusBarIconView)child).getStatusBarIcon().visible;
|
||||
} else {
|
||||
vs.hidden = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static @Nullable ViewState getViewStateFromChild(View child) {
|
||||
return (ViewState) child.getTag(R.id.status_bar_view_state_tag);
|
||||
}
|
||||
}
|
||||
@@ -74,6 +74,8 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
|
||||
|
||||
assertEquals(View.VISIBLE, mFragment.getView().findViewById(R.id.system_icon_area)
|
||||
.getVisibility());
|
||||
assertEquals(View.VISIBLE, mFragment.getView().findViewById(R.id.clock)
|
||||
.getVisibility());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -87,11 +89,15 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
|
||||
|
||||
assertEquals(View.INVISIBLE, mFragment.getView().findViewById(R.id.system_icon_area)
|
||||
.getVisibility());
|
||||
assertEquals(View.INVISIBLE, mFragment.getView().findViewById(R.id.clock)
|
||||
.getVisibility());
|
||||
|
||||
fragment.disable(0, 0, false);
|
||||
|
||||
assertEquals(View.VISIBLE, mFragment.getView().findViewById(R.id.system_icon_area)
|
||||
.getVisibility());
|
||||
assertEquals(View.VISIBLE, mFragment.getView().findViewById(R.id.clock)
|
||||
.getVisibility());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user