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     protected final LayoutInflater mInflater;
38 
39     private final Rect mEnforcedRectangle = new Rect();
40     private final float mEnforcedCornerRadius;
41     private final ViewOutlineProvider mCornerRadiusEnforcementOutline = new ViewOutlineProvider() {
42         @Override
43         public void getOutline(View view, Outline outline) {
44             if (mEnforcedRectangle.isEmpty() || mEnforcedCornerRadius <= 0) {
45                 outline.setEmpty();
46             } else {
47                 outline.setRoundRect(mEnforcedRectangle, mEnforcedCornerRadius);
48             }
49         }
50     };
51 
BaseLauncherAppWidgetHostView(Context context)52     public BaseLauncherAppWidgetHostView(Context context) {
53         super(context);
54 
55         setExecutor(Executors.THREAD_POOL_EXECUTOR);
56 
57         mInflater = LayoutInflater.from(context);
58         mEnforcedCornerRadius = RoundedCornerEnforcement.computeEnforcedRadius(getContext());
59     }
60 
61     @Override
getErrorView()62     protected View getErrorView() {
63         return mInflater.inflate(R.layout.appwidget_error, this, false);
64     }
65 
66     /**
67      * Fall back to error layout instead of showing widget.
68      */
switchToErrorView()69     public void switchToErrorView() {
70         // Update the widget with 0 Layout id, to reset the view to error view.
71         updateAppWidget(new RemoteViews(getAppWidgetInfo().provider.getPackageName(), 0));
72     }
73 
74     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)75     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
76         try {
77             super.onLayout(changed, left, top, right, bottom);
78         } catch (final RuntimeException e) {
79             post(this::switchToErrorView);
80         }
81 
82         enforceRoundedCorners();
83     }
84 
85     @UiThread
resetRoundedCorners()86     private void resetRoundedCorners() {
87         setOutlineProvider(ViewOutlineProvider.BACKGROUND);
88         setClipToOutline(false);
89     }
90 
91     @UiThread
enforceRoundedCorners()92     private void enforceRoundedCorners() {
93         if (mEnforcedCornerRadius <= 0 || !RoundedCornerEnforcement.isRoundedCornerEnabled()) {
94             resetRoundedCorners();
95             return;
96         }
97         View background = RoundedCornerEnforcement.findBackground(this);
98         if (background == null
99                 || RoundedCornerEnforcement.hasAppWidgetOptedOut(this, background)) {
100             resetRoundedCorners();
101             return;
102         }
103         RoundedCornerEnforcement.computeRoundedRectangle(this,
104                 background,
105                 mEnforcedRectangle);
106         setOutlineProvider(mCornerRadiusEnforcementOutline);
107         setClipToOutline(true);
108     }
109 
110     /** Returns the corner radius currently enforced, in pixels. */
getEnforcedCornerRadius()111     public float getEnforcedCornerRadius() {
112         return mEnforcedCornerRadius;
113     }
114 
115     /** Returns true if the corner radius are enforced for this App Widget. */
hasEnforcedCornerRadius()116     public boolean hasEnforcedCornerRadius() {
117         return getClipToOutline();
118     }
119 }
120