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

Commit ef3f2620 authored by Ben Kwa's avatar Ben Kwa
Browse files

Prototype the destination picking.

- Add an intent to open a destination picker, and refactor
DocumentsActivity accordingly.
- Modify CopyService to take a destination for the copy, and to use URIs
and PFDs instead of Files and Streams, for better error handling &
cleanup.

Change-Id: I69dc43823a703674dc29d2215e2df23b33ad7882
parent d99109fc
Loading
Loading
Loading
Loading
+35 −22
Original line number Diff line number Diff line
@@ -24,7 +24,11 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.CancellationSignal;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
import android.os.SystemClock;
import android.provider.DocumentsContract;
import android.text.format.DateUtils;
import android.util.Log;

@@ -82,18 +86,15 @@ public class CopyService extends IntentService {
        }

        ArrayList<DocumentInfo> srcs = intent.getParcelableArrayListExtra(EXTRA_SRC_LIST);
        // Use the app local files dir as a copy destination for now. This resolves to
        // /data/data/com.android.documentsui/files.
        // TODO: Add actual destination picking.
        File destinationDir = getFilesDir();
        Uri destinationUri = intent.getData();

        setupCopyJob(srcs, destinationDir);
        setupCopyJob(srcs, destinationUri);

        ArrayList<String> failedFilenames = new ArrayList<String>();
        for (int i = 0; i < srcs.size() && !mIsCancelled; ++i) {
            DocumentInfo src = srcs.get(i);
            try {
                copyFile(src, destinationDir);
                copyFile(src, destinationUri);
            } catch (IOException e) {
                Log.e(TAG, "Failed to copy " + src.displayName, e);
                failedFilenames.add(src.displayName);
@@ -121,8 +122,9 @@ public class CopyService extends IntentService {
     * files.
     *
     * @param srcs A list of src files to copy.
     * @param destinationUri The URI of the destination directory.
     */
    private void setupCopyJob(ArrayList<DocumentInfo> srcs, File destinationDir) {
    private void setupCopyJob(ArrayList<DocumentInfo> srcs, Uri destinationUri) {
        // Create an ID for this copy job. Use the timestamp.
        mJobId = String.valueOf(SystemClock.elapsedRealtime());
        // Reset the cancellation flag.
@@ -238,42 +240,53 @@ public class CopyService extends IntentService {
     * Copies a file to a given location.
     *
     * @param srcInfo The source file.
     * @param destination The directory to copy into.
     * @param destinationUri The URI of the destination directory.
     * @throws IOException
     */
    private void copyFile(DocumentInfo srcInfo, File destinationDir)
            throws IOException {
    private void copyFile(DocumentInfo srcInfo, Uri destinationUri) throws IOException {
        final Context context = getApplicationContext();
        final ContentResolver resolver = context.getContentResolver();
        final File destinationFile = new File(destinationDir, srcInfo.displayName);
        final Uri destinationUri = Uri.fromFile(destinationFile);

        InputStream source = null;
        OutputStream destination = null;
        final Uri writableDstUri = DocumentsContract.buildDocumentUriUsingTree(destinationUri,
                DocumentsContract.getTreeDocumentId(destinationUri));
        final Uri dstFileUri = DocumentsContract.createDocument(resolver, writableDstUri,
                srcInfo.mimeType, srcInfo.displayName);

        CancellationSignal canceller = new CancellationSignal();
        ParcelFileDescriptor srcFile = null;
        ParcelFileDescriptor dstFile = null;
        InputStream src = null;
        OutputStream dst = null;

        boolean errorOccurred = false;
        try {
            source = resolver.openInputStream(srcInfo.derivedUri);
            destination = resolver.openOutputStream(destinationUri);
            srcFile = resolver.openFileDescriptor(srcInfo.derivedUri, "r", canceller);
            dstFile = resolver.openFileDescriptor(dstFileUri, "w", canceller);
            src = new ParcelFileDescriptor.AutoCloseInputStream(srcFile);
            dst = new ParcelFileDescriptor.AutoCloseOutputStream(dstFile);

            byte[] buffer = new byte[8192];
            int len;
            while (!mIsCancelled && ((len = source.read(buffer)) != -1)) {
                destination.write(buffer, 0, len);
            while (!mIsCancelled && ((len = src.read(buffer)) != -1)) {
                dst.write(buffer, 0, len);
                makeProgress(len);
            }
            srcFile.checkError();
            dstFile.checkError();
        } catch (IOException e) {
            errorOccurred = true;
            Log.e(TAG, "Error while copying " + srcInfo.displayName, e);
        } finally {
            IoUtils.closeQuietly(source);
            IoUtils.closeQuietly(destination);
            // This also ensures the file descriptors are closed.
            IoUtils.closeQuietly(src);
            IoUtils.closeQuietly(dst);
        }

        if (errorOccurred || mIsCancelled) {
            // Clean up half-copied files.
            if (!destinationFile.delete()) {
                Log.w(TAG, "Failed to clean up partially copied file " + srcInfo.displayName);
            canceller.cancel();
            if (!DocumentsContract.deleteDocument(resolver, dstFileUri)) {
                Log.w(TAG, "Failed to clean up: " + srcInfo.displayName);
            }
        }
    }
+40 −10
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import static com.android.documentsui.model.DocumentInfo.getCursorInt;
import static com.android.documentsui.model.DocumentInfo.getCursorLong;
import static com.android.documentsui.model.DocumentInfo.getCursorString;

import android.app.Activity;
import android.app.ActivityManager;
import android.app.Fragment;
import android.app.FragmentManager;
@@ -99,6 +100,8 @@ public class DirectoryFragment extends Fragment {

    private AbsListView mCurrentView;

    private List<DocumentInfo> mSelectedDocumentsForCopy;

    public static final int TYPE_NORMAL = 1;
    public static final int TYPE_SEARCH = 2;
    public static final int TYPE_RECENT_OPEN = 3;
@@ -108,6 +111,8 @@ public class DirectoryFragment extends Fragment {
    public static final int ANIM_DOWN = 3;
    public static final int ANIM_UP = 4;

    public static final int REQUEST_COPY_DESTINATION = 1;

    private int mType = TYPE_NORMAL;
    private String mStateKey;

@@ -336,6 +341,36 @@ public class DirectoryFragment extends Fragment {
        updateDisplayState();
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        final Context context = getActivity();
        final Resources res = context.getResources();

        // There's only one request code right now. Replace this with a switch statement or
        // something more scalable when more codes are added.
        if (requestCode != REQUEST_COPY_DESTINATION) {
            return;
        }
        if (resultCode == Activity.RESULT_CANCELED || data == null) {
            // User pressed the back button or otherwise cancelled the destination pick. Don't
            // proceed with the copy.
            return;
        }

        Uri destination = data.getData();

        List<DocumentInfo> docs = mSelectedDocumentsForCopy;
        Intent copyIntent = new Intent(context, CopyService.class);
        copyIntent.putParcelableArrayListExtra(CopyService.EXTRA_SRC_LIST,
                new ArrayList<DocumentInfo>(docs));
        copyIntent.setData(destination);

        Toast.makeText(context,
                res.getQuantityString(R.plurals.copy_begin, docs.size(), docs.size()),
                Toast.LENGTH_SHORT).show();
        context.startService(copyIntent);
    }

    @Override
    public void onStop() {
        super.onStop();
@@ -634,17 +669,12 @@ public class DirectoryFragment extends Fragment {
    }

    private void onCopyDocuments(List<DocumentInfo> docs) {
        final Context context = getActivity();
        final Resources res = context.getResources();
        mSelectedDocumentsForCopy = docs;

        Intent copyIntent = new Intent(context, CopyService.class);
        copyIntent.putParcelableArrayListExtra(CopyService.EXTRA_SRC_LIST,
                new ArrayList<DocumentInfo>(docs));

        Toast.makeText(context,
                res.getQuantityString(R.plurals.copy_begin, docs.size(), docs.size()),
                Toast.LENGTH_SHORT).show();
        context.startService(copyIntent);
        // Pop up a dialog to pick a destination.  This is inadequate but works for now.
        // TODO: Implement a picker that is to spec.
        Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
        startActivityForResult(intent, REQUEST_COPY_DESTINATION);
    }

    private static State getDisplayState(Fragment fragment) {