Move Privacy dots to new DecorProvider
Provide privacy dot information as DecorProvider, and we can reduce the
dependency to privacy dot, and let ScreenDecorations focus on
interactions between DecorProvider and related views life cycle.
Bug: 213291394
Test: atest ScreenDecorationsTest
PrivacyDotDecorProviderFactoryTest
OverlayWindowTest
Change-Id: I2ecab3f145fbb5c72114455054763b5db614a003
This commit is contained in:
33
packages/SystemUI/res/layout/privacy_dot_bottom_left.xml
Normal file
33
packages/SystemUI/res/layout/privacy_dot_bottom_left.xml
Normal file
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
** Copyright 2022, 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.
|
||||
-->
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/privacy_dot_bottom_left_container"
|
||||
android:layout_height="@dimen/status_bar_height"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_gravity="bottom|left"
|
||||
android:paddingTop="@dimen/status_bar_padding_top"
|
||||
android:visibility="invisible" >
|
||||
<ImageView
|
||||
android:id="@+id/privacy_dot"
|
||||
android:contentDescription="@null"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_gravity="center_vertical|right"
|
||||
android:src="@drawable/system_animation_ongoing_dot"
|
||||
android:visibility="visible" />
|
||||
</FrameLayout>
|
||||
33
packages/SystemUI/res/layout/privacy_dot_bottom_right.xml
Normal file
33
packages/SystemUI/res/layout/privacy_dot_bottom_right.xml
Normal file
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
** Copyright 2022, 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.
|
||||
-->
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/privacy_dot_bottom_right_container"
|
||||
android:layout_height="@dimen/status_bar_height"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_gravity="bottom|right"
|
||||
android:paddingTop="@dimen/status_bar_padding_top"
|
||||
android:visibility="invisible" >
|
||||
<ImageView
|
||||
android:id="@+id/privacy_dot"
|
||||
android:contentDescription="@null"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_gravity="center_vertical|left"
|
||||
android:src="@drawable/system_animation_ongoing_dot"
|
||||
android:visibility="visible" />
|
||||
</FrameLayout>
|
||||
33
packages/SystemUI/res/layout/privacy_dot_top_left.xml
Normal file
33
packages/SystemUI/res/layout/privacy_dot_top_left.xml
Normal file
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
** Copyright 2022, 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.
|
||||
-->
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/privacy_dot_top_left_container"
|
||||
android:layout_height="@dimen/status_bar_height"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_gravity="top|left"
|
||||
android:paddingTop="@dimen/status_bar_padding_top"
|
||||
android:visibility="invisible" >
|
||||
<ImageView
|
||||
android:id="@+id/privacy_dot"
|
||||
android:contentDescription="@null"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_gravity="center_vertical|right"
|
||||
android:src="@drawable/system_animation_ongoing_dot"
|
||||
android:visibility="visible" />
|
||||
</FrameLayout>
|
||||
33
packages/SystemUI/res/layout/privacy_dot_top_right.xml
Normal file
33
packages/SystemUI/res/layout/privacy_dot_top_right.xml
Normal file
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
** Copyright 2022, 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.
|
||||
-->
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/privacy_dot_top_right_container"
|
||||
android:layout_height="@dimen/status_bar_height"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_gravity="top|right"
|
||||
android:paddingTop="@dimen/status_bar_padding_top"
|
||||
android:visibility="invisible" >
|
||||
<ImageView
|
||||
android:id="@+id/privacy_dot"
|
||||
android:contentDescription="@null"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_gravity="center_vertical|left"
|
||||
android:src="@drawable/system_animation_ongoing_dot"
|
||||
android:visibility="visible" />
|
||||
</FrameLayout>
|
||||
@@ -27,22 +27,6 @@
|
||||
android:tint="#ff000000"
|
||||
android:src="@drawable/rounded_corner_bottom"/>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/privacy_dot_left_container"
|
||||
android:layout_height="@dimen/status_bar_height"
|
||||
android:layout_width="wrap_content"
|
||||
android:paddingTop="@dimen/status_bar_padding_top"
|
||||
android:layout_gravity="left|bottom"
|
||||
android:visibility="invisible" >
|
||||
<ImageView
|
||||
android:id="@+id/privacy_dot"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_gravity="center_vertical|right"
|
||||
android:src="@drawable/system_animation_ongoing_dot"
|
||||
android:visibility="visible" />
|
||||
</FrameLayout>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/right"
|
||||
android:layout_width="12dp"
|
||||
@@ -51,20 +35,4 @@
|
||||
android:layout_gravity="right|bottom"
|
||||
android:src="@drawable/rounded_corner_bottom"/>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/privacy_dot_right_container"
|
||||
android:layout_height="@dimen/status_bar_height"
|
||||
android:layout_width="wrap_content"
|
||||
android:paddingTop="@dimen/status_bar_padding_top"
|
||||
android:layout_gravity="right|bottom"
|
||||
android:visibility="invisible" >
|
||||
<ImageView
|
||||
android:id="@+id/privacy_dot"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_gravity="center_vertical|left"
|
||||
android:src="@drawable/system_animation_ongoing_dot"
|
||||
android:visibility="visible" />
|
||||
</FrameLayout>
|
||||
|
||||
</com.android.systemui.RegionInterceptingFrameLayout>
|
||||
|
||||
@@ -27,22 +27,6 @@
|
||||
android:tint="#ff000000"
|
||||
android:src="@drawable/rounded_corner_top"/>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/privacy_dot_left_container"
|
||||
android:layout_height="@dimen/status_bar_height"
|
||||
android:layout_width="wrap_content"
|
||||
android:paddingTop="@dimen/status_bar_padding_top"
|
||||
android:layout_gravity="left|top"
|
||||
android:visibility="invisible" >
|
||||
<ImageView
|
||||
android:id="@+id/privacy_dot"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_gravity="center_vertical|right"
|
||||
android:src="@drawable/system_animation_ongoing_dot"
|
||||
android:visibility="visible" />
|
||||
</FrameLayout>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/right"
|
||||
android:layout_width="12dp"
|
||||
@@ -51,20 +35,4 @@
|
||||
android:layout_gravity="right|top"
|
||||
android:src="@drawable/rounded_corner_top"/>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/privacy_dot_right_container"
|
||||
android:layout_height="@dimen/status_bar_height"
|
||||
android:layout_width="wrap_content"
|
||||
android:paddingTop="@dimen/status_bar_padding_top"
|
||||
android:layout_gravity="right|top"
|
||||
android:visibility="invisible" >
|
||||
<ImageView
|
||||
android:id="@+id/privacy_dot"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_gravity="center_vertical|left"
|
||||
android:src="@drawable/system_animation_ongoing_dot"
|
||||
android:visibility="visible" />
|
||||
</FrameLayout>
|
||||
|
||||
</com.android.systemui.RegionInterceptingFrameLayout>
|
||||
|
||||
@@ -32,6 +32,7 @@ import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.annotation.Dimension;
|
||||
import android.annotation.IdRes;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.app.ActivityManager;
|
||||
@@ -87,6 +88,11 @@ import com.android.systemui.animation.Interpolators;
|
||||
import com.android.systemui.broadcast.BroadcastDispatcher;
|
||||
import com.android.systemui.dagger.SysUISingleton;
|
||||
import com.android.systemui.dagger.qualifiers.Main;
|
||||
import com.android.systemui.decor.DecorProvider;
|
||||
import com.android.systemui.decor.DecorProviderFactory;
|
||||
import com.android.systemui.decor.DecorProviderKt;
|
||||
import com.android.systemui.decor.OverlayWindow;
|
||||
import com.android.systemui.decor.PrivacyDotDecorProviderFactory;
|
||||
import com.android.systemui.qs.SettingObserver;
|
||||
import com.android.systemui.settings.UserTracker;
|
||||
import com.android.systemui.statusbar.events.PrivacyDotViewController;
|
||||
@@ -104,6 +110,8 @@ import java.util.concurrent.Executor;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import kotlin.Pair;
|
||||
|
||||
/**
|
||||
* An overlay that draws screen decorations in software (e.g for rounded corners or display cutout)
|
||||
* for antialiasing and emulation purposes.
|
||||
@@ -135,6 +143,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
|
||||
private final UserTracker mUserTracker;
|
||||
private final PrivacyDotViewController mDotViewController;
|
||||
private final ThreadFactory mThreadFactory;
|
||||
private final DecorProviderFactory mDotFactory;
|
||||
|
||||
//TODO: These are piecemeal being updated to Points for now to support non-square rounded
|
||||
// corners. for now it is only supposed when reading the intrinsic size from the drawables with
|
||||
@@ -146,14 +155,9 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
|
||||
@VisibleForTesting
|
||||
protected Point mRoundedDefaultBottom = new Point(0, 0);
|
||||
@VisibleForTesting
|
||||
protected View[] mOverlays;
|
||||
protected OverlayWindow[] mOverlays = null;
|
||||
@Nullable
|
||||
private DisplayCutoutView[] mCutoutViews;
|
||||
//TODO:
|
||||
View mTopLeftDot;
|
||||
View mTopRightDot;
|
||||
View mBottomLeftDot;
|
||||
View mBottomRightDot;
|
||||
private float mDensity;
|
||||
private WindowManager mWindowManager;
|
||||
private int mRotation;
|
||||
@@ -162,7 +166,6 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
|
||||
private Handler mHandler;
|
||||
private boolean mPendingRotationChange;
|
||||
private boolean mIsRoundedCornerMultipleRadius;
|
||||
private boolean mIsPrivacyDotEnabled;
|
||||
private Drawable mRoundedCornerDrawable;
|
||||
private Drawable mRoundedCornerDrawableTop;
|
||||
private Drawable mRoundedCornerDrawableBottom;
|
||||
@@ -227,7 +230,8 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
|
||||
TunerService tunerService,
|
||||
UserTracker userTracker,
|
||||
PrivacyDotViewController dotViewController,
|
||||
ThreadFactory threadFactory) {
|
||||
ThreadFactory threadFactory,
|
||||
PrivacyDotDecorProviderFactory dotFactory) {
|
||||
super(context);
|
||||
mMainExecutor = mainExecutor;
|
||||
mSecureSettings = secureSettings;
|
||||
@@ -236,6 +240,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
|
||||
mUserTracker = userTracker;
|
||||
mDotViewController = dotViewController;
|
||||
mThreadFactory = threadFactory;
|
||||
mDotFactory = dotFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -250,11 +255,14 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
|
||||
mDotViewController.setUiExecutor(mExecutor);
|
||||
}
|
||||
|
||||
private boolean isPrivacyDotEnabled() {
|
||||
return mDotFactory.getHasProviders();
|
||||
}
|
||||
|
||||
private void startOnScreenDecorationsThread() {
|
||||
mRotation = mContext.getDisplay().getRotation();
|
||||
mDisplayUniqueId = mContext.getDisplay().getUniqueId();
|
||||
mIsRoundedCornerMultipleRadius = isRoundedCornerMultipleRadius(mContext, mDisplayUniqueId);
|
||||
mIsPrivacyDotEnabled = mContext.getResources().getBoolean(R.bool.config_enablePrivacyDot);
|
||||
mWindowManager = mContext.getSystemService(WindowManager.class);
|
||||
mDisplayManager = mContext.getSystemService(DisplayManager.class);
|
||||
updateRoundedCornerDrawable();
|
||||
@@ -292,8 +300,9 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
|
||||
|
||||
for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) {
|
||||
if (mOverlays[i] != null) {
|
||||
mOverlays[i].getViewTreeObserver().addOnPreDrawListener(
|
||||
new RestartingPreDrawListener(mOverlays[i], i, newRotation));
|
||||
final ViewGroup overlayView = mOverlays[i].getRootView();
|
||||
overlayView.getViewTreeObserver().addOnPreDrawListener(
|
||||
new RestartingPreDrawListener(overlayView, i, newRotation));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -313,24 +322,64 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
|
||||
updateOrientation();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private View getOverlayView(@IdRes int id) {
|
||||
if (mOverlays == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (final OverlayWindow overlay : mOverlays) {
|
||||
if (overlay == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final View view = overlay.getView(id);
|
||||
if (view != null) {
|
||||
return view;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void removeOverlayView(@IdRes int id) {
|
||||
if (mOverlays == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (final OverlayWindow overlay : mOverlays) {
|
||||
if (overlay == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
overlay.removeView(id);
|
||||
}
|
||||
}
|
||||
|
||||
private void setupDecorations() {
|
||||
if (hasRoundedCorners() || shouldDrawCutout() || mIsPrivacyDotEnabled) {
|
||||
List<DecorProvider> decorProviders = mDotFactory.getProviders();
|
||||
|
||||
if (hasRoundedCorners() || shouldDrawCutout() || !decorProviders.isEmpty()) {
|
||||
final DisplayCutout cutout = getCutout();
|
||||
for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) {
|
||||
if (shouldShowCutout(i, cutout) || shouldShowRoundedCorner(i, cutout)
|
||||
|| shouldShowPrivacyDot(i, cutout)) {
|
||||
createOverlay(i, cutout);
|
||||
Pair<List<DecorProvider>, List<DecorProvider>> pair =
|
||||
DecorProviderKt.partitionAlignedBound(decorProviders, i);
|
||||
decorProviders = pair.getSecond();
|
||||
createOverlay(i, cutout, pair.getFirst());
|
||||
} else {
|
||||
removeOverlay(i);
|
||||
}
|
||||
}
|
||||
|
||||
if (mTopLeftDot != null && mTopRightDot != null && mBottomLeftDot != null
|
||||
&& mBottomRightDot != null) {
|
||||
final View tl, tr, bl, br;
|
||||
if ((tl = getOverlayView(R.id.privacy_dot_top_left_container)) != null
|
||||
&& (tr = getOverlayView(R.id.privacy_dot_top_right_container)) != null
|
||||
&& (bl = getOverlayView(R.id.privacy_dot_bottom_left_container)) != null
|
||||
&& (br = getOverlayView(R.id.privacy_dot_bottom_right_container)) != null) {
|
||||
// Overlays have been created, send the dots to the controller
|
||||
//TODO: need a better way to do this
|
||||
mDotViewController.initialize(
|
||||
mTopLeftDot, mTopRightDot, mBottomLeftDot, mBottomRightDot);
|
||||
mDotViewController.initialize(tl, tr, bl, br);
|
||||
}
|
||||
} else {
|
||||
removeAllOverlays();
|
||||
@@ -414,13 +463,16 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
|
||||
if (mOverlays == null || mOverlays[pos] == null) {
|
||||
return;
|
||||
}
|
||||
mWindowManager.removeViewImmediate(mOverlays[pos]);
|
||||
mWindowManager.removeViewImmediate(mOverlays[pos].getRootView());
|
||||
mOverlays[pos] = null;
|
||||
}
|
||||
|
||||
private void createOverlay(@BoundsPosition int pos, @Nullable DisplayCutout cutout) {
|
||||
private void createOverlay(
|
||||
@BoundsPosition int pos,
|
||||
@Nullable DisplayCutout cutout,
|
||||
@NonNull List<DecorProvider> decorProviders) {
|
||||
if (mOverlays == null) {
|
||||
mOverlays = new View[BOUNDS_POSITION_LENGTH];
|
||||
mOverlays = new OverlayWindow[BOUNDS_POSITION_LENGTH];
|
||||
}
|
||||
|
||||
if (mCutoutViews == null) {
|
||||
@@ -430,78 +482,49 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
|
||||
if (mOverlays[pos] != null) {
|
||||
return;
|
||||
}
|
||||
mOverlays[pos] = overlayForPosition(pos, cutout);
|
||||
|
||||
mOverlays[pos] = overlayForPosition(pos, decorProviders);
|
||||
|
||||
mCutoutViews[pos] = new DisplayCutoutView(mContext, pos, this);
|
||||
((ViewGroup) mOverlays[pos]).addView(mCutoutViews[pos]);
|
||||
mOverlays[pos].getRootView().addView(mCutoutViews[pos]);
|
||||
|
||||
mOverlays[pos].setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
|
||||
mOverlays[pos].setAlpha(0);
|
||||
mOverlays[pos].setForceDarkAllowed(false);
|
||||
final ViewGroup overlayView = mOverlays[pos].getRootView();
|
||||
overlayView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
|
||||
overlayView.setAlpha(0);
|
||||
overlayView.setForceDarkAllowed(false);
|
||||
|
||||
updateView(pos, cutout);
|
||||
|
||||
mWindowManager.addView(mOverlays[pos], getWindowLayoutParams(pos));
|
||||
mWindowManager.addView(overlayView, getWindowLayoutParams(pos));
|
||||
|
||||
mOverlays[pos].addOnLayoutChangeListener(new OnLayoutChangeListener() {
|
||||
overlayView.addOnLayoutChangeListener(new OnLayoutChangeListener() {
|
||||
@Override
|
||||
public void onLayoutChange(View v, int left, int top, int right, int bottom,
|
||||
int oldLeft, int oldTop, int oldRight, int oldBottom) {
|
||||
mOverlays[pos].removeOnLayoutChangeListener(this);
|
||||
mOverlays[pos].animate()
|
||||
overlayView.removeOnLayoutChangeListener(this);
|
||||
overlayView.animate()
|
||||
.alpha(1)
|
||||
.setDuration(1000)
|
||||
.start();
|
||||
}
|
||||
});
|
||||
|
||||
mOverlays[pos].getViewTreeObserver().addOnPreDrawListener(
|
||||
new ValidatingPreDrawListener(mOverlays[pos]));
|
||||
mOverlays[pos].getRootView().getViewTreeObserver().addOnPreDrawListener(
|
||||
new ValidatingPreDrawListener(mOverlays[pos].getRootView()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow overrides for top/bottom positions
|
||||
*/
|
||||
private View overlayForPosition(@BoundsPosition int pos, @Nullable DisplayCutout cutout) {
|
||||
final int layoutId = (pos == BOUNDS_POSITION_LEFT || pos == BOUNDS_POSITION_TOP)
|
||||
? R.layout.rounded_corners_top : R.layout.rounded_corners_bottom;
|
||||
final ViewGroup vg = (ViewGroup) LayoutInflater.from(mContext).inflate(layoutId, null);
|
||||
initPrivacyDotView(vg, pos, cutout);
|
||||
return vg;
|
||||
}
|
||||
|
||||
private void initPrivacyDotView(@NonNull ViewGroup viewGroup, @BoundsPosition int pos,
|
||||
@Nullable DisplayCutout cutout) {
|
||||
final View left = viewGroup.findViewById(R.id.privacy_dot_left_container);
|
||||
final View right = viewGroup.findViewById(R.id.privacy_dot_right_container);
|
||||
if (!shouldShowPrivacyDot(pos, cutout)) {
|
||||
viewGroup.removeView(left);
|
||||
viewGroup.removeView(right);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (pos) {
|
||||
case BOUNDS_POSITION_LEFT: {
|
||||
mTopLeftDot = left;
|
||||
mBottomLeftDot = right;
|
||||
break;
|
||||
}
|
||||
case BOUNDS_POSITION_TOP: {
|
||||
mTopLeftDot = left;
|
||||
mTopRightDot = right;
|
||||
break;
|
||||
}
|
||||
case BOUNDS_POSITION_RIGHT: {
|
||||
mTopRightDot = left;
|
||||
mBottomRightDot = right;
|
||||
break;
|
||||
}
|
||||
case BOUNDS_POSITION_BOTTOM: {
|
||||
mBottomLeftDot = left;
|
||||
mBottomRightDot = right;
|
||||
break;
|
||||
}
|
||||
}
|
||||
private OverlayWindow overlayForPosition(
|
||||
@BoundsPosition int pos,
|
||||
@NonNull List<DecorProvider> decorProviders) {
|
||||
final OverlayWindow currentOverlay = new OverlayWindow(LayoutInflater.from(mContext), pos);
|
||||
decorProviders.forEach(provider -> {
|
||||
removeOverlayView(provider.getViewId());
|
||||
currentOverlay.addDecorProvider(provider, mRotation);
|
||||
});
|
||||
return currentOverlay;
|
||||
}
|
||||
|
||||
private void updateView(@BoundsPosition int pos, @Nullable DisplayCutout cutout) {
|
||||
@@ -638,12 +661,15 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
|
||||
if (mOverlays[i] == null) {
|
||||
continue;
|
||||
}
|
||||
final int size = ((ViewGroup) mOverlays[i]).getChildCount();
|
||||
final ViewGroup overlayView = mOverlays[i].getRootView();
|
||||
final int size = overlayView.getChildCount();
|
||||
View child;
|
||||
for (int j = 0; j < size; j++) {
|
||||
child = ((ViewGroup) mOverlays[i]).getChildAt(j);
|
||||
if (child.getId() == R.id.privacy_dot_left_container
|
||||
|| child.getId() == R.id.privacy_dot_right_container) {
|
||||
child = overlayView.getChildAt(j);
|
||||
if (child.getId() == R.id.privacy_dot_top_left_container
|
||||
|| child.getId() == R.id.privacy_dot_top_right_container
|
||||
|| child.getId() == R.id.privacy_dot_bottom_left_container
|
||||
|| child.getId() == R.id.privacy_dot_bottom_right_container) {
|
||||
// Exclude privacy dot from color inversion (for now?)
|
||||
continue;
|
||||
}
|
||||
@@ -684,13 +710,18 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
|
||||
pw.println("ScreenDecorations state:");
|
||||
pw.println(" DEBUG_DISABLE_SCREEN_DECORATIONS:" + DEBUG_DISABLE_SCREEN_DECORATIONS);
|
||||
pw.println(" mIsRoundedCornerMultipleRadius:" + mIsRoundedCornerMultipleRadius);
|
||||
pw.println(" mIsPrivacyDotEnabled:" + mIsPrivacyDotEnabled);
|
||||
pw.println(" mIsPrivacyDotEnabled:" + isPrivacyDotEnabled());
|
||||
pw.println(" mPendingRotationChange:" + mPendingRotationChange);
|
||||
pw.println(" mRoundedDefault(x,y)=(" + mRoundedDefault.x + "," + mRoundedDefault.y + ")");
|
||||
pw.println(" mRoundedDefaultTop(x,y)=(" + mRoundedDefaultTop.x + "," + mRoundedDefaultTop.y
|
||||
+ ")");
|
||||
pw.println(" mRoundedDefaultBottom(x,y)=(" + mRoundedDefaultBottom.x + ","
|
||||
+ mRoundedDefaultBottom.y + ")");
|
||||
pw.println(" mOverlays(left,top,right,bottom)=("
|
||||
+ (mOverlays != null && mOverlays[BOUNDS_POSITION_LEFT] != null) + ","
|
||||
+ (mOverlays != null && mOverlays[BOUNDS_POSITION_TOP] != null) + ","
|
||||
+ (mOverlays != null && mOverlays[BOUNDS_POSITION_RIGHT] != null) + ","
|
||||
+ (mOverlays != null && mOverlays[BOUNDS_POSITION_BOTTOM] != null) + ")");
|
||||
}
|
||||
|
||||
private void updateOrientation() {
|
||||
@@ -843,7 +874,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
|
||||
|
||||
private void updateRoundedCornerView(@BoundsPosition int pos, int id,
|
||||
@Nullable DisplayCutout cutout) {
|
||||
final View rounded = mOverlays[pos].findViewById(id);
|
||||
final View rounded = mOverlays[pos].getRootView().findViewById(id);
|
||||
if (rounded == null) {
|
||||
return;
|
||||
}
|
||||
@@ -929,7 +960,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
|
||||
}
|
||||
|
||||
private boolean shouldShowPrivacyDot(@BoundsPosition int pos, @Nullable DisplayCutout cutout) {
|
||||
return mIsPrivacyDotEnabled && isDefaultShownOverlayPos(pos, cutout);
|
||||
return isPrivacyDotEnabled() && isDefaultShownOverlayPos(pos, cutout);
|
||||
}
|
||||
|
||||
private boolean shouldShowCutout(@BoundsPosition int pos, @Nullable DisplayCutout cutout) {
|
||||
@@ -955,7 +986,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
|
||||
if (mOverlays[i] == null) {
|
||||
continue;
|
||||
}
|
||||
mWindowManager.updateViewLayout(mOverlays[i], getWindowLayoutParams(i));
|
||||
mWindowManager.updateViewLayout(mOverlays[i].getRootView(), getWindowLayoutParams(i));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1003,9 +1034,10 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
|
||||
if (mOverlays[i] == null) {
|
||||
continue;
|
||||
}
|
||||
((ImageView) mOverlays[i].findViewById(R.id.left)).setImageDrawable(
|
||||
final ViewGroup overlayView = mOverlays[i].getRootView();
|
||||
((ImageView) overlayView.findViewById(R.id.left)).setImageDrawable(
|
||||
isTopRoundedCorner(i, R.id.left) ? top : bottom);
|
||||
((ImageView) mOverlays[i].findViewById(R.id.right)).setImageDrawable(
|
||||
((ImageView) overlayView.findViewById(R.id.right)).setImageDrawable(
|
||||
isTopRoundedCorner(i, R.id.right) ? top : bottom);
|
||||
}
|
||||
}
|
||||
@@ -1047,9 +1079,10 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
|
||||
if (mOverlays[i] == null) {
|
||||
continue;
|
||||
}
|
||||
setSize(mOverlays[i].findViewById(R.id.left),
|
||||
final ViewGroup overlayView = mOverlays[i].getRootView();
|
||||
setSize(overlayView.findViewById(R.id.left),
|
||||
isTopRoundedCorner(i, R.id.left) ? sizeTop : sizeBottom);
|
||||
setSize(mOverlays[i].findViewById(R.id.right),
|
||||
setSize(overlayView.findViewById(R.id.right),
|
||||
isTopRoundedCorner(i, R.id.right) ? sizeTop : sizeBottom);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.decor
|
||||
import android.view.DisplayCutout
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Surface
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
|
||||
/**
|
||||
* An interface for providing view with a specific functionality. Take an example, if privacy dot
|
||||
* is enabled, there are 4 DecorProviders which are used to provide privacy dot views on top-left,
|
||||
* top-right, bottom-left, bottom-right.
|
||||
*/
|
||||
abstract class DecorProvider {
|
||||
|
||||
/** Id for the view which is created through inflateView() */
|
||||
abstract val viewId: Int
|
||||
|
||||
/** The number of total aligned bounds */
|
||||
val numOfAlignedEdge: Int
|
||||
get() = alignedBounds.size
|
||||
|
||||
/** The aligned bounds for the view which is created through inflateView() */
|
||||
abstract val alignedBounds: List<Int>
|
||||
|
||||
/** Inflate view into parent as current rotation */
|
||||
abstract fun inflateView(
|
||||
inflater: LayoutInflater,
|
||||
parent: ViewGroup,
|
||||
@Surface.Rotation rotation: Int
|
||||
): View
|
||||
}
|
||||
|
||||
/**
|
||||
* Split list to 2 list, and return it back as Pair<>. The providers on the first list contains this
|
||||
* alignedBound element. The providers on the second list do not contain this alignedBound element
|
||||
*/
|
||||
fun List<DecorProvider>.partitionAlignedBound(
|
||||
@DisplayCutout.BoundsPosition alignedBound: Int
|
||||
): Pair<List<DecorProvider>, List<DecorProvider>> {
|
||||
return partition { it.alignedBounds.contains(alignedBound) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A provider for view shown on corner.
|
||||
*/
|
||||
abstract class CornerDecorProvider : DecorProvider() {
|
||||
/** The first bound which a corner view is aligned based on rotation 0 */
|
||||
@DisplayCutout.BoundsPosition protected abstract val alignedBound1: Int
|
||||
/** The second bound which a corner view is aligned based on rotation 0 */
|
||||
@DisplayCutout.BoundsPosition protected abstract val alignedBound2: Int
|
||||
|
||||
override val alignedBounds: List<Int> by lazy {
|
||||
listOf(alignedBound1, alignedBound2)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.decor
|
||||
|
||||
abstract class DecorProviderFactory {
|
||||
abstract val providers: List<DecorProvider>
|
||||
abstract val hasProviders: Boolean
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.decor
|
||||
|
||||
import android.annotation.IdRes
|
||||
import android.view.DisplayCutout
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Surface
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.android.systemui.R
|
||||
import java.util.HashMap
|
||||
|
||||
class OverlayWindow(private val layoutInflater: LayoutInflater, private val pos: Int) {
|
||||
|
||||
private val layoutId: Int
|
||||
get() {
|
||||
return if (pos == DisplayCutout.BOUNDS_POSITION_LEFT ||
|
||||
pos == DisplayCutout.BOUNDS_POSITION_TOP) {
|
||||
R.layout.rounded_corners_top
|
||||
} else {
|
||||
R.layout.rounded_corners_bottom
|
||||
}
|
||||
}
|
||||
|
||||
val rootView = layoutInflater.inflate(layoutId, null) as ViewGroup
|
||||
private val viewProviderMap: MutableMap<Int, Pair<View, DecorProvider>> = HashMap()
|
||||
|
||||
fun addDecorProvider(decorProvider: DecorProvider, @Surface.Rotation rotation: Int) {
|
||||
val view = decorProvider.inflateView(layoutInflater, rootView, rotation)
|
||||
viewProviderMap[decorProvider.viewId] = Pair(view, decorProvider)
|
||||
}
|
||||
|
||||
fun getView(@IdRes id: Int): View? {
|
||||
val pair = viewProviderMap[id]
|
||||
return pair?.first
|
||||
}
|
||||
|
||||
fun removeView(@IdRes id: Int) {
|
||||
val view = getView(id)
|
||||
if (view != null) {
|
||||
rootView.removeView(view)
|
||||
viewProviderMap.remove(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.decor
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.view.DisplayCutout
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.Surface
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.dagger.qualifiers.Main
|
||||
import javax.inject.Inject
|
||||
|
||||
@SysUISingleton
|
||||
class PrivacyDotDecorProviderFactory @Inject constructor(
|
||||
@Main private val res: Resources
|
||||
) : DecorProviderFactory() {
|
||||
|
||||
private val isPrivacyDotEnabled: Boolean
|
||||
get() = res.getBoolean(R.bool.config_enablePrivacyDot)
|
||||
|
||||
override val hasProviders: Boolean
|
||||
get() = isPrivacyDotEnabled
|
||||
|
||||
override val providers: List<DecorProvider>
|
||||
get() {
|
||||
return if (hasProviders) {
|
||||
listOf(
|
||||
PrivacyDotCornerDecorProviderImpl(
|
||||
viewId = R.id.privacy_dot_top_left_container,
|
||||
alignedBound1 = DisplayCutout.BOUNDS_POSITION_TOP,
|
||||
alignedBound2 = DisplayCutout.BOUNDS_POSITION_LEFT,
|
||||
layoutId = R.layout.privacy_dot_top_left),
|
||||
PrivacyDotCornerDecorProviderImpl(
|
||||
viewId = R.id.privacy_dot_top_right_container,
|
||||
alignedBound1 = DisplayCutout.BOUNDS_POSITION_TOP,
|
||||
alignedBound2 = DisplayCutout.BOUNDS_POSITION_RIGHT,
|
||||
layoutId = R.layout.privacy_dot_top_right),
|
||||
PrivacyDotCornerDecorProviderImpl(
|
||||
viewId = R.id.privacy_dot_bottom_left_container,
|
||||
alignedBound1 = DisplayCutout.BOUNDS_POSITION_BOTTOM,
|
||||
alignedBound2 = DisplayCutout.BOUNDS_POSITION_LEFT,
|
||||
layoutId = R.layout.privacy_dot_bottom_left),
|
||||
PrivacyDotCornerDecorProviderImpl(
|
||||
viewId = R.id.privacy_dot_bottom_right_container,
|
||||
alignedBound1 = DisplayCutout.BOUNDS_POSITION_BOTTOM,
|
||||
alignedBound2 = DisplayCutout.BOUNDS_POSITION_RIGHT,
|
||||
layoutId = R.layout.privacy_dot_bottom_right)
|
||||
)
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PrivacyDotCornerDecorProviderImpl(
|
||||
override val viewId: Int,
|
||||
@DisplayCutout.BoundsPosition override val alignedBound1: Int,
|
||||
@DisplayCutout.BoundsPosition override val alignedBound2: Int,
|
||||
private val layoutId: Int
|
||||
) : CornerDecorProvider() {
|
||||
|
||||
override fun inflateView(
|
||||
inflater: LayoutInflater,
|
||||
parent: ViewGroup,
|
||||
@Surface.Rotation rotation: Int
|
||||
): View {
|
||||
inflater.inflate(layoutId, parent, true)
|
||||
return parent.getChildAt(parent.childCount - 1 /* latest new added child */)
|
||||
}
|
||||
}
|
||||
@@ -45,6 +45,7 @@ import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.annotation.IdRes;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Insets;
|
||||
@@ -63,9 +64,15 @@ import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.view.WindowMetrics;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.systemui.broadcast.BroadcastDispatcher;
|
||||
import com.android.systemui.decor.CornerDecorProvider;
|
||||
import com.android.systemui.decor.DecorProvider;
|
||||
import com.android.systemui.decor.OverlayWindow;
|
||||
import com.android.systemui.decor.PrivacyDotCornerDecorProviderImpl;
|
||||
import com.android.systemui.decor.PrivacyDotDecorProviderFactory;
|
||||
import com.android.systemui.settings.UserTracker;
|
||||
import com.android.systemui.statusbar.events.PrivacyDotViewController;
|
||||
import com.android.systemui.tuner.TunerService;
|
||||
@@ -81,6 +88,7 @@ import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
||||
@RunWithLooper
|
||||
@@ -96,6 +104,7 @@ public class ScreenDecorationsTest extends SysuiTestCase {
|
||||
private SecureSettings mSecureSettings;
|
||||
private final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
|
||||
private FakeThreadFactory mThreadFactory;
|
||||
private ArrayList<DecorProvider> mDecorProviders;
|
||||
@Mock
|
||||
private TunerService mTunerService;
|
||||
@Mock
|
||||
@@ -106,6 +115,16 @@ public class ScreenDecorationsTest extends SysuiTestCase {
|
||||
private PrivacyDotViewController mDotViewController;
|
||||
@Mock
|
||||
private TypedArray mMockTypedArray;
|
||||
@Mock
|
||||
private PrivacyDotDecorProviderFactory mPrivacyDotDecorProviderFactory;
|
||||
@Mock
|
||||
private CornerDecorProvider mPrivacyDotTopLeftDecorProvider;
|
||||
@Mock
|
||||
private CornerDecorProvider mPrivacyDotTopRightDecorProvider;
|
||||
@Mock
|
||||
private CornerDecorProvider mPrivacyDotBottomLeftDecorProvider;
|
||||
@Mock
|
||||
private CornerDecorProvider mPrivacyDotBottomRightDecorProvider;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
@@ -129,9 +148,33 @@ public class ScreenDecorationsTest extends SysuiTestCase {
|
||||
mContext.addMockSystemService(DisplayManager.class, mDisplayManager);
|
||||
when(mMockTypedArray.length()).thenReturn(0);
|
||||
|
||||
mPrivacyDotTopLeftDecorProvider = spy(new PrivacyDotCornerDecorProviderImpl(
|
||||
R.id.privacy_dot_top_left_container,
|
||||
DisplayCutout.BOUNDS_POSITION_TOP,
|
||||
DisplayCutout.BOUNDS_POSITION_LEFT,
|
||||
R.layout.privacy_dot_top_left));
|
||||
|
||||
mPrivacyDotTopRightDecorProvider = spy(new PrivacyDotCornerDecorProviderImpl(
|
||||
R.id.privacy_dot_top_right_container,
|
||||
DisplayCutout.BOUNDS_POSITION_TOP,
|
||||
DisplayCutout.BOUNDS_POSITION_RIGHT,
|
||||
R.layout.privacy_dot_top_right));
|
||||
|
||||
mPrivacyDotBottomLeftDecorProvider = spy(new PrivacyDotCornerDecorProviderImpl(
|
||||
R.id.privacy_dot_bottom_left_container,
|
||||
DisplayCutout.BOUNDS_POSITION_BOTTOM,
|
||||
DisplayCutout.BOUNDS_POSITION_LEFT,
|
||||
R.layout.privacy_dot_bottom_left));
|
||||
|
||||
mPrivacyDotBottomRightDecorProvider = spy(new PrivacyDotCornerDecorProviderImpl(
|
||||
R.id.privacy_dot_bottom_right_container,
|
||||
DisplayCutout.BOUNDS_POSITION_BOTTOM,
|
||||
DisplayCutout.BOUNDS_POSITION_RIGHT,
|
||||
R.layout.privacy_dot_bottom_right));
|
||||
|
||||
mScreenDecorations = spy(new ScreenDecorations(mContext, mExecutor, mSecureSettings,
|
||||
mBroadcastDispatcher, mTunerService, mUserTracker, mDotViewController,
|
||||
mThreadFactory) {
|
||||
mThreadFactory, mPrivacyDotDecorProviderFactory) {
|
||||
@Override
|
||||
public void start() {
|
||||
super.start();
|
||||
@@ -157,7 +200,7 @@ public class ScreenDecorationsTest extends SysuiTestCase {
|
||||
private void verifyRoundedCornerViewsVisibility(
|
||||
@DisplayCutout.BoundsPosition final int overlayId,
|
||||
@View.Visibility final int visibility) {
|
||||
final View overlay = mScreenDecorations.mOverlays[overlayId];
|
||||
final View overlay = mScreenDecorations.mOverlays[overlayId].getRootView();
|
||||
final View left = overlay.findViewById(R.id.left);
|
||||
final View right = overlay.findViewById(R.id.right);
|
||||
assertNotNull(left);
|
||||
@@ -166,23 +209,42 @@ public class ScreenDecorationsTest extends SysuiTestCase {
|
||||
assertThat(right.getVisibility()).isEqualTo(visibility);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private View findViewFromOverlays(@IdRes int id) {
|
||||
for (OverlayWindow overlay: mScreenDecorations.mOverlays) {
|
||||
if (overlay == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
View view = overlay.getRootView().findViewById(id);
|
||||
if (view != null) {
|
||||
return view;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void verifyTopDotViewsNullable(final boolean isAssertNull) {
|
||||
View tl = findViewFromOverlays(R.id.privacy_dot_top_left_container);
|
||||
View tr = findViewFromOverlays(R.id.privacy_dot_top_right_container);
|
||||
if (isAssertNull) {
|
||||
assertNull(mScreenDecorations.mTopLeftDot);
|
||||
assertNull(mScreenDecorations.mTopRightDot);
|
||||
assertNull(tl);
|
||||
assertNull(tr);
|
||||
} else {
|
||||
assertNotNull(mScreenDecorations.mTopLeftDot);
|
||||
assertNotNull(mScreenDecorations.mTopRightDot);
|
||||
assertNotNull(tl);
|
||||
assertNotNull(tr);
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyBottomDotViewsNullable(final boolean isAssertNull) {
|
||||
View bl = findViewFromOverlays(R.id.privacy_dot_bottom_left_container);
|
||||
View br = findViewFromOverlays(R.id.privacy_dot_bottom_right_container);
|
||||
if (isAssertNull) {
|
||||
assertNull(mScreenDecorations.mBottomLeftDot);
|
||||
assertNull(mScreenDecorations.mBottomRightDot);
|
||||
assertNull(bl);
|
||||
assertNull(br);
|
||||
} else {
|
||||
assertNotNull(mScreenDecorations.mBottomLeftDot);
|
||||
assertNotNull(mScreenDecorations.mBottomRightDot);
|
||||
assertNotNull(bl);
|
||||
assertNotNull(br);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,14 +255,18 @@ public class ScreenDecorationsTest extends SysuiTestCase {
|
||||
|
||||
private void verifyTopDotViewsVisibility(@View.Visibility final int visibility) {
|
||||
verifyTopDotViewsNullable(false);
|
||||
assertThat(mScreenDecorations.mTopLeftDot.getVisibility()).isEqualTo(visibility);
|
||||
assertThat(mScreenDecorations.mTopRightDot.getVisibility()).isEqualTo(visibility);
|
||||
View tl = findViewFromOverlays(R.id.privacy_dot_top_left_container);
|
||||
View tr = findViewFromOverlays(R.id.privacy_dot_top_right_container);
|
||||
assertThat(tl.getVisibility()).isEqualTo(visibility);
|
||||
assertThat(tr.getVisibility()).isEqualTo(visibility);
|
||||
}
|
||||
|
||||
private void verifyBottomDotViewsVisibility(@View.Visibility final int visibility) {
|
||||
verifyBottomDotViewsNullable(false);
|
||||
assertThat(mScreenDecorations.mBottomLeftDot.getVisibility()).isEqualTo(visibility);
|
||||
assertThat(mScreenDecorations.mBottomRightDot.getVisibility()).isEqualTo(visibility);
|
||||
View bl = findViewFromOverlays(R.id.privacy_dot_bottom_left_container);
|
||||
View br = findViewFromOverlays(R.id.privacy_dot_bottom_right_container);
|
||||
assertThat(bl.getVisibility()).isEqualTo(visibility);
|
||||
assertThat(br.getVisibility()).isEqualTo(visibility);
|
||||
}
|
||||
|
||||
private void verifyDotViewsVisibility(@View.Visibility final int visibility) {
|
||||
@@ -219,32 +285,32 @@ public class ScreenDecorationsTest extends SysuiTestCase {
|
||||
|
||||
if (left) {
|
||||
assertNotNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT]);
|
||||
verify(mWindowManager, times(1))
|
||||
.addView(eq(mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT]), any());
|
||||
verify(mWindowManager, times(1)).addView(
|
||||
eq(mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT].getRootView()), any());
|
||||
} else {
|
||||
assertNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT]);
|
||||
}
|
||||
|
||||
if (top) {
|
||||
assertNotNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP]);
|
||||
verify(mWindowManager, times(1))
|
||||
.addView(eq(mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP]), any());
|
||||
verify(mWindowManager, times(1)).addView(
|
||||
eq(mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP].getRootView()), any());
|
||||
} else {
|
||||
assertNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP]);
|
||||
}
|
||||
|
||||
if (right) {
|
||||
assertNotNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT]);
|
||||
verify(mWindowManager, times(1))
|
||||
.addView(eq(mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT]), any());
|
||||
verify(mWindowManager, times(1)).addView(
|
||||
eq(mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT].getRootView()), any());
|
||||
} else {
|
||||
assertNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT]);
|
||||
}
|
||||
|
||||
if (bottom) {
|
||||
assertNotNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM]);
|
||||
verify(mWindowManager, times(1))
|
||||
.addView(eq(mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM]), any());
|
||||
verify(mWindowManager, times(1)).addView(
|
||||
eq(mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM].getRootView()), any());
|
||||
} else {
|
||||
assertNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM]);
|
||||
}
|
||||
@@ -381,18 +447,18 @@ public class ScreenDecorationsTest extends SysuiTestCase {
|
||||
doReturn(null).when(mScreenDecorations).getCutout();
|
||||
|
||||
mScreenDecorations.start();
|
||||
View leftRoundedCorner =
|
||||
mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP].findViewById(R.id.left);
|
||||
View rightRoundedCorner =
|
||||
mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP].findViewById(R.id.right);
|
||||
View leftRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP].getRootView()
|
||||
.findViewById(R.id.left);
|
||||
View rightRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP].getRootView()
|
||||
.findViewById(R.id.right);
|
||||
verify(mScreenDecorations, atLeastOnce())
|
||||
.setSize(leftRoundedCorner, new Point(testTopRadius, testTopRadius));
|
||||
verify(mScreenDecorations, atLeastOnce())
|
||||
.setSize(rightRoundedCorner, new Point(testTopRadius, testTopRadius));
|
||||
leftRoundedCorner =
|
||||
mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM].findViewById(R.id.left);
|
||||
rightRoundedCorner =
|
||||
mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM].findViewById(R.id.right);
|
||||
leftRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM].getRootView()
|
||||
.findViewById(R.id.left);
|
||||
rightRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM].getRootView()
|
||||
.findViewById(R.id.right);
|
||||
verify(mScreenDecorations, atLeastOnce())
|
||||
.setSize(leftRoundedCorner, new Point(testBottomRadius, testBottomRadius));
|
||||
verify(mScreenDecorations, atLeastOnce())
|
||||
@@ -414,26 +480,26 @@ public class ScreenDecorationsTest extends SysuiTestCase {
|
||||
mScreenDecorations.start();
|
||||
final Point topRadius = new Point(testTopRadius, testTopRadius);
|
||||
final Point bottomRadius = new Point(testBottomRadius, testBottomRadius);
|
||||
View leftRoundedCorner =
|
||||
mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT].findViewById(R.id.left);
|
||||
View leftRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT].getRootView()
|
||||
.findViewById(R.id.left);
|
||||
boolean isTop = mScreenDecorations.isTopRoundedCorner(BOUNDS_POSITION_LEFT, R.id.left);
|
||||
verify(mScreenDecorations, atLeastOnce())
|
||||
.setSize(leftRoundedCorner, isTop ? topRadius : bottomRadius);
|
||||
|
||||
View rightRoundedCorner =
|
||||
mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT].findViewById(R.id.right);
|
||||
View rightRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT].getRootView()
|
||||
.findViewById(R.id.right);
|
||||
isTop = mScreenDecorations.isTopRoundedCorner(BOUNDS_POSITION_LEFT, R.id.right);
|
||||
verify(mScreenDecorations, atLeastOnce())
|
||||
.setSize(rightRoundedCorner, isTop ? topRadius : bottomRadius);
|
||||
|
||||
leftRoundedCorner =
|
||||
mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT].findViewById(R.id.left);
|
||||
leftRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT].getRootView()
|
||||
.findViewById(R.id.left);
|
||||
isTop = mScreenDecorations.isTopRoundedCorner(BOUNDS_POSITION_RIGHT, R.id.left);
|
||||
verify(mScreenDecorations, atLeastOnce())
|
||||
.setSize(leftRoundedCorner, isTop ? topRadius : bottomRadius);
|
||||
|
||||
rightRoundedCorner =
|
||||
mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT].findViewById(R.id.right);
|
||||
rightRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT].getRootView()
|
||||
.findViewById(R.id.right);
|
||||
isTop = mScreenDecorations.isTopRoundedCorner(BOUNDS_POSITION_RIGHT, R.id.right);
|
||||
verify(mScreenDecorations, atLeastOnce())
|
||||
.setSize(rightRoundedCorner, isTop ? topRadius : bottomRadius);
|
||||
@@ -790,6 +856,22 @@ public class ScreenDecorationsTest extends SysuiTestCase {
|
||||
|
||||
mScreenDecorations.onConfigurationChanged(new Configuration());
|
||||
verifyOverlaysExistAndAdded(true, false, true, false);
|
||||
|
||||
// Verify each privacy dot id appears only once
|
||||
mDecorProviders.stream().map(DecorProvider::getViewId).forEach(viewId -> {
|
||||
int findCount = 0;
|
||||
for (OverlayWindow overlay: mScreenDecorations.mOverlays) {
|
||||
if (overlay == null) {
|
||||
continue;
|
||||
}
|
||||
final View view = overlay.getRootView().findViewById(viewId);
|
||||
if (view != null) {
|
||||
findCount++;
|
||||
}
|
||||
}
|
||||
assertEquals(1, findCount);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -985,8 +1067,16 @@ public class ScreenDecorationsTest extends SysuiTestCase {
|
||||
R.bool.config_roundedCornerMultipleRadius, multipleRadius);
|
||||
mContext.getOrCreateTestableResources().addOverride(
|
||||
com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout, fillCutout);
|
||||
mContext.getOrCreateTestableResources().addOverride(
|
||||
R.bool.config_enablePrivacyDot, privacyDot);
|
||||
|
||||
mDecorProviders = new ArrayList<>();
|
||||
if (privacyDot) {
|
||||
mDecorProviders.add(mPrivacyDotTopLeftDecorProvider);
|
||||
mDecorProviders.add(mPrivacyDotTopRightDecorProvider);
|
||||
mDecorProviders.add(mPrivacyDotBottomLeftDecorProvider);
|
||||
mDecorProviders.add(mPrivacyDotBottomRightDecorProvider);
|
||||
}
|
||||
when(mPrivacyDotDecorProviderFactory.getProviders()).thenReturn(mDecorProviders);
|
||||
when(mPrivacyDotDecorProviderFactory.getHasProviders()).thenReturn(privacyDot);
|
||||
}
|
||||
|
||||
private DisplayCutout getDisplayCutoutForRotation(Insets safeInsets, Rect[] cutoutBounds) {
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.decor
|
||||
|
||||
import android.testing.AndroidTestingRunner
|
||||
import android.testing.TestableLooper.RunWithLooper
|
||||
import android.view.DisplayCutout
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Surface
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.util.mockito.eq
|
||||
import org.junit.Assert
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito
|
||||
import org.mockito.Mockito.anyInt
|
||||
import org.mockito.Mockito.spy
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.MockitoAnnotations
|
||||
import org.mockito.Mockito.`when` as whenever
|
||||
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
@RunWithLooper(setAsMainLooper = true)
|
||||
@SmallTest
|
||||
class OverlayWindowTest : SysuiTestCase() {
|
||||
|
||||
companion object {
|
||||
private val TEST_DECOR_VIEW_ID = R.id.privacy_dot_bottom_right_container
|
||||
private val TEST_DECOR_LAYOUT_ID = R.layout.privacy_dot_bottom_right
|
||||
}
|
||||
|
||||
private lateinit var overlay: OverlayWindow
|
||||
|
||||
@Mock private lateinit var layoutInflater: LayoutInflater
|
||||
@Mock private lateinit var decorProvider: DecorProvider
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
|
||||
layoutInflater = spy(LayoutInflater.from(mContext))
|
||||
|
||||
overlay = OverlayWindow(layoutInflater, DisplayCutout.BOUNDS_POSITION_RIGHT)
|
||||
|
||||
whenever(decorProvider.viewId).thenReturn(TEST_DECOR_VIEW_ID)
|
||||
whenever(decorProvider.inflateView(
|
||||
eq(layoutInflater),
|
||||
eq(overlay.rootView),
|
||||
anyInt())
|
||||
).then {
|
||||
val layoutInflater = it.getArgument<LayoutInflater>(0)
|
||||
val parent = it.getArgument<ViewGroup>(1)
|
||||
layoutInflater.inflate(TEST_DECOR_LAYOUT_ID, parent)
|
||||
return@then parent.getChildAt(parent.childCount - 1)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAnyBoundsPositionShallNoExceptionForConstructor() {
|
||||
OverlayWindow(layoutInflater, DisplayCutout.BOUNDS_POSITION_LEFT)
|
||||
OverlayWindow(layoutInflater, DisplayCutout.BOUNDS_POSITION_TOP)
|
||||
OverlayWindow(layoutInflater, DisplayCutout.BOUNDS_POSITION_RIGHT)
|
||||
OverlayWindow(layoutInflater, DisplayCutout.BOUNDS_POSITION_BOTTOM)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAddProvider() {
|
||||
@Surface.Rotation val rotation = Surface.ROTATION_270
|
||||
overlay.addDecorProvider(decorProvider, rotation)
|
||||
verify(decorProvider, Mockito.times(1)).inflateView(
|
||||
eq(layoutInflater), eq(overlay.rootView), eq(rotation))
|
||||
val viewFoundFromRootView = overlay.rootView.findViewById<View>(TEST_DECOR_VIEW_ID)
|
||||
Assert.assertNotNull(viewFoundFromRootView)
|
||||
Assert.assertEquals(viewFoundFromRootView, overlay.getView(TEST_DECOR_VIEW_ID))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRemoveView() {
|
||||
@Surface.Rotation val rotation = Surface.ROTATION_270
|
||||
overlay.addDecorProvider(decorProvider, rotation)
|
||||
overlay.removeView(TEST_DECOR_VIEW_ID)
|
||||
val viewFoundFromRootView = overlay.rootView.findViewById<View>(TEST_DECOR_VIEW_ID)
|
||||
Assert.assertNull(viewFoundFromRootView)
|
||||
Assert.assertNull(overlay.getView(TEST_DECOR_LAYOUT_ID))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.decor
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.testing.AndroidTestingRunner
|
||||
import android.testing.TestableLooper.RunWithLooper
|
||||
import android.view.DisplayCutout
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import org.junit.Assert
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.spy
|
||||
import org.mockito.Mockito.`when` as whenever
|
||||
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
@RunWithLooper(setAsMainLooper = true)
|
||||
@SmallTest
|
||||
class PrivacyDotDecorProviderFactoryTest : SysuiTestCase() {
|
||||
private lateinit var mPrivacyDotDecorProviderFactory: PrivacyDotDecorProviderFactory
|
||||
|
||||
@Mock private lateinit var resources: Resources
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
resources = spy(mContext.resources)
|
||||
mPrivacyDotDecorProviderFactory = PrivacyDotDecorProviderFactory(resources)
|
||||
}
|
||||
|
||||
private fun setPrivacyDotResources(isEnable: Boolean) {
|
||||
whenever(resources.getBoolean(R.bool.config_enablePrivacyDot)).thenReturn(isEnable)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGetNoCornerDecorProviderWithNoPrivacyDot() {
|
||||
setPrivacyDotResources(false)
|
||||
|
||||
Assert.assertEquals(false, mPrivacyDotDecorProviderFactory.hasProviders)
|
||||
Assert.assertEquals(0, mPrivacyDotDecorProviderFactory.providers.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGet4CornerDecorProvidersWithPrivacyDot() {
|
||||
setPrivacyDotResources(true)
|
||||
val providers = mPrivacyDotDecorProviderFactory.providers
|
||||
|
||||
Assert.assertEquals(true, mPrivacyDotDecorProviderFactory.hasProviders)
|
||||
Assert.assertEquals(4, providers.size)
|
||||
Assert.assertEquals(1, providers.count {
|
||||
((it.viewId == R.id.privacy_dot_top_left_container)
|
||||
and it.alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_TOP)
|
||||
and it.alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_LEFT))
|
||||
})
|
||||
Assert.assertEquals(1, providers.count {
|
||||
((it.viewId == R.id.privacy_dot_top_right_container)
|
||||
and it.alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_TOP)
|
||||
and it.alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_RIGHT))
|
||||
})
|
||||
Assert.assertEquals(1, providers.count {
|
||||
((it.viewId == R.id.privacy_dot_bottom_left_container)
|
||||
and it.alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_BOTTOM)
|
||||
and it.alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_LEFT))
|
||||
})
|
||||
Assert.assertEquals(1, providers.count {
|
||||
((it.viewId == R.id.privacy_dot_bottom_right_container)
|
||||
and it.alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_BOTTOM)
|
||||
and it.alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_RIGHT))
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user