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

Side by Side 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 unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 package org.chromium.chrome.browser.bookmarkswidget;
6
7 import android.appwidget.AppWidgetManager;
8 import android.content.Context;
9 import android.content.Intent;
10 import android.content.SharedPreferences;
11 import android.graphics.Bitmap.Config;
12 import android.graphics.BitmapFactory;
13 import android.graphics.BitmapFactory.Options;
14 import android.net.Uri;
15 import android.os.AsyncTask;
16 import android.os.Binder;
17 import android.provider.Browser.BookmarkColumns;
18 import android.text.TextUtils;
19 import android.util.Log;
20 import android.widget.RemoteViews;
21 import android.widget.RemoteViewsService;
22
23 import com.google.android.apps.chrome.R;
24 import com.google.android.apps.chrome.appwidget.bookmarks.BookmarkThumbnailWidge tProvider;
25
26 import org.chromium.base.ThreadUtils;
27 import org.chromium.base.annotations.SuppressFBWarnings;
28 import org.chromium.base.library_loader.ProcessInitException;
29 import org.chromium.chrome.browser.ChromeBrowserProvider.BookmarkNode;
30 import org.chromium.chrome.browser.ChromeBrowserProviderClient;
31 import org.chromium.chrome.browser.ChromiumApplication;
32 import org.chromium.chrome.browser.util.IntentUtils;
33 import org.chromium.sync.AndroidSyncSettings;
34
35 /**
36 * Service to support bookmarks on the Android home screen
37 */
38 public class BookmarkThumbnailWidgetService extends RemoteViewsService {
39
40 static final String TAG = "BookmarkThumbnailWidgetService";
41 static final String ACTION_CHANGE_FOLDER_SUFFIX = ".CHANGE_FOLDER";
42 static final String STATE_CURRENT_FOLDER = "current_folder";
43
44 @Override
45 public RemoteViewsFactory onGetViewFactory(Intent intent) {
46 int widgetId = IntentUtils.safeGetIntExtra(intent, AppWidgetManager.EXTR A_APPWIDGET_ID, -1);
47 if (widgetId < 0) {
48 Log.w(TAG, "Missing EXTRA_APPWIDGET_ID!");
49 return null;
50 }
51 return new BookmarkFactory(this, widgetId);
52 }
53
54 static String getChangeFolderAction(Context context) {
55 return context.getPackageName() + ACTION_CHANGE_FOLDER_SUFFIX;
56 }
57
58 private static SharedPreferences getWidgetState(Context context, int widgetI d) {
59 return context.getSharedPreferences(
60 String.format("widgetState-%d", widgetId),
61 Context.MODE_PRIVATE);
62 }
63
64 static void deleteWidgetState(Context context, int widgetId) {
65 // Android Browser's widget used private API methods to access the share d prefs
66 // files and deleted them. This is the best we can do with the public AP I.
67 SharedPreferences preferences = getWidgetState(context, widgetId);
68 if (preferences != null) preferences.edit().clear().commit();
69 }
70
71 static void changeFolder(Context context, Intent intent) {
72 int widgetId = IntentUtils.safeGetIntExtra(intent, AppWidgetManager.EXTR A_APPWIDGET_ID, -1);
73 long folderId = IntentUtils.safeGetLongExtra(intent, BookmarkColumns._ID ,
74 ChromeBrowserProviderClient.INVALID_BOOKMARK_ID);
75 if (widgetId >= 0 && folderId >= 0) {
76 SharedPreferences prefs = getWidgetState(context, widgetId);
77 prefs.edit().putLong(STATE_CURRENT_FOLDER, folderId).commit();
78 AppWidgetManager.getInstance(context)
79 .notifyAppWidgetViewDataChanged(widgetId, R.id.bookmarks_lis t);
80 }
81 }
82
83 static class BookmarkFactory implements RemoteViewsService.RemoteViewsFactor y,
84 BookmarkWidgetUpdateListener.UpdateListener {
85
86 private final ChromiumApplication mContext;
87 private final int mWidgetId;
88 private final SharedPreferences mPreferences;
89 private BookmarkWidgetUpdateListener mUpdateListener;
90 private BookmarkNode mCurrentFolder;
91 private final Object mLock = new Object();
92
93 public BookmarkFactory(Context context, int widgetId) {
94 mContext = (ChromiumApplication) context.getApplicationContext();
95 mWidgetId = widgetId;
96 mPreferences = getWidgetState(mContext, mWidgetId);
97 }
98
99 private static long getFolderId(BookmarkNode folder) {
100 return folder != null ? folder.id() : ChromeBrowserProviderClient.IN VALID_BOOKMARK_ID;
101 }
102
103 @SuppressFBWarnings("DM_EXIT")
104 @Override
105 public void onCreate() {
106 // Required to be applied here redundantly to prevent crashes in the cases where the
107 // package data is deleted or the Chrome application forced to stop.
108 try {
109 mContext.startBrowserProcessesAndLoadLibrariesSync(mContext, tru e);
110 } catch (ProcessInitException e) {
111 Log.e(TAG, "Failed to start browser process.", e);
112 // Since the library failed to initialize nothing in the applica tion
113 // can work, so kill the whole application not just the activity
114 System.exit(-1);
115 }
116 mUpdateListener = new BookmarkWidgetUpdateListener(mContext, this);
117 }
118
119 @Override
120 public void onDestroy() {
121 if (mUpdateListener != null) mUpdateListener.destroy();
122 deleteWidgetState(mContext, mWidgetId);
123 }
124
125 @Override
126 public void onBookmarkModelUpdated() {
127 refreshWidget();
128 }
129
130 @Override
131 public void onSyncEnabledStatusUpdated(boolean enabled) {
132 synchronized (mLock) {
133 // Need to operate in a separate thread as it involves queries t o our provider.
134 new SyncEnabledStatusUpdatedTask(enabled, getFolderId(mCurrentFo lder)).execute();
135 }
136 }
137
138 @Override
139 public void onThumbnailUpdated(String url) {
140 synchronized (mLock) {
141 if (mCurrentFolder == null) return;
142
143 for (BookmarkNode child : mCurrentFolder.children()) {
144 if (child.isUrl() && url.equals(child.url())) {
145 refreshWidget();
146 break;
147 }
148 }
149 }
150 }
151
152 void refreshWidget() {
153 mContext.sendBroadcast(new Intent(
154 BookmarkThumbnailWidgetProviderBase.getBookmarkAppWidgetUpda teAction(mContext),
155 null, mContext, BookmarkThumbnailWidgetProvider.class)
156 .putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mWidgetId));
157 }
158
159 void requestFolderChange(long folderId) {
160 mContext.sendBroadcast(new Intent(getChangeFolderAction(mContext))
161 .setClass(mContext, BookmarkWidgetProxy.class)
162 .putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mWidgetId )
163 .putExtra(BookmarkColumns._ID, folderId));
164 }
165
166 // Performs the required checks to trigger an update of the widget after changing the sync
167 // enable settings. The required provider methods cannot be accessed in the UI thread.
168 private class SyncEnabledStatusUpdatedTask extends AsyncTask<Void, Void, Void> {
169 private final boolean mEnabled;
170 private final long mCurrentFolderId;
171
172 public SyncEnabledStatusUpdatedTask(boolean enabled, long currentFol derId) {
173 mEnabled = enabled;
174 mCurrentFolderId = currentFolderId;
175 }
176
177 @Override
178 protected Void doInBackground(Void... params) {
179 // If we're in the Mobile Bookmarks folder the icon to go up the hierarchy
180 // will either appear or disappear. Need to refresh.
181 long mobileBookmarksFolderId =
182 ChromeBrowserProviderClient.getMobileBookmarksFolderId(m Context);
183 if (mCurrentFolderId == mobileBookmarksFolderId) {
184 refreshWidget();
185 return null;
186 }
187
188 // If disabling sync, we need to move to the Mobile Bookmarks fo lder if we're
189 // not inside that branch of the bookmark hierarchy (will become not accessible).
190 if (!mEnabled && !ChromeBrowserProviderClient.isBookmarkInMobile BookmarksBranch(
191 mContext, mCurrentFolderId)) {
192 requestFolderChange(mobileBookmarksFolderId);
193 }
194
195 return null;
196 }
197 }
198
199 // ---------------------------------------------------------------- //
200 // ------- Methods below this line run in different thread -------- //
201 // ---------------------------------------------------------------- //
202
203 private void syncState() {
204 long currentFolderId = mPreferences.getLong(STATE_CURRENT_FOLDER,
205 ChromeBrowserProviderClient.INVALID_BOOKMARK_ID);
206
207 // Keep outside the synchronized block to avoid deadlocks in case lo ading the folder
208 // triggers an update that locks when trying to read mCurrentFolder.
209 BookmarkNode newFolder = loadBookmarkFolder(currentFolderId);
210
211 synchronized (mLock) {
212 mCurrentFolder =
213 getFolderId(newFolder) != ChromeBrowserProviderClient.IN VALID_BOOKMARK_ID
214 ? newFolder : null;
215 }
216
217 mPreferences.edit()
218 .putLong(STATE_CURRENT_FOLDER, getFolderId(mCurrentFolder))
219 .apply();
220 }
221
222 private BookmarkNode loadBookmarkFolder(long folderId) {
223 if (ThreadUtils.runningOnUiThread()) {
224 Log.e(TAG, "Trying to load bookmark folder from the UI thread.") ;
225 return null;
226 }
227
228 // If the current folder id doesn't exist (it was deleted) try the c urrent parent.
229 // If this fails too then fallback to Mobile Bookmarks.
230 if (!ChromeBrowserProviderClient.bookmarkNodeExists(mContext, folder Id)) {
231 folderId = mCurrentFolder != null ? getFolderId(mCurrentFolder.p arent())
232 : ChromeBrowserProviderClient.INVALID_BOOKMARK_ID;
233 if (!ChromeBrowserProviderClient.bookmarkNodeExists(mContext, fo lderId)) {
234 folderId = ChromeBrowserProviderClient.INVALID_BOOKMARK_ID;
235 }
236 }
237
238 // Need to verify this always because the package data might be clea red while the
239 // widget is in the Mobile Bookmarks folder with sync enabled. In th at case the
240 // hierarchy up folder would still work (we can't update the widget) but the parent
241 // folders should not be accessible because sync has been reset when clearing data.
242 if (folderId != ChromeBrowserProviderClient.INVALID_BOOKMARK_ID
243 && !AndroidSyncSettings.isSyncEnabled(mContext)
244 && !ChromeBrowserProviderClient.isBookmarkInMobileBookmarksB ranch(
245 mContext, folderId)) {
246 folderId = ChromeBrowserProviderClient.INVALID_BOOKMARK_ID;
247 }
248
249 // Use the Mobile Bookmarks folder by default.
250 if (folderId < 0 || folderId == ChromeBrowserProviderClient.INVALID_ BOOKMARK_ID) {
251 folderId = ChromeBrowserProviderClient.getMobileBookmarksFolderI d(mContext);
252 if (folderId == ChromeBrowserProviderClient.INVALID_BOOKMARK_ID) return null;
253 }
254
255 return ChromeBrowserProviderClient.getBookmarkNode(mContext, folderI d,
256 ChromeBrowserProviderClient.GET_PARENT
257 | ChromeBrowserProviderClient.GET_CHILDREN
258 | ChromeBrowserProviderClient.GET_FAVICONS
259 | ChromeBrowserProviderClient.GET_THUMBNAILS);
260 }
261
262 private BookmarkNode getBookmarkForPosition(int position) {
263 if (mCurrentFolder == null) return null;
264
265 // The position 0 is saved for an entry of the current folder used t o go up.
266 // This is not the case when the current node has no parent (it's th e root node).
267 return (mCurrentFolder.parent() == null)
268 ? mCurrentFolder.children().get(position)
269 : (position == 0
270 ? mCurrentFolder : mCurrentFolder.children().get(pos ition - 1));
271 }
272
273 @Override
274 public void onDataSetChanged() {
275 long token = Binder.clearCallingIdentity();
276 syncState();
277 Binder.restoreCallingIdentity(token);
278 }
279
280 @Override
281 public int getViewTypeCount() {
282 return 2;
283 }
284
285 @Override
286 public boolean hasStableIds() {
287 return false;
288 }
289
290 @Override
291 public int getCount() {
292 if (mCurrentFolder == null) return 0;
293 return mCurrentFolder.children().size() + (mCurrentFolder.parent() ! = null ? 1 : 0);
294 }
295
296 @Override
297 public long getItemId(int position) {
298 return getFolderId(getBookmarkForPosition(position));
299 }
300
301 @Override
302 public RemoteViews getLoadingView() {
303 return new RemoteViews(mContext.getPackageName(),
304 R.layout.bookmark_thumbnail_widget_item);
305 }
306
307 @Override
308 public RemoteViews getViewAt(int position) {
309 if (mCurrentFolder == null) {
310 Log.w(TAG, "No current folder data available.");
311 return null;
312 }
313
314 BookmarkNode bookmark = getBookmarkForPosition(position);
315 if (bookmark == null) {
316 Log.w(TAG, "Couldn't get bookmark for position " + position);
317 return null;
318 }
319
320 if (bookmark == mCurrentFolder && bookmark.parent() == null) {
321 Log.w(TAG, "Invalid bookmark data: loop detected.");
322 return null;
323 }
324
325 String title = bookmark.name();
326 String url = bookmark.url();
327 long id = (bookmark == mCurrentFolder) ? bookmark.parent().id() : bo okmark.id();
328
329 // Two layouts are needed because RemoteView does not supporting cha nging the scale type
330 // of an ImageView: boomarks crop their thumbnails, while folders st retch their icon.
331 RemoteViews views = !bookmark.isUrl()
332 ? new RemoteViews(mContext.getPackageName(),
333 R.layout.bookmark_thumbnail_widget_item_folder)
334 : new RemoteViews(mContext.getPackageName(),
335 R.layout.bookmark_thumbnail_widget_item);
336
337 // Set the title of the bookmark. Use the url as a backup.
338 views.setTextViewText(R.id.label, TextUtils.isEmpty(title) ? url : t itle);
339
340 if (!bookmark.isUrl()) {
341 int thumbId = (bookmark == mCurrentFolder)
342 ? R.drawable.thumb_bookmark_widget_folder_back_holo
343 : R.drawable.thumb_bookmark_widget_folder_holo;
344 views.setImageViewResource(R.id.thumb, thumbId);
345 views.setImageViewResource(R.id.favicon,
346 R.drawable.ic_bookmark_widget_bookmark_holo_dark);
347 } else {
348 // RemoteViews require a valid bitmap config.
349 Options options = new Options();
350 options.inPreferredConfig = Config.ARGB_8888;
351
352 byte[] favicon = bookmark.favicon();
353 if (favicon != null && favicon.length > 0) {
354 views.setImageViewBitmap(R.id.favicon,
355 BitmapFactory.decodeByteArray(favicon, 0, favicon.le ngth, options));
356 } else {
357 views.setImageViewResource(R.id.favicon,
358 org.chromium.chrome.R.drawable.globe_favicon);
359 }
360
361 byte[] thumbnail = bookmark.thumbnail();
362 if (thumbnail != null && thumbnail.length > 0) {
363 views.setImageViewBitmap(R.id.thumb,
364 BitmapFactory.decodeByteArray(thumbnail, 0, thumbnai l.length, options));
365 } else {
366 views.setImageViewResource(R.id.thumb, R.drawable.browser_th umbnail);
367 }
368 }
369
370 Intent fillIn;
371 if (!bookmark.isUrl()) {
372 fillIn = new Intent(getChangeFolderAction(mContext))
373 .putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mWidgetId )
374 .putExtra(BookmarkColumns._ID, id);
375 } else {
376 fillIn = new Intent(Intent.ACTION_VIEW);
377 if (!TextUtils.isEmpty(url)) {
378 fillIn = fillIn.addCategory(Intent.CATEGORY_BROWSABLE)
379 .setData(Uri.parse(url));
380 } else {
381 fillIn = fillIn.addCategory(Intent.CATEGORY_LAUNCHER);
382 }
383 }
384 views.setOnClickFillInIntent(R.id.list_item, fillIn);
385 return views;
386 }
387 }
388 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698