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