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

Commit fa58bfa0 authored by Steven Ng's avatar Steven Ng Committed by Android (Google) Code Review
Browse files

Merge "Refactoring before adding a new view type in the WidgetsListAdapter" into sc-dev

parents 12e547f2 2f5648a9
Loading
Loading
Loading
Loading
+8 −8
Original line number Diff line number Diff line
@@ -25,7 +25,7 @@ import androidx.annotation.Nullable;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.icons.ComponentWithLabelAndIcon;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.widget.WidgetListRowEntry;
import com.android.launcher3.widget.model.WidgetsListBaseEntry;

import java.util.ArrayList;
import java.util.Collections;
@@ -43,17 +43,17 @@ public class WidgetsModel {
    public static final boolean GO_DISABLE_WIDGETS = true;
    public static final boolean GO_DISABLE_NOTIFICATION_DOTS = true;

    private static final ArrayList<WidgetListRowEntry> EMPTY_WIDGET_LIST = new ArrayList<>();
    private static final ArrayList<WidgetsListBaseEntry> EMPTY_WIDGET_LIST = new ArrayList<>();

    /**
     * Returns a list of {@link WidgetListRowEntry}. All {@link WidgetItem} in a single row
     * are sorted (based on label and user), but the overall list of {@link WidgetListRowEntry}s
     * is not sorted. This list is sorted at the UI when using
     * {@link com.android.launcher3.widget.WidgetsDiffReporter}
     * Returns a list of {@link WidgetsListBaseEntry}. All {@link WidgetItem} in a single row are
     * sorted (based on label and user), but the overall list of {@link WidgetsListBaseEntry}s is
     * not sorted. This list is sorted at the UI when using
     * {@link com.android.launcher3.widget.picker.WidgetsDiffReporter}
     *
     * @see com.android.launcher3.widget.WidgetsListAdapter#setWidgets(ArrayList)
     * @see com.android.launcher3.widget.picker.WidgetsListAdapter#setWidgets(List)
     */
    public synchronized ArrayList<WidgetListRowEntry> getWidgetsList(Context context) {
    public synchronized ArrayList<WidgetsListBaseEntry> getWidgetsList(Context context) {
        return EMPTY_WIDGET_LIST;
    }

+3 −3
Original line number Diff line number Diff line
@@ -13,7 +13,7 @@
     See the License for the specific language governing permissions and
     limitations under the License.
-->
<com.android.launcher3.widget.WidgetsFullSheet
<com.android.launcher3.widget.picker.WidgetsFullSheet
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
@@ -27,7 +27,7 @@
        android:background="?android:attr/colorPrimary"
        android:elevation="4dp">

        <com.android.launcher3.widget.WidgetsRecyclerView
        <com.android.launcher3.widget.picker.WidgetsRecyclerView
            android:id="@+id/widgets_list_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
@@ -49,4 +49,4 @@
            android:layout_alignParentTop="true"
            android:layout_marginEnd="@dimen/fastscroll_end_margin" />
    </com.android.launcher3.views.TopRoundedCornerView>
</com.android.launcher3.widget.WidgetsFullSheet>
 No newline at end of file
</com.android.launcher3.widget.picker.WidgetsFullSheet>
 No newline at end of file
+41 −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.testing;

import com.android.launcher3.BaseActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.views.BaseDragLayer;

/** An empty activity for {@link android.app.Fragment}s, {@link android.view.View}s testing. */
public class TestActivity extends BaseActivity implements ActivityContext {

    private DeviceProfile mDeviceProfile;

    @Override
    public BaseDragLayer getDragLayer() {
        return null;
    }

    @Override
    public DeviceProfile getDeviceProfile() {
        return mDeviceProfile;
    }

    public void setDeviceProfile(DeviceProfile deviceProfile) {
        mDeviceProfile = deviceProfile;
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -38,7 +38,7 @@ import com.android.launcher3.folder.FolderPagedView;
import com.android.launcher3.util.LauncherLayoutBuilder;
import com.android.launcher3.util.LauncherLayoutBuilder.FolderBuilder;
import com.android.launcher3.util.LauncherModelHelper;
import com.android.launcher3.widget.WidgetsFullSheet;
import com.android.launcher3.widget.picker.WidgetsFullSheet;

import org.junit.Before;
import org.junit.Test;
+215 −0
Original line number Diff line number Diff line
@@ -13,11 +13,12 @@
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.launcher3.widget;
package com.android.launcher3.widget.picker;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.isNull;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.verify;
import static org.robolectric.Shadows.shadowOf;

@@ -33,9 +34,12 @@ import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
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.widget.model.WidgetsListBaseEntry;
import com.android.launcher3.widget.model.WidgetsListContentEntry;

import org.junit.Before;
import org.junit.Test;
@@ -48,10 +52,13 @@ import org.robolectric.shadows.ShadowPackageManager;
import org.robolectric.util.ReflectionHelpers;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

@RunWith(RobolectricTestRunner.class)
public class WidgetsListAdapterTest {
public final class WidgetsListAdapterTest {

    private static final String TEST_PACKAGE_1 = "com.google.test.1";
    private static final String TEST_PACKAGE_2 = "com.google.test.2";

    @Mock private LayoutInflater mMockLayoutInflater;
    @Mock private WidgetPreviewLoader mMockWidgetCache;
@@ -72,80 +79,137 @@ public class WidgetsListAdapterTest {
        mAdapter = new WidgetsListAdapter(mContext, mMockLayoutInflater, mMockWidgetCache,
                mIconCache, null, null);
        mAdapter.registerAdapterDataObserver(mListener);

        doAnswer(invocation -> ((ComponentWithLabel) invocation.getArgument(0))
                        .getComponent().getPackageName())
                .when(mIconCache).getTitleNoCache(any());
    }

    @Test
    public void test_notifyDataSetChanged() throws Exception {
    public void setWidgets_shouldNotifyDataSetChanged() {
        mAdapter.setWidgets(generateSampleMap(1));
        verify(mListener, times(1)).onChanged();

        verify(mListener).onChanged();
    }

    @Test
    public void test_notifyItemInserted() throws Exception {
    public void setWidgets_withItemInserted_shouldNotifyItemInserted() {
        mAdapter.setWidgets(generateSampleMap(1));
        mAdapter.setWidgets(generateSampleMap(2));
        verify(mListener, times(1)).onChanged();
        verify(mListener, times(1)).onItemRangeInserted(eq(1), eq(1));

        verify(mListener).onItemRangeInserted(eq(1), eq(1));
    }

    @Test
    public void test_notifyItemRemoved() throws Exception {
    public void setWidgets_withItemRemoved_shouldNotifyItemRemoved() {
        mAdapter.setWidgets(generateSampleMap(2));
        mAdapter.setWidgets(generateSampleMap(1));
        verify(mListener, times(1)).onChanged();
        verify(mListener, times(1)).onItemRangeRemoved(eq(1), eq(1));

        verify(mListener).onItemRangeRemoved(eq(1), eq(1));
    }

    @Test
    public void testNotifyItemChanged_PackageIconDiff() throws Exception {
    public void setWidgets_appIconChanged_shouldNotifyItemChanged() {
        mAdapter.setWidgets(generateSampleMap(1));
        mAdapter.setWidgets(generateSampleMap(1));
        verify(mListener, times(1)).onChanged();
        verify(mListener, times(1)).onItemRangeChanged(eq(0), eq(1), isNull());

        verify(mListener).onItemRangeChanged(eq(0), eq(1), isNull());
    }

    @Test
    public void testNotifyItemChanged_widgetItemInfoDiff() throws Exception {
        // TODO: same package name but item number changed
    public void setWidgets_sameApp_moreWidgets_shouldNotifyItemChangedWithWidgetItemInfoDiff() {
        // GIVEN the adapter was first populated with test package 1 & test package 2.
        WidgetsListBaseEntry testPackage1With2WidgetsListEntry =
                generateSampleAppWithWidgets(TEST_PACKAGE_1, /* numOfWidgets= */ 2);
        WidgetsListBaseEntry testPackage2With2WidgetsListEntry =
                generateSampleAppWithWidgets(TEST_PACKAGE_2, /* numOfWidgets= */ 2);
        mAdapter.setWidgets(
                List.of(testPackage1With2WidgetsListEntry, testPackage2With2WidgetsListEntry));

        // WHEN the adapter is updated with the same list of apps but test package 2 has 3 widgets
        // now.
        WidgetsListBaseEntry testPackage1With3WidgetsListEntry =
                generateSampleAppWithWidgets(TEST_PACKAGE_2, /* numOfWidgets= */ 2);
        mAdapter.setWidgets(
                List.of(testPackage1With2WidgetsListEntry, testPackage1With3WidgetsListEntry));

        // THEN the onItemRangeChanged is invoked.
        verify(mListener).onItemRangeChanged(eq(1), eq(1), isNull());
    }

    @Test
    public void testNotifyItemInsertedRemoved_hodgepodge() throws Exception {
        // TODO: insert and remove combined.          curMap
        // newMap [A, C, D]                           [A, B, E]
        // B - C < 0, removed B from index 1          [A, E]
        // E - C > 0, C inserted to index 1           [A, C, E]
        // E - D > 0, D inserted to index 2           [A, C, D, E]
        // E - null = -1, E deleted from index 3      [A, C, D]
    public void setWidgets_hodgepodge_shouldInvokeExpectedDataObserverCallbacks() {
        List<WidgetsListBaseEntry> allAppsWithWidgets = generateSampleMap(5);
        // GIVEN the current widgets list consist of [A, B, E].
        List<WidgetsListBaseEntry> currentList = List.of(
                allAppsWithWidgets.get(0), allAppsWithWidgets.get(1), allAppsWithWidgets.get(4));
        mAdapter.setWidgets(currentList);

        // WHEN the widgets list is updated to [A, C, D].
        List<WidgetsListBaseEntry> newList = List.of(
                allAppsWithWidgets.get(0), allAppsWithWidgets.get(2), allAppsWithWidgets.get(3));
        mAdapter.setWidgets(newList);

        // Computation logic                           | [Intermediate list during computation]
        // THEN B <> C < 0, removed B from index 1     | [A, E]
        verify(mListener).onItemRangeRemoved(/* positionStart= */ 1, /* itemCount= */ 1);
        // THEN E <> C > 0, C inserted to index 1      | [A, C, E]
        verify(mListener).onItemRangeInserted(/* positionStart= */ 1, /* itemCount= */ 1);
        // THEN E <> D > 0, D inserted to index 2      | [A, C, D, E]
        verify(mListener).onItemRangeInserted(/* positionStart= */ 2, /* itemCount= */ 1);
        // THEN E <> null = -1, E deleted from index 3 | [A, C, D]
        verify(mListener).onItemRangeRemoved(/* positionStart= */ 3, /* itemCount= */ 1);
    }

    /**
     * Helper method to generate the sample widget model map that can be used for the tests
     * @param num the number of WidgetItem the map should contain
     */
    private ArrayList<WidgetListRowEntry> generateSampleMap(int num) {
        ArrayList<WidgetListRowEntry> result = new ArrayList<>();
    private ArrayList<WidgetsListBaseEntry> generateSampleMap(int num) {
        ArrayList<WidgetsListBaseEntry> result = new ArrayList<>();
        if (num <= 0) return result;
        ShadowPackageManager spm = shadowOf(mContext.getPackageManager());

        for (int i = 0; i < num; i++) {
            ComponentName cn = new ComponentName("com.placeholder.apk" + i, "PlaceholderWidet");

            AppWidgetProviderInfo widgetInfo = new AppWidgetProviderInfo();
            widgetInfo.provider = cn;
            ReflectionHelpers.setField(widgetInfo, "providerInfo", spm.addReceiverIfNotPresent(cn));
            String packageName = "com.placeholder.apk" + i;

            WidgetItem wi = new WidgetItem(LauncherAppWidgetProviderInfo
                    .fromProviderInfo(mContext, widgetInfo), mTestProfile, mIconCache);
            List<WidgetItem> widgetItems = generateWidgetItems(packageName, /* numOfWidgets= */ 1);

            PackageItemInfo pInfo = new PackageItemInfo(wi.componentName.getPackageName());
            PackageItemInfo pInfo = new PackageItemInfo(packageName);
            pInfo.title = pInfo.packageName;
            pInfo.user = wi.user;
            pInfo.user = widgetItems.get(0).user;
            pInfo.bitmap = BitmapInfo.of(Bitmap.createBitmap(10, 10, Bitmap.Config.ALPHA_8), 0);

            result.add(new WidgetListRowEntry(pInfo, new ArrayList<>(Collections.singleton(wi))));
            result.add(new WidgetsListContentEntry(pInfo, /* titleSectionName= */ "", widgetItems));
        }

        return result;
    }

    private WidgetsListBaseEntry generateSampleAppWithWidgets(String packageName,
            int numOfWidgets) {
        PackageItemInfo appInfo = new PackageItemInfo(packageName);
        appInfo.title = appInfo.packageName;
        appInfo.bitmap = BitmapInfo.of(Bitmap.createBitmap(10, 10, Bitmap.Config.ALPHA_8), 0);

        return new WidgetsListContentEntry(appInfo,
                /* titleSectionName= */ "",
                generateWidgetItems(packageName, numOfWidgets));
    }

    private List<WidgetItem> generateWidgetItems(String packageName, int numOfWidgets) {
        ShadowPackageManager packageManager = shadowOf(mContext.getPackageManager());
        ArrayList<WidgetItem> widgetItems = new ArrayList<>();
        for (int i = 0; i < numOfWidgets; i++) {
            ComponentName cn = ComponentName.createRelative(packageName, ".SampleWidget" + i);
            AppWidgetProviderInfo widgetInfo = new AppWidgetProviderInfo();
            widgetInfo.provider = cn;
            ReflectionHelpers.setField(widgetInfo, "providerInfo",
                    packageManager.addReceiverIfNotPresent(cn));

            widgetItems.add(new WidgetItem(
                    LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, widgetInfo),
                    mTestProfile, mIconCache));
        }
        return widgetItems;
    }
}
Loading