From 058c8aee39927bc9f0df35e0274e7aede59d8b56 Mon Sep 17 00:00:00 2001 From: Evan Laird Date: Fri, 12 Jan 2018 14:26:10 -0500 Subject: [PATCH] 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 --- .../res/layout/battery_percentage_view.xml | 2 +- packages/SystemUI/res/layout/status_bar.xml | 53 ++++-- packages/SystemUI/res/layout/system_icons.xml | 7 +- packages/SystemUI/res/values/ids.xml | 3 + .../android/systemui/BatteryMeterView.java | 1 - .../statusbar/phone/PhoneStatusBarView.java | 127 ++++++++++++- .../phone/StatusBarIconController.java | 2 +- .../statusbar/phone/StatusIconContainer.java | 169 ++++++++++++++++++ .../phone/CollapsedStatusBarFragmentTest.java | 6 + 9 files changed, 344 insertions(+), 26 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java diff --git a/packages/SystemUI/res/layout/battery_percentage_view.xml b/packages/SystemUI/res/layout/battery_percentage_view.xml index 59c0957d98cbf..e52aa14abfa9e 100644 --- a/packages/SystemUI/res/layout/battery_percentage_view.xml +++ b/packages/SystemUI/res/layout/battery_percentage_view.xml @@ -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" /> diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml index 17b38cbce8245..8c0b9bab35a0c 100644 --- a/packages/SystemUI/res/layout/status_bar.xml +++ b/packages/SystemUI/res/layout/status_bar.xml @@ -54,30 +54,47 @@ android:layout_height="match_parent" android:layout="@layout/operator_name" /> - + + + + + + + + + - - - - diff --git a/packages/SystemUI/res/layout/system_icons.xml b/packages/SystemUI/res/layout/system_icons.xml index bfa92ad89a1e8..1fafb2fc72d64 100644 --- a/packages/SystemUI/res/layout/system_icons.xml +++ b/packages/SystemUI/res/layout/system_icons.xml @@ -16,12 +16,13 @@ - diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index fed97c57ce09c..edda613f2fbc2 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -93,5 +93,8 @@ + + + diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java index 2fe66a14a41da..8666b0c873e72 100644 --- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java +++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java @@ -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)); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java index 970d1de251d5b..e8b28f21c2c2c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java @@ -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; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java index bcda60ebc62c1..07610ceff7b0f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java @@ -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; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java new file mode 100644 index 0000000000000..1897171b5e547 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java @@ -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); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java index 66524cc2b1935..2edcd01ed4456 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java @@ -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