private static BookmarkNode createMockHierarchy() {
    // Mock hierarchy.
    // + Bookmarks
    //   - Google
    //   - Google maps
    //   + Youtube
    //     + Empty folder
    //     + Some other folder
    //       - Surprised Vader
    //     - Rickroll'D
    BookmarkNode root = new BookmarkNode(1, Type.FOLDER, "Bookmarks", null, null);
    root.addChild(new BookmarkNode(2, Type.URL, "Google", "http://www.google.com/", root));
    root.addChild(new BookmarkNode(3, Type.URL, "GoogleMaps", "http://maps.google.com/", root));

    BookmarkNode folder1 = new BookmarkNode(4, Type.FOLDER, "Youtube", null, root);
    root.addChild(folder1);
    folder1.addChild(new BookmarkNode(5, Type.FOLDER, "Empty folder", null, folder1));

    BookmarkNode folder2 = new BookmarkNode(6, Type.FOLDER, "Some other folder", null, folder1);
    folder1.addChild(folder2);

    folder1.addChild(
        new BookmarkNode(
            7, Type.URL, "RickRoll'D", "http://www.youtube.com/watch?v=oHg5SJYRHA0", folder1));
    folder2.addChild(
        new BookmarkNode(
            8, Type.URL, "Surprised Vader", "http://www.youtube.com/watch?v=9h1swNWgP8Q", folder2));
    return root;
  }
 @SmallTest
 @Feature({"Android-ContentProvider"})
 public void testInvalidHierarchy() throws InterruptedException {
   BookmarkNode root = new BookmarkNode(1, Type.FOLDER, "Bookmarks", null, null);
   root.addChild(new BookmarkNode(2, Type.URL, "Google", "http://www.google.com/", root));
   root.addChild(new BookmarkNode(2, Type.URL, "GoogleMaps", "http://maps.google.com/", root));
   assertFalse(internalTestNodeHierarchyParceling(root));
 }
    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));
    }
 private static boolean isSameHierarchyDownwards(BookmarkNode n1, BookmarkNode n2) {
   if (n1 == null && n2 == null) return true;
   if (n1 == null || n2 == null) return false;
   if (!n1.equalContents(n2)) return false;
   for (int i = 0; i < n1.children().size(); ++i) {
     if (!isSameHierarchyDownwards(n1.children().get(i), n2.children().get(i))) return false;
   }
   return true;
 }
  private BookmarkNode addImagesRecursive(BookmarkNode node) {
    node.setFavicon(mGenerator.nextBoolean() ? getRandomImageBlob() : null);
    node.setThumbnail(mGenerator.nextBoolean() ? getRandomImageBlob() : null);

    for (BookmarkNode child : node.children()) {
      addImagesRecursive(child);
    }

    return node;
  }
    @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;
          }
        }
      }
    }
  private static BookmarkNode parcelNode(BookmarkNode node) {
    Parcel output = Parcel.obtain();
    Parcel input = Parcel.obtain();
    node.writeToParcel(output, 0);
    byte[] bytes = output.marshall();

    input.unmarshall(bytes, 0, bytes.length);
    input.setDataPosition(0);

    return BookmarkNode.CREATOR.createFromParcel(input);
  }
  // Tests parceling and comparing each of the nodes in the provided hierarchy.
  private boolean internalTestNodeHierarchyParceling(BookmarkNode node) {
    if (node == null) return false;

    BookmarkNode parceled = parcelNode(node);
    if (!isSameHierarchy(node, parceled)) return false;

    for (BookmarkNode child : node.children()) {
      if (!internalTestNodeHierarchyParceling(child)) return false;
    }

    return true;
  }
    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 static long getFolderId(BookmarkNode folder) {
   return folder != null ? folder.id() : ChromeBrowserProviderClient.INVALID_BOOKMARK_ID;
 }
    @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;
    }
 @Override
 public int getCount() {
   if (mCurrentFolder == null) return 0;
   return mCurrentFolder.children().size() + (mCurrentFolder.parent() != null ? 1 : 0);
 }
 private static boolean isSameHierarchy(BookmarkNode h1, BookmarkNode h2) {
   return isSameHierarchyDownwards(h1.getHierarchyRoot(), h2.getHierarchyRoot());
 }