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.content.Context;
20 import android.graphics.Outline;
21 import android.graphics.Rect;
22 import android.view.LayoutInflater;
23 import android.view.View;
24 import android.view.ViewOutlineProvider;
25 import android.widget.RemoteViews;
26 
27 import androidx.annotation.UiThread;
28 
29 import com.android.launcher3.R;
30 import com.android.launcher3.util.Executors;
31 
32 /**
33  * Launcher AppWidgetHostView with support for rounded corners and a fallback View.
34  */
35 public abstract class BaseLauncherAppWidgetHostView extends NavigableAppWidgetHostView {
36 
37     private static final ViewOutlineProvider VIEW_OUTLINE_PROVIDER = new ViewOutlineProvider() {
38         @Override
39         public void getOutline(View view, Outline outline) {
40             // Since ShortcutAndWidgetContainer sets clipChildren to false, we should restrict the
41             // outline to be the view bounds, otherwise widgets might draw themselves outside of
42             // the launcher view. Setting alpha to 0 to match the previous behavior.
43             outline.setRect(0, 0, view.getWidth(), view.getHeight());
44             outline.setAlpha(.0f);
45         }
46     };
47 
48     protected final LayoutInflater mInflater;
49 
50     private final Rect mEnforcedRectangle = new Rect();
51     private final float mEnforcedCornerRadius;
52     private final ViewOutlineProvider mCornerRadiusEnforcementOutline = new ViewOutlineProvider() {
53         @Override
54         public void getOutline(View view, Outline outline) {
55             if (mEnforcedRectangle.isEmpty() || mEnforcedCornerRadius <= 0) {
56                 outline.setEmpty();
57             } else {
58                 outline.setRoundRect(mEnforcedRectangle, mEnforcedCornerRadius);
59             }
60         }
61     };
62 
63     private boolean mIsCornerRadiusEnforced;
64 
BaseLauncherAppWidgetHostView(Context context)65     public BaseLauncherAppWidgetHostView(Context context) {
66         super(context);
67 
68         setExecutor(Executors.THREAD_POOL_EXECUTOR);
69         setClipToOutline(true);
70 
71         mInflater = LayoutInflater.from(context);
72         mEnforcedCornerRadius = RoundedCornerEnforcement.computeEnforcedRadius(getContext());
73     }
74 
75     @Override
getErrorView()76     protected View getErrorView() {
77         return mInflater.inflate(R.layout.appwidget_error, this, false);
78     }
79 
80     /**
81      * Fall back to error layout instead of showing widget.
82      */
switchToErrorView()83     public void switchToErrorView() {
84         // Update the widget with 0 Layout id, to reset the view to error view.
85         updateAppWidget(new RemoteViews(getAppWidgetInfo().provider.getPackageName(), 0));
86     }
87 
88     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)89     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
90         try {
91             super.onLayout(changed, left, top, right, bottom);
92         } catch (final RuntimeException e) {
93             post(this::switchToErrorView);
94         }
95 
96         enforceRoundedCorners();
97     }
98 
99     @UiThread
resetRoundedCorners()100     private void resetRoundedCorners() {
101         setOutlineProvider(VIEW_OUTLINE_PROVIDER);
102         mIsCornerRadiusEnforced = false;
103     }
104 
105     @UiThread
enforceRoundedCorners()106     private void enforceRoundedCorners() {
107         if (mEnforcedCornerRadius <= 0 || !RoundedCornerEnforcement.isRoundedCornerEnabled()) {
108             resetRoundedCorners();
109             return;
110         }
111         View background = RoundedCornerEnforcement.findBackground(this);
112         if (background == null
113                 || RoundedCornerEnforcement.hasAppWidgetOptedOut(this, background)) {
114             resetRoundedCorners();
115             return;
116         }
117         RoundedCornerEnforcement.computeRoundedRectangle(this,
118                 background,
119                 mEnforcedRectangle);
120         setOutlineProvider(mCornerRadiusEnforcementOutline);
121         mIsCornerRadiusEnforced = true;
122         invalidateOutline();
123     }
124 
125     /** Returns the corner radius currently enforced, in pixels. */
getEnforcedCornerRadius()126     public float getEnforcedCornerRadius() {
127         return mEnforcedCornerRadius;
128     }
129 
130     /** Returns true if the corner radius are enforced for this App Widget. */
hasEnforcedCornerRadius()131     public boolean hasEnforcedCornerRadius() {
132         return mIsCornerRadiusEnforced;
133     }
134 }
135