/* * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.launcher3.widget; import android.content.Context; import android.graphics.Outline; import android.graphics.Rect; import android.view.LayoutInflater; import android.view.View; import android.view.ViewOutlineProvider; import android.widget.RemoteViews; import androidx.annotation.UiThread; import com.android.launcher3.R; import com.android.launcher3.util.Executors; /** * Launcher AppWidgetHostView with support for rounded corners and a fallback View. */ public abstract class BaseLauncherAppWidgetHostView extends NavigableAppWidgetHostView { private static final ViewOutlineProvider VIEW_OUTLINE_PROVIDER = new ViewOutlineProvider() { @Override public void getOutline(View view, Outline outline) { // Since ShortcutAndWidgetContainer sets clipChildren to false, we should restrict the // outline to be the view bounds, otherwise widgets might draw themselves outside of // the launcher view. Setting alpha to 0 to match the previous behavior. outline.setRect(0, 0, view.getWidth(), view.getHeight()); outline.setAlpha(.0f); } }; protected final LayoutInflater mInflater; private final Rect mEnforcedRectangle = new Rect(); private final float mEnforcedCornerRadius; private final ViewOutlineProvider mCornerRadiusEnforcementOutline = new ViewOutlineProvider() { @Override public void getOutline(View view, Outline outline) { if (mEnforcedRectangle.isEmpty() || mEnforcedCornerRadius <= 0) { outline.setEmpty(); } else { outline.setRoundRect(mEnforcedRectangle, mEnforcedCornerRadius); } } }; private boolean mIsCornerRadiusEnforced; public BaseLauncherAppWidgetHostView(Context context) { super(context); setExecutor(Executors.THREAD_POOL_EXECUTOR); setClipToOutline(true); mInflater = LayoutInflater.from(context); mEnforcedCornerRadius = RoundedCornerEnforcement.computeEnforcedRadius(getContext()); } @Override protected View getErrorView() { return mInflater.inflate(R.layout.appwidget_error, this, false); } /** * Fall back to error layout instead of showing widget. */ public void switchToErrorView() { // Update the widget with 0 Layout id, to reset the view to error view. updateAppWidget(new RemoteViews(getAppWidgetInfo().provider.getPackageName(), 0)); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { try { super.onLayout(changed, left, top, right, bottom); } catch (final RuntimeException e) { post(this::switchToErrorView); } enforceRoundedCorners(); } @UiThread private void resetRoundedCorners() { setOutlineProvider(VIEW_OUTLINE_PROVIDER); mIsCornerRadiusEnforced = false; } @UiThread private void enforceRoundedCorners() { if (mEnforcedCornerRadius <= 0 || !RoundedCornerEnforcement.isRoundedCornerEnabled()) { resetRoundedCorners(); return; } View background = RoundedCornerEnforcement.findBackground(this); if (background == null || RoundedCornerEnforcement.hasAppWidgetOptedOut(this, background)) { resetRoundedCorners(); return; } RoundedCornerEnforcement.computeRoundedRectangle(this, background, mEnforcedRectangle); setOutlineProvider(mCornerRadiusEnforcementOutline); mIsCornerRadiusEnforced = true; invalidateOutline(); } /** Returns the corner radius currently enforced, in pixels. */ public float getEnforcedCornerRadius() { return mEnforcedCornerRadius; } /** Returns true if the corner radius are enforced for this App Widget. */ public boolean hasEnforcedCornerRadius() { return mIsCornerRadiusEnforced; } }