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

Commit 8ac5bfdd authored by Steve McKay's avatar Steve McKay Committed by Android (Google) Code Review
Browse files

Merge "Simplify State initialization w/ better semantics + improved Task security."

parents 41114089 95cd85ad
Loading
Loading
Loading
Loading
+41 −110
Original line number Diff line number Diff line
@@ -45,22 +45,16 @@ import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Spinner;
import android.widget.Toolbar;

import com.android.documentsui.RecentsProvider.ResumeColumns;
import com.android.documentsui.SearchManager.SearchManagerListener;
import com.android.documentsui.State.ViewMode;
import com.android.documentsui.dirlist.DirectoryFragment;
import com.android.documentsui.model.DocumentInfo;
import com.android.documentsui.model.DocumentStack;
import com.android.documentsui.model.DurableUtils;
import com.android.documentsui.model.RootInfo;
import com.android.internal.util.Preconditions;

import libcore.io.IoUtils;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@@ -86,8 +80,8 @@ public abstract class BaseActivity extends Activity

    abstract void onTaskFinished(Uri... uris);
    abstract void refreshDirectory(int anim);
    abstract void saveStackBlocking();
    abstract State buildState();
    /** Allows sub-classes to include information in a newly created State instance. */
    abstract void includeState(State initialState);

    public BaseActivity(@LayoutRes int layoutId, String tag) {
        mLayoutId = layoutId;
@@ -102,9 +96,7 @@ public abstract class BaseActivity extends Activity
        setContentView(mLayoutId);

        mDrawer = DrawerController.create(this);
        mState = (icicle != null)
                ? icicle.<State>getParcelable(EXTRA_STATE)
                        : buildState();
        mState = getState(icicle);
        Metrics.logActivityLaunch(this, mState, getIntent());

        mRoots = DocumentsApplication.getRootsCache(this);
@@ -113,7 +105,8 @@ public abstract class BaseActivity extends Activity
                new RootsCache.OnCacheUpdateListener() {
                    @Override
                    public void onCacheUpdate() {
                        new HandleRootsChangedTask().execute(getCurrentRoot());
                        new HandleRootsChangedTask(BaseActivity.this)
                                .execute(getCurrentRoot());
                    }
                });

@@ -182,7 +175,20 @@ public abstract class BaseActivity extends Activity
        super.onDestroy();
    }

    State buildDefaultState() {
    private State getState(@Nullable Bundle icicle) {
        if (icicle != null) {
            State state = icicle.<State>getParcelable(EXTRA_STATE);
            if (DEBUG) Log.d(mTag, "Recovered existing state object: " + state);
            return state;
        }

        State state = createSharedState();
        includeState(state);
        if (DEBUG) Log.d(mTag, "Created new state object: " + state);
        return state;
    }

    private State createSharedState() {
        State state = new State();

        final Intent intent = getIntent();
@@ -222,7 +228,7 @@ public abstract class BaseActivity extends Activity
        if (mRoots.isRecentsRoot(root)) {
            refreshCurrentRootAndDirectory(ANIM_NONE);
        } else {
            new PickRootTask(root).executeOnExecutor(getExecutorForCurrentDirectory());
            new PickRootTask(this, root).executeOnExecutor(getExecutorForCurrentDirectory());
        }
    }

@@ -570,115 +576,40 @@ public abstract class BaseActivity extends Activity
        }
    }

    final class PickRootTask extends AsyncTask<Void, Void, DocumentInfo> {
    private static final class PickRootTask extends PairedTask<BaseActivity, Void, DocumentInfo> {
        private RootInfo mRoot;

        public PickRootTask(RootInfo root) {
        public PickRootTask(BaseActivity activity, RootInfo root) {
            super(activity);
            mRoot = root;
        }

        @Override
        protected DocumentInfo doInBackground(Void... params) {
            return getRootDocumentBlocking(mRoot);
        }

        @Override
        protected void onPostExecute(DocumentInfo result) {
            if (result != null && !isDestroyed()) {
                openContainerDocument(result);
            }
        protected DocumentInfo run(Void... params) {
            return mOwner.getRootDocumentBlocking(mRoot);
        }
    }

    final class RestoreStackTask extends AsyncTask<Void, Void, Void> {
        private volatile boolean mRestoredStack;
        private volatile boolean mExternal;

        @Override
        protected Void doInBackground(Void... params) {
            if (DEBUG && !mState.stack.isEmpty()) {
                Log.w(mTag, "Overwriting existing stack.");
            }
            RootsCache roots = DocumentsApplication.getRootsCache(BaseActivity.this);

            // Restore last stack for calling package
            final String packageName = getCallingPackageMaybeExtra();
            final Cursor cursor = getContentResolver()
                    .query(RecentsProvider.buildResume(packageName), null, null, null, null);
            try {
                if (cursor.moveToFirst()) {
                    mExternal = cursor.getInt(cursor.getColumnIndex(ResumeColumns.EXTERNAL)) != 0;
                    final byte[] rawStack = cursor.getBlob(
                            cursor.getColumnIndex(ResumeColumns.STACK));
                    DurableUtils.readFromArray(rawStack, mState.stack);
                    mRestoredStack = true;
                }
            } catch (IOException e) {
                Log.w(mTag, "Failed to resume: " + e);
            } finally {
                IoUtils.closeQuietly(cursor);
        protected void finish(DocumentInfo result) {
            if (result != null) {
                mOwner.openContainerDocument(result);
            }

            if (mRestoredStack) {
                // Update the restored stack to ensure we have freshest data
                final Collection<RootInfo> matchingRoots = roots.getMatchingRootsBlocking(mState);
                try {
                    mState.stack.updateRoot(matchingRoots);
                    mState.stack.updateDocuments(getContentResolver());
                } catch (FileNotFoundException e) {
                    Log.w(mTag, "Failed to restore stack: " + e);
                    mState.stack.reset();
                    mRestoredStack = false;
        }
    }

            return null;
        }

        @Override
        protected void onPostExecute(Void result) {
            if (isDestroyed()) return;
            mState.restored = true;
            refreshCurrentRootAndDirectory(ANIM_NONE);
            onStackRestored(mRestoredStack, mExternal);
        }
    }

    final 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;
    private static final class HandleRootsChangedTask
            extends PairedTask<BaseActivity, RootInfo, RootInfo> {
        DocumentInfo mHome;

            if (root != null) {
                onRootPicked(root);
            } else {
                Log.w(mTag, "Failed to find root: " + mRootUri);
                finish();
            }
        }
        public HandleRootsChangedTask(BaseActivity activity) {
            super(activity);
        }

    final class HandleRootsChangedTask extends AsyncTask<RootInfo, Void, RootInfo> {
        DocumentInfo mHome;

        @Override
        protected RootInfo doInBackground(RootInfo... roots) {
        protected RootInfo run(RootInfo... roots) {
            checkArgument(roots.length == 1);
            final RootInfo currentRoot = roots[0];
            final Collection<RootInfo> cachedRoots = mRoots.getRootsBlocking();
            final Collection<RootInfo> cachedRoots = mOwner.mRoots.getRootsBlocking();
            RootInfo homeRoot = null;
            for (final RootInfo root : cachedRoots) {
                if (root.isHome()) {
@@ -690,17 +621,17 @@ public abstract class BaseActivity extends Activity
                }
            }
            Preconditions.checkNotNull(homeRoot);
            mHome = getRootDocumentBlocking(homeRoot);
            mHome = mOwner.getRootDocumentBlocking(homeRoot);
            return homeRoot;
        }

        @Override
        protected void onPostExecute(RootInfo homeRoot) {
            if (homeRoot != null && mHome != null && !isDestroyed()) {
        protected void finish(RootInfo homeRoot) {
            if (homeRoot != null && mHome != null) {
                // Clear entire backstack and start in new root
                mState.onRootChanged(homeRoot);
                mSearchManager.update(homeRoot);
                openContainerDocument(mHome);
                mOwner.mState.onRootChanged(homeRoot);
                mOwner.mSearchManager.update(homeRoot);
                mOwner.openContainerDocument(mHome);
            }
        }
    }
+113 −38
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.documentsui;

import static com.android.documentsui.Shared.DEBUG;
import static com.android.documentsui.State.ACTION_CREATE;
import static com.android.documentsui.State.ACTION_GET_CONTENT;
import static com.android.documentsui.State.ACTION_OPEN;
@@ -31,11 +32,12 @@ import android.content.ComponentName;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Parcelable;
import android.provider.DocumentsContract;
@@ -52,7 +54,12 @@ import com.android.documentsui.model.DurableUtils;
import com.android.documentsui.model.RootInfo;
import com.android.documentsui.services.FileOperationService;

import libcore.io.IoUtils;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

public class DocumentsActivity extends BaseActivity {
@@ -94,16 +101,14 @@ public class DocumentsActivity extends BaseActivity {
            // In this case, we set the activity title in AsyncTask.onPostExecute().  To prevent
            // talkback from reading aloud the default title, we clear it here.
            setTitle("");
            new RestoreStackTask().execute();
            new RestoreStackTask(this).execute();
        } else {
            refreshCurrentRootAndDirectory(ANIM_NONE);
        }
    }

    @Override
    State buildState() {
        State state = buildDefaultState();

    void includeState(State state) {
        final Intent intent = getIntent();
        final String action = intent.getAction();
        if (Intent.ACTION_OPEN_DOCUMENT.equals(action)) {
@@ -134,8 +139,6 @@ public class DocumentsActivity extends BaseActivity {
            state.transferMode = intent.getIntExtra(FileOperationService.EXTRA_OPERATION,
                    FileOperationService.OPERATION_COPY);
        }

        return state;
    }

    @Override
@@ -319,11 +322,13 @@ public class DocumentsActivity extends BaseActivity {
    }

    void onSaveRequested(DocumentInfo replaceTarget) {
        new ExistingFinishTask(replaceTarget.derivedUri).executeOnExecutor(getExecutorForCurrentDirectory());
        new ExistingFinishTask(this, replaceTarget.derivedUri)
                .executeOnExecutor(getExecutorForCurrentDirectory());
    }

    void onSaveRequested(String mimeType, String displayName) {
        new CreateFinishTask(mimeType, displayName).executeOnExecutor(getExecutorForCurrentDirectory());
        new CreateFinishTask(this, mimeType, displayName)
                .executeOnExecutor(getExecutorForCurrentDirectory());
    }

    @Override
@@ -343,7 +348,8 @@ public class DocumentsActivity extends BaseActivity {
            openContainerDocument(doc);
        } else if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) {
            // Explicit file picked, return
            new ExistingFinishTask(doc.derivedUri).executeOnExecutor(getExecutorForCurrentDirectory());
            new ExistingFinishTask(this, doc.derivedUri)
                    .executeOnExecutor(getExecutorForCurrentDirectory());
        } else if (mState.action == ACTION_CREATE) {
            // Replace selected file
            SaveFragment.get(fm).setReplaceTarget(doc);
@@ -358,7 +364,8 @@ public class DocumentsActivity extends BaseActivity {
            for (int i = 0; i < size; i++) {
                uris[i] = docs.get(i).derivedUri;
            }
            new ExistingFinishTask(uris).executeOnExecutor(getExecutorForCurrentDirectory());
            new ExistingFinishTask(this, uris)
                    .executeOnExecutor(getExecutorForCurrentDirectory());
        }
    }

@@ -373,11 +380,10 @@ public class DocumentsActivity extends BaseActivity {
            // Should not be reached.
            throw new IllegalStateException("Invalid mState.action.");
        }
        new PickFinishTask(result).executeOnExecutor(getExecutorForCurrentDirectory());
        new PickFinishTask(this, result).executeOnExecutor(getExecutorForCurrentDirectory());
    }

    @Override
    void saveStackBlocking() {
    void writeStackToRecentsBlocking() {
        final ContentResolver resolver = getContentResolver();
        final ContentValues values = new ContentValues();

@@ -438,69 +444,138 @@ public class DocumentsActivity extends BaseActivity {
        finish();
    }


    public static DocumentsActivity get(Fragment fragment) {
        return (DocumentsActivity) fragment.getActivity();
    }

    private final class PickFinishTask extends AsyncTask<Void, Void, Void> {
    /**
     * Restores the stack from Recents for the specified package.
     */
    private static final class RestoreStackTask
            extends PairedTask<DocumentsActivity, Void, Void> {

        private volatile boolean mRestoredStack;
        private volatile boolean mExternal;
        private Context mContext;
        private State mState;

        public RestoreStackTask(DocumentsActivity activity) {
            super(activity);
            mState = activity.mState;
        }

        @Override
        protected Void run(Void... params) {
            if (DEBUG && !mState.stack.isEmpty()) {
                Log.w(TAG, "Overwriting existing stack.");
            }
            RootsCache roots = DocumentsApplication.getRootsCache(mContext);

            String packageName = mOwner.getCallingPackageMaybeExtra();
            Uri resumeUri = RecentsProvider.buildResume(packageName);
            Cursor cursor = mOwner.getContentResolver().query(resumeUri, null, null, null, null);
            try {
                if (cursor.moveToFirst()) {
                    mExternal = cursor.getInt(cursor.getColumnIndex(ResumeColumns.EXTERNAL)) != 0;
                    final byte[] rawStack = cursor.getBlob(
                            cursor.getColumnIndex(ResumeColumns.STACK));
                    DurableUtils.readFromArray(rawStack, mState.stack);
                    mRestoredStack = true;
                }
            } catch (IOException e) {
                Log.w(TAG, "Failed to resume: " + e);
            } finally {
                IoUtils.closeQuietly(cursor);
            }

            if (mRestoredStack) {
                // Update the restored stack to ensure we have freshest data
                final Collection<RootInfo> matchingRoots = roots.getMatchingRootsBlocking(mState);
                try {
                    mState.stack.updateRoot(matchingRoots);
                    mState.stack.updateDocuments(mOwner.getContentResolver());
                } catch (FileNotFoundException e) {
                    Log.w(TAG, "Failed to restore stack for package: " + packageName
                            + " because of error: "+ e);
                    mState.stack.reset();
                    mRestoredStack = false;
                }
            }

            return null;
        }

        @Override
        protected void finish(Void result) {
            mState.restored = true;
            mOwner.refreshCurrentRootAndDirectory(ANIM_NONE);
            mOwner.onStackRestored(mRestoredStack, mExternal);
        }
    }

    private static final class PickFinishTask extends PairedTask<DocumentsActivity, Void, Void> {
        private final Uri mUri;

        public PickFinishTask(Uri uri) {
        public PickFinishTask(DocumentsActivity activity, Uri uri) {
            super(activity);
            mUri = uri;
        }

        @Override
        protected Void doInBackground(Void... params) {
            saveStackBlocking();
        protected Void run(Void... params) {
            mOwner.writeStackToRecentsBlocking();
            return null;
        }

        @Override
        protected void onPostExecute(Void result) {
            onTaskFinished(mUri);
        protected void finish(Void result) {
            mOwner.onTaskFinished(mUri);
        }
    }

    final class ExistingFinishTask extends AsyncTask<Void, Void, Void> {
    private static final class ExistingFinishTask extends PairedTask<DocumentsActivity, Void, Void> {
        private final Uri[] mUris;

        public ExistingFinishTask(Uri... uris) {
        public ExistingFinishTask(DocumentsActivity activity, Uri... uris) {
            super(activity);
            mUris = uris;
        }

        @Override
        protected Void doInBackground(Void... params) {
            saveStackBlocking();
        protected Void run(Void... params) {
            mOwner.writeStackToRecentsBlocking();
            return null;
        }

        @Override
        protected void onPostExecute(Void result) {
            onTaskFinished(mUris);
        protected void finish(Void result) {
            mOwner.onTaskFinished(mUris);
        }
    }

    /**
     * Task that creates a new document in the background.
     */
    final class CreateFinishTask extends AsyncTask<Void, Void, Uri> {
    private static final class CreateFinishTask extends PairedTask<DocumentsActivity, Void, Uri> {
        private final String mMimeType;
        private final String mDisplayName;

        public CreateFinishTask(String mimeType, String displayName) {
        public CreateFinishTask(DocumentsActivity activity, String mimeType, String displayName) {
            super(activity);
            mMimeType = mimeType;
            mDisplayName = displayName;
        }

        @Override
        protected void onPreExecute() {
            setPending(true);
        protected void prepare() {
            mOwner.setPending(true);
        }

        @Override
        protected Uri doInBackground(Void... params) {
            final ContentResolver resolver = getContentResolver();
            final DocumentInfo cwd = getCurrentDirectory();
        protected Uri run(Void... params) {
            final ContentResolver resolver = mOwner.getContentResolver();
            final DocumentInfo cwd = mOwner.getCurrentDirectory();

            ContentProviderClient client = null;
            Uri childUri = null;
@@ -516,22 +591,22 @@ public class DocumentsActivity extends BaseActivity {
            }

            if (childUri != null) {
                saveStackBlocking();
                mOwner.writeStackToRecentsBlocking();
            }

            return childUri;
        }

        @Override
        protected void onPostExecute(Uri result) {
        protected void finish(Uri result) {
            if (result != null) {
                onTaskFinished(result);
                mOwner.onTaskFinished(result);
            } else {
                Snackbars.makeSnackbar(
                    DocumentsActivity.this, R.string.save_error, Snackbar.LENGTH_SHORT).show();
                        mOwner, R.string.save_error, Snackbar.LENGTH_SHORT).show();
            }

            setPending(false);
            mOwner.setPending(false);
        }
    }
}
+2 −25
Original line number Diff line number Diff line
@@ -24,8 +24,6 @@ import android.app.Fragment;
import android.app.FragmentManager;
import android.content.ActivityNotFoundException;
import android.content.ClipData;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
@@ -37,10 +35,8 @@ import android.view.Menu;
import android.view.MenuItem;
import android.widget.Toolbar;

import com.android.documentsui.RecentsProvider.ResumeColumns;
import com.android.documentsui.dirlist.DirectoryFragment;
import com.android.documentsui.model.DocumentInfo;
import com.android.documentsui.model.DurableUtils;
import com.android.documentsui.model.RootInfo;
import com.android.internal.util.Preconditions;

@@ -72,23 +68,19 @@ public class DownloadsActivity extends BaseActivity {
            // talkback from reading aloud the default title, we clear it here.
            setTitle("");
            final Uri rootUri = getIntent().getData();
            new RestoreRootTask(rootUri).executeOnExecutor(getExecutorForCurrentDirectory());
            new RestoreRootTask(this, rootUri).executeOnExecutor(getExecutorForCurrentDirectory());
        } else {
            refreshCurrentRootAndDirectory(ANIM_NONE);
        }
    }

    @Override
    State buildState() {
        State state = buildDefaultState();

    void includeState(State state) {
        state.action = ACTION_MANAGE;
        state.acceptMimes = new String[] { "*/*" };
        state.allowMultiple = true;
        state.showSize = true;
        state.excludedAuthorities = getExcludedAuthorities();

        return state;
    }

    @Override
@@ -169,21 +161,6 @@ public class DownloadsActivity extends BaseActivity {
    @Override
    public void onDocumentsPicked(List<DocumentInfo> docs) {}

    @Override
    void saveStackBlocking() {
        final ContentResolver resolver = getContentResolver();
        final ContentValues values = new ContentValues();

        final byte[] rawStack = DurableUtils.writeToArrayOrNull(mState.stack);

        // Remember location for next app launch
        final String packageName = getCallingPackageMaybeExtra();
        values.clear();
        values.put(ResumeColumns.STACK, rawStack);
        values.put(ResumeColumns.EXTERNAL, 0);
        resolver.insert(RecentsProvider.buildResume(packageName), values);
    }

    @Override
    void onTaskFinished(Uri... uris) {
        Log.d(TAG, "onFinished() " + Arrays.toString(uris));
+24 −23

File changed.

Preview size limit exceeded, changes collapsed.

+77 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2016 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.
 */

package com.android.documentsui;

import android.app.Activity;
import android.os.AsyncTask;

/**
 * An {@link AsyncTask} that guards work with checks that a paired {@link Activity}
 * is still alive. Instances of this class make no progress.
 *
 * <p>Use this type of task for greater safety when executing tasks that might complete
 * after an Activity is destroyed.
 *
 * <p>Also useful as tasks can be static, limiting scope, but still have access to
 * the owning class (by way the A template and the mActivity field).
 *
 * @template Owner Activity type.
 * @template Input input type
 * @template Output output type
 */
abstract class PairedTask<Owner extends Activity, Input, Output>
        extends AsyncTask<Input, Void, Output> {

    protected final Owner mOwner;

    public PairedTask(Owner owner) {
        mOwner = owner;
    }

    /** Called prior to run being executed. Analogous to {@link AsyncTask#onPreExecute} */
    void prepare() {}

    /** Analogous to {@link AsyncTask#doInBackground} */
    abstract Output run(Input... input);

    /** Analogous to {@link AsyncTask#onPostExecute} */
    abstract void finish(Output output);

    @Override
    final protected void onPreExecute() {
        if (mOwner.isDestroyed()) {
            return;
        }
        prepare();
    }

    @Override
    final protected Output doInBackground(Input... input) {
        if (mOwner.isDestroyed()) {
            return null;
        }
        return run(input);
    }

    @Override
    final protected void onPostExecute(Output result) {
        if (mOwner.isDestroyed()) {
            return;
        }
        finish(result);
    }
}
Loading