Bug 836450 - Add default bookmark support for distributions. r=mfinkle,wesj
☠☠ backed out by b9c5b66bb94f ☠ ☠
authorMargaret Leibovic <margaret.leibovic@gmail.com>
Wed, 13 Feb 2013 17:19:00 -0800
changeset 121822 4f22708024f9a9409d6e9233b055679f402533e4
parent 121821 8533d88c37f1a49247ca24f3e0c05599ad550ba6
child 121823 6be86adf9bf2011b5f0bffe0f46cff490988b08b
push id22943
push usermleibovic@mozilla.com
push dateThu, 14 Feb 2013 01:19:52 +0000
treeherdermozilla-inbound@4f22708024f9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmfinkle, wesj
bugs836450
milestone21.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 836450 - Add default bookmark support for distributions. r=mfinkle,wesj
mobile/android/base/Distribution.java
mobile/android/base/db/BrowserProvider.java.in
--- a/mobile/android/base/Distribution.java
+++ b/mobile/android/base/Distribution.java
@@ -23,19 +23,19 @@ import java.io.OutputStream;
 
 import java.util.Enumeration;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
 
 public final class Distribution {
     private static final String LOGTAG = "GeckoDistribution";
 
-    private static final int STATE_UNKNOWN = 0;
-    private static final int STATE_NONE = 1;
-    private static final int STATE_SET = 2;
+    public static final int STATE_UNKNOWN = 0;
+    public static final int STATE_NONE = 1;
+    public static final int STATE_SET = 2;
 
     /**
      * Initializes distribution if it hasn't already been initalized.
      */
     public static void init(final Activity activity) {
         // Read/write preferences and files on the background thread.
         GeckoBackgroundThread.getHandler().post(new Runnable() {
             public void run() {
--- a/mobile/android/base/db/BrowserProvider.java.in
+++ b/mobile/android/base/db/BrowserProvider.java.in
@@ -1,31 +1,41 @@
 /* -*- 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/. */
 
 #filter substitution
 package @ANDROID_PACKAGE_NAME@.db;
 
+import java.io.BufferedReader;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
+import java.io.FileInputStream;
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
 import java.lang.Class;
 import java.lang.reflect.Field;
+import java.lang.StringBuilder;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
-import java.util.Iterator;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Random;
 import java.util.regex.Pattern;
 import java.util.regex.Matcher;
-
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+import org.mozilla.gecko.Distribution;
+import org.mozilla.gecko.GeckoApp;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.db.BrowserContract.Bookmarks;
 import org.mozilla.gecko.db.BrowserContract.Combined;
 import org.mozilla.gecko.db.BrowserContract.CommonColumns;
 import org.mozilla.gecko.db.BrowserContract.Control;
 import org.mozilla.gecko.db.BrowserContract.Favicons;
@@ -33,45 +43,52 @@ import org.mozilla.gecko.db.BrowserContr
 import org.mozilla.gecko.db.BrowserContract.History;
 import org.mozilla.gecko.db.BrowserContract.Schema;
 import org.mozilla.gecko.db.BrowserContract.SyncColumns;
 import org.mozilla.gecko.db.BrowserContract.Thumbnails;
 import org.mozilla.gecko.db.BrowserContract.URLColumns;
 import org.mozilla.gecko.db.BrowserContract;
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.db.DBUtils;
+import org.mozilla.gecko.gfx.BitmapUtils;
 import org.mozilla.gecko.ProfileMigrator;
 import org.mozilla.gecko.sync.Utils;
 import org.mozilla.gecko.util.GeckoBackgroundThread;
 import org.mozilla.gecko.util.GeckoJarReader;
 
+import android.app.Activity;
 import android.app.SearchManager;
 import android.content.ContentProvider;
 import android.content.ContentUris;
 import android.content.ContentValues;
 import android.content.ContentProviderResult;
 import android.content.ContentProviderOperation;
+import android.content.Context;
 import android.content.OperationApplicationException;
-import android.content.Context;
+import android.content.SharedPreferences;
 import android.content.UriMatcher;
 import android.database.Cursor;
 import android.database.DatabaseUtils;
 import android.database.MatrixCursor;
 import android.database.SQLException;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteOpenHelper;
 import android.database.sqlite.SQLiteQueryBuilder;
 import android.graphics.BitmapFactory;
 import android.graphics.Bitmap;
 import android.graphics.drawable.BitmapDrawable;
 import android.net.Uri;
 import android.os.Build;
 import android.text.TextUtils;
 import android.util.Log;
 
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
 public class BrowserProvider extends ContentProvider {
     private static final String LOGTAG = "GeckoBrowserProvider";
     private Context mContext;
 
     static final String DATABASE_NAME = "browser.db";
 
     static final int DATABASE_VERSION = 14;
 
@@ -968,129 +985,236 @@ public class BrowserProvider extends Con
             createHistoryWithFaviconsView(db);
             createCombinedViewOn13(db);
 
             createOrUpdateSpecialFolder(db, Bookmarks.PLACES_FOLDER_GUID,
                 R.string.bookmarks_folder_places, 0);
 
             createOrUpdateAllSpecialFolders(db);
 
-            createDefaultBookmarks(db, "^bookmarkdefaults_title_");
+            // Create distribution bookmarks before our own default bookmarks
+            int pos = createDistributionBookmarks(db);
+            createDefaultBookmarks(db, pos);
+        }
+
+        private JSONArray inputStreamToJSONArray(InputStream inputStream) throws IOException, JSONException {
+            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
+            StringBuilder stringBuilder = new StringBuilder();
+            String s;
+            while ((s = reader.readLine()) != null) {
+                stringBuilder.append(s);
+            }
+            return new JSONArray(stringBuilder.toString());
         }
 
-        private void createDefaultBookmarks(SQLiteDatabase db, String pattern) {
+        private JSONArray getDistributionBookmarks() {
+            SharedPreferences settings = mContext.getSharedPreferences(GeckoApp.PREFS_NAME, Activity.MODE_PRIVATE);
+            String keyName = mContext.getPackageName() + ".distribution_state";
+            int state = settings.getInt(keyName, Distribution.STATE_UNKNOWN);
+            if (state == Distribution.STATE_NONE) {
+                return null;
+            }
+
+            ZipFile zip = null;
+            InputStream inputStream = null;
+            try {
+                if (state == Distribution.STATE_UNKNOWN) {
+                    // If the distribution hasn't been set, get bookmarks.json out of the APK
+                    File applicationPackage = new File(mContext.getPackageResourcePath());
+                    zip = new ZipFile(applicationPackage);
+                    ZipEntry zipEntry = zip.getEntry("distribution/bookmarks.json");
+                    if (zipEntry == null) {
+                        return null;
+                    }
+                    inputStream = zip.getInputStream(zipEntry);
+                } else {
+                    // Otherwise, get bookmarks.json out of the data directory
+                    File dataDir = new File(mContext.getApplicationInfo().dataDir);
+                    File file = new File(dataDir, "distribution/bookmarks.json");
+                    inputStream = new FileInputStream(file);
+                }
+                return inputStreamToJSONArray(inputStream);
+            } catch (IOException e) {
+                Log.e(LOGTAG, "Error getting distribution bookmarks", e);
+            } catch (JSONException e) {
+                Log.e(LOGTAG, "Error parsing bookmarks.json", e);
+            } finally {
+                try {
+                    if (zip != null) {
+                        zip.close();
+                    }
+                    if (inputStream != null) {
+                        inputStream.close();
+                    }
+                } catch (IOException e) {
+                    Log.e(LOGTAG, "Error closing distribution streams", e);
+                } 
+            }
+            return null;
+        }
+
+        private String getLocalizedProperty(JSONObject bookmark, String property, Locale locale) throws JSONException {
+            // Try the full locale
+            String fullLocale = property + "." + locale.toString();
+            if (bookmark.has(fullLocale)) {
+                return bookmark.getString(fullLocale);
+            }
+            // Try without a variant
+            if (!TextUtils.isEmpty(locale.getVariant())) {
+                String noVariant = fullLocale.substring(0, fullLocale.lastIndexOf("_"));
+                if (bookmark.has(noVariant)) {
+                    return bookmark.getString(noVariant);
+                }
+            }
+            // Try just the language
+            String lang = property + "." + locale.getLanguage();
+            if (bookmark.has(lang)) {
+                return bookmark.getString(lang);
+            }
+            // Default to the non-localized property name
+            return bookmark.getString(property);
+        }
+
+        // Returns the number of bookmarks inserted in the db
+        private int createDistributionBookmarks(SQLiteDatabase db) {
+            JSONArray bookmarks = getDistributionBookmarks();
+            if (bookmarks == null) {
+                return 0;
+            }
+
+            Locale locale = Locale.getDefault();
+            int pos = 0;
+            for (int i = 0; i < bookmarks.length(); i++) {
+                try {
+                    JSONObject bookmark = bookmarks.getJSONObject(i);
+
+                    String title = getLocalizedProperty(bookmark, "title", locale);
+                    String url = getLocalizedProperty(bookmark, "url", locale);
+
+                    // Look for an optional icon data URI
+                    Bitmap icon = null;
+                    if (bookmark.has("icon")) {
+                        String iconData = bookmark.getString("icon");
+                        icon = BitmapUtils.getBitmapFromDataURI(iconData);
+                    }
+
+                    createBookmark(db, title, url, pos, icon);
+                    pos++;
+                } catch (JSONException e) {
+                    Log.e(LOGTAG, "Error creating distribution bookmark", e);
+                }
+            }
+            return pos;
+        }
+
+        // Inserts default bookmarks, starting at a specified position
+        private void createDefaultBookmarks(SQLiteDatabase db, int pos) {
             Class<?> stringsClass = R.string.class;
-
             Field[] fields = stringsClass.getFields();
-            Pattern p = Pattern.compile(pattern);
-
-            ContentValues bookmarksValues = new ContentValues();
-            bookmarksValues.put(Bookmarks.PARENT, guidToID(db, Bookmarks.MOBILE_FOLDER_GUID));
-            long now = System.currentTimeMillis();
-            bookmarksValues.put(Bookmarks.DATE_CREATED, now);
-            bookmarksValues.put(Bookmarks.DATE_MODIFIED, now);
-
-            int pos = 0;
+            Pattern p = Pattern.compile("^bookmarkdefaults_title_");
+
             for (int i = 0; i < fields.length; i++) {
                 String name = fields[i].getName();
                 Matcher m = p.matcher(name);
-                if (!m.find())
+                if (!m.find()) {
                     continue;
-
+                }
                 try {
                     int titleid = fields[i].getInt(null);
                     String title = mContext.getString(titleid);
 
                     Field urlField = stringsClass.getField(name.replace("_title_", "_url_"));
                     int urlId = urlField.getInt(null);
                     String url = mContext.getString(urlId);
 
-                    bookmarksValues.put(Bookmarks.TITLE, title);
-                    bookmarksValues.put(Bookmarks.URL, url);
-                    bookmarksValues.put(Bookmarks.GUID, Utils.generateGuid());
-                    bookmarksValues.put(Bookmarks.POSITION, pos);
-                    db.insertOrThrow(TABLE_BOOKMARKS, Bookmarks.TITLE, bookmarksValues);
-
-                    setDefaultFavicon(db, name, url);
+                    Bitmap icon = getDefaultFaviconFromPath(name);
+                    if (icon == null) {
+                        icon = getDefaultFaviconFromDrawable(name);
+                    }
+                    createBookmark(db, title, url, pos, icon);
                     pos++;
                 } catch (java.lang.IllegalAccessException ex) {
                     Log.e(LOGTAG, "Can't create bookmark " + name, ex);
                 } catch (java.lang.NoSuchFieldException ex) {
                     Log.e(LOGTAG, "Can't create bookmark " + name, ex);
                 }
             }
         }
 
-        private void setDefaultFavicon(SQLiteDatabase db, String name, String url) {
-            ByteArrayOutputStream stream = getDefaultFaviconFromPath(db, name, url);
-            if (stream == null) {
-              stream = getDefaultFaviconFromDrawable(db, name, url);
+        private void createBookmark(SQLiteDatabase db, String title, String url, int pos, Bitmap icon) {
+            ContentValues bookmarkValues = new ContentValues();
+            bookmarkValues.put(Bookmarks.PARENT, guidToID(db, Bookmarks.MOBILE_FOLDER_GUID));
+
+            long now = System.currentTimeMillis();
+            bookmarkValues.put(Bookmarks.DATE_CREATED, now);
+            bookmarkValues.put(Bookmarks.DATE_MODIFIED, now);
+
+            bookmarkValues.put(Bookmarks.TITLE, title);
+            bookmarkValues.put(Bookmarks.URL, url);
+            bookmarkValues.put(Bookmarks.GUID, Utils.generateGuid());
+            bookmarkValues.put(Bookmarks.POSITION, pos);
+            db.insertOrThrow(TABLE_BOOKMARKS, Bookmarks.TITLE, bookmarkValues);
+
+            // Return early if there's no icon to set
+            if (icon == null) {
+                return;
             }
-            if (stream != null) {
-                ContentValues values = new ContentValues();
-                values.put(Favicons.DATA, stream.toByteArray());
-                values.put(Favicons.PAGE_URL, url);
-                insertFavicon(db, values);
-            }
+
+            ByteArrayOutputStream stream = new ByteArrayOutputStream();
+            icon.compress(Bitmap.CompressFormat.PNG, 100, stream);
+
+            ContentValues iconValues = new ContentValues();
+            iconValues.put(Favicons.DATA, stream.toByteArray());
+            iconValues.put(Favicons.PAGE_URL, url);
+            insertFavicon(db, iconValues);
         }
 
-        private ByteArrayOutputStream getDefaultFaviconFromPath(SQLiteDatabase db, String name, String url) {
-            ByteArrayOutputStream stream = null;
+        private Bitmap getDefaultFaviconFromPath(String name) {
             Class<?> stringClass = R.string.class;
             try {
                 // Look for a drawable with the id R.drawable.bookmarkdefaults_favicon_*
                 Field faviconField = stringClass.getField(name.replace("_title_", "_favicon_"));
-                if (faviconField == null)
-                  return null;
+                if (faviconField == null) {
+                    return null;
+                }
                 int faviconId = faviconField.getInt(null);
                 String path = mContext.getString(faviconId);
 
                 String apkPath = mContext.getPackageResourcePath();
                 File apkFile = new File(apkPath);
                 BitmapDrawable bitmapDrawable = GeckoJarReader.getBitmapDrawable(mContext.getResources(),
                                                                                  "jar:jar:" + apkFile.toURI() + "!/omni.ja!/" + path);
                 if (bitmapDrawable == null) {
-                  return null;
+                    return null;
                 }
-                Bitmap bitmap = bitmapDrawable.getBitmap();
-                if (bitmap == null) {
-                  return null;
-                }
-                stream = new ByteArrayOutputStream();
-                bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
+                return bitmapDrawable.getBitmap();
             } catch (java.lang.IllegalAccessException ex) {
                 Log.e(LOGTAG, "[Path] Can't create favicon " + name, ex);
             } catch (java.lang.NoSuchFieldException ex) {
-                // if there is no such field, create the bookmark without a favicon
-                Log.d(LOGTAG, "[Path] Can't create favicon " + name);
+                Log.e(LOGTAG, "[Path] Can't create favicon " + name, ex);
             }
-            return stream;
+            return null;
         }
 
-        private ByteArrayOutputStream getDefaultFaviconFromDrawable(SQLiteDatabase db, String name, String url) {
+        private Bitmap getDefaultFaviconFromDrawable(String name) {
             Class<?> drawablesClass = R.drawable.class;
-            ByteArrayOutputStream stream = null;
             try {
                 // Look for a drawable with the id R.drawable.bookmarkdefaults_favicon_*
                 Field faviconField = drawablesClass.getField(name.replace("_title_", "_favicon_"));
-                if (faviconField == null)
-                  return null;
-
+                if (faviconField == null) {
+                    return null;
+                }
                 int faviconId = faviconField.getInt(null);
-                Bitmap bitmap = BitmapFactory.decodeResource(mContext.getResources(), faviconId);
-                stream = new ByteArrayOutputStream();
-                bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
+                return BitmapFactory.decodeResource(mContext.getResources(), faviconId);
             } catch (java.lang.IllegalAccessException ex) {
                 Log.e(LOGTAG, "[Drawable] Can't create favicon " + name, ex);
             } catch (java.lang.NoSuchFieldException ex) {
-                // if there is no such field, create the bookmark without a favicon
-                Log.d(LOGTAG, "[Drawable] Can't create favicon " + name);
+                Log.e(LOGTAG, "[Drawable] Can't create favicon " + name, ex);
             }
-
-            return stream;
+            return null;
         }
 
         private void createOrUpdateAllSpecialFolders(SQLiteDatabase db) {
             createOrUpdateSpecialFolder(db, Bookmarks.MOBILE_FOLDER_GUID,
                 R.string.bookmarks_folder_mobile, 0);
             createOrUpdateSpecialFolder(db, Bookmarks.TOOLBAR_FOLDER_GUID,
                 R.string.bookmarks_folder_toolbar, 1);
             createOrUpdateSpecialFolder(db, Bookmarks.MENU_FOLDER_GUID,