Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b448660a authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

Request more documents when EXTRA_HAS_MORE.

Implement EXTRA_HAS_MORE and EXTRA_REQUEST_MORE contract with
document providers.  Providers can include EXTRA_HAS_MORE when
additional data is available with additional cost, such as a network
request.

Listen to content changes based on returned cursor instead of
original Uri.  Include a test backend to exercise.  UX still under
development.

Bug: 10350207
Change-Id: Iaa8954df55a1a1c0aa96eb8a4fd288e12c2fbb01
parent 4eb407a8
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -42,4 +42,12 @@
        android:paddingStart="?android:attr/listPreferredItemPaddingStart"
        android:visibility="gone" />

    <Button
        android:id="@+id/more"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"
        android:text="@string/more"
        android:visibility="gone" />

</FrameLayout>
+3 −0
Original line number Diff line number Diff line
@@ -60,4 +60,7 @@
    <string name="toast_no_application">Can\'t open file</string>
    <string name="toast_failed_delete">Unable to delete some documents</string>

    <string name="more">More</string>
    <string name="loading">Loading\u2026</string>

</resources>
+19 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2013 The Android Open Source Project

     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
     You may obtain a copy of the License at

          http://www.apache.org/licenses/LICENSE-2.0

     Unless required by applicable law or agreed to in writing, software
     distributed under the License is distributed on an "AS IS" BASIS,
     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     See the License for the specific language governing permissions and
     limitations under the License.
-->

<documents-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:customRoots="true">
</documents-provider>
+34 −7
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.Loader;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.net.Uri;
@@ -54,6 +55,7 @@ import android.widget.AbsListView.MultiChoiceModeListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.ListView;
@@ -79,6 +81,7 @@ public class DirectoryFragment extends Fragment {
    private View mEmptyView;
    private ListView mListView;
    private GridView mGridView;
    private Button mMoreView;

    private AbsListView mCurrentView;

@@ -93,7 +96,7 @@ public class DirectoryFragment extends Fragment {
    private Point mThumbSize;

    private DocumentsAdapter mAdapter;
    private LoaderCallbacks<List<Document>> mCallbacks;
    private LoaderCallbacks<DirectoryResult> mCallbacks;

    private static final String EXTRA_TYPE = "type";
    private static final String EXTRA_URI = "uri";
@@ -150,14 +153,16 @@ public class DirectoryFragment extends Fragment {
        mGridView.setOnItemClickListener(mItemListener);
        mGridView.setMultiChoiceModeListener(mMultiListener);

        mMoreView = (Button) view.findViewById(R.id.more);

        mAdapter = new DocumentsAdapter();

        final Uri uri = getArguments().getParcelable(EXTRA_URI);
        mType = getArguments().getInt(EXTRA_TYPE);

        mCallbacks = new LoaderCallbacks<List<Document>>() {
        mCallbacks = new LoaderCallbacks<DirectoryResult>() {
            @Override
            public Loader<List<Document>> onCreateLoader(int id, Bundle args) {
            public Loader<DirectoryResult> onCreateLoader(int id, Bundle args) {
                final DisplayState state = getDisplayState(DirectoryFragment.this);
                mFilter = new MimePredicate(state.acceptMimes);

@@ -189,12 +194,34 @@ public class DirectoryFragment extends Fragment {
            }

            @Override
            public void onLoadFinished(Loader<List<Document>> loader, List<Document> data) {
                mAdapter.swapDocuments(data);
            public void onLoadFinished(Loader<DirectoryResult> loader, DirectoryResult result) {
                mAdapter.swapDocuments(result.contents);

                final Cursor cursor = result.cursor;
                if (cursor != null && cursor.getExtras()
                        .getBoolean(DocumentsContract.EXTRA_HAS_MORE, false)) {
                    mMoreView.setText(R.string.more);
                    mMoreView.setVisibility(View.VISIBLE);
                    mMoreView.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            mMoreView.setText(R.string.loading);
                            final Bundle bundle = new Bundle();
                            bundle.putBoolean(DocumentsContract.EXTRA_REQUEST_MORE, true);
                            try {
                                cursor.respond(bundle);
                            } catch (Exception e) {
                                Log.w(TAG, "Failed to respond: " + e);
                            }
                        }
                    });
                } else {
                    mMoreView.setVisibility(View.GONE);
                }
            }

            @Override
            public void onLoaderReset(Loader<List<Document>> loader) {
            public void onLoaderReset(Loader<DirectoryResult> loader) {
                mAdapter.swapDocuments(null);
            }
        };
@@ -407,7 +434,7 @@ public class DirectoryFragment extends Fragment {
        public void swapDocuments(List<Document> documents) {
            mDocuments = documents;

            if (documents != null && documents.isEmpty()) {
            if (mDocuments != null && mDocuments.isEmpty()) {
                mEmptyView.setVisibility(View.VISIBLE);
            } else {
                mEmptyView.setVisibility(View.GONE);
+43 −49
Original line number Diff line number Diff line
@@ -36,29 +36,27 @@ import com.google.android.collect.Lists;
import libcore.io.IoUtils;

import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;

public class DirectoryLoader extends UriDerivativeLoader<List<Document>> {
class DirectoryResult implements AutoCloseable {
    Cursor cursor;
    List<Document> contents = Lists.newArrayList();
    Exception e;

    @Override
    public void close() throws Exception {
        IoUtils.closeQuietly(cursor);
    }
}

public class DirectoryLoader extends UriDerivativeLoader<Uri, DirectoryResult> {

    private final int mType;
    private Predicate<Document> mFilter;
    private Comparator<Document> mSortOrder;

    /**
     * Stub result that represents an internal error.
     */
    public static class ExceptionResult extends LinkedList<Document> {
        public final Exception e;

        public ExceptionResult(Exception e) {
            this.e = e;
        }
    }

    public DirectoryLoader(Context context, Uri uri, int type, Predicate<Document> filter,
            Comparator<Document> sortOrder) {
        super(context, uri);
@@ -68,23 +66,24 @@ public class DirectoryLoader extends UriDerivativeLoader<List<Document>> {
    }

    @Override
    public List<Document> loadInBackground(Uri uri, CancellationSignal signal) {
    public DirectoryResult loadInBackground(Uri uri, CancellationSignal signal) {
        final DirectoryResult result = new DirectoryResult();
        try {
            return loadInBackgroundInternal(uri, signal);
            loadInBackgroundInternal(result, uri, signal);
        } catch (Exception e) {
            return new ExceptionResult(e);
            result.e = e;
        }
        return result;
    }

    private List<Document> loadInBackgroundInternal(Uri uri, CancellationSignal signal) {
        final ArrayList<Document> result = Lists.newArrayList();

        // TODO: subscribe to the notify uri from query

    private void loadInBackgroundInternal(
            DirectoryResult result, Uri uri, CancellationSignal signal) {
        final ContentResolver resolver = getContext().getContentResolver();
        final Cursor cursor = resolver.query(uri, null, null, null, getQuerySortOrder(), signal);
        try {
            while (cursor != null && cursor.moveToNext()) {
        result.cursor = cursor;
        result.cursor.registerContentObserver(mObserver);

        while (cursor.moveToNext()) {
            Document doc = null;
            switch (mType) {
                case TYPE_NORMAL:
@@ -103,18 +102,13 @@ public class DirectoryLoader extends UriDerivativeLoader<List<Document>> {
            }

            if (doc != null && (mFilter == null || mFilter.apply(doc))) {
                    result.add(doc);
                }
                result.contents.add(doc);
            }
        } finally {
            IoUtils.closeQuietly(cursor);
        }

        if (mSortOrder != null) {
            Collections.sort(result, mSortOrder);
            Collections.sort(result.contents, mSortOrder);
        }

        return result;
    }

    private String getQuerySortOrder() {
Loading