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

Commit c93f687f authored by Shamali P's avatar Shamali P
Browse files

Avoid cropping image previews.

In most cases, cropping image-based previews to just fit width leads to
degraded UX. Instead we try to fit them to the widget size if possible.

* Before: See attached bug for examples of cropped previews.
* After: http://screen/Bg7Eoydr4qBQdVZ

Additionally center align previews vertically for non-categorized
suggestions too.

Bug: 319152349, 317366201
Flag: N/A
Test: Includes a unit test & manual with the child cls
Change-Id: I4447f99028129d5d5379ead9ca404f7a011d1551
parent a1dbbeab
Loading
Loading
Loading
Loading
+21 −9
Original line number Diff line number Diff line
@@ -82,15 +82,27 @@ public class WidgetImageView extends View {
    private void updateDstRectF() {
        float myWidth = getWidth();
        float myHeight = getHeight();
        float bitmapWidth = mDrawable.getIntrinsicWidth();

        final float scale = bitmapWidth > myWidth ? myWidth / bitmapWidth : 1;
        float scaledWidth = bitmapWidth * scale;
        float scaledHeight = mDrawable.getIntrinsicHeight() * scale;

        final float bitmapWidth = mDrawable.getIntrinsicWidth();
        final float bitmapHeight = mDrawable.getIntrinsicHeight();
        final float bitmapAspectRatio = bitmapWidth / bitmapHeight;
        final float containerAspectRatio = myWidth / myHeight;

        // Scale by width if image has larger aspect ratio than the container else by height; and
        // avoid cropping the previews
        final float scale = bitmapAspectRatio > containerAspectRatio ? myWidth / bitmapWidth
                : myHeight / bitmapHeight;

        final float scaledWidth = bitmapWidth * scale;
        final float scaledHeight = bitmapHeight * scale;

        // Avoid cropping by checking bounds after scaling.
        if (scaledWidth > myWidth) {
            mDstRectF.left = 0;
            mDstRectF.right = scaledWidth;
        } else {
            mDstRectF.left = (myWidth - scaledWidth) / 2;
            mDstRectF.right = (myWidth + scaledWidth) / 2;

        }
        if (scaledHeight > myHeight) {
            mDstRectF.top = 0;
            mDstRectF.bottom = scaledHeight;
+3 −8
Original line number Diff line number Diff line
@@ -16,7 +16,6 @@

package com.android.launcher3.widget;

import static com.android.launcher3.Flags.enableCategorizedWidgetSuggestions;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_BOTTOM_WIDGETS_TRAY;

import android.content.Context;
@@ -188,13 +187,9 @@ public class WidgetsBottomSheet extends BaseWidgetSheet {
                mWidgetCellHorizontalPadding)
                .forEach(row -> {
                    TableRow tableRow = new TableRow(getContext());
                    if (enableCategorizedWidgetSuggestions()) {
                    // Vertically center align items, so that even if they don't fill bounds,
                    // they can look organized when placed together in a row.
                    tableRow.setGravity(Gravity.CENTER_VERTICAL);
                    } else {
                        tableRow.setGravity(Gravity.TOP);
                    }
                    row.forEach(widgetItem -> {
                        WidgetCell widget = addItemCell(tableRow);
                        widget.applyFromCellItem(widgetItem);
+3 −9
Original line number Diff line number Diff line
@@ -15,8 +15,6 @@
 */
package com.android.launcher3.widget.picker;

import static com.android.launcher3.Flags.enableCategorizedWidgetSuggestions;

import android.content.Context;
import android.graphics.Bitmap;
import android.util.Log;
@@ -150,13 +148,9 @@ public final class WidgetsListTableViewHolderBinder
                tableRow = (TableRow) table.getChildAt(i);
            } else {
                tableRow = new TableRow(table.getContext());
                if (enableCategorizedWidgetSuggestions()) {
                // Vertically center align items, so that even if they don't fill bounds, they
                // can look organized when placed together in a row.
                tableRow.setGravity(Gravity.CENTER_VERTICAL);
                } else {
                    tableRow.setGravity(Gravity.TOP);
                }
                table.addView(tableRow);
            }
            if (tableRow.getChildCount() > widgetItems.size()) {
+3 −7
Original line number Diff line number Diff line
@@ -109,13 +109,9 @@ public final class WidgetsRecommendationTableLayout extends TableLayout {
        for (int i = 0; i < data.mRecommendationTable.size(); i++) {
            List<WidgetItem> widgetItems = data.mRecommendationTable.get(i);
            TableRow tableRow = new TableRow(getContext());
            if (enableCategorizedWidgetSuggestions()) {
            // Vertically center align items, so that even if they don't fill bounds, they can
            // look organized when placed together in a row.
            tableRow.setGravity(Gravity.CENTER_VERTICAL);
            } else {
                tableRow.setGravity(Gravity.TOP);
            }
            for (WidgetItem widgetItem : widgetItems) {
                WidgetCell widgetCell = addItemCell(tableRow);
                widgetCell.applyFromCellItem(widgetItem, data.mPreviewScale);
+99 −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.widget.picker

import android.content.Context
import android.graphics.Rect
import android.graphics.drawable.Drawable
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import com.android.launcher3.util.ActivityContextWrapper
import com.android.launcher3.widget.WidgetImageView
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.spy
import org.mockito.MockitoAnnotations
import org.mockito.kotlin.whenever

@MediumTest
@RunWith(AndroidJUnit4::class)
class WidgetImageViewTest {
    private lateinit var context: Context
    private lateinit var widgetImageView: WidgetImageView

    @Mock private lateinit var testDrawable: Drawable

    @Before
    fun setUp() {
        MockitoAnnotations.initMocks(this)

        context = ActivityContextWrapper(ApplicationProvider.getApplicationContext())
        widgetImageView = spy(WidgetImageView(context))
    }

    @Test
    fun getBitmapBounds_aspectRatioLargerThanView_scaledByWidth() {
        // view - 100 x 100
        whenever(widgetImageView.width).thenReturn(100)
        whenever(widgetImageView.height).thenReturn(100)
        // bitmap - 200 x 100
        whenever(testDrawable.intrinsicWidth).thenReturn(200)
        whenever(testDrawable.intrinsicHeight).thenReturn(100)

        widgetImageView.drawable = testDrawable
        val bitmapBounds = widgetImageView.bitmapBounds

        // new scaled width of bitmap is = 100, and height is scaled to 1/2 = 50
        assertThat(bitmapBounds).isEqualTo(Rect(0, 25, 100, 75))
    }

    @Test
    fun getBitmapBounds_aspectRatioSmallerThanView_scaledByHeight() {
        // view - 100 x 100
        whenever(widgetImageView.width).thenReturn(100)
        whenever(widgetImageView.height).thenReturn(100)
        // bitmap - 100 x 200
        whenever(testDrawable.intrinsicWidth).thenReturn(100)
        whenever(testDrawable.intrinsicHeight).thenReturn(200)
        widgetImageView.drawable = testDrawable

        val bitmapBounds = widgetImageView.bitmapBounds

        // new scaled height of bitmap is = 100, and width is scaled to 1/2 = 50
        assertThat(bitmapBounds).isEqualTo(Rect(25, 0, 75, 100))
    }

    @Test
    fun getBitmapBounds_noScale_returnsOriginalDrawableBounds() {
        // view - 200 x 100
        whenever(widgetImageView.width).thenReturn(200)
        whenever(widgetImageView.height).thenReturn(100)
        // bitmap - 200 x 100
        whenever(testDrawable.intrinsicWidth).thenReturn(200)
        whenever(testDrawable.intrinsicHeight).thenReturn(100)

        widgetImageView.drawable = testDrawable
        val bitmapBounds = widgetImageView.bitmapBounds

        // no scaling
        assertThat(bitmapBounds).isEqualTo(Rect(0, 0, 200, 100))
    }
}