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:
committed by
Android (Google) Code Review
commit
cb6dff62d9
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user