Bug 1157539 - Create "speed dial" dynamic home panel layout. r=mhaigh
authorSebastian Kaspari <s.kaspari@gmail.com>
Tue, 16 Jun 2015 13:53:59 +0200
changeset 280887 80b18a9c888e87d163c07bee19071554a1ce773d
parent 280886 fa660238a158f27768b8f588042460d2a6806448
child 280888 dc22e118ec7eef395c1e8abe5c0a59174917a1e0
push id4932
push userjlund@mozilla.com
push dateMon, 10 Aug 2015 18:23:06 +0000
treeherdermozilla-beta@6dd5a4f5f745 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmhaigh
bugs1157539
milestone41.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1157539 - Create "speed dial" dynamic home panel layout. r=mhaigh
mobile/android/base/db/BrowserContract.java
mobile/android/base/db/HomeProvider.java
mobile/android/base/home/HomeConfig.java
mobile/android/base/home/PanelGridView.java
mobile/android/base/home/PanelItemView.java
mobile/android/base/moz.build
mobile/android/base/resources/layout/panel_icon_item.xml
mobile/android/base/resources/values-land/integers.xml
mobile/android/base/resources/values-xlarge-v11/integers.xml
mobile/android/base/resources/values/attrs.xml
mobile/android/base/resources/values/colors.xml
mobile/android/base/resources/values/integers.xml
mobile/android/base/resources/values/styles.xml
mobile/android/base/resources/values/themes.xml
mobile/android/base/widget/SquaredRelativeLayout.java
mobile/android/modules/Home.jsm
mobile/android/modules/HomeProvider.jsm
mobile/android/tests/browser/robocop/testHomeProvider.js
--- a/mobile/android/base/db/BrowserContract.java
+++ b/mobile/android/base/db/BrowserContract.java
@@ -330,21 +330,23 @@ public class BrowserContract {
         public static final String CONTENT_TYPE = "vnd.android.cursor.dir/homeitem";
         public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/homeitem";
 
         public static final String DATASET_ID = "dataset_id";
         public static final String URL = "url";
         public static final String TITLE = "title";
         public static final String DESCRIPTION = "description";
         public static final String IMAGE_URL = "image_url";
+        public static final String BACKGROUND_COLOR = "background_color";
+        public static final String BACKGROUND_URL = "background_url";
         public static final String CREATED = "created";
         public static final String FILTER = "filter";
 
         public static final String[] DEFAULT_PROJECTION =
-            new String[] { _ID, DATASET_ID, URL, TITLE, DESCRIPTION, IMAGE_URL, FILTER };
+            new String[] { _ID, DATASET_ID, URL, TITLE, DESCRIPTION, IMAGE_URL, BACKGROUND_COLOR, BACKGROUND_URL, FILTER };
     }
 
     @RobocopTarget
     public static final class ReadingListItems implements CommonColumns, URLColumns {
         public static final String EXCERPT = "excerpt";
         public static final String CLIENT_LAST_MODIFIED = "client_last_modified";
         public static final String GUID = "guid";
         public static final String SERVER_LAST_MODIFIED = "last_modified";
--- a/mobile/android/base/db/HomeProvider.java
+++ b/mobile/android/base/db/HomeProvider.java
@@ -22,17 +22,17 @@ import android.database.Cursor;
 import android.database.MatrixCursor;
 import android.net.Uri;
 import android.util.Log;
 
 public class HomeProvider extends SQLiteBridgeContentProvider {
     private static final String LOGTAG = "GeckoHomeProvider";
 
     // This should be kept in sync with the db version in mobile/android/modules/HomeProvider.jsm
-    private static final int DB_VERSION = 2;
+    private static final int DB_VERSION = 3;
     private static final String DB_FILENAME = "home.sqlite";
     private static final String TELEMETRY_TAG = "SQLITEBRIDGE_PROVIDER_HOME";
 
     private static final String TABLE_ITEMS = "items";
 
     // Endpoint to return static fake data.
     static final int ITEMS_FAKE = 100;
     static final int ITEMS = 101;
--- a/mobile/android/base/home/HomeConfig.java
+++ b/mobile/android/base/home/HomeConfig.java
@@ -519,17 +519,18 @@ public final class HomeConfig {
             public ViewType[] newArray(final int size) {
                 return new ViewType[size];
             }
         };
     }
 
     public static enum ItemType implements Parcelable {
         ARTICLE("article"),
-        IMAGE("image");
+        IMAGE("image"),
+        ICON("icon");
 
         private final String mId;
 
         ItemType(String id) {
             mId = id;
         }
 
         public static ItemType fromId(String id) {
--- a/mobile/android/base/home/PanelGridView.java
+++ b/mobile/android/base/home/PanelGridView.java
@@ -1,22 +1,17 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.home;
 
-import java.util.EnumSet;
-
 import org.mozilla.gecko.R;
-import org.mozilla.gecko.db.BrowserContract.HomeItems;
-import org.mozilla.gecko.home.HomeConfig.ItemHandler;
 import org.mozilla.gecko.home.HomeConfig.ViewConfig;
-import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
 import org.mozilla.gecko.home.PanelLayout.DatasetBacked;
 import org.mozilla.gecko.home.PanelLayout.FilterManager;
 import org.mozilla.gecko.home.PanelLayout.OnItemOpenListener;
 import org.mozilla.gecko.home.PanelLayout.PanelView;
 
 import android.content.Context;
 import android.database.Cursor;
 import android.util.Log;
@@ -31,17 +26,18 @@ public class PanelGridView extends GridV
     private final ViewConfig viewConfig;
     private final PanelViewAdapter adapter;
     private final PanelViewItemHandler itemHandler;
     private OnItemOpenListener itemOpenListener;
     private HomeContextMenuInfo mContextMenuInfo;
     private HomeContextMenuInfo.Factory mContextMenuInfoFactory;
 
     public PanelGridView(Context context, ViewConfig viewConfig) {
-        super(context, null, R.attr.panelGridViewStyle);
+        super(context, null, (viewConfig.getItemType() == HomeConfig.ItemType.ICON) ?
+                R.attr.panelIconGridViewStyle : R.attr.panelGridViewStyle);
 
         this.viewConfig = viewConfig;
         itemHandler = new PanelViewItemHandler(viewConfig);
 
         adapter = new PanelViewAdapter(context, viewConfig);
         setAdapter(adapter);
 
         setOnItemClickListener(new PanelGridItemClickListener());
--- a/mobile/android/base/home/PanelItemView.java
+++ b/mobile/android/base/home/PanelItemView.java
@@ -6,76 +6,100 @@
 package org.mozilla.gecko.home;
 
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.db.BrowserContract.HomeItems;
 import org.mozilla.gecko.home.HomeConfig.ItemType;
 
 import android.content.Context;
 import android.database.Cursor;
+import android.graphics.Color;
 import android.text.TextUtils;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
-import com.squareup.picasso.Picasso;
-
 class PanelItemView extends LinearLayout {
-    private final TextView title;
-    private final TextView description;
-    private final ImageView image;
-    private final LinearLayout titleDescContainer;
+    private final TextView titleView;
+    private final TextView descriptionView;
+    private final ImageView imageView;
+    private final LinearLayout titleDescContainerView;
+    private final ImageView backgroundView;
 
     private PanelItemView(Context context, int layoutId) {
         super(context);
 
         LayoutInflater.from(context).inflate(layoutId, this);
-        title = (TextView) findViewById(R.id.title);
-        description = (TextView) findViewById(R.id.description);
-        image = (ImageView) findViewById(R.id.image);
-        titleDescContainer = (LinearLayout) findViewById(R.id.title_desc_container);
+        titleView = (TextView) findViewById(R.id.title);
+        descriptionView = (TextView) findViewById(R.id.description);
+        imageView = (ImageView) findViewById(R.id.image);
+        backgroundView = (ImageView) findViewById(R.id.background);
+        titleDescContainerView = (LinearLayout) findViewById(R.id.title_desc_container);
     }
 
     public void updateFromCursor(Cursor cursor) {
         int titleIndex = cursor.getColumnIndexOrThrow(HomeItems.TITLE);
         final String titleText = cursor.getString(titleIndex);
 
         // Only show title if the item has one
         final boolean hasTitle = !TextUtils.isEmpty(titleText);
-        title.setVisibility(hasTitle ? View.VISIBLE : View.GONE);
-        titleDescContainer.setVisibility(hasTitle ? View.VISIBLE : View.GONE);
+        titleView.setVisibility(hasTitle ? View.VISIBLE : View.GONE);
         if (hasTitle) {
-            title.setText(titleText);
+            titleView.setText(titleText);
         }
 
         int descriptionIndex = cursor.getColumnIndexOrThrow(HomeItems.DESCRIPTION);
         final String descriptionText = cursor.getString(descriptionIndex);
 
         // Only show description if the item has one
+        // Descriptions are not supported for IconItemView objects (Bug 1157539)
         final boolean hasDescription = !TextUtils.isEmpty(descriptionText);
-        description.setVisibility(hasDescription ? View.VISIBLE : View.GONE);
-        if (hasDescription) {
-            description.setText(descriptionText);
+        if (descriptionView != null) {
+            descriptionView.setVisibility(hasDescription ? View.VISIBLE : View.GONE);
+            if (hasDescription) {
+                descriptionView.setText(descriptionText);
+            }
         }
-
-        titleDescContainer.setVisibility(hasTitle || hasDescription ? View.VISIBLE : View.GONE);
+        if (titleDescContainerView != null) {
+            titleDescContainerView.setVisibility(hasTitle || hasDescription ? View.VISIBLE : View.GONE);
+        }
 
         int imageIndex = cursor.getColumnIndexOrThrow(HomeItems.IMAGE_URL);
         final String imageUrl = cursor.getString(imageIndex);
 
         // Only try to load the image if the item has define image URL
         final boolean hasImageUrl = !TextUtils.isEmpty(imageUrl);
-        image.setVisibility(hasImageUrl ? View.VISIBLE : View.GONE);
+        imageView.setVisibility(hasImageUrl ? View.VISIBLE : View.GONE);
 
         if (hasImageUrl) {
             ImageLoader.with(getContext())
                        .load(imageUrl)
-                       .into(image);
+                       .into(imageView);
+        }
+
+        final int columnIndexBackgroundColor = cursor.getColumnIndex(HomeItems.BACKGROUND_COLOR);
+        if (columnIndexBackgroundColor != -1) {
+            final String color = cursor.getString(columnIndexBackgroundColor);
+            if (!TextUtils.isEmpty(color)) {
+                setBackgroundColor(Color.parseColor(color));
+            }
+        }
+
+        // Backgrounds are only supported for IconItemView objects (Bug 1157539)
+        final int columnIndexBackgroundUrl = cursor.getColumnIndex(HomeItems.BACKGROUND_URL);
+        if (columnIndexBackgroundUrl != -1) {
+            final String backgroundUrl = cursor.getString(columnIndexBackgroundUrl);
+            if (backgroundView != null && !TextUtils.isEmpty(backgroundUrl)) {
+                ImageLoader.with(getContext())
+                        .load(backgroundUrl)
+                        .fit()
+                        .into(backgroundView);
+            }
         }
     }
 
     private static class ArticleItemView extends PanelItemView {
         private ArticleItemView(Context context) {
             super(context, R.layout.panel_article_item);
             setOrientation(LinearLayout.HORIZONTAL);
         }
@@ -83,21 +107,30 @@ class PanelItemView extends LinearLayout
 
     private static class ImageItemView extends PanelItemView {
         private ImageItemView(Context context) {
             super(context, R.layout.panel_image_item);
             setOrientation(LinearLayout.VERTICAL);
         }
     }
 
+    private static class IconItemView extends PanelItemView {
+        private IconItemView(Context context) {
+            super(context, R.layout.panel_icon_item);
+        }
+    }
+
     public static PanelItemView create(Context context, ItemType itemType) {
         switch(itemType) {
             case ARTICLE:
                 return new ArticleItemView(context);
 
             case IMAGE:
                 return new ImageItemView(context);
 
+            case ICON:
+                return new IconItemView(context);
+
             default:
                 throw new IllegalArgumentException("Could not create panel item view from " + itemType);
         }
     }
 }
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -520,16 +520,17 @@ gbjar.sources += [
     'widget/GeckoPopupMenu.java',
     'widget/GeckoSwipeRefreshLayout.java',
     'widget/GeckoViewFlipper.java',
     'widget/IconTabWidget.java',
     'widget/LoginDoorHanger.java',
     'widget/ResizablePathDrawable.java',
     'widget/SiteLogins.java',
     'widget/SquaredImageView.java',
+    'widget/SquaredRelativeLayout.java',
     'widget/SwipeDismissListViewTouchListener.java',
     'widget/TabThumbnailWrapper.java',
     'widget/ThumbnailView.java',
     'widget/TwoWayView.java',
     'ZoomConstraints.java',
     'ZoomedView.java',
 ]
 # The following sources are checked in to version control but
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/resources/layout/panel_icon_item.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<org.mozilla.gecko.widget.SquaredRelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+        <ImageView
+            android:id="@+id/background"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:scaleType="centerCrop" />
+
+        <ImageView
+            android:id="@+id/image"
+            android:layout_width="50dp"
+            android:layout_height="50dp"
+            android:scaleType="centerInside"
+            android:layout_centerInParent="true" />
+
+        <TextView
+            android:id="@+id/title"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:singleLine="true"
+            android:ellipsize="end"
+            android:textColor="@android:color/white"
+            android:layout_alignParentBottom="true"
+            android:textSize="12sp"
+            android:padding="8dp"
+            android:background="@color/panel_icon_item_title_background"
+            android:layout_gravity="center_horizontal"/>
+
+</org.mozilla.gecko.widget.SquaredRelativeLayout>
--- a/mobile/android/base/resources/values-land/integers.xml
+++ b/mobile/android/base/resources/values-land/integers.xml
@@ -1,10 +1,11 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 <resources>
 
     <integer name="number_of_top_sites_cols">3</integer>
+    <integer name="panel_icon_grid_view_columns">6</integer>
 
 </resources>
--- a/mobile/android/base/resources/values-xlarge-v11/integers.xml
+++ b/mobile/android/base/resources/values-xlarge-v11/integers.xml
@@ -2,10 +2,11 @@
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 <resources>
 
     <integer name="number_of_top_sites">9</integer>
     <integer name="number_of_top_sites_cols">3</integer>
+    <integer name="panel_icon_grid_view_columns">6</integer>
 
 </resources>
--- a/mobile/android/base/resources/values/attrs.xml
+++ b/mobile/android/base/resources/values/attrs.xml
@@ -32,18 +32,20 @@
         <attr name="menuItemShareActionButtonStyle" format="reference"/>
 
         <!-- Default style for the BookmarksListView -->
         <attr name="bookmarksListViewStyle" format="reference" />
 
         <!-- Default style for the TopSitesGridItemView -->
         <attr name="topSitesGridItemViewStyle" format="reference" />
 
-        <!-- Style for the PanelGridView -->
+        <!-- Styles for dynamic panel grid views -->
         <attr name="panelGridViewStyle" format="reference" />
+        <attr name="panelIconGridViewStyle" format="reference" />
+        <attr name="panelIconViewStyle" format="reference" />
 
         <!-- Style for the TabsGridLayout -->
         <attr name="tabGridLayoutViewStyle" format="reference" />
 
         <!-- Default style for the TopSitesGridView -->
         <attr name="topSitesGridViewStyle" format="reference" />
 
         <!-- Default style for the TopSitesThumbnailView -->
--- a/mobile/android/base/resources/values/colors.xml
+++ b/mobile/android/base/resources/values/colors.xml
@@ -120,16 +120,17 @@
   <color name="url_bar_domaintext">#000</color>
   <color name="url_bar_domaintext_private">#FFF</color>
   <color name="url_bar_blockedtext">#b14646</color>
   <color name="url_bar_shadow">#12000000</color>
 
   <color name="home_button_bar_bg">#FFF5F7F9</color>
 
   <color name="panel_image_item_background">#D1D9E1</color>
+  <color name="panel_icon_item_title_background">#32000000</color>
 
   <!-- Swipe to refresh colors for dynamic panel -->
   <color name="swipe_refresh_orange">#FFFFC26C</color>
   <color name="swipe_refresh_white">#FFFFFFFF</color>
 
   <!-- Swipe to refresh colors for remote tabs -->
   <color name="swipe_refresh_orange1">#EE6700</color>
   <color name="swipe_refresh_orange2">#FF9400</color>
--- a/mobile/android/base/resources/values/integers.xml
+++ b/mobile/android/base/resources/values/integers.xml
@@ -3,10 +3,11 @@
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 <resources>
 
     <integer name="number_of_top_sites">6</integer>
     <integer name="number_of_top_sites_cols">2</integer>
     <integer name="max_icon_grid_columns">4</integer>
+    <integer name="panel_icon_grid_view_columns">3</integer>
 
 </resources>
--- a/mobile/android/base/resources/values/styles.xml
+++ b/mobile/android/base/resources/values/styles.xml
@@ -186,16 +186,27 @@
         <item name="android:stretchMode">columnWidth</item>
         <item name="android:numColumns">auto_fit</item>
         <item name="android:columnWidth">@dimen/panel_grid_view_column_width</item>
         <item name="android:horizontalSpacing">2dp</item>
         <item name="android:verticalSpacing">2dp</item>
         <item name="android:drawSelectorOnTop">true</item>
     </style>
 
+    <style name="Widget.PanelIconGridView" parent="Widget.GridView">
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">match_parent</item>
+        <item name="android:numColumns">@integer/panel_icon_grid_view_columns</item>
+        <item name="android:horizontalSpacing">6dp</item>
+        <item name="android:verticalSpacing">6dp</item>
+        <item name="android:drawSelectorOnTop">true</item>
+        <item name="android:padding">6dp</item>
+        <item name="android:clipToPadding">false</item>
+    </style>
+
     <style name="Widget.TabsGridLayout" parent="Widget.GridView">
         <item name="android:layout_width">match_parent</item>
         <item name="android:layout_height">match_parent</item>
         <item name="android:paddingTop">0dp</item>
         <item name="android:stretchMode">spacingWidth</item>
         <item name="android:scrollbarStyle">outsideOverlay</item>
         <item name="android:gravity">center</item>
         <item name="android:numColumns">auto_fit</item>
--- a/mobile/android/base/resources/values/themes.xml
+++ b/mobile/android/base/resources/values/themes.xml
@@ -90,16 +90,17 @@
         <item name="floatingHintEditTextStyle">@style/FloatingHintEditText</item>
         <item name="tabGridLayoutViewStyle">@style/Widget.TabsGridLayout</item>
         <item name="geckoMenuListViewStyle">@style/Widget.GeckoMenuListView</item>
         <item name="homeListViewStyle">@style/Widget.HomeListView</item>
         <item name="menuItemActionBarStyle">@style/Widget.MenuItemActionBar</item>
         <item name="menuItemActionModeStyle">@style/GeckoActionBar.Button</item>
         <item name="menuItemShareActionButtonStyle">@style/Widget.MenuItemSecondaryActionBar</item>
         <item name="panelGridViewStyle">@style/Widget.PanelGridView</item>
+        <item name="panelIconGridViewStyle">@style/Widget.PanelIconGridView</item>
         <item name="topSitesGridItemViewStyle">@style/Widget.TopSitesGridItemView</item>
         <item name="topSitesGridViewStyle">@style/Widget.TopSitesGridView</item>
         <item name="topSitesThumbnailViewStyle">@style/Widget.TopSitesThumbnailView</item>
     </style>
 
     <style name="Gecko.Preferences" parent="GeckoPreferencesBase">
         <item name="floatingHintEditTextStyle">@style/FloatingHintEditText</item>
     </style>
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/widget/SquaredRelativeLayout.java
@@ -0,0 +1,33 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.widget;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.RelativeLayout;
+
+public class SquaredRelativeLayout extends RelativeLayout {
+    public SquaredRelativeLayout(Context context) {
+        super(context);
+    }
+
+    public SquaredRelativeLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public SquaredRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+        int squareMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY);
+
+        super.onMeasure(squareMeasureSpec, squareMeasureSpec);
+    }
+}
--- a/mobile/android/modules/Home.jsm
+++ b/mobile/android/modules/Home.jsm
@@ -296,17 +296,18 @@ let HomePanels = (function () {
   let View = Object.freeze({
     LIST: "list",
     GRID: "grid"
   });
 
   // Valid item types for a panel view.
   let Item = Object.freeze({
     ARTICLE: "article",
-    IMAGE: "image"
+    IMAGE: "image",
+    ICON: "icon"
   });
 
   // Valid item handlers for a panel view.
   let ItemHandler = Object.freeze({
     BROWSER: "browser",
     INTENT: "intent"
   });
 
--- a/mobile/android/modules/HomeProvider.jsm
+++ b/mobile/android/modules/HomeProvider.jsm
@@ -16,18 +16,19 @@ Cu.import("resource://gre/modules/Servic
 Cu.import("resource://gre/modules/Sqlite.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 /*
  * SCHEMA_VERSION history:
  *   1: Create HomeProvider (bug 942288)
  *   2: Add filter column to items table (bug 942295/975841)
+ *   3: Add background_color and background_url columns (bug 1157539)
  */
-const SCHEMA_VERSION = 2;
+const SCHEMA_VERSION = 3;
 
 // The maximum number of items you can attempt to save at once.
 const MAX_SAVE_COUNT = 100;
 
 XPCOMUtils.defineLazyGetter(this, "DB_PATH", function() {
   return OS.Path.join(OS.Constants.Path.profileDir, "home.sqlite");
 });
 
@@ -49,29 +50,37 @@ const SQL = {
   createItemsTable:
     "CREATE TABLE items (" +
       "_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
       "dataset_id TEXT NOT NULL, " +
       "url TEXT," +
       "title TEXT," +
       "description TEXT," +
       "image_url TEXT," +
+      "background_color TEXT," +
+      "background_url TEXT," +
       "filter TEXT," +
       "created INTEGER" +
     ")",
 
   dropItemsTable:
     "DROP TABLE items",
 
   insertItem:
-    "INSERT INTO items (dataset_id, url, title, description, image_url, filter, created) " +
-      "VALUES (:dataset_id, :url, :title, :description, :image_url, :filter, :created)",
+    "INSERT INTO items (dataset_id, url, title, description, image_url, background_color, background_url, filter, created) " +
+      "VALUES (:dataset_id, :url, :title, :description, :image_url, :background_color, :background_url, :filter, :created)",
 
   deleteFromDataset:
-    "DELETE FROM items WHERE dataset_id = :dataset_id"
+    "DELETE FROM items WHERE dataset_id = :dataset_id",
+
+  addColumnBackgroundColor:
+    "ALTER TABLE items ADD COLUMN background_color TEXT",
+
+  addColumnBackgroundUrl:
+    "ALTER TABLE items ADD COLUMN background_url TEXT",
 }
 
 /**
  * Technically this function checks to see if the user is on a local network,
  * but we express this as "wifi" to the user.
  */
 function isUsingWifi() {
   let network = Cc["@mozilla.org/network/network-link-service;1"].getService(Ci.nsINetworkLinkService);
@@ -209,25 +218,31 @@ function createDatabase(db) {
   });
 }
 
 /**
  * Migrates the database schema to a new version.
  */
 function upgradeDatabase(db, oldVersion, newVersion) {
   return Task.spawn(function upgrade_database_task() {
-    for (let v = oldVersion + 1; v <= newVersion; v++) {
-      switch(v) {
-        case 2:
-          // Recreate the items table discarding any
-          // existing data.
-          yield db.execute(SQL.dropItemsTable);
-          yield db.execute(SQL.createItemsTable);
-          break;
-      }
+    switch (oldVersion) {
+      case 1:
+        // Migration from v1 to latest:
+        // Recreate the items table discarding any
+        // existing data.
+        yield db.execute(SQL.dropItemsTable);
+        yield db.execute(SQL.createItemsTable);
+        break;
+
+      case 2:
+        // Migration from v2 to latest:
+        // Add new columns: background_color, background_url
+        yield db.execute(SQL.addColumnBackgroundColor);
+        yield db.execute(SQL.addColumnBackgroundUrl);
+        break;
     }
   });
 }
 
 /**
  * Opens a database connection and makes sure that the database schema version
  * is correct, performing migrations if necessary. Consumers should be sure
  * to close any database connections they open.
@@ -349,16 +364,18 @@ HomeStorage.prototype = {
 
             // XXX: Directly pass item as params? More validation for item?
             let params = {
               dataset_id: this.datasetId,
               url: item.url,
               title: item.title,
               description: item.description,
               image_url: item.image_url,
+              background_color: item.background_color,
+              background_url: item.background_url,
               filter: item.filter,
               created: Date.now()
             };
             yield db.executeCached(SQL.insertItem, params);
           }
         }.bind(this));
       } finally {
         yield db.close();
--- a/mobile/android/tests/browser/robocop/testHomeProvider.js
+++ b/mobile/android/tests/browser/robocop/testHomeProvider.js
@@ -9,16 +9,18 @@ Cu.import("resource://gre/modules/HomePr
 Cu.import("resource://gre/modules/osfile.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Sqlite.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 
 const TEST_DATASET_ID = "test-dataset-id";
 const TEST_URL = "http://test.com";
 const TEST_TITLE = "Test";
+const TEST_BACKGROUND_URL = "http://example.com/background";
+const TEST_BACKGROUND_COLOR = "#FF9500";
 
 const PREF_SYNC_CHECK_INTERVAL_SECS = "home.sync.checkIntervalSecs";
 const TEST_INTERVAL_SECS = 1;
 
 const DB_PATH = OS.Path.join(OS.Constants.Path.profileDir, "home.sqlite");
 
 add_test(function test_request_sync() {
   // The current implementation of requestSync is synchronous.
@@ -43,28 +45,35 @@ add_test(function test_periodic_sync() {
     do_check_eq(datasetId, TEST_DATASET_ID);
     run_next_test();
   });
 });
 
 add_task(function test_save_and_delete() {
   // Use the HomeProvider API to save some data.
   let storage = HomeProvider.getStorage(TEST_DATASET_ID);
-  yield storage.save([{ title: TEST_TITLE, url: TEST_URL }]);
+  yield storage.save([{
+    title: TEST_TITLE,
+    url: TEST_URL,
+    background_url: TEST_BACKGROUND_URL,
+    background_color: TEST_BACKGROUND_COLOR
+  }]);
 
   // Peek in the DB to make sure we have the right data.
   let db = yield Sqlite.openConnection({ path: DB_PATH });
 
   // Make sure the items table was created.
   do_check_true(yield db.tableExists("items"));
 
   // Make sure the correct values for the item ended up in there.
   let result = yield db.execute("SELECT * FROM items", null, function onRow(row){
     do_check_eq(row.getResultByName("dataset_id"), TEST_DATASET_ID);
     do_check_eq(row.getResultByName("url"), TEST_URL);
+    do_check_eq(row.getResultByName("background_url"), TEST_BACKGROUND_URL);
+    do_check_eq(row.getResultByName("background_color"), TEST_BACKGROUND_COLOR);
   });
 
   // Use the HomeProvider API to delete the data.
   yield storage.deleteAll();
 
   // Make sure the data was deleted.
   result = yield db.execute("SELECT * FROM items");
   do_check_eq(result.length, 0);