Bug 836450 - Add default bookmark support for distributions. r=mfinkle,wesj

This commit is contained in:
Margaret Leibovic 2013-02-14 15:35:39 -08:00
parent e2aa595942
commit 1a271a3897
2 changed files with 188 additions and 68 deletions

View File

@ -12,19 +12,26 @@ package org.mozilla.gecko;
import org.mozilla.gecko.util.GeckoBackgroundThread;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
public final class Distribution {
private static final String LOGTAG = "GeckoDistribution";
@ -35,13 +42,13 @@ public final class Distribution {
/**
* Initializes distribution if it hasn't already been initalized.
*/
public static void init(final Activity activity) {
public static void init(final Context context) {
// Read/write preferences and files on the background thread.
GeckoBackgroundThread.getHandler().post(new Runnable() {
public void run() {
// Bail if we've already initialized the distribution.
SharedPreferences settings = activity.getPreferences(Activity.MODE_PRIVATE);
String keyName = activity.getPackageName() + ".distribution_state";
SharedPreferences settings = context.getSharedPreferences(GeckoApp.PREFS_NAME, Activity.MODE_PRIVATE);
String keyName = context.getPackageName() + ".distribution_state";
int state = settings.getInt(keyName, STATE_UNKNOWN);
if (state == STATE_NONE)
return;
@ -54,7 +61,7 @@ public final class Distribution {
boolean distributionSet = false;
try {
distributionSet = copyFiles(activity);
distributionSet = copyFiles(context);
} catch (IOException e) {
Log.e(LOGTAG, "Error copying distribution files", e);
}
@ -73,8 +80,8 @@ public final class Distribution {
* Copies the /distribution folder out of the APK and into the app's data directory.
* Returns true if distribution files were found and copied.
*/
private static boolean copyFiles(Activity activity) throws IOException {
File applicationPackage = new File(activity.getPackageResourcePath());
private static boolean copyFiles(Context context) throws IOException {
File applicationPackage = new File(context.getPackageResourcePath());
ZipFile zip = new ZipFile(applicationPackage);
boolean distributionSet = false;
@ -88,7 +95,7 @@ public final class Distribution {
distributionSet = true;
File dataDir = new File(activity.getApplicationInfo().dataDir);
File dataDir = new File(context.getApplicationInfo().dataDir);
File outFile = new File(dataDir, name);
File dir = outFile.getParentFile();
@ -111,4 +118,62 @@ public final class Distribution {
return distributionSet;
}
/**
* Returns parsed contents of bookmarks.json.
* This method should only be called from a background thread.
*/
public static JSONArray getBookmarks(Context context) {
SharedPreferences settings = context.getSharedPreferences(GeckoApp.PREFS_NAME, Activity.MODE_PRIVATE);
String keyName = context.getPackageName() + ".distribution_state";
int state = settings.getInt(keyName, STATE_UNKNOWN);
if (state == STATE_NONE) {
return null;
}
ZipFile zip = null;
InputStream inputStream = null;
try {
if (state == STATE_UNKNOWN) {
// If the distribution hasn't been set yet, get bookmarks.json out of the APK
File applicationPackage = new File(context.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(context.getApplicationInfo().dataDir);
File file = new File(dataDir, "distribution/bookmarks.json");
inputStream = new FileInputStream(file);
}
// Convert input stream to JSONArray
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());
} catch (IOException e) {
Log.e(LOGTAG, "Error getting 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 streams", e);
}
}
return null;
}
}

View File

@ -8,20 +8,19 @@ package @ANDROID_PACKAGE_NAME@.db;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.lang.Class;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
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 org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.Distribution;
import org.mozilla.gecko.GeckoProfile;
import org.mozilla.gecko.R;
import org.mozilla.gecko.db.BrowserContract.Bookmarks;
@ -38,6 +37,7 @@ 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;
@ -49,8 +49,8 @@ import android.content.ContentUris;
import android.content.ContentValues;
import android.content.ContentProviderResult;
import android.content.ContentProviderOperation;
import android.content.OperationApplicationException;
import android.content.Context;
import android.content.OperationApplicationException;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.DatabaseUtils;
@ -67,6 +67,10 @@ 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;
@ -973,28 +977,77 @@ public class BrowserProvider extends ContentProvider {
createOrUpdateAllSpecialFolders(db);
createDefaultBookmarks(db, "^bookmarkdefaults_title_");
// Create distribution bookmarks before our own default bookmarks
int pos = createDistributionBookmarks(db);
createDefaultBookmarks(db, pos);
}
private void createDefaultBookmarks(SQLiteDatabase db, String pattern) {
Class<?> stringsClass = R.string.class;
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);
}
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);
// Returns the number of bookmarks inserted in the db
private int createDistributionBookmarks(SQLiteDatabase db) {
JSONArray bookmarks = Distribution.getBookmarks(mContext);
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("^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);
@ -1003,13 +1056,11 @@ public class BrowserProvider extends ContentProvider {
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);
@ -1019,27 +1070,42 @@ public class BrowserProvider extends ContentProvider {
}
}
private void setDefaultFavicon(SQLiteDatabase db, String name, String url) {
ByteArrayOutputStream stream = getDefaultFaviconFromPath(db, name, url);
if (stream == null) {
stream = getDefaultFaviconFromDrawable(db, name, url);
}
if (stream != null) {
ContentValues values = new ContentValues();
values.put(Favicons.DATA, stream.toByteArray());
values.put(Favicons.PAGE_URL, url);
insertFavicon(db, values);
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;
}
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);
@ -1048,44 +1114,33 @@ public class BrowserProvider extends ContentProvider {
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) {