mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1016611 - Don't process distribution files for webapp profiles. r=margaret
This commit is contained in:
parent
56f089d300
commit
5b0f784f2e
@ -58,7 +58,6 @@ import org.mozilla.gecko.util.HardwareUtils;
|
||||
import org.mozilla.gecko.util.NativeEventListener;
|
||||
import org.mozilla.gecko.util.NativeJSObject;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
import org.mozilla.gecko.util.UiAsyncTask;
|
||||
import org.mozilla.gecko.webapp.EventListener;
|
||||
import org.mozilla.gecko.webapp.UninstallListener;
|
||||
import org.mozilla.gecko.widget.ButtonToast;
|
||||
@ -111,7 +110,6 @@ import android.view.MotionEvent;
|
||||
import android.view.OrientationEventListener;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.SurfaceView;
|
||||
import android.view.TextureView;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewStub;
|
||||
|
@ -5,17 +5,6 @@
|
||||
|
||||
package org.mozilla.gecko;
|
||||
|
||||
import org.mozilla.gecko.GeckoProfileDirectories.NoMozillaDirectoryException;
|
||||
import org.mozilla.gecko.GeckoProfileDirectories.NoSuchProfileException;
|
||||
import org.mozilla.gecko.Telemetry;
|
||||
import org.mozilla.gecko.TelemetryContract;
|
||||
import org.mozilla.gecko.util.INIParser;
|
||||
import org.mozilla.gecko.util.INISection;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileReader;
|
||||
@ -26,6 +15,18 @@ import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Hashtable;
|
||||
|
||||
import org.mozilla.gecko.GeckoProfileDirectories.NoMozillaDirectoryException;
|
||||
import org.mozilla.gecko.GeckoProfileDirectories.NoSuchProfileException;
|
||||
import org.mozilla.gecko.db.BrowserDB;
|
||||
import org.mozilla.gecko.distribution.Distribution;
|
||||
import org.mozilla.gecko.util.INIParser;
|
||||
import org.mozilla.gecko.util.INISection;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
public final class GeckoProfile {
|
||||
private static final String LOGTAG = "GeckoProfile";
|
||||
|
||||
@ -37,11 +38,25 @@ public final class GeckoProfile {
|
||||
private static HashMap<String, GeckoProfile> sProfileCache = new HashMap<String, GeckoProfile>();
|
||||
private static String sDefaultProfileName = null;
|
||||
|
||||
// Caches the guest profile dir.
|
||||
private static File sGuestDir = null;
|
||||
private static GeckoProfile sGuestProfile = null;
|
||||
|
||||
public static boolean sIsUsingCustomProfile = false;
|
||||
|
||||
private final String mName;
|
||||
private final File mMozillaDir;
|
||||
private final boolean mIsWebAppProfile;
|
||||
private final Context mApplicationContext;
|
||||
|
||||
private File mProfileDir; // Not final because this is lazily computed.
|
||||
|
||||
// Caches whether or not a profile is "locked".
|
||||
// Only used by the guest profile to determine if it should be reused or
|
||||
// deleted on startup
|
||||
private LockState mLocked = LockState.UNDEFINED;
|
||||
private boolean mInGuestMode = false;
|
||||
|
||||
// Constants to cache whether or not a profile is "locked".
|
||||
private enum LockState {
|
||||
LOCKED,
|
||||
@ -49,17 +64,6 @@ public final class GeckoProfile {
|
||||
UNDEFINED
|
||||
};
|
||||
|
||||
// Caches whether or not a profile is "locked". Only used by the guest profile to determine if it should
|
||||
// be reused or deleted on startup
|
||||
private LockState mLocked = LockState.UNDEFINED;
|
||||
|
||||
// Caches the guest profile dir.
|
||||
private static File sGuestDir = null;
|
||||
private static GeckoProfile sGuestProfile = null;
|
||||
|
||||
private boolean mInGuestMode = false;
|
||||
|
||||
|
||||
public static GeckoProfile get(Context context) {
|
||||
boolean isGeckoApp = false;
|
||||
try {
|
||||
@ -155,6 +159,11 @@ public final class GeckoProfile {
|
||||
}
|
||||
|
||||
public static boolean removeProfile(Context context, String profileName) {
|
||||
if (profileName == null) {
|
||||
Log.w(LOGTAG, "Unable to remove profile: null profile name.");
|
||||
return false;
|
||||
}
|
||||
|
||||
final boolean success;
|
||||
try {
|
||||
success = new GeckoProfile(context, profileName).remove();
|
||||
@ -266,7 +275,13 @@ public final class GeckoProfile {
|
||||
}
|
||||
|
||||
private GeckoProfile(Context context, String profileName) throws NoMozillaDirectoryException {
|
||||
if (TextUtils.isEmpty(profileName)) {
|
||||
throw new IllegalArgumentException("Unable to create GeckoProfile for empty profile name.");
|
||||
}
|
||||
|
||||
mApplicationContext = context.getApplicationContext();
|
||||
mName = profileName;
|
||||
mIsWebAppProfile = profileName.startsWith("webapp");
|
||||
mMozillaDir = GeckoProfileDirectories.getMozillaDirectory(context);
|
||||
}
|
||||
|
||||
@ -576,7 +591,7 @@ public final class GeckoProfile {
|
||||
parser.addSection(generalSection);
|
||||
}
|
||||
|
||||
if (!isDefaultSet && !mName.startsWith("webapp")) {
|
||||
if (!isDefaultSet && !mIsWebAppProfile) {
|
||||
// only set as default if this is the first non-webapp
|
||||
// profile we're creating
|
||||
profileSection.setProperty("Default", 1);
|
||||
@ -590,6 +605,11 @@ public final class GeckoProfile {
|
||||
parser.addSection(profileSection);
|
||||
parser.write();
|
||||
|
||||
// Trigger init for non-webapp profiles.
|
||||
if (!mIsWebAppProfile) {
|
||||
enqueueInitialization();
|
||||
}
|
||||
|
||||
// Write out profile creation time, mirroring the logic in nsToolkitProfileService.
|
||||
try {
|
||||
FileOutputStream stream = new FileOutputStream(profileDir.getAbsolutePath() + File.separator + "times.json");
|
||||
@ -606,4 +626,29 @@ public final class GeckoProfile {
|
||||
|
||||
return profileDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called once, immediately before creation of the profile
|
||||
* directory completes.
|
||||
*
|
||||
* It queues up work to be done in the background to prepare the profile,
|
||||
* such as adding default bookmarks.
|
||||
*/
|
||||
private void enqueueInitialization() {
|
||||
final Context context = mApplicationContext;
|
||||
|
||||
// Add everything when we're done loading the distribution.
|
||||
final Distribution distribution = Distribution.getInstance(context);
|
||||
distribution.addOnDistributionReadyCallback(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Log.d(LOGTAG, "Running post-distribution task: bookmarks.");
|
||||
|
||||
final ContentResolver cr = context.getContentResolver();
|
||||
|
||||
final int offset = BrowserDB.addDistributionBookmarks(cr, distribution, 0);
|
||||
BrowserDB.addDefaultBookmarks(context, cr, offset);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -10,11 +10,13 @@ import java.util.List;
|
||||
|
||||
import org.mozilla.gecko.db.BrowserContract.ExpirePriority;
|
||||
import org.mozilla.gecko.db.SuggestedSites;
|
||||
import org.mozilla.gecko.distribution.Distribution;
|
||||
import org.mozilla.gecko.favicons.decoders.LoadFaviconResult;
|
||||
import org.mozilla.gecko.mozglue.RobocopTarget;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.ContentObserver;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
@ -138,6 +140,9 @@ public class BrowserDB {
|
||||
|
||||
@RobocopTarget
|
||||
public Cursor getBookmarkForUrl(ContentResolver cr, String url);
|
||||
|
||||
public int addDefaultBookmarks(Context context, ContentResolver cr, int offset);
|
||||
public int addDistributionBookmarks(ContentResolver cr, Distribution distribution, int offset);
|
||||
}
|
||||
|
||||
static {
|
||||
@ -149,6 +154,14 @@ public class BrowserDB {
|
||||
sDb = new LocalBrowserDB(profile);
|
||||
}
|
||||
|
||||
public static int addDefaultBookmarks(Context context, ContentResolver cr, final int offset) {
|
||||
return sDb.addDefaultBookmarks(context, cr, offset);
|
||||
}
|
||||
|
||||
public static int addDistributionBookmarks(ContentResolver cr, Distribution distribution, int offset) {
|
||||
return sDb.addDistributionBookmarks(cr, distribution, offset);
|
||||
}
|
||||
|
||||
public static void setSuggestedSites(SuggestedSites suggestedSites) {
|
||||
sSuggestedSites = suggestedSites;
|
||||
}
|
||||
|
@ -5,19 +5,9 @@
|
||||
|
||||
package org.mozilla.gecko.db;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.mozilla.gecko.AppConstants;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.db.BrowserContract.Bookmarks;
|
||||
import org.mozilla.gecko.db.BrowserContract.Combined;
|
||||
@ -27,11 +17,7 @@ import org.mozilla.gecko.db.BrowserContract.History;
|
||||
import org.mozilla.gecko.db.BrowserContract.Obsolete;
|
||||
import org.mozilla.gecko.db.BrowserContract.ReadingListItems;
|
||||
import org.mozilla.gecko.db.BrowserContract.Thumbnails;
|
||||
import org.mozilla.gecko.distribution.Distribution;
|
||||
import org.mozilla.gecko.gfx.BitmapUtils;
|
||||
import org.mozilla.gecko.sync.Utils;
|
||||
import org.mozilla.gecko.util.GeckoJarReader;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
@ -40,7 +26,6 @@ import android.database.DatabaseUtils;
|
||||
import android.database.SQLException;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
@ -764,125 +749,9 @@ final class BrowserDatabaseHelper extends SQLiteOpenHelper {
|
||||
R.string.bookmarks_folder_places, 0);
|
||||
|
||||
createOrUpdateAllSpecialFolders(db);
|
||||
|
||||
int offset = createDefaultBookmarks(db, 0);
|
||||
|
||||
// We'd like to create distribution bookmarks before our own default bookmarks,
|
||||
// but we won't necessarily have loaded the distribution yet. Oh well.
|
||||
enqueueDistributionBookmarkLoad(db, offset);
|
||||
|
||||
createReadingListTable(db);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// We can't rely on the distribution file having been loaded,
|
||||
// so we delegate that work to the distribution handler.
|
||||
private void enqueueDistributionBookmarkLoad(final SQLiteDatabase db, final int start) {
|
||||
final Distribution distribution = Distribution.getInstance(mContext);
|
||||
distribution.addOnDistributionReadyCallback(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Log.d(LOGTAG, "Running post-distribution task: bookmarks.");
|
||||
if (!distribution.exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// This runnable is always executed on the background thread,
|
||||
// thanks to Distribution's guarantees.
|
||||
// Yes, the user might have added a bookmark in the interim,
|
||||
// in which case `start` will be wrong. Sync will need to fix
|
||||
// up the indices.
|
||||
createDistributionBookmarks(distribution, db, start);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch distribution bookmarks and insert them into the database.
|
||||
*
|
||||
* @param db the destination database.
|
||||
* @param start the initial index at which to insert.
|
||||
* @return the number of inserted bookmarks.
|
||||
*/
|
||||
private int createDistributionBookmarks(Distribution distribution, SQLiteDatabase db, int start) {
|
||||
JSONArray bookmarks = distribution.getBookmarks();
|
||||
if (bookmarks == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
Locale locale = Locale.getDefault();
|
||||
int pos = start;
|
||||
Integer mobileFolderId = getMobileFolderId(db);
|
||||
if (mobileFolderId == null) {
|
||||
Log.e(LOGTAG, "Error creating distribution bookmarks: mobileFolderId is null.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (int i = 0; i < bookmarks.length(); i++) {
|
||||
try {
|
||||
final JSONObject bookmark = bookmarks.getJSONObject(i);
|
||||
|
||||
String title = getLocalizedProperty(bookmark, "title", locale);
|
||||
final String url = getLocalizedProperty(bookmark, "url", locale);
|
||||
createBookmark(db, title, url, pos, mobileFolderId);
|
||||
|
||||
if (bookmark.has("pinned")) {
|
||||
try {
|
||||
// Create a fake bookmark in the hidden pinned folder to pin bookmark
|
||||
// to about:home top sites. Pass pos as the pinned position to pin
|
||||
// sites in the order that bookmarks are specified in bookmarks.json.
|
||||
if (bookmark.getBoolean("pinned")) {
|
||||
createBookmark(db, title, url, pos, Bookmarks.FIXED_PINNED_LIST_ID);
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
Log.e(LOGTAG, "Error pinning bookmark to top sites.", e);
|
||||
}
|
||||
}
|
||||
|
||||
pos++;
|
||||
|
||||
// Return early if there is no icon for this bookmark.
|
||||
if (!bookmark.has("icon")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
final String iconData = bookmark.getString("icon");
|
||||
final Bitmap icon = BitmapUtils.getBitmapFromDataURI(iconData);
|
||||
if (icon != null) {
|
||||
createFavicon(db, url, icon);
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
Log.e(LOGTAG, "Error creating distribution bookmark icon.", e);
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
Log.e(LOGTAG, "Error creating distribution bookmark.", e);
|
||||
}
|
||||
}
|
||||
return pos - start;
|
||||
}
|
||||
|
||||
private void createReadingListTable(SQLiteDatabase db) {
|
||||
debug("Creating " + TABLE_READING_LIST + " table");
|
||||
|
||||
@ -904,139 +773,6 @@ final class BrowserDatabaseHelper extends SQLiteOpenHelper {
|
||||
+ ReadingListItems.GUID + ")");
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert default bookmarks into the database.
|
||||
*
|
||||
* @param db the destination database.
|
||||
* @param start the initial index at which to insert.
|
||||
* @return the number of inserted bookmarks.
|
||||
*/
|
||||
private int createDefaultBookmarks(SQLiteDatabase db, int start) {
|
||||
Class<?> stringsClass = R.string.class;
|
||||
Field[] fields = stringsClass.getFields();
|
||||
Pattern p = Pattern.compile("^bookmarkdefaults_title_");
|
||||
|
||||
Integer mobileFolderId = getMobileFolderId(db);
|
||||
if (mobileFolderId == null) {
|
||||
Log.e(LOGTAG, "Error creating default bookmarks: mobileFolderId is null");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pos = start;
|
||||
|
||||
for (int i = 0; i < fields.length; i++) {
|
||||
final String name = fields[i].getName();
|
||||
Matcher m = p.matcher(name);
|
||||
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);
|
||||
final String url = mContext.getString(urlId);
|
||||
createBookmark(db, title, url, pos, mobileFolderId);
|
||||
|
||||
// Create icons in a separate thread to avoid blocking about:home on startup.
|
||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
SQLiteDatabase db = getWritableDatabase();
|
||||
Bitmap icon = getDefaultFaviconFromPath(name);
|
||||
if (icon == null) {
|
||||
icon = getDefaultFaviconFromDrawable(name);
|
||||
}
|
||||
if (icon != null) {
|
||||
createFavicon(db, url, 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);
|
||||
}
|
||||
}
|
||||
|
||||
return pos - start;
|
||||
}
|
||||
|
||||
private void createBookmark(SQLiteDatabase db, String title, String url, int pos, int parent) {
|
||||
ContentValues bookmarkValues = new ContentValues();
|
||||
bookmarkValues.put(Bookmarks.PARENT, parent);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
private void createFavicon(SQLiteDatabase db, String url, Bitmap icon) {
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
|
||||
ContentValues iconValues = new ContentValues();
|
||||
iconValues.put(Favicons.PAGE_URL, url);
|
||||
|
||||
byte[] data = null;
|
||||
if (icon.compress(Bitmap.CompressFormat.PNG, 100, stream)) {
|
||||
data = stream.toByteArray();
|
||||
} else {
|
||||
Log.w(LOGTAG, "Favicon compression failed.");
|
||||
}
|
||||
iconValues.put(Favicons.DATA, data);
|
||||
|
||||
insertFavicon(db, iconValues);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
int faviconId = faviconField.getInt(null);
|
||||
String path = mContext.getString(faviconId);
|
||||
|
||||
String apkPath = mContext.getPackageResourcePath();
|
||||
File apkFile = new File(apkPath);
|
||||
String bitmapPath = "jar:jar:" + apkFile.toURI() + "!/" + AppConstants.OMNIJAR_NAME + "!/" + path;
|
||||
return GeckoJarReader.getBitmap(mContext.getResources(), bitmapPath);
|
||||
} catch (java.lang.IllegalAccessException ex) {
|
||||
Log.e(LOGTAG, "[Path] Can't create favicon " + name, ex);
|
||||
} catch (java.lang.NoSuchFieldException ex) {
|
||||
// If the field does not exist, that means we intend to load via a drawable
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Bitmap getDefaultFaviconFromDrawable(String name) {
|
||||
Class<?> drawablesClass = R.drawable.class;
|
||||
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;
|
||||
}
|
||||
int faviconId = faviconField.getInt(null);
|
||||
return BitmapUtils.decodeResource(mContext, faviconId);
|
||||
} catch (java.lang.IllegalAccessException ex) {
|
||||
Log.e(LOGTAG, "[Drawable] Can't create favicon " + name, ex);
|
||||
} catch (java.lang.NoSuchFieldException ex) {
|
||||
// If the field does not exist, that means we intend to load via a file path
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void createOrUpdateAllSpecialFolders(SQLiteDatabase db) {
|
||||
createOrUpdateSpecialFolder(db, Bookmarks.MOBILE_FOLDER_GUID,
|
||||
R.string.bookmarks_folder_mobile, 0);
|
||||
|
@ -12,12 +12,10 @@ import java.util.Map;
|
||||
|
||||
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.FaviconColumns;
|
||||
import org.mozilla.gecko.db.BrowserContract.Favicons;
|
||||
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.sync.Utils;
|
||||
|
||||
@ -1100,8 +1098,8 @@ public class BrowserProvider extends SharedBrowserDatabaseProvider {
|
||||
}
|
||||
|
||||
long insertFavicon(SQLiteDatabase db, ContentValues values) {
|
||||
// This method is a dupicate of BrowserDatabaseHelper.insertFavicon.
|
||||
// If changes are needed, please update both
|
||||
// This method is a duplicate of BrowserDatabaseHelper.insertFavicon.
|
||||
// If changes are needed, please update both.
|
||||
String faviconUrl = values.getAsString(Favicons.URL);
|
||||
String pageUrl = null;
|
||||
|
||||
|
@ -6,11 +6,22 @@
|
||||
package org.mozilla.gecko.db;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.mozilla.gecko.AboutPages;
|
||||
import org.mozilla.gecko.AppConstants;
|
||||
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.ExpirePriority;
|
||||
@ -21,12 +32,17 @@ import org.mozilla.gecko.db.BrowserContract.ReadingListItems;
|
||||
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.distribution.Distribution;
|
||||
import org.mozilla.gecko.favicons.decoders.FaviconDecoder;
|
||||
import org.mozilla.gecko.favicons.decoders.LoadFaviconResult;
|
||||
import org.mozilla.gecko.gfx.BitmapUtils;
|
||||
import org.mozilla.gecko.sync.Utils;
|
||||
import org.mozilla.gecko.util.GeckoJarReader;
|
||||
|
||||
import android.content.ContentProviderOperation;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.ContentObserver;
|
||||
import android.database.Cursor;
|
||||
import android.database.CursorWrapper;
|
||||
@ -99,6 +115,246 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
|
||||
appendQueryParameter(BrowserContract.PARAM_INSERT_IF_NEEDED, "true").build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add default bookmarks to the database.
|
||||
* Takes an offset; returns a new offset.
|
||||
*/
|
||||
@Override
|
||||
public int addDefaultBookmarks(Context context, ContentResolver cr, final int offset) {
|
||||
long folderId = getFolderIdFromGuid(cr, Bookmarks.MOBILE_FOLDER_GUID);
|
||||
if (folderId == -1L) {
|
||||
Log.e(LOGTAG, "No mobile folder: cannot add default bookmarks.");
|
||||
return offset;
|
||||
}
|
||||
|
||||
// Use reflection to walk the set of bookmark defaults.
|
||||
// This is horrible.
|
||||
final Class<?> stringsClass = R.string.class;
|
||||
final Field[] fields = stringsClass.getFields();
|
||||
final Pattern p = Pattern.compile("^bookmarkdefaults_title_");
|
||||
|
||||
int pos = offset;
|
||||
final long now = System.currentTimeMillis();
|
||||
|
||||
final ArrayList<ContentValues> bookmarkValues = new ArrayList<ContentValues>();
|
||||
final ArrayList<ContentValues> faviconValues = new ArrayList<ContentValues>();
|
||||
for (int i = 0; i < fields.length; i++) {
|
||||
final String name = fields[i].getName();
|
||||
final Matcher m = p.matcher(name);
|
||||
if (!m.find()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
final int titleid = fields[i].getInt(null);
|
||||
final String title = context.getString(titleid);
|
||||
|
||||
final Field urlField = stringsClass.getField(name.replace("_title_", "_url_"));
|
||||
final int urlId = urlField.getInt(null);
|
||||
final String url = context.getString(urlId);
|
||||
|
||||
bookmarkValues.add(createBookmark(now, title, url, pos++, folderId));
|
||||
|
||||
Bitmap icon = getDefaultFaviconFromPath(context, name);
|
||||
if (icon == null) {
|
||||
icon = getDefaultFaviconFromDrawable(context, name);
|
||||
}
|
||||
if (icon == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final ContentValues iconValue = createFavicon(url, icon);
|
||||
if (iconValue != null) {
|
||||
faviconValues.add(iconValue);
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
Log.wtf(LOGTAG, "Reflection failure.", e);
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.wtf(LOGTAG, "Reflection failure.", e);
|
||||
} catch (NoSuchFieldException e) {
|
||||
Log.wtf(LOGTAG, "Reflection failure.", e);
|
||||
}
|
||||
}
|
||||
|
||||
cr.bulkInsert(mFaviconsUriWithProfile, faviconValues.toArray(new ContentValues[faviconValues.size()]));
|
||||
final int inserted = cr.bulkInsert(mBookmarksUriWithProfile, bookmarkValues.toArray(new ContentValues[bookmarkValues.size()]));
|
||||
return offset + inserted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add bookmarks from the provided distribution.
|
||||
* Takes an offset; returns a new offset.
|
||||
*/
|
||||
@Override
|
||||
public int addDistributionBookmarks(ContentResolver cr, Distribution distribution, int offset) {
|
||||
if (!distribution.exists()) {
|
||||
Log.d(LOGTAG, "No distribution from which to add bookmarks.");
|
||||
return offset;
|
||||
}
|
||||
|
||||
final JSONArray bookmarks = distribution.getBookmarks();
|
||||
if (bookmarks == null) {
|
||||
Log.d(LOGTAG, "No distribution bookmarks.");
|
||||
return offset;
|
||||
}
|
||||
|
||||
long folderId = getFolderIdFromGuid(cr, Bookmarks.MOBILE_FOLDER_GUID);
|
||||
if (folderId == -1L) {
|
||||
Log.e(LOGTAG, "No mobile folder: cannot add distribution bookmarks.");
|
||||
return offset;
|
||||
}
|
||||
|
||||
final Locale locale = Locale.getDefault();
|
||||
final long now = System.currentTimeMillis();
|
||||
int mobilePos = offset;
|
||||
int pinnedPos = 0; // Assume nobody has pinned anything yet.
|
||||
|
||||
final ArrayList<ContentValues> bookmarkValues = new ArrayList<ContentValues>();
|
||||
final ArrayList<ContentValues> faviconValues = new ArrayList<ContentValues>();
|
||||
for (int i = 0; i < bookmarks.length(); i++) {
|
||||
try {
|
||||
final JSONObject bookmark = bookmarks.getJSONObject(i);
|
||||
|
||||
final String title = getLocalizedProperty(bookmark, "title", locale);
|
||||
final String url = getLocalizedProperty(bookmark, "url", locale);
|
||||
final long parent;
|
||||
final int pos;
|
||||
if (bookmark.has("pinned")) {
|
||||
parent = Bookmarks.FIXED_PINNED_LIST_ID;
|
||||
pos = pinnedPos++;
|
||||
} else {
|
||||
parent = folderId;
|
||||
pos = mobilePos++;
|
||||
}
|
||||
|
||||
bookmarkValues.add(createBookmark(now, title, url, pos, parent));
|
||||
|
||||
// Return early if there is no icon for this bookmark.
|
||||
if (!bookmark.has("icon")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
final String iconData = bookmark.getString("icon");
|
||||
final Bitmap icon = BitmapUtils.getBitmapFromDataURI(iconData);
|
||||
if (icon == null) {
|
||||
continue;
|
||||
}
|
||||
final ContentValues iconValue = createFavicon(url, icon);
|
||||
if (iconValue != null) {
|
||||
faviconValues.add(iconValue);
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
Log.e(LOGTAG, "Error creating distribution bookmark icon.", e);
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
Log.e(LOGTAG, "Error creating distribution bookmark.", e);
|
||||
}
|
||||
}
|
||||
|
||||
cr.bulkInsert(mFaviconsUriWithProfile, faviconValues.toArray(new ContentValues[faviconValues.size()]));
|
||||
final int inserted = cr.bulkInsert(mBookmarksUriWithProfile, bookmarkValues.toArray(new ContentValues[bookmarkValues.size()]));
|
||||
return offset + inserted;
|
||||
}
|
||||
|
||||
private static ContentValues createBookmark(final long timestamp, final String title, final String url, final int pos, final long parent) {
|
||||
final ContentValues v = new ContentValues();
|
||||
|
||||
v.put(Bookmarks.DATE_CREATED, timestamp);
|
||||
v.put(Bookmarks.DATE_MODIFIED, timestamp);
|
||||
v.put(Bookmarks.GUID, Utils.generateGuid());
|
||||
|
||||
v.put(Bookmarks.PARENT, parent);
|
||||
v.put(Bookmarks.POSITION, pos);
|
||||
v.put(Bookmarks.TITLE, title);
|
||||
v.put(Bookmarks.URL, url);
|
||||
return v;
|
||||
}
|
||||
|
||||
private static ContentValues createFavicon(final String url, final Bitmap icon) {
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
|
||||
ContentValues iconValues = new ContentValues();
|
||||
iconValues.put(Favicons.PAGE_URL, url);
|
||||
|
||||
byte[] data = null;
|
||||
if (icon.compress(Bitmap.CompressFormat.PNG, 100, stream)) {
|
||||
data = stream.toByteArray();
|
||||
} else {
|
||||
Log.w(LOGTAG, "Favicon compression failed.");
|
||||
return null;
|
||||
}
|
||||
|
||||
iconValues.put(Favicons.DATA, data);
|
||||
return iconValues;
|
||||
}
|
||||
|
||||
private static String getLocalizedProperty(final JSONObject bookmark, final String property, final Locale locale) throws JSONException {
|
||||
// Try the full locale.
|
||||
final 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);
|
||||
}
|
||||
|
||||
private static Bitmap getDefaultFaviconFromPath(Context context, 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;
|
||||
}
|
||||
int faviconId = faviconField.getInt(null);
|
||||
String path = context.getString(faviconId);
|
||||
|
||||
String apkPath = context.getPackageResourcePath();
|
||||
File apkFile = new File(apkPath);
|
||||
String bitmapPath = "jar:jar:" + apkFile.toURI() + "!/" + AppConstants.OMNIJAR_NAME + "!/" + path;
|
||||
return GeckoJarReader.getBitmap(context.getResources(), bitmapPath);
|
||||
} catch (java.lang.IllegalAccessException ex) {
|
||||
Log.e(LOGTAG, "[Path] Can't create favicon " + name, ex);
|
||||
} catch (java.lang.NoSuchFieldException ex) {
|
||||
// If the field does not exist, that means we intend to load via a drawable.
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Bitmap getDefaultFaviconFromDrawable(Context context, String name) {
|
||||
Class<?> drawablesClass = R.drawable.class;
|
||||
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;
|
||||
}
|
||||
int faviconId = faviconField.getInt(null);
|
||||
return BitmapUtils.decodeResource(context, faviconId);
|
||||
} catch (java.lang.IllegalAccessException ex) {
|
||||
Log.e(LOGTAG, "[Drawable] Can't create favicon " + name, ex);
|
||||
} catch (java.lang.NoSuchFieldException ex) {
|
||||
Log.wtf(LOGTAG, "No field, and presumably no drawable, for " + name);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Invalidate cached data
|
||||
@Override
|
||||
public void invalidateCachedState() {
|
||||
@ -577,29 +833,28 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
|
||||
return null;
|
||||
}
|
||||
|
||||
private synchronized long getFolderIdFromGuid(ContentResolver cr, String guid) {
|
||||
if (mFolderIdMap.containsKey(guid))
|
||||
private synchronized long getFolderIdFromGuid(final ContentResolver cr, final String guid) {
|
||||
if (mFolderIdMap.containsKey(guid)) {
|
||||
return mFolderIdMap.get(guid);
|
||||
|
||||
long folderId = -1;
|
||||
Cursor c = null;
|
||||
|
||||
try {
|
||||
c = cr.query(mBookmarksUriWithProfile,
|
||||
new String[] { Bookmarks._ID },
|
||||
Bookmarks.GUID + " = ?",
|
||||
new String[] { guid },
|
||||
null);
|
||||
|
||||
if (c.moveToFirst())
|
||||
folderId = c.getLong(c.getColumnIndexOrThrow(Bookmarks._ID));
|
||||
} finally {
|
||||
if (c != null)
|
||||
c.close();
|
||||
}
|
||||
|
||||
mFolderIdMap.put(guid, folderId);
|
||||
return folderId;
|
||||
final Cursor c = cr.query(mBookmarksUriWithProfile,
|
||||
new String[] { Bookmarks._ID },
|
||||
Bookmarks.GUID + " = ?",
|
||||
new String[] { guid },
|
||||
null);
|
||||
try {
|
||||
final int col = c.getColumnIndexOrThrow(Bookmarks._ID);
|
||||
if (!c.moveToFirst() || c.isNull(col)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
final long id = c.getLong(col);
|
||||
mFolderIdMap.put(guid, id);
|
||||
return id;
|
||||
} finally {
|
||||
c.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user