CopyService.java revision 61686593fd32babb6e86754ea5bfa6bc95cd3690
1d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa/* 2d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa * Copyright (C) 2015 The Android Open Source Project 3d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa * 4d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa * Licensed under the Apache License, Version 2.0 (the "License"); 5d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa * you may not use this file except in compliance with the License. 6d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa * You may obtain a copy of the License at 7d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa * 8d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa * http://www.apache.org/licenses/LICENSE-2.0 9d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa * 10d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa * Unless required by applicable law or agreed to in writing, software 11d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa * distributed under the License is distributed on an "AS IS" BASIS, 12d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa * See the License for the specific language governing permissions and 14d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa * limitations under the License. 15d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa */ 16d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa 17d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwapackage com.android.documentsui; 18d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa 19726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwaimport static com.android.documentsui.model.DocumentInfo.getCursorLong; 20726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwaimport static com.android.documentsui.model.DocumentInfo.getCursorString; 21726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa 22d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwaimport android.app.IntentService; 23d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwaimport android.app.Notification; 24d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwaimport android.app.NotificationManager; 25d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwaimport android.app.PendingIntent; 26726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwaimport android.content.ContentProviderClient; 27d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwaimport android.content.Context; 28d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwaimport android.content.Intent; 29726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwaimport android.database.Cursor; 30d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwaimport android.net.Uri; 31ef3f2620b3a755856d70345fc7a90df896985c26Ben Kwaimport android.os.CancellationSignal; 32ef3f2620b3a755856d70345fc7a90df896985c26Ben Kwaimport android.os.ParcelFileDescriptor; 33e1a03f8eec2719279037ab348df306764dc45a70Tomasz Mikolajewskiimport android.os.Parcelable; 34726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwaimport android.os.RemoteException; 35d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwaimport android.os.SystemClock; 36ef3f2620b3a755856d70345fc7a90df896985c26Ben Kwaimport android.provider.DocumentsContract; 37726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwaimport android.provider.DocumentsContract.Document; 38d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwaimport android.text.format.DateUtils; 39d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwaimport android.util.Log; 40d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa 41d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwaimport com.android.documentsui.model.DocumentInfo; 42e1a03f8eec2719279037ab348df306764dc45a70Tomasz Mikolajewskiimport com.android.documentsui.model.DocumentStack; 43d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa 44d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwaimport libcore.io.IoUtils; 45d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa 46d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwaimport java.io.IOException; 47d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwaimport java.io.InputStream; 48d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwaimport java.io.OutputStream; 49d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwaimport java.text.NumberFormat; 50d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwaimport java.util.ArrayList; 51726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwaimport java.util.List; 52726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwaimport java.util.Objects; 53d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa 54d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwapublic class CopyService extends IntentService { 55d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa public static final String TAG = "CopyService"; 56e1a03f8eec2719279037ab348df306764dc45a70Tomasz Mikolajewski 57d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa private static final String EXTRA_CANCEL = "com.android.documentsui.CANCEL"; 58e1a03f8eec2719279037ab348df306764dc45a70Tomasz Mikolajewski public static final String EXTRA_SRC_LIST = "com.android.documentsui.SRC_LIST"; 59e1a03f8eec2719279037ab348df306764dc45a70Tomasz Mikolajewski public static final String EXTRA_STACK = "com.android.documentsui.STACK"; 6061686593fd32babb6e86754ea5bfa6bc95cd3690Tomasz Mikolajewski public static final String EXTRA_FAILURE = "com.android.documentsui.FAILURE"; 6161686593fd32babb6e86754ea5bfa6bc95cd3690Tomasz Mikolajewski 6261686593fd32babb6e86754ea5bfa6bc95cd3690Tomasz Mikolajewski // TODO: Move it to a shared file when more operations are implemented. 6361686593fd32babb6e86754ea5bfa6bc95cd3690Tomasz Mikolajewski public static final int FAILURE_COPY = 1; 64d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa 65d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa private NotificationManager mNotificationManager; 66d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa private Notification.Builder mProgressBuilder; 67d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa 68d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa // Jobs are serialized but a job ID is used, to avoid mixing up cancellation requests. 69d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa private String mJobId; 70d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa private volatile boolean mIsCancelled; 71d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa // Parameters of the copy job. Requests to an IntentService are serialized so this code only 72d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa // needs to deal with one job at a time. 7361686593fd32babb6e86754ea5bfa6bc95cd3690Tomasz Mikolajewski private final ArrayList<Uri> mFailedFiles; 74d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa private long mBatchSize; 75d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa private long mBytesCopied; 76d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa private long mStartTime; 77d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa private long mLastNotificationTime; 78d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa // Speed estimation 79d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa private long mBytesCopiedSample; 80d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa private long mSampleTime; 81d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa private long mSpeed; 82d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa private long mRemainingTime; 83726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa // Provider clients are acquired for the duration of each copy job. Note that there is an 84726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa // implicit assumption that all srcs come from the same authority. 85726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa private ContentProviderClient mSrcClient; 86726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa private ContentProviderClient mDstClient; 87d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa 88d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa public CopyService() { 89d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa super("CopyService"); 90726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa 91726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa mFailedFiles = new ArrayList<Uri>(); 92d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa } 93d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa 94d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa @Override 95d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa public int onStartCommand(Intent intent, int flags, int startId) { 96d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa if (intent.hasExtra(EXTRA_CANCEL)) { 97d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa handleCancel(intent); 98d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa } 99d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa return super.onStartCommand(intent, flags, startId); 100d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa } 101d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa 102d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa @Override 103d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa protected void onHandleIntent(Intent intent) { 104d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa if (intent.hasExtra(EXTRA_CANCEL)) { 105d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa handleCancel(intent); 106d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa return; 107d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa } 108d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa 109e1a03f8eec2719279037ab348df306764dc45a70Tomasz Mikolajewski final ArrayList<DocumentInfo> srcs = intent.getParcelableArrayListExtra(EXTRA_SRC_LIST); 110e1a03f8eec2719279037ab348df306764dc45a70Tomasz Mikolajewski final DocumentStack stack = intent.getParcelableExtra(EXTRA_STACK); 111d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa 112726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa try { 113726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa // Acquire content providers. 114726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa mSrcClient = DocumentsApplication.acquireUnstableProviderOrThrow(getContentResolver(), 115726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa srcs.get(0).authority); 116726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa mDstClient = DocumentsApplication.acquireUnstableProviderOrThrow(getContentResolver(), 117e1a03f8eec2719279037ab348df306764dc45a70Tomasz Mikolajewski stack.peek().authority); 118d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa 119e1a03f8eec2719279037ab348df306764dc45a70Tomasz Mikolajewski setupCopyJob(srcs, stack); 120726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa 121726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa for (int i = 0; i < srcs.size() && !mIsCancelled; ++i) { 122e1a03f8eec2719279037ab348df306764dc45a70Tomasz Mikolajewski copy(srcs.get(i), stack.peek()); 123d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa } 124726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa } catch (Exception e) { 125726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa // Catch-all to prevent any copy errors from wedging the app. 126726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa Log.e(TAG, "Exceptions occurred during copying", e); 127726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa } finally { 128726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa ContentProviderClient.releaseQuietly(mSrcClient); 129726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa ContentProviderClient.releaseQuietly(mDstClient); 130d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa 131726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa // Dismiss the ongoing copy notification when the copy is done. 132726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa mNotificationManager.cancel(mJobId, 0); 133d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa 134726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa if (mFailedFiles.size() > 0) { 13561686593fd32babb6e86754ea5bfa6bc95cd3690Tomasz Mikolajewski final Context context = getApplicationContext(); 13661686593fd32babb6e86754ea5bfa6bc95cd3690Tomasz Mikolajewski final Intent navigateIntent = new Intent(context, StandaloneActivity.class); 13761686593fd32babb6e86754ea5bfa6bc95cd3690Tomasz Mikolajewski navigateIntent.putExtra(EXTRA_STACK, (Parcelable) stack); 13861686593fd32babb6e86754ea5bfa6bc95cd3690Tomasz Mikolajewski navigateIntent.putExtra(EXTRA_FAILURE, FAILURE_COPY); 13961686593fd32babb6e86754ea5bfa6bc95cd3690Tomasz Mikolajewski navigateIntent.putParcelableArrayListExtra(EXTRA_SRC_LIST, mFailedFiles); 14061686593fd32babb6e86754ea5bfa6bc95cd3690Tomasz Mikolajewski 14161686593fd32babb6e86754ea5bfa6bc95cd3690Tomasz Mikolajewski final Notification.Builder errorBuilder = new Notification.Builder(this) 14261686593fd32babb6e86754ea5bfa6bc95cd3690Tomasz Mikolajewski .setContentTitle(context.getResources(). 14361686593fd32babb6e86754ea5bfa6bc95cd3690Tomasz Mikolajewski getQuantityString(R.plurals.copy_error_notification_title, 14461686593fd32babb6e86754ea5bfa6bc95cd3690Tomasz Mikolajewski mFailedFiles.size(), mFailedFiles.size())) 14561686593fd32babb6e86754ea5bfa6bc95cd3690Tomasz Mikolajewski .setContentText(getString(R.string.notification_touch_for_details)) 14661686593fd32babb6e86754ea5bfa6bc95cd3690Tomasz Mikolajewski .setContentIntent(PendingIntent.getActivity(context, 0, navigateIntent, 14761686593fd32babb6e86754ea5bfa6bc95cd3690Tomasz Mikolajewski PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_ONE_SHOT)) 14861686593fd32babb6e86754ea5bfa6bc95cd3690Tomasz Mikolajewski .setCategory(Notification.CATEGORY_ERROR) 14961686593fd32babb6e86754ea5bfa6bc95cd3690Tomasz Mikolajewski .setSmallIcon(R.drawable.ic_menu_copy) 15061686593fd32babb6e86754ea5bfa6bc95cd3690Tomasz Mikolajewski .setAutoCancel(true); 15161686593fd32babb6e86754ea5bfa6bc95cd3690Tomasz Mikolajewski mNotificationManager.notify(mJobId, 0, errorBuilder.build()); 152726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa } 153d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa 154726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa // TODO: Display a toast if the copy was cancelled. 155726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa } 156d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa } 157d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa 158d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa @Override 159d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa public void onCreate() { 160d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa super.onCreate(); 161d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); 162d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa } 163d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa 164d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa /** 165d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa * Sets up the CopyService to start tracking and sending notifications for the given batch of 166d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa * files. 167d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa * 168d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa * @param srcs A list of src files to copy. 169e1a03f8eec2719279037ab348df306764dc45a70Tomasz Mikolajewski * @param stack The copy destination stack. 170726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa * @throws RemoteException 171d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa */ 172e1a03f8eec2719279037ab348df306764dc45a70Tomasz Mikolajewski private void setupCopyJob(ArrayList<DocumentInfo> srcs, DocumentStack stack) 173726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa throws RemoteException { 174d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa // Create an ID for this copy job. Use the timestamp. 175d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa mJobId = String.valueOf(SystemClock.elapsedRealtime()); 176d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa // Reset the cancellation flag. 177d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa mIsCancelled = false; 178d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa 179e1a03f8eec2719279037ab348df306764dc45a70Tomasz Mikolajewski final Context context = getApplicationContext(); 180e1a03f8eec2719279037ab348df306764dc45a70Tomasz Mikolajewski final Intent navigateIntent = new Intent(context, StandaloneActivity.class); 18161686593fd32babb6e86754ea5bfa6bc95cd3690Tomasz Mikolajewski navigateIntent.putExtra(EXTRA_STACK, (Parcelable) stack); 182e1a03f8eec2719279037ab348df306764dc45a70Tomasz Mikolajewski 183d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa mProgressBuilder = new Notification.Builder(this) 184d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa .setContentTitle(getString(R.string.copy_notification_title)) 185e1a03f8eec2719279037ab348df306764dc45a70Tomasz Mikolajewski .setContentIntent(PendingIntent.getActivity(context, 0, navigateIntent, 0)) 186d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa .setCategory(Notification.CATEGORY_PROGRESS) 18761686593fd32babb6e86754ea5bfa6bc95cd3690Tomasz Mikolajewski .setSmallIcon(R.drawable.ic_menu_copy) 18861686593fd32babb6e86754ea5bfa6bc95cd3690Tomasz Mikolajewski .setOngoing(true); 189d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa 190e1a03f8eec2719279037ab348df306764dc45a70Tomasz Mikolajewski final Intent cancelIntent = new Intent(this, CopyService.class); 191d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa cancelIntent.putExtra(EXTRA_CANCEL, mJobId); 192d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa mProgressBuilder.addAction(R.drawable.ic_cab_cancel, 193d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa getString(R.string.cancel), PendingIntent.getService(this, 0, 194d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa cancelIntent, PendingIntent.FLAG_ONE_SHOT)); 195d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa 196d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa // Send an initial progress notification. 197726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa mProgressBuilder.setProgress(0, 0, true); // Indeterminate progress while setting up. 198726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa mProgressBuilder.setContentText(getString(R.string.copy_preparing)); 199d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa mNotificationManager.notify(mJobId, 0, mProgressBuilder.build()); 200d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa 201d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa // Reset batch parameters. 202726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa mFailedFiles.clear(); 203726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa mBatchSize = calculateFileSizes(srcs); 204d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa mBytesCopied = 0; 205d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa mStartTime = SystemClock.elapsedRealtime(); 206d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa mLastNotificationTime = 0; 207d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa mBytesCopiedSample = 0; 208d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa mSampleTime = 0; 209d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa mSpeed = 0; 210d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa mRemainingTime = 0; 211d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa 212d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa // TODO: Check preconditions for copy. 213d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa // - check that the destination has enough space and is writeable? 214d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa // - check MIME types? 215d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa } 216d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa 217d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa /** 218726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa * Calculates the cumulative size of all the documents in the list. Directories are recursed 219726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa * into and totaled up. 220726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa * 221726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa * @param srcs 222726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa * @return Size in bytes. 223726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa * @throws RemoteException 224726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa */ 225726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa private long calculateFileSizes(List<DocumentInfo> srcs) throws RemoteException { 226726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa long result = 0; 227726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa for (DocumentInfo src : srcs) { 228726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa if (Document.MIME_TYPE_DIR.equals(src.mimeType)) { 229726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa // Directories need to be recursed into. 230726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa result += calculateFileSizesHelper(src.derivedUri); 231726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa } else { 232726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa result += src.size; 233726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa } 234726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa } 235726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa return result; 236726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa } 237726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa 238726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa /** 239726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa * Calculates (recursively) the cumulative size of all the files under the given directory. 240726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa * 241726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa * @throws RemoteException 242726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa */ 243726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa private long calculateFileSizesHelper(Uri uri) throws RemoteException { 244726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa final String authority = uri.getAuthority(); 245726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa final Uri queryUri = DocumentsContract.buildChildDocumentsUri(authority, 246726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa DocumentsContract.getDocumentId(uri)); 247726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa final String queryColumns[] = new String[] { 248726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa Document.COLUMN_DOCUMENT_ID, 249726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa Document.COLUMN_MIME_TYPE, 250726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa Document.COLUMN_SIZE 251726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa }; 252726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa 253726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa long result = 0; 254726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa Cursor cursor = null; 255726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa try { 256726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa cursor = mSrcClient.query(queryUri, queryColumns, null, null, null); 257726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa while (cursor.moveToNext()) { 258726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa if (Document.MIME_TYPE_DIR.equals( 259726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa getCursorString(cursor, Document.COLUMN_MIME_TYPE))) { 260726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa // Recurse into directories. 261726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa final Uri subdirUri = DocumentsContract.buildDocumentUri(authority, 262726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa getCursorString(cursor, Document.COLUMN_DOCUMENT_ID)); 263726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa result += calculateFileSizesHelper(subdirUri); 264726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa } else { 265726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa // This may return -1 if the size isn't defined. Ignore those cases. 266726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa long size = getCursorLong(cursor, Document.COLUMN_SIZE); 267726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa result += size > 0 ? size : 0; 268726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa } 269726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa } 270726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa } finally { 271726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa IoUtils.closeQuietly(cursor); 272726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa } 273726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa 274726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa return result; 275726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa } 276726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa 277726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa /** 278d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa * Cancels the current copy job, if its ID matches the given ID. 279d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa * 280d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa * @param intent The cancellation intent. 281d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa */ 282d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa private void handleCancel(Intent intent) { 283d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa final String cancelledId = intent.getStringExtra(EXTRA_CANCEL); 284d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa // Do nothing if the cancelled ID doesn't match the current job ID. This prevents racey 285d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa // cancellation requests from affecting unrelated copy jobs. 286726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa if (Objects.equals(mJobId, cancelledId)) { 287d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa // Set the cancel flag. This causes the copy loops to exit. 288d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa mIsCancelled = true; 289d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa // Dismiss the progress notification here rather than in the copy loop. This preserves 290d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa // interactivity for the user in case the copy loop is stalled. 291d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa mNotificationManager.cancel(mJobId, 0); 292d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa } 293d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa } 294d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa 295d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa /** 296d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa * Logs progress on the current copy operation. Displays/Updates the progress notification. 297d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa * 298d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa * @param bytesCopied 299d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa */ 300d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa private void makeProgress(long bytesCopied) { 301d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa mBytesCopied += bytesCopied; 302d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa double done = (double) mBytesCopied / mBatchSize; 303d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa String percent = NumberFormat.getPercentInstance().format(done); 304d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa 305d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa // Update time estimate 306d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa long currentTime = SystemClock.elapsedRealtime(); 307d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa long elapsedTime = currentTime - mStartTime; 308d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa 309d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa // Send out progress notifications once a second. 310d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa if (currentTime - mLastNotificationTime > 1000) { 311d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa updateRemainingTimeEstimate(elapsedTime); 312d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa mProgressBuilder.setProgress(100, (int) (done * 100), false); 313d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa mProgressBuilder.setContentInfo(percent); 314d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa if (mRemainingTime > 0) { 315d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa mProgressBuilder.setContentText(getString(R.string.copy_remaining, 316d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa DateUtils.formatDuration(mRemainingTime))); 317d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa } else { 318d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa mProgressBuilder.setContentText(null); 319d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa } 320d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa mNotificationManager.notify(mJobId, 0, mProgressBuilder.build()); 321d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa mLastNotificationTime = currentTime; 322d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa } 323d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa } 324d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa 325d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa /** 326d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa * Generates an estimate of the remaining time in the copy. 327d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa * 328d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa * @param elapsedTime The time elapsed so far. 329d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa */ 330d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa private void updateRemainingTimeEstimate(long elapsedTime) { 331d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa final long sampleDuration = elapsedTime - mSampleTime; 332d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa final long sampleSpeed = ((mBytesCopied - mBytesCopiedSample) * 1000) / sampleDuration; 333d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa if (mSpeed == 0) { 334d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa mSpeed = sampleSpeed; 335d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa } else { 336d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa mSpeed = ((3 * mSpeed) + sampleSpeed) / 4; 337d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa } 338d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa 339d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa if (mSampleTime > 0 && mSpeed > 0) { 340d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa mRemainingTime = ((mBatchSize - mBytesCopied) * 1000) / mSpeed; 341d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa } else { 342d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa mRemainingTime = 0; 343d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa } 344d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa 345d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa mSampleTime = elapsedTime; 346d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa mBytesCopiedSample = mBytesCopied; 347d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa } 348d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa 349d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa /** 350726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa * Copies a the given documents to the given location. 351d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa * 352726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa * @param srcInfo DocumentInfos for the documents to copy. 353e1a03f8eec2719279037ab348df306764dc45a70Tomasz Mikolajewski * @param dstDirInfo The destination directory. 354726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa * @throws RemoteException 355d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa */ 356e1a03f8eec2719279037ab348df306764dc45a70Tomasz Mikolajewski private void copy(DocumentInfo srcInfo, DocumentInfo dstDirInfo) throws RemoteException { 357e1a03f8eec2719279037ab348df306764dc45a70Tomasz Mikolajewski final Uri dstUri = DocumentsContract.createDocument(mDstClient, dstDirInfo.derivedUri, 358ef3f2620b3a755856d70345fc7a90df896985c26Ben Kwa srcInfo.mimeType, srcInfo.displayName); 359726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa if (dstUri == null) { 360726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa // If this is a directory, the entire subdir will not be copied over. 361726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa Log.e(TAG, "Error while copying " + srcInfo.displayName); 362726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa mFailedFiles.add(srcInfo.derivedUri); 363726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa return; 364726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa } 365726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa 366726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa if (Document.MIME_TYPE_DIR.equals(srcInfo.mimeType)) { 367726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa copyDirectoryHelper(srcInfo.derivedUri, dstUri); 368726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa } else { 369726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa copyFileHelper(srcInfo.derivedUri, dstUri); 370726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa } 371726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa } 372ef3f2620b3a755856d70345fc7a90df896985c26Ben Kwa 373726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa /** 374726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa * Handles recursion into a directory and copying its contents. Note that in linux terms, this 375726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa * does the equivalent of "cp src/* dst", not "cp -r src dst". 376726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa * 377726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa * @param srcDirUri URI of the directory to copy from. The routine will copy the directory's 378726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa * contents, not the directory itself. 379726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa * @param dstDirUri URI of the directory to copy to. Must be created beforehand. 380726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa * @throws RemoteException 381726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa */ 382726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa private void copyDirectoryHelper(Uri srcDirUri, Uri dstDirUri) throws RemoteException { 383726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa // Recurse into directories. Copy children into the new subdirectory. 384726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa final String queryColumns[] = new String[] { 385726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa Document.COLUMN_DISPLAY_NAME, 386726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa Document.COLUMN_DOCUMENT_ID, 387726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa Document.COLUMN_MIME_TYPE, 388726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa Document.COLUMN_SIZE 389726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa }; 390726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa final Uri queryUri = DocumentsContract.buildChildDocumentsUri(srcDirUri.getAuthority(), 391726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa DocumentsContract.getDocumentId(srcDirUri)); 392726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa Cursor cursor = null; 393726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa try { 394726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa // Iterate over srcs in the directory; copy to the destination directory. 395726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa cursor = mSrcClient.query(queryUri, queryColumns, null, null, null); 396726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa while (cursor.moveToNext()) { 397726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa final String childMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE); 398726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa final Uri dstUri = DocumentsContract.createDocument(mDstClient, dstDirUri, 399726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa childMimeType, getCursorString(cursor, Document.COLUMN_DISPLAY_NAME)); 400726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa final Uri childUri = DocumentsContract.buildDocumentUri(srcDirUri.getAuthority(), 401726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa getCursorString(cursor, Document.COLUMN_DOCUMENT_ID)); 402726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa if (Document.MIME_TYPE_DIR.equals(childMimeType)) { 403726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa copyDirectoryHelper(childUri, dstUri); 404726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa } else { 405726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa copyFileHelper(childUri, dstUri); 406726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa } 407726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa } 408726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa } finally { 409726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa IoUtils.closeQuietly(cursor); 410726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa } 411726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa } 412726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa 413726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa /** 414726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa * Handles copying a single file. 415726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa * 416726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa * @param srcUri URI of the file to copy from. 417726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa * @param dstUri URI of the *file* to copy to. Must be created beforehand. 418726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa * @throws RemoteException 419726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa */ 420726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa private void copyFileHelper(Uri srcUri, Uri dstUri) throws RemoteException { 421726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa // Copy an individual file. 422ef3f2620b3a755856d70345fc7a90df896985c26Ben Kwa CancellationSignal canceller = new CancellationSignal(); 423ef3f2620b3a755856d70345fc7a90df896985c26Ben Kwa ParcelFileDescriptor srcFile = null; 424ef3f2620b3a755856d70345fc7a90df896985c26Ben Kwa ParcelFileDescriptor dstFile = null; 425ef3f2620b3a755856d70345fc7a90df896985c26Ben Kwa InputStream src = null; 426ef3f2620b3a755856d70345fc7a90df896985c26Ben Kwa OutputStream dst = null; 427d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa 428d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa boolean errorOccurred = false; 429d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa try { 430726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa srcFile = mSrcClient.openFile(srcUri, "r", canceller); 431726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa dstFile = mDstClient.openFile(dstUri, "w", canceller); 432ef3f2620b3a755856d70345fc7a90df896985c26Ben Kwa src = new ParcelFileDescriptor.AutoCloseInputStream(srcFile); 433ef3f2620b3a755856d70345fc7a90df896985c26Ben Kwa dst = new ParcelFileDescriptor.AutoCloseOutputStream(dstFile); 434d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa 435d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa byte[] buffer = new byte[8192]; 436d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa int len; 437ef3f2620b3a755856d70345fc7a90df896985c26Ben Kwa while (!mIsCancelled && ((len = src.read(buffer)) != -1)) { 438ef3f2620b3a755856d70345fc7a90df896985c26Ben Kwa dst.write(buffer, 0, len); 439d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa makeProgress(len); 440d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa } 441ef3f2620b3a755856d70345fc7a90df896985c26Ben Kwa srcFile.checkError(); 442ef3f2620b3a755856d70345fc7a90df896985c26Ben Kwa dstFile.checkError(); 443d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa } catch (IOException e) { 444d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa errorOccurred = true; 445726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa Log.e(TAG, "Error while copying " + srcUri.toString(), e); 446726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa mFailedFiles.add(srcUri); 447d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa } finally { 448ef3f2620b3a755856d70345fc7a90df896985c26Ben Kwa // This also ensures the file descriptors are closed. 449ef3f2620b3a755856d70345fc7a90df896985c26Ben Kwa IoUtils.closeQuietly(src); 450ef3f2620b3a755856d70345fc7a90df896985c26Ben Kwa IoUtils.closeQuietly(dst); 451d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa } 452d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa 453d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa if (errorOccurred || mIsCancelled) { 454d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa // Clean up half-copied files. 455ef3f2620b3a755856d70345fc7a90df896985c26Ben Kwa canceller.cancel(); 456726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa try { 457726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa DocumentsContract.deleteDocument(mDstClient, dstUri); 458726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa } catch (RemoteException e) { 459726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa Log.w(TAG, "Failed to clean up: " + srcUri, e); 460726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa // RemoteExceptions usually signal that the connection is dead, so there's no point 461726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa // attempting to continue. Propagate the exception up so the copy job is cancelled. 462726cf70fd4e436ba0ba5fff1a42e5c726a9786c5Ben Kwa throw e; 463d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa } 464d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa } 465d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa } 466d99109fca847895233b0bdfafa131ebca8dfe3d5Ben Kwa} 467