• 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 static com.android.launcher3.Flags.useSystemRadiusForAppWidgets;
20 
21 import android.appwidget.AppWidgetHostView;
22 import android.content.Context;
23 import android.content.res.Resources;
24 import android.graphics.Rect;
25 import android.view.View;
26 import android.view.ViewGroup;
27 
28 import androidx.annotation.IdRes;
29 import androidx.annotation.NonNull;
30 import androidx.annotation.Nullable;
31 
32 import com.android.launcher3.R;
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 background)70     public static boolean hasAppWidgetOptedOut(@NonNull View background) {
71         return background.getId() == android.R.id.background && background.getClipToOutline();
72     }
73 
74     /**
75      * Computes the rounded rectangle needed for this app widget.
76      *
77      * @param appWidget View onto which the rounded rectangle will be applied.
78      * @param background Background view. This must be either {@code appWidget} or a descendant
79      *                  of {@code appWidget}.
80      * @param outRect Rectangle set to the rounded rectangle coordinates, in the reference frame
81      *                of {@code appWidget}.
82      */
computeRoundedRectangle(@onNull View appWidget, @NonNull View background, @NonNull Rect outRect)83     public static void computeRoundedRectangle(@NonNull View appWidget, @NonNull View background,
84             @NonNull Rect outRect) {
85         outRect.left = 0;
86         outRect.right = background.getWidth();
87         outRect.top = 0;
88         outRect.bottom = background.getHeight();
89         while (background != appWidget) {
90             outRect.offset(background.getLeft(), background.getTop());
91             background = (View) background.getParent();
92         }
93     }
94 
95     /**
96      * Computes the radius of the rounded rectangle that should be applied to a widget expanded
97      * in the given context.
98      */
computeEnforcedRadius(@onNull Context context)99     public static float computeEnforcedRadius(@NonNull Context context) {
100         Resources res = context.getResources();
101         float systemRadius = res.getDimension(android.R.dimen.system_app_widget_background_radius);
102         if (useSystemRadiusForAppWidgets()) {
103             return systemRadius;
104         }
105 
106         float defaultRadius = res.getDimension(R.dimen.enforced_rounded_corner_max_radius);
107         return Math.min(defaultRadius, systemRadius);
108     }
109 
findViewsWithId(View view, @IdRes int viewId)110     private static List<View> findViewsWithId(View view, @IdRes int viewId) {
111         List<View> output = new ArrayList<>();
112         accumulateViewsWithId(view, viewId, output);
113         return output;
114     }
115 
116     // Traverse views. If the predicate returns true, continue on the children, otherwise, don't.
accumulateViewsWithId(View view, @IdRes int viewId, List<View> output)117     private static void accumulateViewsWithId(View view, @IdRes int viewId, List<View> output) {
118         if (view.getId() == viewId) {
119             output.add(view);
120             return;
121         }
122         if (view instanceof ViewGroup) {
123             ViewGroup vg = (ViewGroup) view;
124             for (int i = 0; i < vg.getChildCount(); i++) {
125                 accumulateViewsWithId(vg.getChildAt(i), viewId, output);
126             }
127         }
128     }
129 
isViewVisible(View view)130     private static boolean isViewVisible(View view) {
131         if (view.getVisibility() != View.VISIBLE) {
132             return false;
133         }
134         return !view.willNotDraw() || view.getForeground() != null || view.getBackground() != null;
135     }
136 
137     @Nullable
findUndefinedBackground(View current)138     private static View findUndefinedBackground(View current) {
139         if (current.getVisibility() != View.VISIBLE) {
140             return null;
141         }
142         if (isViewVisible(current)) {
143             return current;
144         }
145         View lastVisibleView = null;
146         // Find the first view that is either not a ViewGroup, or a ViewGroup which will draw
147         // something, or a ViewGroup that contains more than one view.
148         if (current instanceof ViewGroup) {
149             ViewGroup vg = (ViewGroup) current;
150             for (int i = 0; i < vg.getChildCount(); i++) {
151                 View visibleView = findUndefinedBackground(vg.getChildAt(i));
152                 if (visibleView != null) {
153                     if (lastVisibleView != null) {
154                         return current; // At least two visible children
155                     }
156                     lastVisibleView = visibleView;
157                 }
158             }
159         }
160         return lastVisibleView;
161     }
162 }
163