Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(3)

Unified Diff: chrome/android/java_staging/src/org/chromium/chrome/browser/bookmarkswidget/BookmarkThumbnailWidgetService.java

Issue 1141283003: Upstream oodles of Chrome for Android code into Chromium. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: final patch? Created 5 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/android/java_staging/src/org/chromium/chrome/browser/bookmarkswidget/BookmarkThumbnailWidgetService.java
diff --git a/chrome/android/java_staging/src/org/chromium/chrome/browser/bookmarkswidget/BookmarkThumbnailWidgetService.java b/chrome/android/java_staging/src/org/chromium/chrome/browser/bookmarkswidget/BookmarkThumbnailWidgetService.java
new file mode 100644
index 0000000000000000000000000000000000000000..13c2ce8480863f6ad85612e5f173b3677636adee
--- /dev/null
+++ b/chrome/android/java_staging/src/org/chromium/chrome/browser/bookmarkswidget/BookmarkThumbnailWidgetService.java
@@ -0,0 +1,388 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.bookmarkswidget;
+
+import android.appwidget.AppWidgetManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.graphics.Bitmap.Config;
+import android.graphics.BitmapFactory;
+import android.graphics.BitmapFactory.Options;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Binder;
+import android.provider.Browser.BookmarkColumns;
+import android.text.TextUtils;
+import android.util.Log;
+import android.widget.RemoteViews;
+import android.widget.RemoteViewsService;
+
+import com.google.android.apps.chrome.R;
+import com.google.android.apps.chrome.appwidget.bookmarks.BookmarkThumbnailWidgetProvider;
+
+import org.chromium.base.ThreadUtils;
+import org.chromium.base.annotations.SuppressFBWarnings;
+import org.chromium.base.library_loader.ProcessInitException;
+import org.chromium.chrome.browser.ChromeBrowserProvider.BookmarkNode;
+import org.chromium.chrome.browser.ChromeBrowserProviderClient;
+import org.chromium.chrome.browser.ChromiumApplication;
+import org.chromium.chrome.browser.util.IntentUtils;
+import org.chromium.sync.AndroidSyncSettings;
+
+/**
+ * Service to support bookmarks on the Android home screen
+ */
+public class BookmarkThumbnailWidgetService extends RemoteViewsService {
+
+ static final String TAG = "BookmarkThumbnailWidgetService";
+ static final String ACTION_CHANGE_FOLDER_SUFFIX = ".CHANGE_FOLDER";
+ static final String STATE_CURRENT_FOLDER = "current_folder";
+
+ @Override
+ public RemoteViewsFactory onGetViewFactory(Intent intent) {
+ int widgetId = IntentUtils.safeGetIntExtra(intent, AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
+ if (widgetId < 0) {
+ Log.w(TAG, "Missing EXTRA_APPWIDGET_ID!");
+ return null;
+ }
+ return new BookmarkFactory(this, widgetId);
+ }
+
+ static String getChangeFolderAction(Context context) {
+ return context.getPackageName() + ACTION_CHANGE_FOLDER_SUFFIX;
+ }
+
+ private static SharedPreferences getWidgetState(Context context, int widgetId) {
+ return context.getSharedPreferences(
+ String.format("widgetState-%d", widgetId),
+ Context.MODE_PRIVATE);
+ }
+
+ static void deleteWidgetState(Context context, int widgetId) {
+ // Android Browser's widget used private API methods to access the shared prefs
+ // files and deleted them. This is the best we can do with the public API.
+ SharedPreferences preferences = getWidgetState(context, widgetId);
+ if (preferences != null) preferences.edit().clear().commit();
+ }
+
+ static void changeFolder(Context context, Intent intent) {
+ int widgetId = IntentUtils.safeGetIntExtra(intent, AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
+ long folderId = IntentUtils.safeGetLongExtra(intent, BookmarkColumns._ID,
+ ChromeBrowserProviderClient.INVALID_BOOKMARK_ID);
+ if (widgetId >= 0 && folderId >= 0) {
+ SharedPreferences prefs = getWidgetState(context, widgetId);
+ prefs.edit().putLong(STATE_CURRENT_FOLDER, folderId).commit();
+ AppWidgetManager.getInstance(context)
+ .notifyAppWidgetViewDataChanged(widgetId, R.id.bookmarks_list);
+ }
+ }
+
+ static class BookmarkFactory implements RemoteViewsService.RemoteViewsFactory,
+ BookmarkWidgetUpdateListener.UpdateListener {
+
+ private final ChromiumApplication mContext;
+ private final int mWidgetId;
+ private final SharedPreferences mPreferences;
+ private BookmarkWidgetUpdateListener mUpdateListener;
+ private BookmarkNode mCurrentFolder;
+ private final Object mLock = new Object();
+
+ public BookmarkFactory(Context context, int widgetId) {
+ mContext = (ChromiumApplication) context.getApplicationContext();
+ mWidgetId = widgetId;
+ mPreferences = getWidgetState(mContext, mWidgetId);
+ }
+
+ private static long getFolderId(BookmarkNode folder) {
+ return folder != null ? folder.id() : ChromeBrowserProviderClient.INVALID_BOOKMARK_ID;
+ }
+
+ @SuppressFBWarnings("DM_EXIT")
+ @Override
+ public void onCreate() {
+ // Required to be applied here redundantly to prevent crashes in the cases where the
+ // package data is deleted or the Chrome application forced to stop.
+ try {
+ mContext.startBrowserProcessesAndLoadLibrariesSync(mContext, true);
+ } catch (ProcessInitException e) {
+ Log.e(TAG, "Failed to start browser process.", e);
+ // Since the library failed to initialize nothing in the application
+ // can work, so kill the whole application not just the activity
+ System.exit(-1);
+ }
+ mUpdateListener = new BookmarkWidgetUpdateListener(mContext, this);
+ }
+
+ @Override
+ public void onDestroy() {
+ if (mUpdateListener != null) mUpdateListener.destroy();
+ deleteWidgetState(mContext, mWidgetId);
+ }
+
+ @Override
+ public void onBookmarkModelUpdated() {
+ refreshWidget();
+ }
+
+ @Override
+ public void onSyncEnabledStatusUpdated(boolean enabled) {
+ synchronized (mLock) {
+ // Need to operate in a separate thread as it involves queries to our provider.
+ new SyncEnabledStatusUpdatedTask(enabled, getFolderId(mCurrentFolder)).execute();
+ }
+ }
+
+ @Override
+ public void onThumbnailUpdated(String url) {
+ synchronized (mLock) {
+ if (mCurrentFolder == null) return;
+
+ for (BookmarkNode child : mCurrentFolder.children()) {
+ if (child.isUrl() && url.equals(child.url())) {
+ refreshWidget();
+ break;
+ }
+ }
+ }
+ }
+
+ void refreshWidget() {
+ mContext.sendBroadcast(new Intent(
+ BookmarkThumbnailWidgetProviderBase.getBookmarkAppWidgetUpdateAction(mContext),
+ null, mContext, BookmarkThumbnailWidgetProvider.class)
+ .putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mWidgetId));
+ }
+
+ void requestFolderChange(long folderId) {
+ mContext.sendBroadcast(new Intent(getChangeFolderAction(mContext))
+ .setClass(mContext, BookmarkWidgetProxy.class)
+ .putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mWidgetId)
+ .putExtra(BookmarkColumns._ID, folderId));
+ }
+
+ // Performs the required checks to trigger an update of the widget after changing the sync
+ // enable settings. The required provider methods cannot be accessed in the UI thread.
+ private class SyncEnabledStatusUpdatedTask extends AsyncTask<Void, Void, Void> {
+ private final boolean mEnabled;
+ private final long mCurrentFolderId;
+
+ public SyncEnabledStatusUpdatedTask(boolean enabled, long currentFolderId) {
+ mEnabled = enabled;
+ mCurrentFolderId = currentFolderId;
+ }
+
+ @Override
+ protected Void doInBackground(Void... params) {
+ // If we're in the Mobile Bookmarks folder the icon to go up the hierarchy
+ // will either appear or disappear. Need to refresh.
+ long mobileBookmarksFolderId =
+ ChromeBrowserProviderClient.getMobileBookmarksFolderId(mContext);
+ if (mCurrentFolderId == mobileBookmarksFolderId) {
+ refreshWidget();
+ return null;
+ }
+
+ // If disabling sync, we need to move to the Mobile Bookmarks folder if we're
+ // not inside that branch of the bookmark hierarchy (will become not accessible).
+ if (!mEnabled && !ChromeBrowserProviderClient.isBookmarkInMobileBookmarksBranch(
+ mContext, mCurrentFolderId)) {
+ requestFolderChange(mobileBookmarksFolderId);
+ }
+
+ return null;
+ }
+ }
+
+ // ---------------------------------------------------------------- //
+ // ------- Methods below this line run in different thread -------- //
+ // ---------------------------------------------------------------- //
+
+ private void syncState() {
+ long currentFolderId = mPreferences.getLong(STATE_CURRENT_FOLDER,
+ ChromeBrowserProviderClient.INVALID_BOOKMARK_ID);
+
+ // Keep outside the synchronized block to avoid deadlocks in case loading the folder
+ // triggers an update that locks when trying to read mCurrentFolder.
+ BookmarkNode newFolder = loadBookmarkFolder(currentFolderId);
+
+ synchronized (mLock) {
+ mCurrentFolder =
+ getFolderId(newFolder) != ChromeBrowserProviderClient.INVALID_BOOKMARK_ID
+ ? newFolder : null;
+ }
+
+ mPreferences.edit()
+ .putLong(STATE_CURRENT_FOLDER, getFolderId(mCurrentFolder))
+ .apply();
+ }
+
+ private BookmarkNode loadBookmarkFolder(long folderId) {
+ if (ThreadUtils.runningOnUiThread()) {
+ Log.e(TAG, "Trying to load bookmark folder from the UI thread.");
+ return null;
+ }
+
+ // If the current folder id doesn't exist (it was deleted) try the current parent.
+ // If this fails too then fallback to Mobile Bookmarks.
+ if (!ChromeBrowserProviderClient.bookmarkNodeExists(mContext, folderId)) {
+ folderId = mCurrentFolder != null ? getFolderId(mCurrentFolder.parent())
+ : ChromeBrowserProviderClient.INVALID_BOOKMARK_ID;
+ if (!ChromeBrowserProviderClient.bookmarkNodeExists(mContext, folderId)) {
+ folderId = ChromeBrowserProviderClient.INVALID_BOOKMARK_ID;
+ }
+ }
+
+ // Need to verify this always because the package data might be cleared while the
+ // widget is in the Mobile Bookmarks folder with sync enabled. In that case the
+ // hierarchy up folder would still work (we can't update the widget) but the parent
+ // folders should not be accessible because sync has been reset when clearing data.
+ if (folderId != ChromeBrowserProviderClient.INVALID_BOOKMARK_ID
+ && !AndroidSyncSettings.isSyncEnabled(mContext)
+ && !ChromeBrowserProviderClient.isBookmarkInMobileBookmarksBranch(
+ mContext, folderId)) {
+ folderId = ChromeBrowserProviderClient.INVALID_BOOKMARK_ID;
+ }
+
+ // Use the Mobile Bookmarks folder by default.
+ if (folderId < 0 || folderId == ChromeBrowserProviderClient.INVALID_BOOKMARK_ID) {
+ folderId = ChromeBrowserProviderClient.getMobileBookmarksFolderId(mContext);
+ if (folderId == ChromeBrowserProviderClient.INVALID_BOOKMARK_ID) return null;
+ }
+
+ return ChromeBrowserProviderClient.getBookmarkNode(mContext, folderId,
+ ChromeBrowserProviderClient.GET_PARENT
+ | ChromeBrowserProviderClient.GET_CHILDREN
+ | ChromeBrowserProviderClient.GET_FAVICONS
+ | ChromeBrowserProviderClient.GET_THUMBNAILS);
+ }
+
+ private BookmarkNode getBookmarkForPosition(int position) {
+ if (mCurrentFolder == null) return null;
+
+ // The position 0 is saved for an entry of the current folder used to go up.
+ // This is not the case when the current node has no parent (it's the root node).
+ return (mCurrentFolder.parent() == null)
+ ? mCurrentFolder.children().get(position)
+ : (position == 0
+ ? mCurrentFolder : mCurrentFolder.children().get(position - 1));
+ }
+
+ @Override
+ public void onDataSetChanged() {
+ long token = Binder.clearCallingIdentity();
+ syncState();
+ Binder.restoreCallingIdentity(token);
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ return 2;
+ }
+
+ @Override
+ public boolean hasStableIds() {
+ return false;
+ }
+
+ @Override
+ public int getCount() {
+ if (mCurrentFolder == null) return 0;
+ return mCurrentFolder.children().size() + (mCurrentFolder.parent() != null ? 1 : 0);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return getFolderId(getBookmarkForPosition(position));
+ }
+
+ @Override
+ public RemoteViews getLoadingView() {
+ return new RemoteViews(mContext.getPackageName(),
+ R.layout.bookmark_thumbnail_widget_item);
+ }
+
+ @Override
+ public RemoteViews getViewAt(int position) {
+ if (mCurrentFolder == null) {
+ Log.w(TAG, "No current folder data available.");
+ return null;
+ }
+
+ BookmarkNode bookmark = getBookmarkForPosition(position);
+ if (bookmark == null) {
+ Log.w(TAG, "Couldn't get bookmark for position " + position);
+ return null;
+ }
+
+ if (bookmark == mCurrentFolder && bookmark.parent() == null) {
+ Log.w(TAG, "Invalid bookmark data: loop detected.");
+ return null;
+ }
+
+ String title = bookmark.name();
+ String url = bookmark.url();
+ long id = (bookmark == mCurrentFolder) ? bookmark.parent().id() : bookmark.id();
+
+ // Two layouts are needed because RemoteView does not supporting changing the scale type
+ // of an ImageView: boomarks crop their thumbnails, while folders stretch their icon.
+ RemoteViews views = !bookmark.isUrl()
+ ? new RemoteViews(mContext.getPackageName(),
+ R.layout.bookmark_thumbnail_widget_item_folder)
+ : new RemoteViews(mContext.getPackageName(),
+ R.layout.bookmark_thumbnail_widget_item);
+
+ // Set the title of the bookmark. Use the url as a backup.
+ views.setTextViewText(R.id.label, TextUtils.isEmpty(title) ? url : title);
+
+ if (!bookmark.isUrl()) {
+ int thumbId = (bookmark == mCurrentFolder)
+ ? R.drawable.thumb_bookmark_widget_folder_back_holo
+ : R.drawable.thumb_bookmark_widget_folder_holo;
+ views.setImageViewResource(R.id.thumb, thumbId);
+ views.setImageViewResource(R.id.favicon,
+ R.drawable.ic_bookmark_widget_bookmark_holo_dark);
+ } else {
+ // RemoteViews require a valid bitmap config.
+ Options options = new Options();
+ options.inPreferredConfig = Config.ARGB_8888;
+
+ byte[] favicon = bookmark.favicon();
+ if (favicon != null && favicon.length > 0) {
+ views.setImageViewBitmap(R.id.favicon,
+ BitmapFactory.decodeByteArray(favicon, 0, favicon.length, options));
+ } else {
+ views.setImageViewResource(R.id.favicon,
+ org.chromium.chrome.R.drawable.globe_favicon);
+ }
+
+ byte[] thumbnail = bookmark.thumbnail();
+ if (thumbnail != null && thumbnail.length > 0) {
+ views.setImageViewBitmap(R.id.thumb,
+ BitmapFactory.decodeByteArray(thumbnail, 0, thumbnail.length, options));
+ } else {
+ views.setImageViewResource(R.id.thumb, R.drawable.browser_thumbnail);
+ }
+ }
+
+ Intent fillIn;
+ if (!bookmark.isUrl()) {
+ fillIn = new Intent(getChangeFolderAction(mContext))
+ .putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mWidgetId)
+ .putExtra(BookmarkColumns._ID, id);
+ } else {
+ fillIn = new Intent(Intent.ACTION_VIEW);
+ if (!TextUtils.isEmpty(url)) {
+ fillIn = fillIn.addCategory(Intent.CATEGORY_BROWSABLE)
+ .setData(Uri.parse(url));
+ } else {
+ fillIn = fillIn.addCategory(Intent.CATEGORY_LAUNCHER);
+ }
+ }
+ views.setOnClickFillInIntent(R.id.list_item, fillIn);
+ return views;
+ }
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698