Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit fb16517f authored by Fengjiang Li's avatar Fengjiang Li
Browse files

[2/n] Avoid flicker to drop a widget that needs a config activity

1. Don't show background in PendingAppWidgetHostView if we are showing preview bitmap
2. Extract bitmap from DropView instead of LauncherAppHostView
3. Letterbox bitmap into PendingAppWidgetHostView's canvas if canvas has different aspect ratio

Fix: 284236964
Flag: aconfig launcher.enable_add_app_widget_via_config_activity_v2 DISABLED
Test: manual
Change-Id: I76de215186b96ffe65c909b28155fb19ac90d4f1
parent 544f6e22
Loading
Loading
Loading
Loading
+5 −4
Original line number Diff line number Diff line
@@ -1483,11 +1483,10 @@ public class Launcher extends StatefulActivity<LauncherState>
        if (showPendingWidget) {
            launcherInfo.restoreStatus = LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
            PendingAppWidgetHostView pendingAppWidgetHostView = new PendingAppWidgetHostView(
                    this, mAppWidgetHolder, launcherInfo, appWidgetInfo);
            pendingAppWidgetHostView.setPreviewBitmap(widgetPreviewBitmap);
                    this, mAppWidgetHolder, launcherInfo, appWidgetInfo, widgetPreviewBitmap);
            hostView = pendingAppWidgetHostView;
        } else if (hostView instanceof PendingAppWidgetHostView) {
            ((PendingAppWidgetHostView) hostView).setPreviewBitmap(null);
            ((PendingAppWidgetHostView) hostView).setPreviewBitmapAndUpdateBackground(null);
            // User has selected a widget config and exited the config activity, we can trigger
            // re-inflation of PendingAppWidgetHostView to replace it with
            // LauncherAppWidgetHostView in workspace.
@@ -1822,7 +1821,9 @@ public class Launcher extends StatefulActivity<LauncherState>
        if (isActivityStarted) {
            DragView dropView = getDragLayer().clearAnimatedView();
            if (dropView != null && dropView.containsAppWidgetHostView()) {
                widgetPreviewBitmap = getBitmapFromView(dropView.getContentView());
                // Extracting Bitmap from dropView instead of its content view produces the correct
                // bitmap.
                widgetPreviewBitmap = getBitmapFromView(dropView);
            }
        }

+60 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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

import android.graphics.Rect

/**
 * Fit [this] into [targetRect] with letter boxing. After calling this method, [this] will be
 * modified to be letter boxed.
 *
 * @param targetRect target [Rect] that [this] should be fitted into
 */
fun Rect.letterBox(targetRect: Rect) {
    letterBox(targetRect, this)
}

/**
 * Fit [this] into [targetRect] with letter boxing. After calling this method, [resultRect] will be
 * modified to be letter boxed.
 *
 * @param targetRect target [Rect] that [this] should be fitted into
 * @param resultRect the letter boxed [Rect]
 */
fun Rect.letterBox(targetRect: Rect, resultRect: Rect) {
    val widthRatio: Float = 1f * targetRect.width() / width()
    val heightRatio: Float = 1f * targetRect.height() / height()
    if (widthRatio < heightRatio) {
        val scaledHeight: Int = (widthRatio * height()).toInt()
        val verticalPadding: Int = (targetRect.height() - scaledHeight) / 2
        resultRect.set(
            targetRect.left,
            targetRect.top + verticalPadding,
            targetRect.right,
            targetRect.bottom - verticalPadding
        )
    } else {
        val scaledWidth: Int = (heightRatio * width()).toInt()
        val horizontalPadding: Int = (targetRect.width() - scaledWidth) / 2
        resultRect.set(
            targetRect.left + horizontalPadding,
            targetRect.top,
            targetRect.right - horizontalPadding,
            targetRect.bottom
        )
    }
}
+28 −9
Original line number Diff line number Diff line
@@ -53,6 +53,7 @@ import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.RectUtilsKt;
import com.android.launcher3.icons.FastBitmapDrawable;
import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
import com.android.launcher3.model.data.ItemInfoWithIcon;
@@ -76,6 +77,10 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView

    private final Rect mRect = new Rect();

    private final Rect mPreviewBitmapRect = new Rect();
    private final Rect mCanvasRect = new Rect();
    private final Rect mLetterBoxedPreviewBitmapRect = new Rect();

    private final LauncherWidgetHolder mWidgetHolder;
    private final LauncherAppWidgetProviderInfo mAppwidget;
    private final LauncherAppWidgetInfo mInfo;
@@ -103,9 +108,14 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView

    public PendingAppWidgetHostView(Context context, LauncherWidgetHolder widgetHolder,
            LauncherAppWidgetInfo info, @Nullable LauncherAppWidgetProviderInfo appWidget) {
        this(context, widgetHolder, info, appWidget,
                context.getResources().getText(R.string.gadget_complete_setup_text));
        this(context, widgetHolder, info, appWidget, null);
    }

    public PendingAppWidgetHostView(Context context, LauncherWidgetHolder widgetHolder,
            LauncherAppWidgetInfo info, @Nullable LauncherAppWidgetProviderInfo appWidget,
            @Nullable Bitmap previewBitmap) {
        this(context, widgetHolder, info, appWidget,
                context.getResources().getText(R.string.gadget_complete_setup_text), previewBitmap);
        super.updateAppWidget(null);
        setOnClickListener(mActivityContext.getItemOnClickListener());

@@ -123,7 +133,7 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView
            Context context, LauncherWidgetHolder widgetHolder,
            int appWidgetId, @NonNull LauncherAppWidgetProviderInfo appWidget) {
        this(context, widgetHolder, new LauncherAppWidgetInfo(appWidgetId, appWidget.provider),
                appWidget, appWidget.label);
                appWidget, appWidget.label, null);
        getBackground().mutate().setAlpha(DEFERRED_ALPHA);

        mCenterDrawable = new ColorDrawable(Color.TRANSPARENT);
@@ -132,8 +142,12 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView
        mIsDeferredWidget = true;
    }

    /** Set {@link Bitmap} of widget preview. */
    public void setPreviewBitmap(@Nullable Bitmap previewBitmap) {
    /**
     * Set {@link Bitmap} of widget preview and update background drawable. When showing preview
     * bitmap, we shouldn't draw background.
     */
    public void setPreviewBitmapAndUpdateBackground(@Nullable Bitmap previewBitmap) {
        setBackgroundResource(previewBitmap != null ? 0 : R.drawable.pending_widget_bg);
        if (this.mPreviewBitmap == previewBitmap) {
            return;
        }
@@ -143,7 +157,8 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView

    private PendingAppWidgetHostView(Context context,
            LauncherWidgetHolder widgetHolder, LauncherAppWidgetInfo info,
            LauncherAppWidgetProviderInfo appwidget, CharSequence label) {
            LauncherAppWidgetProviderInfo appwidget, CharSequence label,
            @Nullable Bitmap previewBitmap) {
        super(new ContextThemeWrapper(context, R.style.WidgetContainerTheme));
        mWidgetHolder = widgetHolder;
        mAppwidget = appwidget;
@@ -161,7 +176,7 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView
        mPreviewPaint = new Paint(ANTI_ALIAS_FLAG | DITHER_FLAG | FILTER_BITMAP_FLAG);

        setWillNotDraw(false);
        setBackgroundResource(R.drawable.pending_widget_bg);
        setPreviewBitmapAndUpdateBackground(previewBitmap);
    }

    @Override
@@ -440,7 +455,12 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView
    protected void onDraw(Canvas canvas) {
        if (mPreviewBitmap != null
                && (mInfo.restoreStatus & LauncherAppWidgetInfo.FLAG_UI_NOT_READY) != 0) {
            canvas.drawBitmap(mPreviewBitmap, 0, 0, mPreviewPaint);
            mPreviewBitmapRect.set(0, 0, mPreviewBitmap.getWidth(), mPreviewBitmap.getHeight());
            mCanvasRect.set(0, 0, getWidth(), getHeight());

            RectUtilsKt.letterBox(mPreviewBitmapRect, mCanvasRect, mLetterBoxedPreviewBitmapRect);
            canvas.drawBitmap(mPreviewBitmap, mPreviewBitmapRect, mLetterBoxedPreviewBitmapRect,
                    mPreviewPaint);
            return;
        }
        if (mCenterDrawable == null) {
@@ -463,7 +483,6 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView
            mSetupTextLayout.draw(canvas);
            canvas.restore();
        }

    }

    /**
+138 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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

import android.graphics.Rect
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith

@SmallTest
@RunWith(AndroidJUnit4::class)
class RectUtilsTest {

    private val srcRect = Rect()
    private val destRect = Rect()
    private val letterBoxedRect = Rect()

    @Test
    fun letterBoxSelf_toSameRect_noScale() {
        srcRect.set(0, 0, 100, 100)
        destRect.set(0, 0, 100, 100)

        srcRect.letterBox(destRect)

        assertThat(srcRect).isEqualTo(Rect(0, 0, 100, 100))
    }

    @Test
    fun letterBox_toSameRect_noScale() {
        srcRect.set(0, 0, 100, 100)
        destRect.set(0, 0, 100, 100)

        srcRect.letterBox(destRect, letterBoxedRect)

        assertThat(letterBoxedRect).isEqualTo(Rect(0, 0, 100, 100))
        assertThat(srcRect).isEqualTo(Rect(0, 0, 100, 100))
    }

    @Test
    fun letterBoxSelf_toSmallHeight_scaleDownHorizontally() {
        srcRect.set(0, 0, 2893, 2114)
        destRect.set(0, 0, 939, 520)

        srcRect.letterBox(destRect)

        assertThat(srcRect).isEqualTo(Rect(114, 0, 825, 520))
    }

    @Test
    fun letterBoxRect_toSmallHeight_scaleDownHorizontally() {
        srcRect.set(0, 0, 2893, 2114)
        destRect.set(0, 0, 939, 520)

        srcRect.letterBox(destRect, letterBoxedRect)

        assertThat(letterBoxedRect).isEqualTo(Rect(114, 0, 825, 520))
        assertThat(srcRect).isEqualTo(Rect(0, 0, 2893, 2114))
    }

    @Test
    fun letterBoxSelf_toSmallHeightWithOffset_scaleDownHorizontally() {
        srcRect.set(0, 0, 2893, 2114)
        destRect.set(10, 20, 949, 540)

        srcRect.letterBox(destRect)

        assertThat(srcRect).isEqualTo(Rect(124, 20, 835, 540))
    }

    @Test
    fun letterBoxRect_toSmallHeightWithOffset_scaleDownHorizontally() {
        srcRect.set(0, 0, 2893, 2114)
        destRect.set(10, 20, 949, 540)

        srcRect.letterBox(destRect, letterBoxedRect)

        assertThat(letterBoxedRect).isEqualTo(Rect(124, 20, 835, 540))
        assertThat(srcRect).isEqualTo(Rect(0, 0, 2893, 2114))
    }

    @Test
    fun letterBoxSelf_toSmallWidth_scaleDownVertically() {
        srcRect.set(0, 0, 2893, 2114)
        destRect.set(0, 0, 520, 939)

        srcRect.letterBox(destRect)

        assertThat(srcRect).isEqualTo(Rect(0, 280, 520, 659))
    }

    @Test
    fun letterBoxRect_toSmallWidth_scaleDownVertically() {
        srcRect.set(0, 0, 2893, 2114)
        destRect.set(0, 0, 520, 939)

        srcRect.letterBox(destRect, letterBoxedRect)

        assertThat(letterBoxedRect).isEqualTo(Rect(0, 280, 520, 659))
        assertThat(srcRect).isEqualTo(Rect(0, 0, 2893, 2114))
    }

    @Test
    fun letterBoxSelf_toSmallWidthWithOffset_scaleDownVertically() {
        srcRect.set(0, 0, 2893, 2114)
        destRect.set(40, 60, 560, 999)

        srcRect.letterBox(destRect)

        assertThat(srcRect).isEqualTo(Rect(40, 340, 560, 719))
    }

    @Test
    fun letterBoxRect_toSmallWidthWithOffset_scaleDownVertically() {
        srcRect.set(0, 0, 2893, 2114)
        destRect.set(40, 60, 560, 999)

        srcRect.letterBox(destRect, letterBoxedRect)

        assertThat(letterBoxedRect).isEqualTo(Rect(40, 340, 560, 719))
        assertThat(srcRect).isEqualTo(Rect(0, 0, 2893, 2114))
    }
}