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

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

Load RootInfo in background, invalidation.

Move all RootInfo queries to background threads to avoid janking
the UI.  Update passes happen on spawed task, which swaps out updated
cache results when finished.  Support partial updates when only a
single package/authority has changed.  Watch for change notifications
for roots, since flags can change over time.

Ignore stopped packages when in background, but query them for roots
when launching any picker UI.

Optimize management launches by treating as one-shot requests that
don't need to wait for all RootInfo.

Bug: 10600454, 10745490
Change-Id: Ibc7b15688ef6b41bd7e9dd0d7564b501e60e49a9
parent bdac73fe
Loading
Loading
Loading
Loading
+0 −8
Original line number Diff line number Diff line
@@ -48,14 +48,6 @@
            android:authorities="com.android.documentsui.recents"
            android:exported="false" />

        <receiver android:name=".DocumentChangedReceiver">
            <intent-filter>
                <action android:name="android.provider.action.DOCUMENT_CHANGED" />
                <data android:mimeType="vnd.android.cursor.dir/root" />
                <data android:mimeType="vnd.android.cursor.item/root" />
            </intent-filter>
        </receiver>

        <!-- TODO: remove when we have real clients -->
        <activity android:name=".TestActivity" android:enabled="false">
            <intent-filter>
+5 −4
Original line number Diff line number Diff line
@@ -249,8 +249,7 @@ public class DirectoryFragment extends Fragment {
                                context, mType, root, doc, contentsUri, state.userSortOrder);
                    case TYPE_RECENT_OPEN:
                        final RootsCache roots = DocumentsApplication.getRootsCache(context);
                        final List<RootInfo> matchingRoots = roots.getMatchingRoots(state);
                        return new RecentLoader(context, matchingRoots, state.acceptMimes);
                        return new RecentLoader(context, roots, state);
                    default:
                        throw new IllegalStateException("Unknown type " + mType);
                }
@@ -797,7 +796,9 @@ public class DirectoryFragment extends Fragment {

            Drawable iconDrawable = null;
            if (mType == TYPE_RECENT_OPEN) {
                final RootInfo root = roots.getRoot(docAuthority, docRootId);
                // We've already had to enumerate roots before any results can
                // be shown, so this will never block.
                final RootInfo root = roots.getRootBlocking(docAuthority, docRootId);
                iconDrawable = root.loadIcon(context);

                if (summary != null) {
@@ -808,7 +809,7 @@ public class DirectoryFragment extends Fragment {
                        summary.setVisibility(View.VISIBLE);
                        hasLine2 = true;
                    } else {
                        if (iconDrawable != null && roots.isIconUnique(root)) {
                        if (iconDrawable != null && roots.isIconUniqueBlocking(root)) {
                            // No summary needed if icon speaks for itself
                            summary.setVisibility(View.INVISIBLE);
                        } else {
+56 −13
Original line number Diff line number Diff line
@@ -46,6 +46,7 @@ import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.InsetDrawable;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
@@ -87,6 +88,7 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Collection;
import java.util.List;

public class DocumentsActivity extends Activity {
@@ -207,8 +209,17 @@ public class DocumentsActivity extends Activity {
            RootsFragment.show(getFragmentManager(), null);
        }

        if (!mState.restored) {
            if (mState.action == ACTION_MANAGE) {
                final Uri rootUri = getIntent().getData();
                new RestoreRootTask(rootUri).execute();
            } else {
                new RestoreStackTask().execute();
            }
        } else {
            onCurrentDirectoryChanged(ANIM_NONE);
        }
    }

    private void buildDefaultState() {
        mState = new State();
@@ -241,22 +252,41 @@ public class DocumentsActivity extends Activity {

        mState.localOnly = intent.getBooleanExtra(Intent.EXTRA_LOCAL_ONLY, false);
        mState.showAdvanced = SettingsActivity.getDisplayAdvancedDevices(this);
    }

    private class RestoreRootTask extends AsyncTask<Void, Void, RootInfo> {
        private Uri mRootUri;

        public RestoreRootTask(Uri rootUri) {
            mRootUri = rootUri;
        }

        @Override
        protected RootInfo doInBackground(Void... params) {
            final String rootId = DocumentsContract.getRootId(mRootUri);
            return mRoots.getRootOneshot(mRootUri.getAuthority(), rootId);
        }

        @Override
        protected void onPostExecute(RootInfo root) {
            if (isDestroyed()) return;
            mState.restored = true;

        if (mState.action == ACTION_MANAGE) {
            final Uri uri = intent.getData();
            final String rootId = DocumentsContract.getRootId(uri);
            final RootInfo root = mRoots.getRoot(uri.getAuthority(), rootId);
            if (root != null) {
                onRootPicked(root, true);
            } else {
                Log.w(TAG, "Failed to find root: " + uri);
                Log.w(TAG, "Failed to find root: " + mRootUri);
                finish();
            }
        }
    }

        } else {
    private class RestoreStackTask extends AsyncTask<Void, Void, Void> {
        private volatile boolean mRestoredStack;

        @Override
        protected Void doInBackground(Void... params) {
            // Restore last stack for calling package
            // TODO: move into async loader
            boolean restoredStack = false;
            final String packageName = getCallingPackage();
            final Cursor cursor = getContentResolver()
                    .query(RecentsProvider.buildResume(packageName), null, null, null, null);
@@ -265,7 +295,7 @@ public class DocumentsActivity extends Activity {
                    final byte[] rawStack = cursor.getBlob(
                            cursor.getColumnIndex(ResumeColumns.STACK));
                    DurableUtils.readFromArray(rawStack, mState.stack);
                    restoredStack = true;
                    mRestoredStack = true;
                }
            } catch (IOException e) {
                Log.w(TAG, "Failed to resume", e);
@@ -275,18 +305,28 @@ public class DocumentsActivity extends Activity {

            // If restored root isn't valid, fall back to recents
            final RootInfo root = getCurrentRoot();
            final List<RootInfo> matchingRoots = mRoots.getMatchingRoots(mState);
            final Collection<RootInfo> matchingRoots = mRoots.getMatchingRootsBlocking(mState);
            if (!matchingRoots.contains(root)) {
                mState.stack.reset();
                restoredStack = false;
                mRestoredStack = false;
            }

            return null;
        }

        @Override
        protected void onPostExecute(Void result) {
            if (isDestroyed()) return;
            mState.restored = true;

            // Only open drawer when not restoring stack, and when not showing
            // visual content.
            if (!restoredStack
            if (!mRestoredStack
                    && !MimePredicate.mimeMatches(MimePredicate.VISUAL_MIMES, mState.acceptMimes)) {
                setRootsDrawerOpen(true);
            }

            onCurrentDirectoryChanged(ANIM_NONE);
        }
    }

@@ -935,6 +975,7 @@ public class DocumentsActivity extends Activity {
        public boolean localOnly = false;
        public boolean showAdvanced = false;
        public boolean stackTouched = false;
        public boolean restored = false;

        /** Current user navigation stack; empty implies recents. */
        public DocumentStack stack = new DocumentStack();
@@ -974,6 +1015,7 @@ public class DocumentsActivity extends Activity {
            out.writeInt(localOnly ? 1 : 0);
            out.writeInt(showAdvanced ? 1 : 0);
            out.writeInt(stackTouched ? 1 : 0);
            out.writeInt(restored ? 1 : 0);
            DurableUtils.writeToParcel(out, stack);
            out.writeString(currentSearch);
            out.writeMap(dirState);
@@ -992,6 +1034,7 @@ public class DocumentsActivity extends Activity {
                state.localOnly = in.readInt() != 0;
                state.showAdvanced = in.readInt() != 0;
                state.stackTouched = in.readInt() != 0;
                state.restored = in.readInt() != 0;
                DurableUtils.readFromParcel(in, state.stack);
                state.currentSearch = in.readString();
                in.readMap(state.dirState, null);
+10 −2
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Point;
import android.net.Uri;

public class DocumentsApplication extends Application {
    private RootsCache mRoots;
@@ -49,6 +50,8 @@ public class DocumentsApplication extends Application {
        final int memoryClassBytes = am.getMemoryClass() * 1024 * 1024;

        mRoots = new RootsCache(this);
        mRoots.updateAsync();

        mThumbnails = new ThumbnailCache(memoryClassBytes / 4);

        final IntentFilter packageFilter = new IntentFilter();
@@ -77,8 +80,13 @@ public class DocumentsApplication extends Application {
    private BroadcastReceiver mCacheReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            // TODO: narrow changed/removed to only packages that have backends
            mRoots.update();
            final Uri data = intent.getData();
            if (data != null) {
                final String packageName = data.getSchemeSpecificPart();
                mRoots.updatePackageAsync(packageName);
            } else {
                mRoots.updateAsync();
            }
        }
    };
}
+8 −6
Original line number Diff line number Diff line
@@ -41,6 +41,7 @@ import libcore.io.IoUtils;

import java.io.Closeable;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.CountDownLatch;
@@ -80,8 +81,8 @@ public class RecentLoader extends AsyncTaskLoader<DirectoryResult> {
        return executor;
    }

    private final List<RootInfo> mRoots;
    private final String[] mAcceptMimes;
    private final RootsCache mRoots;
    private final State mState;

    private final HashMap<RootInfo, RecentTask> mTasks = Maps.newHashMap();

@@ -138,10 +139,10 @@ public class RecentLoader extends AsyncTaskLoader<DirectoryResult> {
        }
    }

    public RecentLoader(Context context, List<RootInfo> roots, String[] acceptMimes) {
    public RecentLoader(Context context, RootsCache roots, State state) {
        super(context);
        mRoots = roots;
        mAcceptMimes = acceptMimes;
        mState = state;
    }

    @Override
@@ -150,7 +151,8 @@ public class RecentLoader extends AsyncTaskLoader<DirectoryResult> {
            // First time through we kick off all the recent tasks, and wait
            // around to see if everyone finishes quickly.

            for (RootInfo root : mRoots) {
            final Collection<RootInfo> roots = mRoots.getMatchingRootsBlocking(mState);
            for (RootInfo root : roots) {
                if ((root.flags & Root.FLAG_SUPPORTS_RECENTS) != 0) {
                    final RecentTask task = new RecentTask(root.authority, root.rootId);
                    mTasks.put(root, task);
@@ -177,7 +179,7 @@ public class RecentLoader extends AsyncTaskLoader<DirectoryResult> {
                try {
                    final Cursor cursor = task.get();
                    final FilteringCursorWrapper filtered = new FilteringCursorWrapper(
                            cursor, mAcceptMimes, new String[] { Document.MIME_TYPE_DIR }) {
                            cursor, mState.acceptMimes, new String[] { Document.MIME_TYPE_DIR }) {
                        @Override
                        public void close() {
                            // Ignored, since we manage cursor lifecycle internally
Loading