chromium/chrome/android/java/src/org/chromium/chrome/browser/partnerbookmarks/PartnerBookmarksProviderIterator.java

// Copyright 2015 The Chromium Authors
// 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.partnerbookmarks;

import android.database.Cursor;
import android.net.Uri;

import org.chromium.base.ContextUtils;
import org.chromium.base.Log;
import org.chromium.components.embedder_support.util.UrlConstants;

import java.util.NoSuchElementException;

/** Imports bookmarks from partner content provider using the private provider API. */
public class PartnerBookmarksProviderIterator implements PartnerBookmark.BookmarkIterator {
    private static final String TAG = "PartnerBookmarks";
    private static final String PROVIDER_AUTHORITY = "com.android.partnerbookmarks";
    private static final Uri CONTENT_URI =
            new Uri.Builder()
                    .scheme(UrlConstants.CONTENT_SCHEME)
                    .authority(PROVIDER_AUTHORITY)
                    .build();

    // Private bookmarks structure.
    private static final String BOOKMARKS_PATH = "bookmarks";
    private static final Uri BOOKMARKS_CONTENT_URI =
            CONTENT_URI.buildUpon().appendPath(BOOKMARKS_PATH).build();
    private static final String BOOKMARKS_COLUMN_ID = "_id";
    private static final String BOOKMARKS_COLUMN_URL = "url";
    private static final String BOOKMARKS_COLUMN_TITLE = "title";
    private static final String BOOKMARKS_COLUMN_TYPE = "type";
    private static final String BOOKMARKS_COLUMN_PARENT = "parent";
    private static final String BOOKMARKS_COLUMN_FAVICON = "favicon";
    private static final String BOOKMARKS_COLUMN_TOUCHICON = "touchicon";

    private static final int BOOKMARK_TYPE_FOLDER = 2;

    // Bookmark id of the main container folder.
    private static final long BOOKMARK_CONTAINER_FOLDER_ID = 0;

    private static final String BOOKMARKS_SORT_ORDER =
            BOOKMARKS_COLUMN_TYPE + " DESC, " + BOOKMARKS_COLUMN_ID + " ASC";

    private static final String[] BOOKMARKS_PROJECTION = {
        BOOKMARKS_COLUMN_ID,
        BOOKMARKS_COLUMN_URL,
        BOOKMARKS_COLUMN_TITLE,
        BOOKMARKS_COLUMN_TYPE,
        BOOKMARKS_COLUMN_PARENT,
        BOOKMARKS_COLUMN_FAVICON,
        BOOKMARKS_COLUMN_TOUCHICON
    };

    private final Cursor mCursor;

    /**
     * Creates the bookmarks iterator if possible.
     * @return                Iterator over bookmarks or null.
     */
    public static PartnerBookmarksProviderIterator createIfAvailable() {
        try {
            Cursor cursor =
                    ContextUtils.getApplicationContext()
                            .getContentResolver()
                            .query(
                                    BOOKMARKS_CONTENT_URI,
                                    BOOKMARKS_PROJECTION,
                                    null,
                                    null,
                                    BOOKMARKS_SORT_ORDER);
            if (cursor == null) return null;
            return new PartnerBookmarksProviderIterator(cursor);
        } catch (Exception ex) {
            // Depending on the OEM version of Android query() may throw a variety of different
            // exception types. See crbug/1466882.
            Log.e(TAG, "Unable to read partner bookmark database", ex);
            return null;
        }
    }

    private PartnerBookmarksProviderIterator(Cursor cursor) {
        mCursor = cursor;
    }

    @Override
    public void close() {
        if (mCursor == null) throw new IllegalStateException();
        mCursor.close();
    }

    @Override
    public boolean hasNext() {
        if (mCursor == null) throw new IllegalStateException();
        // While the combination of !isLast() && !isAfterLast() should have been
        // sufficient, Android Cursor API doesn't specify isAfterLast's behavior
        // in the case of an empty cursor; hence, we fall back to
        // getCount() > 0 check.
        return mCursor.getCount() > 0 && !mCursor.isLast() && !mCursor.isAfterLast();
    }

    @Override
    public PartnerBookmark next() {
        if (mCursor == null) throw new IllegalStateException();
        if (!mCursor.moveToNext()) throw new NoSuchElementException();

        PartnerBookmark bookmark = new PartnerBookmark();
        try {
            bookmark.mId = mCursor.getLong(mCursor.getColumnIndexOrThrow(BOOKMARKS_COLUMN_ID));
            // The container folder should not be among the results.
            if (bookmark.mId == BOOKMARK_CONTAINER_FOLDER_ID) {
                Log.i(TAG, "Dropping the bookmark: reserved _id was used");
                return null;
            }

            bookmark.mParentId =
                    mCursor.getLong(mCursor.getColumnIndexOrThrow(BOOKMARKS_COLUMN_PARENT));
            if (bookmark.mParentId == BOOKMARK_CONTAINER_FOLDER_ID) {
                bookmark.mParentId = PartnerBookmarksReader.ROOT_FOLDER_ID;
            }
            bookmark.mIsFolder =
                    mCursor.getInt(mCursor.getColumnIndexOrThrow(BOOKMARKS_COLUMN_TYPE))
                            == BOOKMARK_TYPE_FOLDER;
            bookmark.mUrl = mCursor.getString(mCursor.getColumnIndexOrThrow(BOOKMARKS_COLUMN_URL));
            bookmark.mTitle =
                    mCursor.getString(mCursor.getColumnIndexOrThrow(BOOKMARKS_COLUMN_TITLE));
            bookmark.mFavicon =
                    mCursor.getBlob(mCursor.getColumnIndexOrThrow(BOOKMARKS_COLUMN_FAVICON));
            bookmark.mTouchicon =
                    mCursor.getBlob(mCursor.getColumnIndexOrThrow(BOOKMARKS_COLUMN_TOUCHICON));
        } catch (IllegalArgumentException e) {
            Log.i(TAG, "Dropping the bookmark: " + e.getMessage());
            return null;
        }

        if ((!bookmark.mIsFolder && bookmark.mUrl == null) || bookmark.mTitle == null) {
            Log.i(TAG, "Dropping the bookmark: no title, or no url on a non-foler");
            return null;
        }

        return bookmark;
    }
}