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

Commit dd19a247 authored by Stevie Kideckel's avatar Stevie Kideckel Committed by Android (Google) Code Review
Browse files

Merge "Load widget preview images before adding the rows to the adapter" into sc-dev

parents 57617919 a0935232
Loading
Loading
Loading
Loading
+409 −0
Original line number Diff line number Diff line
/*
 * 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 static com.google.common.truth.Truth.assertThat;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;

import android.content.ComponentName;
import android.graphics.Bitmap;
import android.os.CancellationSignal;
import android.os.UserHandle;
import android.util.Size;

import com.android.launcher3.DeviceProfile;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.testing.TestActivity;
import com.android.launcher3.widget.WidgetPreviewLoader.WidgetPreviewLoadedCallback;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;

import java.util.Arrays;
import java.util.Collections;

@RunWith(RobolectricTestRunner.class)
public class CachingWidgetPreviewLoaderTest {
    private static final Size SIZE_10_10 = new Size(10, 10);
    private static final Size SIZE_20_20 = new Size(20, 20);
    private static final String TEST_PACKAGE = "com.example.test";
    private static final ComponentName TEST_PROVIDER =
            new ComponentName(TEST_PACKAGE, ".WidgetProvider");
    private static final ComponentName TEST_PROVIDER2 =
            new ComponentName(TEST_PACKAGE, ".WidgetProvider2");
    private static final Bitmap BITMAP = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888);
    private static final Bitmap BITMAP2 = Bitmap.createBitmap(20, 20, Bitmap.Config.ARGB_8888);


    @Mock private CancellationSignal mCancellationSignal;
    @Mock private WidgetPreviewLoader mDelegate;
    @Mock private IconCache mIconCache;
    @Mock private DeviceProfile mDeviceProfile;
    @Mock private LauncherAppWidgetProviderInfo mProviderInfo;
    @Mock private LauncherAppWidgetProviderInfo mProviderInfo2;
    @Mock private WidgetPreviewLoadedCallback mPreviewLoadedCallback;
    @Mock private WidgetPreviewLoadedCallback mPreviewLoadedCallback2;
    @Captor private ArgumentCaptor<WidgetPreviewLoadedCallback> mCallbackCaptor;

    private TestActivity mTestActivity;
    private CachingWidgetPreviewLoader mLoader;
    private WidgetItem mWidgetItem;
    private WidgetItem mWidgetItem2;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        mLoader = new CachingWidgetPreviewLoader(mDelegate);

        mTestActivity = Robolectric.buildActivity(TestActivity.class).setup().get();
        mTestActivity.setDeviceProfile(mDeviceProfile);

        when(mDelegate.loadPreview(any(), any(), any(), any())).thenReturn(mCancellationSignal);

        mProviderInfo.provider = TEST_PROVIDER;
        when(mProviderInfo.getProfile()).thenReturn(new UserHandle(0));

        mProviderInfo2.provider = TEST_PROVIDER2;
        when(mProviderInfo2.getProfile()).thenReturn(new UserHandle(0));

        InvariantDeviceProfile testProfile = new InvariantDeviceProfile();
        testProfile.numRows = 5;
        testProfile.numColumns = 5;

        mWidgetItem = new WidgetItem(mProviderInfo, testProfile, mIconCache);
        mWidgetItem2 = new WidgetItem(mProviderInfo2, testProfile, mIconCache);
    }

    @Test
    public void getPreview_notInCache_shouldReturnNull() {
        assertThat(mLoader.getPreview(mWidgetItem, SIZE_10_10)).isNull();
    }

    @Test
    public void getPreview_notInCache_shouldNotCallDelegate() {
        mLoader.getPreview(mWidgetItem, SIZE_10_10);

        verifyZeroInteractions(mDelegate);
    }

    @Test
    public void getPreview_inCache_shouldReturnCachedBitmap() {
        loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);

        assertThat(mLoader.getPreview(mWidgetItem, SIZE_10_10)).isEqualTo(BITMAP);
    }

    @Test
    public void getPreview_otherSizeInCache_shouldReturnNull() {
        loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);

        assertThat(mLoader.getPreview(mWidgetItem, SIZE_20_20)).isNull();
    }

    @Test
    public void getPreview_otherItemInCache_shouldReturnNull() {
        loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);

        assertThat(mLoader.getPreview(mWidgetItem2, SIZE_10_10)).isNull();
    }

    @Test
    public void getPreview_shouldStoreMultipleSizesPerItem() {
        loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);
        loadPreviewIntoCache(mWidgetItem, SIZE_20_20, BITMAP2);

        assertThat(mLoader.getPreview(mWidgetItem, SIZE_10_10)).isEqualTo(BITMAP);
        assertThat(mLoader.getPreview(mWidgetItem, SIZE_20_20)).isEqualTo(BITMAP2);
    }

    @Test
    public void loadPreview_notInCache_shouldStartLoading() {
        mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback);

        verify(mDelegate).loadPreview(eq(mTestActivity), eq(mWidgetItem), eq(SIZE_10_10), any());
        verifyZeroInteractions(mPreviewLoadedCallback);
    }

    @Test
    public void loadPreview_thenLoaded_shouldCallBack() {
        mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback);
        verify(mDelegate).loadPreview(any(), any(), any(), mCallbackCaptor.capture());
        WidgetPreviewLoadedCallback loaderCallback = mCallbackCaptor.getValue();

        loaderCallback.onPreviewLoaded(BITMAP);

        verify(mPreviewLoadedCallback).onPreviewLoaded(BITMAP);
    }

    @Test
    public void loadPreview_thenCancelled_shouldCancelDelegateRequest() {
        CancellationSignal cancellationSignal =
                mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback);

        cancellationSignal.cancel();

        verify(mCancellationSignal).cancel();
        verifyZeroInteractions(mPreviewLoadedCallback);
        assertThat(mLoader.getPreview(mWidgetItem, SIZE_10_10)).isNull();
    }

    @Test
    public void loadPreview_thenCancelled_otherCallListening_shouldNotCancelDelegateRequest() {
        CancellationSignal cancellationSignal1 =
                mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback);
        mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback2);

        cancellationSignal1.cancel();

        verifyZeroInteractions(mCancellationSignal);
    }

    @Test
    public void loadPreview_thenCancelled_otherCallListening_loaded_shouldCallBackToNonCancelled() {
        CancellationSignal cancellationSignal1 =
                mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback);
        mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback2);
        verify(mDelegate).loadPreview(any(), any(), any(), mCallbackCaptor.capture());
        WidgetPreviewLoadedCallback loaderCallback = mCallbackCaptor.getValue();

        cancellationSignal1.cancel();
        loaderCallback.onPreviewLoaded(BITMAP);

        verifyZeroInteractions(mPreviewLoadedCallback);
        verify(mPreviewLoadedCallback2).onPreviewLoaded(BITMAP);
        assertThat(mLoader.getPreview(mWidgetItem, SIZE_10_10)).isEqualTo(BITMAP);
    }

    @Test
    public void loadPreview_thenCancelled_bothCallsCancelled_shouldCancelDelegateRequest() {
        CancellationSignal cancellationSignal1 =
                mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback);
        CancellationSignal cancellationSignal2 =
                mLoader.loadPreview(
                        mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback2);

        cancellationSignal1.cancel();
        cancellationSignal2.cancel();

        verify(mCancellationSignal).cancel();
        verifyZeroInteractions(mPreviewLoadedCallback);
        verifyZeroInteractions(mPreviewLoadedCallback2);
        assertThat(mLoader.getPreview(mWidgetItem, SIZE_10_10)).isNull();
    }

    @Test
    public void loadPreview_multipleCallbacks_shouldOnlyCallDelegateOnce() {
        mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback);
        mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback2);

        verify(mDelegate).loadPreview(any(), any(), any(), any());
    }

    @Test
    public void loadPreview_multipleCallbacks_shouldForwardResultToEachCallback() {
        mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback);
        mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback2);

        verify(mDelegate).loadPreview(any(), any(), any(), mCallbackCaptor.capture());
        WidgetPreviewLoadedCallback loaderCallback = mCallbackCaptor.getValue();

        loaderCallback.onPreviewLoaded(BITMAP);

        verify(mPreviewLoadedCallback).onPreviewLoaded(BITMAP);
        verify(mPreviewLoadedCallback2).onPreviewLoaded(BITMAP);
    }

    @Test
    public void loadPreview_inCache_shouldCallBackImmediately() {
        loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);
        reset(mDelegate);

        mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback);

        verify(mPreviewLoadedCallback).onPreviewLoaded(BITMAP);
        verifyZeroInteractions(mDelegate);
    }

    @Test
    public void loadPreview_thenLoaded_thenCancelled_shouldNotRemovePreviewFromCache() {
        CancellationSignal cancellationSignal =
                mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback);
        verify(mDelegate).loadPreview(any(), any(), any(), mCallbackCaptor.capture());
        WidgetPreviewLoadedCallback loaderCallback = mCallbackCaptor.getValue();
        loaderCallback.onPreviewLoaded(BITMAP);

        cancellationSignal.cancel();

        assertThat(mLoader.getPreview(mWidgetItem, SIZE_10_10)).isEqualTo(BITMAP);
    }

    @Test
    public void isPreviewLoaded_notLoaded_shouldReturnFalse() {
        assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse();
    }

    @Test
    public void isPreviewLoaded_otherSizeLoaded_shouldReturnFalse() {
        loadPreviewIntoCache(mWidgetItem, SIZE_20_20, BITMAP);

        assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse();
    }

    @Test
    public void isPreviewLoaded_otherItemLoaded_shouldReturnFalse() {
        loadPreviewIntoCache(mWidgetItem2, SIZE_10_10, BITMAP);

        assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse();
    }

    @Test
    public void isPreviewLoaded_loaded_shouldReturnTrue() {
        loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);

        assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isTrue();
    }

    @Test
    public void clearPreviews_notInCache_shouldBeNoOp() {
        mLoader.clearPreviews(Collections.singletonList(mWidgetItem));

        assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse();
    }

    @Test
    public void clearPreviews_inCache_shouldRemovePreview() {
        loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);

        mLoader.clearPreviews(Collections.singletonList(mWidgetItem));

        assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse();
    }

    @Test
    public void clearPreviews_inCache_multipleSizes_shouldRemoveAllSizes() {
        loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);
        loadPreviewIntoCache(mWidgetItem, SIZE_20_20, BITMAP);

        mLoader.clearPreviews(Collections.singletonList(mWidgetItem));

        assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse();
        assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_20_20)).isFalse();
    }

    @Test
    public void clearPreviews_inCache_otherItems_shouldOnlyRemoveSpecifiedItems() {
        loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);
        loadPreviewIntoCache(mWidgetItem2, SIZE_10_10, BITMAP);

        mLoader.clearPreviews(Collections.singletonList(mWidgetItem));

        assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse();
        assertThat(mLoader.isPreviewLoaded(mWidgetItem2, SIZE_10_10)).isTrue();
    }

    @Test
    public void clearPreviews_inCache_otherItems_shouldRemoveAllSpecifiedItems() {
        loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);
        loadPreviewIntoCache(mWidgetItem2, SIZE_10_10, BITMAP);

        mLoader.clearPreviews(Arrays.asList(mWidgetItem, mWidgetItem2));

        assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse();
        assertThat(mLoader.isPreviewLoaded(mWidgetItem2, SIZE_10_10)).isFalse();
    }

    @Test
    public void clearPreviews_loading_shouldCancelLoad() {
        mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback);

        mLoader.clearPreviews(Collections.singletonList(mWidgetItem));

        verify(mCancellationSignal).cancel();
    }

    @Test
    public void clearAll_cacheEmpty_shouldBeNoOp() {
        mLoader.clearAll();

        assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse();
    }

    @Test
    public void clearAll_inCache_shouldRemovePreview() {
        loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);

        mLoader.clearAll();

        assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse();
    }

    @Test
    public void clearAll_inCache_multipleSizes_shouldRemoveAllSizes() {
        loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);
        loadPreviewIntoCache(mWidgetItem, SIZE_20_20, BITMAP);

        mLoader.clearAll();

        assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse();
        assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_20_20)).isFalse();
    }

    @Test
    public void clearAll_inCache_multipleItems_shouldRemoveAll() {
        loadPreviewIntoCache(mWidgetItem, SIZE_10_10, BITMAP);
        loadPreviewIntoCache(mWidgetItem, SIZE_20_20, BITMAP);
        loadPreviewIntoCache(mWidgetItem2, SIZE_20_20, BITMAP);

        mLoader.clearAll();

        assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_10_10)).isFalse();
        assertThat(mLoader.isPreviewLoaded(mWidgetItem, SIZE_20_20)).isFalse();
        assertThat(mLoader.isPreviewLoaded(mWidgetItem2, SIZE_20_20)).isFalse();
    }

    @Test
    public void clearAll_loading_shouldCancelLoad() {
        mLoader.loadPreview(mTestActivity, mWidgetItem, SIZE_10_10, mPreviewLoadedCallback);

        mLoader.clearAll();

        verify(mCancellationSignal).cancel();
    }

    private void loadPreviewIntoCache(WidgetItem widgetItem, Size size, Bitmap bitmap) {
        reset(mDelegate);
        mLoader.loadPreview(mTestActivity, widgetItem, size, ignored -> {});
        verify(mDelegate).loadPreview(any(), any(), any(), mCallbackCaptor.capture());
        WidgetPreviewLoadedCallback loaderCallback = mCallbackCaptor.getValue();

        loaderCallback.onPreviewLoaded(bitmap);
    }
}
+3 −4
Original line number Diff line number Diff line
@@ -207,10 +207,9 @@ public final class WidgetsDiffReporterTest {
        // GIVEN the current list has app headers [A, B, E content].
        ArrayList<WidgetsListBaseEntry> currentList = new ArrayList<>(
                List.of(mHeaderA, mHeaderB, mContentE));
        // GIVEN the new list has app headers [A, B, E content].
        List<WidgetsListBaseEntry> newList = List.of(mHeaderA, mHeaderB, mContentE);
        // GIVEN the user has interacted with B.
        mHeaderB.setIsWidgetListShown(true);
        // GIVEN the new list has app headers [A, B, E content] and the user has interacted with B.
        List<WidgetsListBaseEntry> newList =
                List.of(mHeaderA, mHeaderB.withWidgetListShown(), mContentE);

        // WHEN computing the list difference.
        mWidgetsDiffReporter.process(currentList, newList, COMPARATOR);
+2 −2
Original line number Diff line number Diff line
@@ -33,13 +33,13 @@ import android.view.LayoutInflater;
import androidx.recyclerview.widget.RecyclerView;

import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.WidgetPreviewLoader;
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.ComponentWithLabel;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.model.data.PackageItemInfo;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.widget.DatabaseWidgetPreviewLoader;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
import com.android.launcher3.widget.model.WidgetsListContentEntry;
@@ -64,7 +64,7 @@ public final class WidgetsListAdapterTest {
    private static final String TEST_PACKAGE_PLACEHOLDER = "com.google.test";

    @Mock private LayoutInflater mMockLayoutInflater;
    @Mock private WidgetPreviewLoader mMockWidgetCache;
    @Mock private DatabaseWidgetPreviewLoader mMockWidgetCache;
    @Mock private RecyclerView.AdapterDataObserver mListener;
    @Mock private IconCache mIconCache;

+2 −2
Original line number Diff line number Diff line
@@ -34,7 +34,6 @@ import android.widget.TextView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.WidgetPreviewLoader;
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.ComponentWithLabel;
import com.android.launcher3.icons.IconCache;
@@ -42,6 +41,7 @@ import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.model.data.PackageItemInfo;
import com.android.launcher3.testing.TestActivity;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.widget.DatabaseWidgetPreviewLoader;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.model.WidgetsListHeaderEntry;

@@ -79,7 +79,7 @@ public final class WidgetsListHeaderViewHolderBinderTest {
    @Mock
    private DeviceProfile mDeviceProfile;
    @Mock
    private WidgetPreviewLoader mWidgetPreviewLoader;
    private DatabaseWidgetPreviewLoader mWidgetPreviewLoader;
    @Mock
    private OnHeaderClickListener mOnHeaderClickListener;

+2 −2
Original line number Diff line number Diff line
@@ -34,7 +34,6 @@ import android.widget.TextView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.WidgetPreviewLoader;
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.ComponentWithLabel;
import com.android.launcher3.icons.IconCache;
@@ -42,6 +41,7 @@ import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.model.data.PackageItemInfo;
import com.android.launcher3.testing.TestActivity;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.widget.DatabaseWidgetPreviewLoader;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.model.WidgetsListSearchHeaderEntry;

@@ -79,7 +79,7 @@ public final class WidgetsListSearchHeaderViewHolderBinderTest {
    @Mock
    private DeviceProfile mDeviceProfile;
    @Mock
    private WidgetPreviewLoader mWidgetPreviewLoader;
    private DatabaseWidgetPreviewLoader mWidgetPreviewLoader;
    @Mock
    private OnHeaderClickListener mOnHeaderClickListener;

Loading