• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.launcher3.widget;
18 
19 import android.appwidget.AppWidgetHostView;
20 import android.content.Context;
21 import android.content.res.Resources;
22 import android.graphics.Rect;
23 import android.view.View;
24 import android.view.ViewGroup;
25 
26 import androidx.annotation.IdRes;
27 import androidx.annotation.NonNull;
28 import androidx.annotation.Nullable;
29 
30 import com.android.launcher3.R;
31 import com.android.launcher3.Utilities;
32 import com.android.launcher3.config.FeatureFlags;
33 
34 import java.util.ArrayList;
35 import java.util.List;
36 
37 /**
38  * Utilities to compute the enforced the use of rounded corners on App Widgets.
39  */
40 public class RoundedCornerEnforcement {
41     // This class is only a namespace and not meant to be instantiated.
RoundedCornerEnforcement()42     private RoundedCornerEnforcement() {
43     }
44 
45     /**
46      * Find the background view for a widget.
47      *
48      * @param appWidget the view containing the App Widget (typically the instance of
49      * {@link AppWidgetHostView}).
50      */
51     @Nullable
findBackground(@onNull View appWidget)52     public static View findBackground(@NonNull View appWidget) {
53         List<View> backgrounds = findViewsWithId(appWidget, android.R.id.background);
54         if (backgrounds.size() == 1) {
55             return backgrounds.get(0);
56         }
57         // Really, the argument should contain the widget, so it cannot be the background.
58         if (appWidget instanceof ViewGroup) {
59             ViewGroup vg = (ViewGroup) appWidget;
60             if (vg.getChildCount() > 0) {
61                 return findUndefinedBackground(vg.getChildAt(0));
62             }
63         }
64         return appWidget;
65     }
66 
67     /**
68      * Check whether the app widget has opted out of the enforcement.
69      */
hasAppWidgetOptedOut(@onNull View appWidget, @NonNull View background)70     public static boolean hasAppWidgetOptedOut(@NonNull View appWidget, @NonNull View background) {
71         return background.getId() == android.R.id.background && background.getClipToOutline();
72     }
73 
74     /** Check if the app widget is in the deny list. */
isRoundedCornerEnabled()75     public static boolean isRoundedCornerEnabled() {
76         return Utilities.ATLEAST_S && FeatureFlags.ENABLE_ENFORCED_ROUNDED_CORNERS.get();
77     }
78 
79     /**
80      * Computes the rounded rectangle needed for this app widget.
81      *
82      * @param appWidget View onto which the rounded rectangle will be applied.
83      * @param background Background view. This must be either {@code appWidget} or a descendant
84      *                  of {@code appWidget}.
85      * @param outRect Rectangle set to the rounded rectangle coordinates, in the reference frame
86      *                of {@code appWidget}.
87      */
computeRoundedRectangle(@onNull View appWidget, @NonNull View background, @NonNull Rect outRect)88     public static void computeRoundedRectangle(@NonNull View appWidget, @NonNull View background,
89             @NonNull Rect outRect) {
90         outRect.left = 0;
91         outRect.right = background.getWidth();
92         outRect.top = 0;
93         outRect.bottom = background.getHeight();
94         while (background != appWidget) {
95             outRect.offset(background.getLeft(), background.getTop());
96             background = (View) background.getParent();
97         }
98     }
99 
100     /**
101      * Computes the radius of the rounded rectangle that should be applied to a widget expanded
102      * in the given context.
103      */
computeEnforcedRadius(@onNull Context context)104     public static float computeEnforcedRadius(@NonNull Context context) {
105         if (!Utilities.ATLEAST_S) {
106             return 0;
107         }
108         Resources res = context.getResources();
109         float systemRadius = res.getDimension(android.R.dimen.system_app_widget_background_radius);
110         float defaultRadius = res.getDimension(R.dimen.enforced_rounded_corner_max_radius);
111         return Math.min(defaultRadius, systemRadius);
112     }
113 
findViewsWithId(View view, @IdRes int viewId)114     private static List<View> findViewsWithId(View view, @IdRes int viewId) {
115         List<View> output = new ArrayList<>();
116         accumulateViewsWithId(view, viewId, output);
117         return output;
118     }
119 
120     // Traverse views. If the predicate returns true, continue on the children, otherwise, don't.
accumulateViewsWithId(View view, @IdRes int viewId, List<View> output)121     private static void accumulateViewsWithId(View view, @IdRes int viewId, List<View> output) {
122         if (view.getId() == viewId) {
123             output.add(view);
124             return;
125         }
126         if (view instanceof ViewGroup) {
127             ViewGroup vg = (ViewGroup) view;
128             for (int i = 0; i < vg.getChildCount(); i++) {
129                 accumulateViewsWithId(vg.getChildAt(i), viewId, output);
130             }
131         }
132     }
133 
isViewVisible(View view)134     private static boolean isViewVisible(View view) {
135         if (view.getVisibility() != View.VISIBLE) {
136             return false;
137         }
138         return !view.willNotDraw() || view.getForeground() != null || view.getBackground() != null;
139     }
140 
141     @Nullable
findUndefinedBackground(View current)142     private static View findUndefinedBackground(View current) {
143         if (current.getVisibility() != View.VISIBLE) {
144             return null;
145         }
146         if (isViewVisible(current)) {
147             return current;
148         }
149         View lastVisibleView = null;
150         // Find the first view that is either not a ViewGroup, or a ViewGroup which will draw
151         // something, or a ViewGroup that contains more than one view.
152         if (current instanceof ViewGroup) {
153             ViewGroup vg = (ViewGroup) current;
154             for (int i = 0; i < vg.getChildCount(); i++) {
155                 View visibleView = findUndefinedBackground(vg.getChildAt(i));
156                 if (visibleView != null) {
157                     if (lastVisibleView != null) {
158                         return current; // At least two visible children
159                     }
160                     lastVisibleView = visibleView;
161                 }
162             }
163         }
164         return lastVisibleView;
165     }
166 }
167