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

Side by Side Diff: chrome/android/java_staging/src/org/chromium/chrome/browser/bookmarkimport/BookmarkImporter.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.bookmarkimport;
6
7 import android.content.ContentValues;
8 import android.content.Context;
9 import android.database.Cursor;
10 import android.net.Uri;
11 import android.os.AsyncTask;
12 import android.provider.Browser;
13 import android.provider.Browser.BookmarkColumns;
14 import android.util.Log;
15
16 import org.chromium.chrome.browser.ChromeBrowserProvider;
17 import org.chromium.chrome.browser.ChromeBrowserProviderClient;
18
19 import java.util.ArrayList;
20 import java.util.HashSet;
21 import java.util.Iterator;
22 import java.util.LinkedHashMap;
23
24 /**
25 * Imports bookmarks from another browser into Chrome.
26 */
27 public abstract class BookmarkImporter {
28 private static final String TAG = "BookmarkImporter";
29
30 /** Class containing the results of a bookmark import operation */
31 public static class ImportResults {
32 public int newBookmarks; // Number of new bookmarks that could be import ed.
33 public int numImported; // Number of bookmarks that were successfully im ported.
34 public long rootFolderId; // ID of the folder where the bookmarks were i mported.
35 }
36
37 /** Listener for asynchronous import events. */
38 public interface OnBookmarksImportedListener {
39 /**
40 * Triggered after finishing the bookmark importing operation.
41 * @param results Results of the importing operation. Will be null in ca se of failure.
42 */
43 public void onBookmarksImported(ImportResults results);
44 }
45
46 /** Object defining an imported bookmark. */
47 static class Bookmark {
48 // To be provided by the bookmark extractors.
49 public long id; // Local id of the imported bookmark. Value ROOT_FOLDER_ ID is reserved.
50 public long parentId; // Import id of the parent node.
51 public boolean isFolder; // True if the object describes a bookmark fold er.
52 public String url; // URL of the bookmark. Required for non-folders.
53 public String title; // Title of the bookmark.
54 public Long created; // Creation date (timestamp) of the bookmark. Optio nal.
55 public Long lastVisit; // Date (timestamp) of the last visit. Optional.
56 public Long visits; // Number of visits to the page. Optional.
57 public byte[] favicon; // Favicon of the bookmark. Optional.
58
59 // For auxiliary use while importing. Not to be set by the bookmark extr actors.
60 public long nativeId;
61 public Bookmark parent;
62 public ArrayList<Bookmark> entries = new ArrayList<Bookmark>();
63 public boolean processed;
64 }
65
66 /** Closable iterator for available bookmarks. */
67 public interface BookmarkIterator extends Iterator<Bookmark> {
68 public void close();
69 }
70
71 /**
72 * Returns an array of iterators to the available bookmarks.
73 * The first one is tried and in case of complete importing failure the seco nd one is then used
74 * and so on until the array is exhausted. Note that no new bookmarks is not a failure.
75 *
76 * Called by an async task.
77 */
78 protected abstract BookmarkIterator[] availableBookmarks();
79
80 /** Imported bookmark id reserved for the root folder. */
81 static final long ROOT_FOLDER_ID = 0;
82
83 // Auxiliary query constants.
84 private static final Integer VALUE_IS_BOOKMARK = 1;
85 private static final String SELECT_IS_BOOKMARK = Browser.BookmarkColumns.BOO KMARK + "="
86 + VALUE_IS_BOOKMARK.toString();
87 private static final String HAS_URL = Browser.BookmarkColumns.URL + "=?";
88 private static final String[] EXISTS_PROJECTION = new String[]{ BookmarkColu mns.URL };
89
90 protected final Context mContext;
91
92 private ImportBookmarksTask mTask;
93
94 protected BookmarkImporter(Context context) {
95 mContext = context;
96 }
97
98 /** Asynchronously import bookmarks from another browser */
99 public void importBookmarks(OnBookmarksImportedListener listener) {
100 mTask = new ImportBookmarksTask(listener);
101 mTask.execute();
102 }
103
104 public void cancel() {
105 mTask.cancel(true);
106 }
107
108 /**
109 * Handles loading Android Browser bookmarks in a background thread.
110 */
111 private class ImportBookmarksTask extends AsyncTask<Void, Void, ImportResult s> {
112 private final OnBookmarksImportedListener mBookmarksImportedListener;
113
114 ImportBookmarksTask(OnBookmarksImportedListener listener) {
115 mBookmarksImportedListener = listener;
116 }
117
118 @Override
119 protected ImportResults doInBackground(Void... params) {
120 BookmarkIterator[] iterators = null;
121 try {
122 iterators = availableBookmarks();
123 } catch (Exception e) {
124 Log.w(TAG, "Unexpected exception while requesting available book marks: "
125 + e.getMessage());
126 return null;
127 }
128
129 if (iterators == null) {
130 Log.e(TAG, "No bookmark iterators found.");
131 return null;
132 }
133
134 for (BookmarkIterator iterator : iterators) {
135 ImportResults results = importFromIterator(iterator);
136 if (results != null) return results;
137 }
138
139 return null;
140 }
141
142 @Override
143 protected void onPostExecute(ImportResults results) {
144 if (mBookmarksImportedListener != null) {
145 mBookmarksImportedListener.onBookmarksImported(results);
146 }
147 }
148
149 private ImportResults importFromIterator(BookmarkIterator bookmarkIterat or) {
150 try {
151 if (bookmarkIterator == null) return null;
152
153 // Get a snapshot of the bookmarks.
154 LinkedHashMap<Long, Bookmark> idMap = new LinkedHashMap<Long, Bo okmark>();
155 HashSet<String> urlSet = new HashSet<String>();
156
157 // The root folder is used for hierarchy reconstruction purposes only.
158 // Bookmarks are directly imported into the Mobile Bookmarks fol der.
159 Bookmark rootFolder = createRootFolderBookmark();
160 idMap.put(ROOT_FOLDER_ID, rootFolder);
161
162 int failedImports = 0;
163 while (bookmarkIterator.hasNext()) {
164 Bookmark bookmark = bookmarkIterator.next();
165 if (bookmark == null) {
166 ++failedImports;
167 continue;
168 }
169
170 // Check for duplicate ids.
171 if (idMap.containsKey(bookmark.id)) {
172 Log.e(TAG, "Duplicate bookmark id: " + bookmark.id
173 + ". Dropping bookmark.");
174 ++failedImports;
175 continue;
176 }
177
178 // Check for duplicate URLs.
179 if (!bookmark.isFolder && urlSet.contains(bookmark.url)) {
180 Log.i(TAG, "More than one bookmark pointing to " + bookm ark.url
181 + ". Keeping only the first one for consistency with Chromium.");
182 continue;
183 }
184
185 // Reject bookmarks that already exist in the native model.
186 if (alreadyExists(bookmark)) continue;
187
188 idMap.put(bookmark.id, bookmark);
189 urlSet.add(bookmark.url);
190 }
191 bookmarkIterator.close();
192
193 // Abort if no new bookmarks to import.
194 ImportResults results = new ImportResults();
195 results.rootFolderId = rootFolder.nativeId;
196 results.newBookmarks = idMap.size() + failedImports - 1;
197 if (results.newBookmarks == 0) return results;
198
199 // Check if all imports failed.
200 if (idMap.size() == 1 && failedImports > 0) return null;
201
202 // Recreate the folder hierarchy and import it.
203 recreateFolderHierarchy(idMap);
204 importBookmarkHierarchy(rootFolder, results);
205
206 return results;
207 } catch (Exception e) {
208 Log.w(TAG, "Unexpected exception while importing bookmarks: " + e.getMessage());
209 return null;
210 }
211 }
212
213 private ContentValues getBookmarkValues(Bookmark bookmark) {
214 ContentValues values = new ContentValues();
215 values.put(BookmarkColumns.BOOKMARK, VALUE_IS_BOOKMARK);
216 values.put(BookmarkColumns.URL, bookmark.url);
217 values.put(BookmarkColumns.TITLE, bookmark.title);
218 values.put(ChromeBrowserProvider.BOOKMARK_PARENT_ID_PARAM, bookmark. parent.nativeId);
219 if (bookmark.created != null) values.put(BookmarkColumns.CREATED, bo okmark.created);
220 if (bookmark.lastVisit != null) values.put(BookmarkColumns.DATE, boo kmark.lastVisit);
221 if (bookmark.visits != null) {
222 // TODO(michaelbai) http://crbug.com/149376, http://b/6362473
223 // See android_provider_backend.cc IsHistoryAndBookmarkRowValid( ).
224 if (bookmark.created != null && bookmark.lastVisit != null
225 && bookmark.visits.longValue() > 2
226 && bookmark.lastVisit.longValue() - bookmark.created.lon gValue()
227 > bookmark.visits.longValue()) {
228 values.put(BookmarkColumns.VISITS, bookmark.visits);
229 }
230 }
231 if (bookmark.favicon != null) values.put(BookmarkColumns.FAVICON, bo okmark.favicon);
232 return values;
233 }
234
235 private boolean alreadyExists(Bookmark bookmark) {
236 // Folders are re-used if they already exist. No need to filter them out.
237 if (bookmark.isFolder) return false;
238
239 Cursor cursor = mContext.getContentResolver().query(
240 ChromeBrowserProvider.getBookmarksApiUri(mContext), EXISTS_P ROJECTION,
241 SELECT_IS_BOOKMARK + " AND " + HAS_URL, new String[]{ bookma rk.url }, null);
242 if (cursor != null) {
243 boolean exists = cursor.getCount() > 0;
244 cursor.close();
245 return exists;
246 }
247 return false;
248 }
249
250 private void recreateFolderHierarchy(LinkedHashMap<Long, Bookmark> idMap ) {
251 for (Bookmark bookmark : idMap.values()) {
252 if (bookmark.id == ROOT_FOLDER_ID) continue;
253
254 // Look for invalid parent ids and self-cycles.
255 if (!idMap.containsKey(bookmark.parentId) || bookmark.parentId = = bookmark.id) {
256 bookmark.parent = idMap.get(ROOT_FOLDER_ID);
257 bookmark.parent.entries.add(bookmark);
258 continue;
259 }
260
261 bookmark.parent = idMap.get(bookmark.parentId);
262 bookmark.parent.entries.add(bookmark);
263 }
264 }
265
266 private Bookmark createRootFolderBookmark() {
267 Bookmark root = new Bookmark();
268 root.id = ROOT_FOLDER_ID;
269 root.nativeId = ChromeBrowserProviderClient.getMobileBookmarksFolder Id(mContext);
270 root.parentId = ROOT_FOLDER_ID;
271 root.parent = root;
272 root.isFolder = true;
273 return root;
274 }
275
276 private void importBookmarkHierarchy(Bookmark bookmark, ImportResults re sults) {
277 // Avoid cycles in the hierarchy that could lead to infinite loops.
278 if (bookmark.processed) return;
279 bookmark.processed = true;
280
281 if (bookmark.isFolder) {
282 if (bookmark.id != ROOT_FOLDER_ID) {
283 bookmark.nativeId = ChromeBrowserProviderClient.createBookma rksFolderOnce(
284 mContext, bookmark.title, bookmark.parent.nativeId);
285 ++results.numImported;
286 }
287
288 if (bookmark.nativeId == ChromeBrowserProviderClient.INVALID_BOO KMARK_ID
289 && bookmark.id != ROOT_FOLDER_ID) {
290 Log.e(TAG, "Error creating the folder '" + bookmark.title
291 + "'. Skipping entries.");
292 return;
293 }
294
295 for (Bookmark entry : bookmark.entries) {
296 if (entry.parent != bookmark) {
297 Log.w(TAG, "Hierarchy error in bookmark '" + bookmark.ti tle
298 + "'. Skipping.");
299 continue;
300 }
301 importBookmarkHierarchy(entry, results);
302 }
303 } else {
304 sanitizeBookmarkDates(bookmark);
305 ContentValues values = getBookmarkValues(bookmark);
306 try {
307 // Check if the URL already exists in the database.
308 String[] urlArgs = new String[]{ bookmark.url };
309 Uri bookmarksApiUri = ChromeBrowserProvider.getBookmarksApiU ri(mContext);
310 Cursor history = mContext.getContentResolver().query(
311 bookmarksApiUri, null, HAS_URL, urlArgs, null);
312 boolean alreadyExists = history != null && history.getCount( ) > 0;
313 if (history != null) history.close();
314
315 if (alreadyExists) {
316 // If so, update the existing information.
317 if (mContext.getContentResolver().update(
318 bookmarksApiUri, values, HAS_URL, urlArgs) == 0) {
319 throw new IllegalArgumentException(
320 "Couldn't update the existing history inform ation");
321 }
322 } else {
323 // Otherwise insert the new information.
324 if (mContext.getContentResolver().insert(
325 bookmarksApiUri, values) == null) {
326 throw new IllegalArgumentException(
327 "Couldn't insert the bookmark");
328 }
329 }
330 ++results.numImported;
331 } catch (IllegalArgumentException e) {
332 Log.w(TAG, "Error inserting bookmark " + bookmark.title + ": "
333 + e.getMessage());
334 }
335 }
336 }
337
338 // Sanitize timestamp inputs as the provider backend might reject some o f the bookmarks
339 // if the values are inconsistent.
340 private void sanitizeBookmarkDates(Bookmark bookmark) {
341 final long now = System.currentTimeMillis();
342 if (bookmark.created != null && bookmark.created.longValue() > now) {
343 bookmark.created = Long.valueOf(now);
344 }
345
346 if (bookmark.lastVisit != null && bookmark.lastVisit.longValue() > n ow) {
347 bookmark.lastVisit = Long.valueOf(now);
348 }
349
350 if (bookmark.created != null && bookmark.lastVisit != null
351 && bookmark.created.longValue() > bookmark.lastVisit.longVal ue()) {
352 bookmark.created = bookmark.lastVisit;
353 }
354
355 // The provider backend assumes one visit per timestamp and actually checks this.
356 if (bookmark.lastVisit != null && bookmark.created != null && bookma rk.visits != null) {
357 long maxVisits = bookmark.lastVisit.longValue() - bookmark.creat ed.longValue() + 1;
358 if (bookmark.visits.longValue() > maxVisits) {
359 bookmark.visits = Long.valueOf(maxVisits);
360 }
361 }
362 }
363 }
364 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698