diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index c1ebbe58a2..7267e637f4 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -1483,11 +1483,10 @@ public class Launcher extends StatefulActivity 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 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); } } diff --git a/src/com/android/launcher3/RectUtils.kt b/src/com/android/launcher3/RectUtils.kt new file mode 100644 index 0000000000..68d2eaf9ab --- /dev/null +++ b/src/com/android/launcher3/RectUtils.kt @@ -0,0 +1,60 @@ +/* + * 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 + ) + } +} diff --git a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java index 86400baaf2..7f9a1fc4c2 100644 --- a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java +++ b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java @@ -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(); } - } /** diff --git a/tests/src/com/android/launcher3/RectUtilsTest.kt b/tests/src/com/android/launcher3/RectUtilsTest.kt new file mode 100644 index 0000000000..f0d22eb9d4 --- /dev/null +++ b/tests/src/com/android/launcher3/RectUtilsTest.kt @@ -0,0 +1,138 @@ +/* + * 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)) + } +}