Defining the list of predefined device profiles in xml.
This allows the profiles to be easily customized by derivatives.

Change-Id: Iaec2b7a0eda34581c8e48b7dc44817013632dabe
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 21a05c0..308c71c 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -85,4 +85,28 @@
 
     <!-- Fallback attr for pre-API 25 support -->
     <attr name="colorSecondary" format="reference|color" />
+
+    <declare-styleable name="InvariantDeviceProfile">
+        <attr name="name" format="string" />
+        <attr name="minWidthDps" format="float" />
+        <attr name="minHeightDps" format="float" />
+
+        <attr name="numRows" format="integer" />
+        <attr name="numColumns" format="integer" />
+        <!-- numFolderRows & numFolderColumns defaults to numRows & numColumns, if not specified -->
+        <attr name="numFolderRows" format="integer" />
+        <attr name="numFolderColumns" format="integer" />
+        <!-- minAllAppsPredictionColumns defaults to numColumns, if not specified -->
+        <attr name="minAllAppsPredictionColumns" format="integer" />
+        <!-- numHotseatIcons defaults to numColumns, if not specified -->
+        <attr name="numHotseatIcons" format="integer" />
+
+        <attr name="iconSize" format="float" />
+        <attr name="iconTextSize" format="float" />
+        <!-- hotseatIconSize defaults to iconSize, if not specified -->
+        <attr name="hotseatIconSize" format="float" />
+
+        <attr name="defaultLayoutId" format="reference" />
+    </declare-styleable>
+
 </resources>
diff --git a/res/xml/device_profiles.xml b/res/xml/device_profiles.xml
new file mode 100644
index 0000000..aeda1a2
--- /dev/null
+++ b/res/xml/device_profiles.xml
@@ -0,0 +1,196 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2016 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.
+-->
+
+<profiles xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3" >
+
+    <profile
+        launcher:name="Super Short Stubby"
+        launcher:minWidthDps="255"
+        launcher:minHeightDps="300"
+        launcher:numRows="2"
+        launcher:numColumns="3"
+        launcher:numFolderRows="2"
+        launcher:numFolderColumns="3"
+        launcher:minAllAppsPredictionColumns="3"
+        launcher:iconSize="48"
+        launcher:iconTextSize="13.0"
+        launcher:numHotseatIcons="3"
+        launcher:hotseatIconSize="48"
+        launcher:defaultLayoutId="@xml/default_workspace_3x3"
+        />
+
+    <profile
+        launcher:name="Shorter Stubby"
+        launcher:minWidthDps="255"
+        launcher:minHeightDps="400"
+        launcher:numRows="3"
+        launcher:numColumns="3"
+        launcher:numFolderRows="3"
+        launcher:numFolderColumns="3"
+        launcher:minAllAppsPredictionColumns="3"
+        launcher:iconSize="48"
+        launcher:iconTextSize="13.0"
+        launcher:numHotseatIcons="3"
+        launcher:hotseatIconSize="48"
+        launcher:defaultLayoutId="@xml/default_workspace_3x3"
+        />
+
+    <profile
+        launcher:name="Short Stubby"
+        launcher:minWidthDps="275"
+        launcher:minHeightDps="420"
+        launcher:numRows="3"
+        launcher:numColumns="4"
+        launcher:numFolderRows="3"
+        launcher:numFolderColumns="4"
+        launcher:minAllAppsPredictionColumns="4"
+        launcher:iconSize="48"
+        launcher:iconTextSize="13.0"
+        launcher:numHotseatIcons="5"
+        launcher:hotseatIconSize="48"
+        launcher:defaultLayoutId="@xml/default_workspace_4x4"
+        />
+
+    <profile
+        launcher:name="Stubby"
+        launcher:minWidthDps="255"
+        launcher:minHeightDps="450"
+        launcher:numRows="3"
+        launcher:numColumns="4"
+        launcher:numFolderRows="3"
+        launcher:numFolderColumns="4"
+        launcher:minAllAppsPredictionColumns="4"
+        launcher:iconSize="48"
+        launcher:iconTextSize="13.0"
+        launcher:numHotseatIcons="5"
+        launcher:hotseatIconSize="48"
+        launcher:defaultLayoutId="@xml/default_workspace_4x4"
+        />
+
+    <profile
+        launcher:name="Nexus S"
+        launcher:minWidthDps="296"
+        launcher:minHeightDps="491.33"
+        launcher:numRows="4"
+        launcher:numColumns="4"
+        launcher:numFolderRows="4"
+        launcher:numFolderColumns="4"
+        launcher:minAllAppsPredictionColumns="4"
+        launcher:iconSize="48"
+        launcher:iconTextSize="13.0"
+        launcher:numHotseatIcons="5"
+        launcher:hotseatIconSize="48"
+        launcher:defaultLayoutId="@xml/default_workspace_4x4"
+        />
+
+    <profile
+        launcher:name="Nexus 4"
+        launcher:minWidthDps="359"
+        launcher:minHeightDps="567"
+        launcher:numRows="4"
+        launcher:numColumns="4"
+        launcher:numFolderRows="4"
+        launcher:numFolderColumns="4"
+        launcher:minAllAppsPredictionColumns="4"
+        launcher:iconSize="60"
+        launcher:iconTextSize="13.0"
+        launcher:numHotseatIcons="5"
+        launcher:hotseatIconSize="56"
+        launcher:defaultLayoutId="@xml/default_workspace_4x4"
+        />
+
+    <profile
+        launcher:name="Nexus 5"
+        launcher:minWidthDps="335"
+        launcher:minHeightDps="567"
+        launcher:numRows="4"
+        launcher:numColumns="4"
+        launcher:numFolderRows="4"
+        launcher:numFolderColumns="4"
+        launcher:minAllAppsPredictionColumns="4"
+        launcher:iconSize="60"
+        launcher:iconTextSize="13.0"
+        launcher:numHotseatIcons="5"
+        launcher:hotseatIconSize="56"
+        launcher:defaultLayoutId="@xml/default_workspace_4x4"
+        />
+
+    <profile
+        launcher:name="Large Phone"
+        launcher:minWidthDps="406"
+        launcher:minHeightDps="694"
+        launcher:numRows="5"
+        launcher:numColumns="5"
+        launcher:numFolderRows="4"
+        launcher:numFolderColumns="4"
+        launcher:minAllAppsPredictionColumns="4"
+        launcher:iconSize="64"
+        launcher:iconTextSize="14.4"
+        launcher:numHotseatIcons="5"
+        launcher:hotseatIconSize="56"
+        launcher:defaultLayoutId="@xml/default_workspace_5x5"
+        />
+
+    <profile
+        launcher:name="Nexus 7"
+        launcher:minWidthDps="575"
+        launcher:minHeightDps="904"
+        launcher:numRows="5"
+        launcher:numColumns="6"
+        launcher:numFolderRows="4"
+        launcher:numFolderColumns="5"
+        launcher:minAllAppsPredictionColumns="4"
+        launcher:iconSize="72"
+        launcher:iconTextSize="14.4"
+        launcher:numHotseatIcons="7"
+        launcher:hotseatIconSize="60"
+        launcher:defaultLayoutId="@xml/default_workspace_5x6"
+        />
+
+    <profile
+        launcher:name="Nexus 10"
+        launcher:minWidthDps="727"
+        launcher:minHeightDps="1207"
+        launcher:numRows="5"
+        launcher:numColumns="6"
+        launcher:numFolderRows="4"
+        launcher:numFolderColumns="5"
+        launcher:minAllAppsPredictionColumns="4"
+        launcher:iconSize="76"
+        launcher:iconTextSize="14.4"
+        launcher:numHotseatIcons="7"
+        launcher:hotseatIconSize="76"
+        launcher:defaultLayoutId="@xml/default_workspace_5x6"
+        />
+
+    <profile
+        launcher:name="20-inch Tablet"
+        launcher:minWidthDps="1527"
+        launcher:minHeightDps="2527"
+        launcher:numRows="7"
+        launcher:numColumns="7"
+        launcher:numFolderRows="6"
+        launcher:numFolderColumns="6"
+        launcher:minAllAppsPredictionColumns="4"
+        launcher:iconSize="100"
+        launcher:iconTextSize="20.0"
+        launcher:numHotseatIcons="7"
+        launcher:hotseatIconSize="72"
+        launcher:defaultLayoutId="@xml/default_workspace_5x6"
+        />
+
+</profiles>
\ No newline at end of file
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index da95d66..4e6911c 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -18,8 +18,11 @@
 
 import android.annotation.TargetApi;
 import android.content.Context;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
 import android.graphics.Point;
 import android.util.DisplayMetrics;
+import android.util.Xml;
 import android.view.Display;
 import android.view.WindowManager;
 
@@ -27,6 +30,10 @@
 import com.android.launcher3.config.ProviderConfig;
 import com.android.launcher3.util.Thunk;
 
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
@@ -131,8 +138,8 @@
         minWidthDps = Utilities.dpiFromPx(Math.min(smallestSize.x, smallestSize.y), dm);
         minHeightDps = Utilities.dpiFromPx(Math.min(largestSize.x, largestSize.y), dm);
 
-        ArrayList<InvariantDeviceProfile> closestProfiles =
-                findClosestDeviceProfiles(minWidthDps, minHeightDps, getPredefinedDeviceProfiles());
+        ArrayList<InvariantDeviceProfile> closestProfiles = findClosestDeviceProfiles(
+                minWidthDps, minHeightDps, getPredefinedDeviceProfiles(context));
         InvariantDeviceProfile interpolatedDeviceProfileOut =
                 invDistWeightedInterpolate(minWidthDps,  minHeightDps, closestProfiles);
 
@@ -178,36 +185,41 @@
         }
     }
 
-    ArrayList<InvariantDeviceProfile> getPredefinedDeviceProfiles() {
-        ArrayList<InvariantDeviceProfile> predefinedDeviceProfiles = new ArrayList<>();
-        // width, height, #rows, #columns, #folder rows, #folder columns,
-        // iconSize, iconTextSize, #hotseat, #hotseatIconSize, defaultLayoutId.
-        predefinedDeviceProfiles.add(new InvariantDeviceProfile("Super Short Stubby",
-                255, 300,     2, 3, 2, 3, 3, 48, 13, 3, 48, R.xml.default_workspace_3x3));
-        predefinedDeviceProfiles.add(new InvariantDeviceProfile("Shorter Stubby",
-                255, 400,     3, 3, 3, 3, 3, 48, 13, 3, 48, R.xml.default_workspace_3x3));
-        predefinedDeviceProfiles.add(new InvariantDeviceProfile("Short Stubby",
-                275, 420,     3, 4, 3, 4, 4, 48, 13, 5, 48, R.xml.default_workspace_4x4));
-        predefinedDeviceProfiles.add(new InvariantDeviceProfile("Stubby",
-                255, 450,     3, 4, 3, 4, 4, 48, 13, 5, 48, R.xml.default_workspace_4x4));
-        predefinedDeviceProfiles.add(new InvariantDeviceProfile("Nexus S",
-                296, 491.33f, 4, 4, 4, 4, 4, 48, 13, 5, 48, R.xml.default_workspace_4x4));
-        predefinedDeviceProfiles.add(new InvariantDeviceProfile("Nexus 4",
-                359, 567,     4, 4, 4, 4, 4, DEFAULT_ICON_SIZE_DP, 13, 5, 56, R.xml.default_workspace_4x4));
-        predefinedDeviceProfiles.add(new InvariantDeviceProfile("Nexus 5",
-                335, 567,     4, 4, 4, 4, 4, DEFAULT_ICON_SIZE_DP, 13, 5, 56, R.xml.default_workspace_4x4));
-        predefinedDeviceProfiles.add(new InvariantDeviceProfile("Large Phone",
-                406, 694,     5, 5, 4, 4, 4, 64, 14.4f,  5, 56, R.xml.default_workspace_5x5));
-        // The tablet profile is odd in that the landscape orientation
-        // also includes the nav bar on the side
-        predefinedDeviceProfiles.add(new InvariantDeviceProfile("Nexus 7",
-                575, 904,     5, 6, 4, 5, 4, 72, 14.4f,  7, 60, R.xml.default_workspace_5x6));
-        // Larger tablet profiles always have system bars on the top & bottom
-        predefinedDeviceProfiles.add(new InvariantDeviceProfile("Nexus 10",
-                727, 1207,    5, 6, 4, 5, 4, 76, 14.4f,  7, 76, R.xml.default_workspace_5x6));
-        predefinedDeviceProfiles.add(new InvariantDeviceProfile("20-inch Tablet",
-                1527, 2527,   7, 7, 6, 6, 4, 100, 20,  7, 72, R.xml.default_workspace_5x6));
-        return predefinedDeviceProfiles;
+    ArrayList<InvariantDeviceProfile> getPredefinedDeviceProfiles(Context context) {
+        ArrayList<InvariantDeviceProfile> profiles = new ArrayList<>();
+        try (XmlResourceParser parser = context.getResources().getXml(R.xml.device_profiles)) {
+            final int depth = parser.getDepth();
+            int type;
+
+            while (((type = parser.next()) != XmlPullParser.END_TAG ||
+                    parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
+                if ((type == XmlPullParser.START_TAG) && "profile".equals(parser.getName())) {
+                    TypedArray a = context.obtainStyledAttributes(
+                            Xml.asAttributeSet(parser), R.styleable.InvariantDeviceProfile);
+                    int numRows = a.getInt(R.styleable.InvariantDeviceProfile_numRows, 0);
+                    int numColumns = a.getInt(R.styleable.InvariantDeviceProfile_numColumns, 0);
+                    float iconSize = a.getFloat(R.styleable.InvariantDeviceProfile_iconSize, 0);
+                    profiles.add(new InvariantDeviceProfile(
+                            a.getString(R.styleable.InvariantDeviceProfile_name),
+                            a.getFloat(R.styleable.InvariantDeviceProfile_minWidthDps, 0),
+                            a.getFloat(R.styleable.InvariantDeviceProfile_minHeightDps, 0),
+                            numRows,
+                            numColumns,
+                            a.getInt(R.styleable.InvariantDeviceProfile_numFolderRows, numRows),
+                            a.getInt(R.styleable.InvariantDeviceProfile_numFolderColumns, numColumns),
+                            a.getInt(R.styleable.InvariantDeviceProfile_minAllAppsPredictionColumns, numColumns),
+                            iconSize,
+                            a.getFloat(R.styleable.InvariantDeviceProfile_iconTextSize, 0),
+                            a.getInt(R.styleable.InvariantDeviceProfile_numHotseatIcons, numColumns),
+                            a.getFloat(R.styleable.InvariantDeviceProfile_hotseatIconSize, iconSize),
+                            a.getResourceId(R.styleable.InvariantDeviceProfile_defaultLayoutId, 0)));
+                    a.recycle();
+                }
+            }
+        } catch (IOException|XmlPullParserException e) {
+            throw new RuntimeException(e);
+        }
+        return profiles;
     }
 
     private int getLauncherIconDensity(int requiredSize) {
diff --git a/tests/src/com/android/launcher3/InvariantDeviceProfileTest.java b/tests/src/com/android/launcher3/InvariantDeviceProfileTest.java
index 35f686f..230d623 100644
--- a/tests/src/com/android/launcher3/InvariantDeviceProfileTest.java
+++ b/tests/src/com/android/launcher3/InvariantDeviceProfileTest.java
@@ -41,7 +41,7 @@
     protected void setUp() throws Exception {
         super.setUp();
         mInvariantProfile = new InvariantDeviceProfile(getContext());
-        mPredefinedDeviceProfiles = mInvariantProfile.getPredefinedDeviceProfiles();
+        mPredefinedDeviceProfiles = mInvariantProfile.getPredefinedDeviceProfiles(getContext());
     }
 
     @Override