| Index: chrome/android/java/src/org/chromium/chrome/browser/bookmarkswidget/BookmarkThumbnailWidgetService.java
|
| diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarkswidget/BookmarkThumbnailWidgetService.java b/chrome/android/java/src/org/chromium/chrome/browser/bookmarkswidget/BookmarkThumbnailWidgetService.java
|
| deleted file mode 100644
|
| index 50de3d912f0db768512f70706adeb6c894f6be3b..0000000000000000000000000000000000000000
|
| --- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarkswidget/BookmarkThumbnailWidgetService.java
|
| +++ /dev/null
|
| @@ -1,502 +0,0 @@
|
| -// 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.content.res.Resources;
|
| -import android.graphics.Bitmap;
|
| -import android.net.Uri;
|
| -import android.support.annotation.BinderThread;
|
| -import android.support.annotation.UiThread;
|
| -import android.text.TextUtils;
|
| -import android.widget.RemoteViews;
|
| -import android.widget.RemoteViewsService;
|
| -
|
| -import com.google.android.apps.chrome.appwidget.bookmarks.BookmarkThumbnailWidgetProvider;
|
| -
|
| -import org.chromium.base.ApiCompatibilityUtils;
|
| -import org.chromium.base.Log;
|
| -import org.chromium.base.ThreadUtils;
|
| -import org.chromium.base.annotations.SuppressFBWarnings;
|
| -import org.chromium.base.library_loader.ProcessInitException;
|
| -import org.chromium.base.metrics.RecordUserAction;
|
| -import org.chromium.chrome.R;
|
| -import org.chromium.chrome.browser.bookmarks.BookmarkBridge.BookmarkItem;
|
| -import org.chromium.chrome.browser.bookmarks.BookmarkBridge.BookmarkModelObserver;
|
| -import org.chromium.chrome.browser.bookmarks.BookmarkModel;
|
| -import org.chromium.chrome.browser.favicon.LargeIconBridge;
|
| -import org.chromium.chrome.browser.favicon.LargeIconBridge.LargeIconCallback;
|
| -import org.chromium.chrome.browser.init.ChromeBrowserInitializer;
|
| -import org.chromium.chrome.browser.partnerbookmarks.PartnerBookmarksShim;
|
| -import org.chromium.chrome.browser.profiles.Profile;
|
| -import org.chromium.chrome.browser.tab.Tab;
|
| -import org.chromium.chrome.browser.util.IntentUtils;
|
| -import org.chromium.chrome.browser.widget.RoundedIconGenerator;
|
| -import org.chromium.components.bookmarks.BookmarkId;
|
| -import org.chromium.components.bookmarks.BookmarkType;
|
| -
|
| -import java.util.ArrayList;
|
| -import java.util.Collections;
|
| -import java.util.Comparator;
|
| -import java.util.List;
|
| -import java.util.concurrent.LinkedBlockingQueue;
|
| -
|
| -import javax.annotation.Nullable;
|
| -
|
| -/**
|
| - * Service to support the bookmarks widget.
|
| - *
|
| - * This provides the list of bookmarks to show in the widget via a RemoteViewsFactory (the
|
| - * RemoteViews equivalent of an Adapter), and updates the widget when the bookmark model changes.
|
| - *
|
| - * Threading note: Be careful! Android calls some methods in this class on the UI thread and others
|
| - * on (multiple) binder threads. Additionally, all interaction with the BookmarkModel must happen on
|
| - * the UI thread. To keep the situation clear, every non-static method is annotated with either
|
| - * {@link UiThread} or {@link BinderThread}.
|
| - */
|
| -public class BookmarkThumbnailWidgetService extends RemoteViewsService {
|
| -
|
| - private static final String TAG = "BookmarkWidget";
|
| - private static final String ACTION_CHANGE_FOLDER_SUFFIX = ".CHANGE_FOLDER";
|
| - private static final String PREF_CURRENT_FOLDER = "current_folder";
|
| - private static final String EXTRA_FOLDER_ID = "folderId";
|
| -
|
| - @UiThread
|
| - @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 BookmarkAdapter(this, widgetId);
|
| - }
|
| -
|
| - static String getChangeFolderAction(Context context) {
|
| - return context.getPackageName() + ACTION_CHANGE_FOLDER_SUFFIX;
|
| - }
|
| -
|
| - 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) {
|
| - SharedPreferences preferences = getWidgetState(context, widgetId);
|
| - if (preferences != null) preferences.edit().clear().apply();
|
| - }
|
| -
|
| - static void changeFolder(Context context, Intent intent) {
|
| - int widgetId = IntentUtils.safeGetIntExtra(intent, AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
|
| - long folderId = IntentUtils.safeGetLongExtra(intent, EXTRA_FOLDER_ID, -1);
|
| - if (widgetId >= 0 && folderId >= 0) {
|
| - SharedPreferences prefs = getWidgetState(context, widgetId);
|
| - prefs.edit().putLong(PREF_CURRENT_FOLDER, folderId).apply();
|
| - AppWidgetManager.getInstance(context)
|
| - .notifyAppWidgetViewDataChanged(widgetId, R.id.bookmarks_list);
|
| - }
|
| - }
|
| -
|
| - /**
|
| - * Holds data describing a bookmark or bookmark folder.
|
| - */
|
| - private static class Bookmark {
|
| - public String title;
|
| - public String url;
|
| - public BookmarkId id;
|
| - public BookmarkId parentId;
|
| - public boolean isFolder;
|
| - public Bitmap favicon;
|
| -
|
| - public static Bookmark fromBookmarkItem(BookmarkItem item) {
|
| - if (item == null) return null;
|
| -
|
| - // The bookmarks widget doesn't support showing partner bookmarks. The main hurdle is
|
| - // that the current folder ID is stored in shared prefs as a long, not a BookmarkId.
|
| - // This support could be added if there's a strong desire.
|
| - if (item.getId().getType() == BookmarkType.PARTNER) return null;
|
| -
|
| - Bookmark bookmark = new Bookmark();
|
| - bookmark.title = item.getTitle();
|
| - bookmark.url = item.getUrl();
|
| - bookmark.id = item.getId();
|
| - bookmark.parentId = item.getParentId();
|
| - bookmark.isFolder = item.isFolder();
|
| - return bookmark;
|
| - }
|
| - }
|
| -
|
| - /**
|
| - * Holds the list of bookmarks in a folder, as well as information about the folder itself and
|
| - * its parent folder, if any.
|
| - */
|
| - private static class BookmarkFolder {
|
| - public Bookmark folder;
|
| - @Nullable public Bookmark parent;
|
| - public final List<Bookmark> children = new ArrayList<>();
|
| - }
|
| -
|
| - /**
|
| - * Called when the BookmarkLoader has finished loading the bookmark folder.
|
| - */
|
| - private interface BookmarkLoaderCallback {
|
| - @UiThread
|
| - void onBookmarksLoaded(BookmarkFolder folder);
|
| - }
|
| -
|
| - /**
|
| - * Loads a BookmarkFolder asynchronously, and returns the result via BookmarkLoaderCallback.
|
| - *
|
| - * This class must be used only on the UI thread.
|
| - */
|
| - @UiThread
|
| - private static class BookmarkLoader {
|
| - private BookmarkLoaderCallback mCallback;
|
| - private BookmarkFolder mFolder;
|
| - private BookmarkModel mBookmarkModel;
|
| - private LargeIconBridge mLargeIconBridge;
|
| - private RoundedIconGenerator mIconGenerator;
|
| - private int mMinIconSizeDp;
|
| - private int mDisplayedIconSize;
|
| - private int mCornerRadius;
|
| - private int mRemainingTaskCount;
|
| -
|
| - BookmarkLoader(Context context, final BookmarkId folderId,
|
| - BookmarkLoaderCallback callback) {
|
| - mCallback = callback;
|
| -
|
| - Resources res = context.getResources();
|
| - mLargeIconBridge = new LargeIconBridge(
|
| - Profile.getLastUsedProfile().getOriginalProfile());
|
| - mMinIconSizeDp = (int) res.getDimension(R.dimen.bookmark_item_min_icon_size);
|
| - mDisplayedIconSize = res.getDimensionPixelSize(R.dimen.bookmark_item_icon_size);
|
| - mCornerRadius = res.getDimensionPixelSize(R.dimen.bookmark_item_corner_radius);
|
| - int textSize = res.getDimensionPixelSize(R.dimen.bookmark_item_icon_text_size);
|
| - int iconColor = ApiCompatibilityUtils.getColor(res,
|
| - R.color.bookmark_icon_background_color);
|
| - mIconGenerator = new RoundedIconGenerator(mDisplayedIconSize, mDisplayedIconSize,
|
| - mCornerRadius, iconColor, textSize);
|
| -
|
| - mRemainingTaskCount = 1;
|
| - mBookmarkModel = new BookmarkModel();
|
| - mBookmarkModel.runAfterBookmarkModelLoaded(new Runnable() {
|
| - @Override
|
| - public void run() {
|
| - loadBookmarks(folderId);
|
| - }
|
| - });
|
| - }
|
| -
|
| - private void loadBookmarks(BookmarkId folderId) {
|
| - mFolder = new BookmarkFolder();
|
| -
|
| - // Load the requested folder if it exists. Otherwise, fall back to the default folder.
|
| - if (folderId != null) {
|
| - mFolder.folder = Bookmark.fromBookmarkItem(mBookmarkModel.getBookmarkById(
|
| - folderId));
|
| - }
|
| - if (mFolder.folder == null) {
|
| - folderId = mBookmarkModel.getDefaultFolder();
|
| - mFolder.folder = Bookmark.fromBookmarkItem(mBookmarkModel.getBookmarkById(
|
| - folderId));
|
| - }
|
| -
|
| - mFolder.parent = Bookmark.fromBookmarkItem(mBookmarkModel.getBookmarkById(
|
| - mFolder.folder.parentId));
|
| -
|
| - List<BookmarkItem> items = mBookmarkModel.getBookmarksForFolder(folderId);
|
| -
|
| - // Move folders to the beginning of the list.
|
| - Collections.sort(items, new Comparator<BookmarkItem>() {
|
| - @Override
|
| - public int compare(BookmarkItem lhs, BookmarkItem rhs) {
|
| - return lhs.isFolder() == rhs.isFolder() ? 0 : lhs.isFolder() ? -1 : 1;
|
| - }
|
| - });
|
| -
|
| - for (BookmarkItem item : items) {
|
| - Bookmark bookmark = Bookmark.fromBookmarkItem(item);
|
| - loadFavicon(bookmark);
|
| - mFolder.children.add(bookmark);
|
| - }
|
| -
|
| - taskFinished();
|
| - }
|
| -
|
| - private void loadFavicon(final Bookmark bookmark) {
|
| - if (bookmark.isFolder) return;
|
| -
|
| - mRemainingTaskCount++;
|
| - LargeIconCallback callback = new LargeIconCallback() {
|
| - @Override
|
| - public void onLargeIconAvailable(Bitmap icon, int fallbackColor) {
|
| - if (icon == null) {
|
| - mIconGenerator.setBackgroundColor(fallbackColor);
|
| - icon = mIconGenerator.generateIconForUrl(bookmark.url);
|
| - } else {
|
| - icon = Bitmap.createScaledBitmap(icon, mDisplayedIconSize,
|
| - mDisplayedIconSize, true);
|
| - }
|
| - bookmark.favicon = icon;
|
| - taskFinished();
|
| - }
|
| - };
|
| - mLargeIconBridge.getLargeIconForUrl(bookmark.url, mMinIconSizeDp, callback);
|
| - }
|
| -
|
| - private void taskFinished() {
|
| - mRemainingTaskCount--;
|
| - if (mRemainingTaskCount == 0) {
|
| - mCallback.onBookmarksLoaded(mFolder);
|
| - destroy();
|
| - }
|
| - }
|
| -
|
| - private void destroy() {
|
| - mBookmarkModel.destroy();
|
| - mLargeIconBridge.destroy();
|
| - }
|
| - }
|
| -
|
| - /**
|
| - * Provides the RemoteViews, one per bookmark, to be shown in the widget.
|
| - */
|
| - private static class BookmarkAdapter implements RemoteViewsService.RemoteViewsFactory {
|
| -
|
| - // Can be accessed on any thread
|
| - private final Context mContext;
|
| - private final int mWidgetId;
|
| - private final SharedPreferences mPreferences;
|
| -
|
| - // Accessed only on the UI thread
|
| - private BookmarkModel mBookmarkModel;
|
| -
|
| - // Accessed only on binder threads.
|
| - private BookmarkFolder mCurrentFolder;
|
| -
|
| - @UiThread
|
| - public BookmarkAdapter(Context context, int widgetId) {
|
| - mContext = context;
|
| - mWidgetId = widgetId;
|
| - mPreferences = getWidgetState(mContext, mWidgetId);
|
| - }
|
| -
|
| - @UiThread
|
| - @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 {
|
| - ChromeBrowserInitializer.getInstance(mContext).handleSynchronousStartup();
|
| - } 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);
|
| - }
|
| - if (isWidgetNewlyCreated()) {
|
| - RecordUserAction.record("BookmarkNavigatorWidgetAdded");
|
| - }
|
| -
|
| - // Partner bookmarks need to be loaded explicitly.
|
| - PartnerBookmarksShim.kickOffReading(mContext);
|
| -
|
| - mBookmarkModel = new BookmarkModel();
|
| - mBookmarkModel.addObserver(new BookmarkModelObserver() {
|
| - @Override
|
| - public void bookmarkModelLoaded() {
|
| - // Do nothing. No need to refresh.
|
| - }
|
| -
|
| - @Override
|
| - public void bookmarkModelChanged() {
|
| - refreshWidget();
|
| - }
|
| - });
|
| - }
|
| -
|
| - @UiThread
|
| - private boolean isWidgetNewlyCreated() {
|
| - // This method relies on the fact that PREF_CURRENT_FOLDER is not yet
|
| - // set when onCreate is called for a newly created widget.
|
| - long currentFolder = mPreferences.getLong(PREF_CURRENT_FOLDER, Tab.INVALID_BOOKMARK_ID);
|
| - return currentFolder == Tab.INVALID_BOOKMARK_ID;
|
| - }
|
| -
|
| - @UiThread
|
| - private void refreshWidget() {
|
| - mContext.sendBroadcast(new Intent(
|
| - BookmarkThumbnailWidgetProviderBase.getBookmarkAppWidgetUpdateAction(mContext),
|
| - null, mContext, BookmarkThumbnailWidgetProvider.class)
|
| - .putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mWidgetId));
|
| - }
|
| -
|
| - // ---------------------------------------------------------------- //
|
| - // Methods below this line are called on binder threads. //
|
| - // ---------------------------------------------------------------- //
|
| - // Different methods may be called on *different* binder threads, //
|
| - // but the system ensures that the effects of each method call will //
|
| - // be visible before the next method is called. Thus, additional //
|
| - // synchronization is not needed when accessing mCurrentFolder. //
|
| - // ---------------------------------------------------------------- //
|
| -
|
| - @BinderThread
|
| - @Override
|
| - public void onDestroy() {
|
| - ThreadUtils.runOnUiThread(new Runnable() {
|
| - @Override
|
| - public void run() {
|
| - if (mBookmarkModel != null) mBookmarkModel.destroy();
|
| - }
|
| - });
|
| - deleteWidgetState(mContext, mWidgetId);
|
| - }
|
| -
|
| - @BinderThread
|
| - @Override
|
| - public void onDataSetChanged() {
|
| - updateBookmarkList();
|
| - }
|
| -
|
| - @BinderThread
|
| - private void updateBookmarkList() {
|
| - long folderIdLong = mPreferences.getLong(PREF_CURRENT_FOLDER, Tab.INVALID_BOOKMARK_ID);
|
| - BookmarkId folderId = folderIdLong != Tab.INVALID_BOOKMARK_ID
|
| - ? new BookmarkId(folderIdLong, BookmarkType.NORMAL)
|
| - : null;
|
| -
|
| - mCurrentFolder = loadBookmarks(folderId);
|
| -
|
| - mPreferences.edit()
|
| - .putLong(PREF_CURRENT_FOLDER, mCurrentFolder != null
|
| - ? mCurrentFolder.folder.id.getId()
|
| - : Tab.INVALID_BOOKMARK_ID)
|
| - .apply();
|
| - }
|
| -
|
| - @BinderThread
|
| - private BookmarkFolder loadBookmarks(final BookmarkId folderId) {
|
| - final LinkedBlockingQueue<BookmarkFolder> resultQueue = new LinkedBlockingQueue<>(1);
|
| - ThreadUtils.runOnUiThread(new Runnable() {
|
| - @Override
|
| - public void run() {
|
| - new BookmarkLoader(mContext, folderId, new BookmarkLoaderCallback() {
|
| - @Override
|
| - public void onBookmarksLoaded(BookmarkFolder folder) {
|
| - resultQueue.add(folder);
|
| - }
|
| - });
|
| - }
|
| - });
|
| - try {
|
| - return resultQueue.take();
|
| - } catch (InterruptedException e) {
|
| - return null;
|
| - }
|
| - }
|
| -
|
| - @BinderThread
|
| - private Bookmark 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).
|
| - if (mCurrentFolder.parent != null) {
|
| - if (position == 0) return mCurrentFolder.folder;
|
| - position--;
|
| - }
|
| - return mCurrentFolder.children.get(position);
|
| - }
|
| -
|
| - @BinderThread
|
| - @Override
|
| - public int getViewTypeCount() {
|
| - return 2;
|
| - }
|
| -
|
| - @BinderThread
|
| - @Override
|
| - public boolean hasStableIds() {
|
| - return false;
|
| - }
|
| -
|
| - @BinderThread
|
| - @Override
|
| - public int getCount() {
|
| - if (mCurrentFolder == null) return 0;
|
| - return mCurrentFolder.children.size() + (mCurrentFolder.parent != null ? 1 : 0);
|
| - }
|
| -
|
| - @BinderThread
|
| - @Override
|
| - public long getItemId(int position) {
|
| - return getBookmarkForPosition(position).id.getId();
|
| - }
|
| -
|
| - @BinderThread
|
| - @Override
|
| - public RemoteViews getLoadingView() {
|
| - return new RemoteViews(mContext.getPackageName(),
|
| - R.layout.bookmark_thumbnail_widget_item);
|
| - }
|
| -
|
| - @BinderThread
|
| - @Override
|
| - public RemoteViews getViewAt(int position) {
|
| - if (mCurrentFolder == null) {
|
| - Log.w(TAG, "No current folder data available.");
|
| - return null;
|
| - }
|
| -
|
| - Bookmark bookmark = getBookmarkForPosition(position);
|
| - if (bookmark == null) {
|
| - Log.w(TAG, "Couldn't get bookmark for position %d", position);
|
| - return null;
|
| - }
|
| -
|
| - String title = bookmark.title;
|
| - String url = bookmark.url;
|
| - long id = (bookmark == mCurrentFolder.folder)
|
| - ? mCurrentFolder.parent.id.getId()
|
| - : bookmark.id.getId();
|
| -
|
| - RemoteViews views = 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.title, TextUtils.isEmpty(title) ? url : title);
|
| -
|
| - if (bookmark == mCurrentFolder.folder) {
|
| - views.setImageViewResource(R.id.favicon, R.drawable.bookmark_back_normal);
|
| - } else if (bookmark.isFolder) {
|
| - views.setImageViewResource(R.id.favicon, R.drawable.bookmark_folder);
|
| - } else {
|
| - views.setImageViewBitmap(R.id.favicon, bookmark.favicon);
|
| - }
|
| -
|
| - Intent fillIn;
|
| - if (bookmark.isFolder) {
|
| - fillIn = new Intent(getChangeFolderAction(mContext))
|
| - .putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mWidgetId)
|
| - .putExtra(EXTRA_FOLDER_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;
|
| - }
|
| - }
|
| -}
|
|
|