Merge "Move Privacy dots to new DecorProvider"

This commit is contained in:
TreeHugger Robot
2022-01-27 16:21:47 +00:00
committed by Android (Google) Code Review
14 changed files with 811 additions and 187 deletions

View 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>

View 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>

View 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>

View 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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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);
}
}

View File

@@ -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)
}
}

View File

@@ -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
}

View File

@@ -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)
}
}
}

View File

@@ -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 */)
}
}

View File

@@ -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) {

View File

@@ -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))
}
}

View File

@@ -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))
})
}
}