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

Commit f13b79e7 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Merge commit 'b09f6012' into browse"

parents 056966ff 65655f53
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -344,4 +344,7 @@

    <!-- File name of an archive file created when compressing files, without the file extension (.zip). -->
    <string name="new_archive_file_name">archive<xliff:g id="extension" example=".zip">%s</xliff:g></string>

    <!-- Dialog text shown when confirming if they want to overwrite a file -->
    <string name="overwrite_file_confirmation_message">Overwrite <xliff:g id="name" example="foobar.txt">%1$s</xliff:g>?</string>
</resources>
+0 −9
Original line number Diff line number Diff line
@@ -547,15 +547,6 @@ public abstract class BaseActivity
        return mState.stack.peek();
    }

    public Executor getExecutorForCurrentDirectory() {
        final DocumentInfo cwd = getCurrentDirectory();
        if (cwd != null && cwd.authority != null) {
            return ProviderExecutor.forAuthority(cwd.authority);
        } else {
            return AsyncTask.THREAD_POOL_EXECUTOR;
        }
    }

    @Override
    public void onBackPressed() {
        // While action bar is expanded, the state stack UI is hidden.
+124 −6
Original line number Diff line number Diff line
@@ -17,11 +17,19 @@
package com.android.documentsui.picker;

import static com.android.documentsui.base.Shared.DEBUG;
import static com.android.documentsui.base.State.ACTION_CREATE;
import static com.android.documentsui.base.State.ACTION_GET_CONTENT;
import static com.android.documentsui.base.State.ACTION_OPEN_TREE;
import static com.android.documentsui.base.State.ACTION_PICK_COPY_DESTINATION;

import android.app.Activity;
import android.app.FragmentManager;
import android.content.ClipData;
import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Parcelable;
import android.provider.DocumentsContract;
import android.provider.Settings;
import android.util.Log;
@@ -31,6 +39,7 @@ import com.android.documentsui.ActivityConfig;
import com.android.documentsui.DocumentsAccess;
import com.android.documentsui.Injector;
import com.android.documentsui.Metrics;
import com.android.documentsui.base.BooleanConsumer;
import com.android.documentsui.base.DocumentInfo;
import com.android.documentsui.base.DocumentStack;
import com.android.documentsui.base.Features;
@@ -44,8 +53,10 @@ import com.android.documentsui.Model;
import com.android.documentsui.picker.ActionHandler.Addons;
import com.android.documentsui.queries.SearchViewManager;
import com.android.documentsui.roots.RootsAccess;
import com.android.documentsui.services.FileOperationService;
import com.android.internal.annotations.VisibleForTesting;

import java.util.Arrays;
import java.util.concurrent.Executor;

import javax.annotation.Nullable;
@@ -59,7 +70,8 @@ class ActionHandler<T extends Activity & Addons> extends AbstractActionHandler<T

    private final Features mFeatures;
    private final ActivityConfig mConfig;
    private @Nullable Model mModel;
    private final Model mModel;
    private final LastAccessedStorage mLastAccessed;

    ActionHandler(
            T activity,
@@ -68,13 +80,15 @@ class ActionHandler<T extends Activity & Addons> extends AbstractActionHandler<T
            DocumentsAccess docs,
            SearchViewManager searchMgr,
            Lookup<String, Executor> executors,
            Injector injector) {
            Injector injector,
            LastAccessedStorage lastAccessed) {

        super(activity, state, roots, docs, searchMgr, executors, injector);

        mConfig = injector.config;
        mFeatures = injector.features;
        mModel = injector.getModel();
        mLastAccessed = lastAccessed;
    }

    @Override
@@ -136,7 +150,8 @@ class ActionHandler<T extends Activity & Addons> extends AbstractActionHandler<T

    private void loadLastAccessedStack() {
        if (DEBUG) Log.d(TAG, "Attempting to load last used stack for calling package.");
        new LoadLastAccessedStackTask<>(mActivity, mState, mRoots, this::onLastAccessedStackLoaded)
        new LoadLastAccessedStackTask<>(
                mActivity, mLastAccessed, mState, mRoots, this::onLastAccessedStackLoaded)
                .execute();
    }

@@ -152,13 +167,13 @@ class ActionHandler<T extends Activity & Addons> extends AbstractActionHandler<T

    private void loadDefaultLocation() {
        switch (mState.action) {
            case State.ACTION_PICK_COPY_DESTINATION:
            case ACTION_PICK_COPY_DESTINATION:
            case State.ACTION_CREATE:
                loadHomeDir();
                break;
            case State.ACTION_GET_CONTENT:
            case ACTION_GET_CONTENT:
            case State.ACTION_OPEN:
            case State.ACTION_OPEN_TREE:
            case ACTION_OPEN_TREE:
                mState.stack.changeRoot(mRoots.getRecentsRoot());
                mActivity.refreshCurrentRootAndDirectory(AnimationView.ANIM_NONE);
                break;
@@ -221,6 +236,109 @@ class ActionHandler<T extends Activity & Addons> extends AbstractActionHandler<T
        return false;
    }

    void pickDocument(DocumentInfo pickTarget) {
        assert(pickTarget != null);
        Uri result;
        switch (mState.action) {
            case ACTION_OPEN_TREE:
                result = DocumentsContract.buildTreeDocumentUri(
                        pickTarget.authority, pickTarget.documentId);
                break;
            case ACTION_PICK_COPY_DESTINATION:
                result = pickTarget.derivedUri;
                break;
            default:
                // Should not be reached
                throw new IllegalStateException("Invalid mState.action");
        }
        finishPicking(result);
    }

    void saveDocument(
            String mimeType, String displayName, BooleanConsumer inProgressStateListener) {
        assert(mState.action == ACTION_CREATE);
        new CreatePickedDocumentTask(
                mActivity,
                mLastAccessed,
                mState.stack,
                mimeType,
                displayName,
                inProgressStateListener,
                this::onPickFinished)
                .executeOnExecutor(getExecutorForCurrentDirectory());
    }

    // User requested to overwrite a target. If confirmed by user #finishPicking() will be
    // called.
    void saveDocument(FragmentManager fm, DocumentInfo replaceTarget) {
        assert(mState.action == ACTION_CREATE);
        assert(replaceTarget != null);

        mInjector.dialogs.confirmOverwrite(fm, replaceTarget);
    }

    void finishPicking(Uri... docs) {
        new SetLastAccessedStackTask(
                mActivity,
                mLastAccessed,
                mState.stack,
                () -> {
                    onPickFinished(docs);
                }
        ) .executeOnExecutor(getExecutorForCurrentDirectory());
    }

    private void onPickFinished(Uri... uris) {
        if (DEBUG) Log.d(TAG, "onFinished() " + Arrays.toString(uris));

        final Intent intent = new Intent();
        if (uris.length == 1) {
            intent.setData(uris[0]);
        } else if (uris.length > 1) {
            final ClipData clipData = new ClipData(
                    null, mState.acceptMimes, new ClipData.Item(uris[0]));
            for (int i = 1; i < uris.length; i++) {
                clipData.addItem(new ClipData.Item(uris[i]));
            }
            intent.setClipData(clipData);
        }

        // TODO: Separate this piece of logic per action.
        // We don't instantiate different objects for different actions at the first place, so it's
        // not a easy task to separate this logic cleanly.
        // Maybe we can add an ActionPolicy class for IoC and provide various behaviors through its
        // inheritance structure.
        if (mState.action == ACTION_GET_CONTENT) {
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        } else if (mState.action == ACTION_OPEN_TREE) {
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
                    | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
                    | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
                    | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
        } else if (mState.action == ACTION_PICK_COPY_DESTINATION) {
            // Picking a copy destination is only used internally by us, so we
            // don't need to extend permissions to the caller.
            intent.putExtra(Shared.EXTRA_STACK, (Parcelable) mState.stack);
            intent.putExtra(FileOperationService.EXTRA_OPERATION_TYPE, mState.copyOperationSubType);
        } else {
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
                    | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
                    | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
        }

        mActivity.setResult(Activity.RESULT_OK, intent);
        mActivity.finish();
    }

    private Executor getExecutorForCurrentDirectory() {
        final DocumentInfo cwd = mActivity.getCurrentDirectory();
        if (cwd != null && cwd.authority != null) {
            return mExecutors.lookup(cwd.authority);
        } else {
            return AsyncTask.THREAD_POOL_EXECUTOR;
        }
    }

    public interface Addons extends CommonAddons {
        void onAppPicked(ResolveInfo info);
        void onDocumentPicked(DocumentInfo doc);
+108 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2017 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.picker;

import android.app.Activity;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.net.Uri;
import android.provider.DocumentsContract;
import android.support.design.widget.Snackbar;
import android.util.Log;

import com.android.documentsui.DocumentsApplication;
import com.android.documentsui.R;
import com.android.documentsui.base.BooleanConsumer;
import com.android.documentsui.base.DocumentInfo;
import com.android.documentsui.base.DocumentStack;
import com.android.documentsui.base.PairedTask;
import com.android.documentsui.ui.Snackbars;

import java.util.function.Consumer;

/**
 * Task that creates a new document in the background.
 */
class CreatePickedDocumentTask extends PairedTask<Activity, Void, Uri> {
    private static final String TAG = "CreatePickedDocumentTas";

    private final LastAccessedStorage mLastAccessed;
    private final DocumentStack mStack;
    private final String mMimeType;
    private final String mDisplayName;
    private final BooleanConsumer mInProgressStateListener;
    private final Consumer<Uri> mCallback;

    CreatePickedDocumentTask(
            Activity activity,
            LastAccessedStorage lastAccessed,
            DocumentStack stack,
            String mimeType,
            String displayName,
            BooleanConsumer inProgressStateListener,
            Consumer<Uri> callback) {
        super(activity);
        mLastAccessed = lastAccessed;
        mStack = stack;
        mMimeType = mimeType;
        mDisplayName = displayName;
        mInProgressStateListener = inProgressStateListener;
        mCallback = callback;
    }

    @Override
    protected void prepare() {
        mInProgressStateListener.accept(true);
    }

    @Override
    protected Uri run(Void... params) {
        DocumentInfo cwd = mStack.peek();

        final ContentResolver resolver = mOwner.getContentResolver();
        ContentProviderClient client = null;
        Uri childUri = null;
        try {
            client = DocumentsApplication.acquireUnstableProviderOrThrow(
                    resolver, cwd.derivedUri.getAuthority());
            childUri = DocumentsContract.createDocument(
                    client, cwd.derivedUri, mMimeType, mDisplayName);
        } catch (Exception e) {
            Log.w(TAG, "Failed to create document", e);
        } finally {
            ContentProviderClient.releaseQuietly(client);
        }

        if (childUri != null) {
            mLastAccessed.setLastAccessed(mOwner, mStack);
        }

        return childUri;
    }

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

        mInProgressStateListener.accept(false);
    }
}
+7 −1
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.documentsui.picker;

import static com.android.documentsui.base.DocumentInfo.getCursorString;

import android.app.Activity;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.ContentValues;
@@ -114,7 +115,12 @@ public class LastAccessedProvider extends ContentProvider {
        }
    }

    public static void setLastAccessed(
    /**
     * Rather than concretely depending on LastAccessedProvider, consider using
     * {@link LastAccessedStorage#setLastAccessed(Activity, DocumentStack)}.
     */
    @Deprecated
    static void setLastAccessed(
            ContentResolver resolver, String packageName, DocumentStack stack) {
        final ContentValues values = new ContentValues();
        final byte[] rawStack = DurableUtils.writeToArrayOrNull(stack);
Loading