New feature “Text and reading options” for SetupWizard, Wallpaper, and Settings (4/n).

- Extending the interface of AppGridView to be flexible. It will be used
in the TextReadingPreferenceFragment.
  1. Add a new attribute "appCounts" into the view.
  2. Default app counts as 6 if not set the attribute.

Bug: 211503117
Test: make -j64 RunSettingsRoboTests ROBOTEST_FILTER=AppGridViewTest
Change-Id: I94a5f1d037551f6faba0300a176feb11fba1f203
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 579ac35..e4822d8 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -103,6 +103,11 @@
         <attr name="iconBackgroundColor" format="reference|color" />
     </declare-styleable>
 
+    <!-- For AppGridView -->
+    <declare-styleable name="AppGridView">
+        <attr name="appCount" format="integer" />
+    </declare-styleable>
+
     <attr name="switchBarTheme" format="reference" />
     <attr name="switchBarMarginStart" format="dimension" />
     <attr name="switchBarMarginEnd" format="dimension" />
diff --git a/src/com/android/settings/display/AppGridView.java b/src/com/android/settings/display/AppGridView.java
index cda1445..a0f2a63 100644
--- a/src/com/android/settings/display/AppGridView.java
+++ b/src/com/android/settings/display/AppGridView.java
@@ -20,10 +20,12 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.content.res.TypedArray;
 import android.graphics.drawable.Drawable;
 import android.os.UserHandle;
 import android.util.AttributeSet;
 import android.util.IconDrawableFactory;
+import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.ArrayAdapter;
@@ -31,6 +33,7 @@
 import android.widget.ImageView;
 
 import androidx.annotation.VisibleForTesting;
+import androidx.core.util.Preconditions;
 
 import com.android.settings.R;
 
@@ -38,7 +41,18 @@
 import java.util.Collections;
 import java.util.List;
 
+/**
+ * The grid view for displaying the application entries.
+ *
+ * <p> The attribute value {@code appCount} from XML should be more than or equal to 1, otherwise
+ * throws an {@link IllegalArgumentException}.</p>
+ */
 public class AppGridView extends GridView {
+    private static final String TAG = "AppGridView";
+
+    private static final int APP_COUNT_DEF_VALUE = 6;
+    private int mAppCount = APP_COUNT_DEF_VALUE;
+
     public AppGridView(Context context) {
         super(context);
         init(context);
@@ -46,24 +60,36 @@
 
     public AppGridView(Context context, AttributeSet attrs) {
         super(context, attrs);
+        applyAttributeSet(context, attrs);
         init(context);
     }
 
     public AppGridView(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
+        applyAttributeSet(context, attrs);
         init(context);
     }
 
     public AppGridView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleResId) {
         super(context, attrs, defStyleAttr, defStyleResId);
-
+        applyAttributeSet(context, attrs);
         init(context);
-
     }
 
     private void init(Context context) {
         setAdapter(new AppsAdapter(context, R.layout.screen_zoom_preview_app_icon,
-                android.R.id.text1, android.R.id.icon1));
+                android.R.id.text1, android.R.id.icon1, mAppCount));
+    }
+
+    private void applyAttributeSet(Context context, AttributeSet attrs) {
+        final TypedArray styledAttrs =
+                context.obtainStyledAttributes(attrs, R.styleable.AppGridView);
+        mAppCount =
+                styledAttrs.getInteger(R.styleable.AppGridView_appCount, APP_COUNT_DEF_VALUE);
+        Preconditions.checkArgument(mAppCount >= 1,
+                /* errorMessage= */ "App count may not be negative or zero");
+
+        styledAttrs.recycle();
     }
 
     /**
@@ -73,12 +99,15 @@
     public static class AppsAdapter extends ArrayAdapter<ActivityEntry> {
         private final PackageManager mPackageManager;
         private final int mIconResId;
+        private final int mAppCount;
 
-        public AppsAdapter(Context context, int layout, int textResId, int iconResId) {
+        public AppsAdapter(Context context, int layout, int textResId, int iconResId,
+                int appCount) {
             super(context, layout, textResId);
 
             mIconResId = iconResId;
             mPackageManager = context.getPackageManager();
+            mAppCount = appCount;
 
             loadAllApps();
         }
@@ -108,20 +137,24 @@
         }
 
         private void loadAllApps() {
-            final int needAppCount = 6;
             final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
             mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
 
             final PackageManager pm = mPackageManager;
             final ArrayList<ActivityEntry> results = new ArrayList<>();
             final List<ResolveInfo> infos = pm.queryIntentActivities(mainIntent, 0);
+
+            if (mAppCount > infos.size()) {
+                Log.d(TAG, "Visible app icon count does not meet the target count.");
+            }
+
             final IconDrawableFactory iconFactory = IconDrawableFactory.newInstance(getContext());
             for (ResolveInfo info : infos) {
                 final CharSequence label = info.loadLabel(pm);
                 if (label != null) {
                     results.add(new ActivityEntry(info, label.toString(), iconFactory));
                 }
-                if (results.size() >= needAppCount) {
+                if (results.size() >= mAppCount) {
                     break;
                 }
             }
diff --git a/tests/robotests/src/com/android/settings/display/AppGridViewTest.java b/tests/robotests/src/com/android/settings/display/AppGridViewTest.java
index e7daac8..22e1cc3 100644
--- a/tests/robotests/src/com/android/settings/display/AppGridViewTest.java
+++ b/tests/robotests/src/com/android/settings/display/AppGridViewTest.java
@@ -18,26 +18,39 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
+import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
 import android.util.IconDrawableFactory;
 
+import com.android.settings.R;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.robolectric.Robolectric;
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
 
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests for {@link AppGridView}.
+ */
 @RunWith(RobolectricTestRunner.class)
 public class AppGridViewTest {
 
@@ -87,4 +100,72 @@
         assertThat(entry1.compareTo(entry2)).isEqualTo(0);
         assertThat(entry1.compareTo(entry3)).isNotEqualTo(0);
     }
+
+    @Test
+    public void noAppCountAttribute_matchListSize() {
+        final int appCountFromSystem = 8;
+        setUpResolveInfos(appCountFromSystem);
+
+        final AppGridView appGridView = new AppGridView(mContext, /* attrs= */ null);
+
+        assertThat(appGridView.getAdapter().getCount()).isEqualTo(/* expected= */ 6);
+    }
+
+    @Test
+    public void setAppCountAttribute_matchListSize() {
+        final int appCountFromSystem = 8;
+        final int appCountFromAttr = 7;
+        setUpResolveInfos(appCountFromSystem);
+
+        final AppGridView appGridView =
+                new AppGridView(mContext, createAttributeSet(appCountFromAttr));
+
+        assertThat(appGridView.getAdapter().getCount()).isEqualTo(appCountFromAttr);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void setAppCountAttribute_belowLowerBound_matchListSize() {
+        final int appCountFromSystem = 9;
+        final int appCountFromAttr = -1; // The num is just for the test.
+        setUpResolveInfos(appCountFromSystem);
+
+        new AppGridView(mContext, createAttributeSet(appCountFromAttr));
+    }
+
+    @Test
+    public void setAppCountAttribute_aboveUpperBound_matchListSize() {
+        final int appCountFromSystem = 10;
+        final int appCountFromAttr = 15;
+        setUpResolveInfos(appCountFromSystem);
+
+        final AppGridView appGridView =
+                new AppGridView(mContext, createAttributeSet(appCountFromAttr));
+
+        assertThat(appGridView.getAdapter().getCount()).isEqualTo(appCountFromSystem);
+    }
+
+    private AttributeSet createAttributeSet(int appCount) {
+        return Robolectric.buildAttributeSet()
+                .addAttribute(R.attr.appCount, String.valueOf(appCount))
+                .build();
+    }
+
+    private void setUpResolveInfos(int appCount) {
+        when(mContext.getPackageManager().queryIntentActivities(
+                any(Intent.class), anyInt()))
+                .thenReturn(createFakeResolveInfos(appCount));
+    }
+
+    private List<ResolveInfo> createFakeResolveInfos(int count) {
+        final List<ResolveInfo> list = new ArrayList<>();
+
+        for (int i = 0; i < count; i++) {
+            final ResolveInfo info = new ResolveInfo();
+            info.nonLocalizedLabel = String.valueOf(i);
+
+            list.add(info);
+        }
+
+        return list;
+    }
 }