Merge changes I6ce23251,I56fca1b7 into sc-dev

* changes:
  Update the resize frame to use the forced radius.
  Enforce rounded corners on App Widgets.
This commit is contained in:
TreeHugger Robot
2021-03-24 00:39:17 +00:00
committed by Android (Google) Code Review
7 changed files with 264 additions and 1 deletions
+1
View File
@@ -26,6 +26,7 @@
<!-- Frame -->
<ImageView
android:id="@+id/widget_resize_frame"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
+3
View File
@@ -283,4 +283,7 @@
<!-- Taskbar related (placeholders to compile in Launcher3 without Quickstep) -->
<dimen name="taskbar_size">0dp</dimen>
<!-- Size of the maximum radius for the enforced rounded rectangles. -->
<dimen name="enforced_rounded_corner_max_radius">16dp</dimen>
</resources>
@@ -14,12 +14,15 @@ import android.appwidget.AppWidgetProviderInfo;
import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.os.Bundle;
import android.util.AttributeSet;
import android.util.SizeF;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageView;
import androidx.annotation.Nullable;
@@ -166,6 +169,15 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O
DragLayer dl = launcher.getDragLayer();
AppWidgetResizeFrame frame = (AppWidgetResizeFrame) launcher.getLayoutInflater()
.inflate(R.layout.app_widget_resize_frame, dl, false);
if (widget.hasEnforcedCornerRadius()) {
float enforcedCornerRadius = widget.getEnforcedCornerRadius();
ImageView imageView = frame.findViewById(R.id.widget_resize_frame);
Drawable d = imageView.getDrawable();
if (d instanceof GradientDrawable) {
GradientDrawable gd = (GradientDrawable) d.mutate();
gd.setCornerRadius(enforcedCornerRadius);
}
}
frame.setupForWidget(widget, cellLayout, dl);
((DragLayer.LayoutParams) frame.getLayoutParams()).customPosition = true;
@@ -215,6 +215,9 @@ public final class FeatureFlags {
public static final BooleanFlag ENABLE_SPLIT_SELECT = getDebugFlag(
"ENABLE_SPLIT_SELECT", false, "Uses new split screen selection overview UI");
public static final BooleanFlag ENABLE_ENFORCED_ROUNDED_CORNERS = new DeviceFlag(
"ENABLE_ENFORCED_ROUNDED_CORNERS", true, "Enforce rounded corners on all App Widgets");
public static void initialize(Context context) {
synchronized (sDebugFlags) {
for (DebugFlag flag : sDebugFlags) {
@@ -17,8 +17,12 @@ package com.android.launcher3.dragndrop;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import com.android.launcher3.widget.LauncherAppWidgetHostView;
@@ -28,14 +32,30 @@ public final class AppWidgetHostViewDrawable extends Drawable {
private final LauncherAppWidgetHostView mAppWidgetHostView;
private Paint mPaint = new Paint();
private final Path mClipPath;
public AppWidgetHostViewDrawable(LauncherAppWidgetHostView appWidgetHostView) {
mAppWidgetHostView = appWidgetHostView;
Path clipPath = null;
if (appWidgetHostView.getClipToOutline()) {
Outline outline = new Outline();
mAppWidgetHostView.getOutlineProvider().getOutline(mAppWidgetHostView, outline);
Rect rect = new Rect();
if (outline.getRect(rect)) {
float radius = outline.getRadius();
clipPath = new Path();
clipPath.addRoundRect(new RectF(rect), radius, radius, Path.Direction.CCW);
}
}
mClipPath = clipPath;
}
@Override
public void draw(Canvas canvas) {
int saveCount = canvas.saveLayer(0, 0, getIntrinsicWidth(), getIntrinsicHeight(), mPaint);
if (mClipPath != null) {
canvas.clipPath(mClipPath);
}
mAppWidgetHostView.draw(canvas);
canvas.restoreToCount(saveCount);
}
@@ -20,6 +20,7 @@ import android.appwidget.AppWidgetProviderInfo;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Canvas;
import android.graphics.Outline;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Handler;
@@ -32,12 +33,14 @@ import android.view.MotionEvent;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.AdapterView;
import android.widget.Advanceable;
import android.widget.RemoteViews;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import com.android.launcher3.CheckLongPressHelper;
import com.android.launcher3.Launcher;
@@ -95,6 +98,18 @@ public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView
private final Rect mWidgetSizeAtDrag = new Rect();
private final RectF mTempRectF = new RectF();
private final boolean mIsRtl;
private final Rect mEnforcedRectangle = new Rect();
private final float mEnforcedCornerRadius;
private final ViewOutlineProvider mCornerRadiusEnforcementOutline = new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
if (mEnforcedRectangle.isEmpty() || mEnforcedCornerRadius <= 0) {
outline.setEmpty();
} else {
outline.setRoundRect(mEnforcedRectangle, mEnforcedCornerRadius);
}
}
};
public LauncherAppWidgetHostView(Context context) {
super(context);
@@ -112,6 +127,8 @@ public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView
mIsRtl = Utilities.isRtl(context.getResources());
mColorExtractor = LocalColorExtractor.newInstance(getContext());
mColorExtractor.setListener(this);
mEnforcedCornerRadius = RoundedCornerEnforcement.computeEnforcedRadius(getContext());
}
@Override
@@ -169,7 +186,7 @@ public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView
if (viewGroup instanceof AdapterView) {
return true;
} else {
for (int i=0; i < viewGroup.getChildCount(); i++) {
for (int i = 0; i < viewGroup.getChildCount(); i++) {
View child = viewGroup.getChildAt(i);
if (child instanceof ViewGroup) {
if (checkScrollableRecursively((ViewGroup) child)) {
@@ -272,6 +289,8 @@ public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView
int pageId = mWorkspace.getPageIndexForScreenId(info.screenId);
updateColorExtraction(mCurrentWidgetSize, pageId);
}
enforceRoundedCorners();
}
/** Starts the drag mode. */
@@ -469,4 +488,39 @@ public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView
}
return false;
}
@UiThread
private void resetRoundedCorners() {
setOutlineProvider(ViewOutlineProvider.BACKGROUND);
setClipToOutline(false);
}
@UiThread
private void enforceRoundedCorners() {
if (mEnforcedCornerRadius <= 0 || !RoundedCornerEnforcement.isRoundedCornerEnabled(this)) {
resetRoundedCorners();
return;
}
View background = RoundedCornerEnforcement.findBackground(this);
if (RoundedCornerEnforcement.hasAppWidgetOptedOut(this, background)) {
resetRoundedCorners();
return;
}
RoundedCornerEnforcement.computeRoundedRectangle(this,
background,
mEnforcedRectangle);
setOutlineProvider(mCornerRadiusEnforcementOutline);
setClipToOutline(true);
}
/** Returns the corner radius currently enforced, in pixels. */
public float getEnforcedCornerRadius() {
return mEnforcedCornerRadius;
}
/** Returns true if the corner radius are enforced for this App Widget. */
public boolean hasEnforcedCornerRadius() {
return getClipToOutline();
}
}
@@ -0,0 +1,170 @@
/*
* Copyright (C) 2021 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.launcher3.widget;
import android.appwidget.AppWidgetHostView;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Rect;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
import java.util.ArrayList;
import java.util.List;
/**
* Utilities to compute the enforced the use of rounded corners on App Widgets.
*/
public class RoundedCornerEnforcement {
// This class is only a namespace and not meant to be instantiated.
private RoundedCornerEnforcement() {
}
/**
* Find the background view for a widget.
*
* @param appWidget the view containing the App Widget (typically the instance of
* {@link AppWidgetHostView}).
*/
@Nullable
public static View findBackground(@NonNull View appWidget) {
List<View> backgrounds = findViewsWithId(appWidget, android.R.id.background);
if (backgrounds.size() == 1) {
return backgrounds.get(0);
}
// Really, the argument should contain the widget, so it cannot be the background.
if (appWidget instanceof ViewGroup) {
ViewGroup vg = (ViewGroup) appWidget;
if (vg.getChildCount() > 0) {
return findUndefinedBackground(vg.getChildAt(0));
}
}
return appWidget;
}
/**
* Check whether the app widget has opted out of the enforcement.
*/
public static boolean hasAppWidgetOptedOut(@NonNull View appWidget, @NonNull View background) {
return background.getId() == android.R.id.background && background.getClipToOutline();
}
/** Check if the app widget is in the deny list. */
public static boolean isRoundedCornerEnabled(@NonNull View view) {
if (!Utilities.ATLEAST_S || !FeatureFlags.ENABLE_ENFORCED_ROUNDED_CORNERS.get()) {
return false;
}
// Here we need to test if the view's component is in the (to be created) deny list.
return true;
}
/**
* Computes the rounded rectangle needed for this app widget.
*
* @param appWidget View onto which the rounded rectangle will be applied.
* @param background Background view. This must be either {@code appWidget} or a descendant
* of {@code appWidget}.
* @param outRect Rectangle set to the rounded rectangle coordinates, in the reference frame
* of {@code appWidget}.
*/
public static void computeRoundedRectangle(@NonNull View appWidget, @NonNull View background,
@NonNull Rect outRect) {
outRect.left = 0;
outRect.right = background.getWidth();
outRect.top = 0;
outRect.bottom = background.getHeight();
while (background != appWidget) {
outRect.offset(background.getLeft(), background.getTop());
background = (View) background.getParent();
}
}
/**
* Computes the radius of the rounded rectangle that should be applied to a widget expanded
* in the given context.
*/
public static float computeEnforcedRadius(@NonNull Context context) {
if (!Utilities.ATLEAST_S) {
return 0;
}
Resources res = context.getResources();
float systemRadius = res.getDimension(android.R.dimen.system_app_widget_background_radius);
float defaultRadius = res.getDimension(R.dimen.enforced_rounded_corner_max_radius);
return Math.min(defaultRadius, systemRadius);
}
private static List<View> findViewsWithId(View view, @IdRes int viewId) {
List<View> output = new ArrayList<>();
accumulateViewsWithId(view, viewId, output);
return output;
}
// Traverse views. If the predicate returns true, continue on the children, otherwise, don't.
private static void accumulateViewsWithId(View view, @IdRes int viewId, List<View> output) {
if (view.getId() == viewId) {
output.add(view);
return;
}
if (view instanceof ViewGroup) {
ViewGroup vg = (ViewGroup) view;
for (int i = 0; i < vg.getChildCount(); i++) {
accumulateViewsWithId(vg.getChildAt(i), viewId, output);
}
}
}
private static boolean isViewVisible(View view) {
if (view.getVisibility() != View.VISIBLE) {
return false;
}
return !view.willNotDraw() || view.getForeground() != null || view.getBackground() != null;
}
@Nullable
private static View findUndefinedBackground(View current) {
if (current.getVisibility() != View.VISIBLE) {
return null;
}
if (isViewVisible(current)) {
return current;
}
View lastVisibleView = null;
// Find the first view that is either not a ViewGroup, or a ViewGroup which will draw
// something, or a ViewGroup that contains more than one view.
if (current instanceof ViewGroup) {
ViewGroup vg = (ViewGroup) current;
for (int i = 0; i < vg.getChildCount(); i++) {
View visibleView = findUndefinedBackground(vg.getChildAt(i));
if (visibleView != null) {
if (lastVisibleView != null) {
return current; // At least two visible children
}
lastVisibleView = visibleView;
}
}
}
return lastVisibleView;
}
}