1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.documentsui.services; 18 19 import static com.android.documentsui.base.Shared.DEBUG; 20 import static com.android.documentsui.services.FileOperationService.OPERATION_MOVE; 21 22 import android.app.Notification; 23 import android.app.Notification.Builder; 24 import android.content.ContentResolver; 25 import android.content.Context; 26 import android.net.Uri; 27 import android.os.ParcelFileDescriptor; 28 import android.os.RemoteException; 29 import android.provider.DocumentsContract; 30 import android.provider.DocumentsContract.Document; 31 import android.util.Log; 32 33 import com.android.documentsui.Metrics; 34 import com.android.documentsui.R; 35 import com.android.documentsui.archives.ArchivesProvider; 36 import com.android.documentsui.base.DocumentInfo; 37 import com.android.documentsui.base.DocumentStack; 38 import com.android.documentsui.base.Features; 39 import com.android.documentsui.clipping.UrisSupplier; 40 41 import java.io.FileNotFoundException; 42 43 import javax.annotation.Nullable; 44 45 // TODO: Stop extending CopyJob. 46 final class CompressJob extends CopyJob { 47 48 private static final String TAG = "CompressJob"; 49 private static final String NEW_ARCHIVE_EXTENSION = ".zip"; 50 51 /** 52 * Moves files to a destination identified by {@code destination}. 53 * Performs most work by delegating to CopyJob, then deleting 54 * a file after it has been copied. 55 * 56 * @see @link {@link Job} constructor for most param descriptions. 57 */ CompressJob(Context service, Listener listener, String id, DocumentStack destination, UrisSupplier srcs, Features features)58 CompressJob(Context service, Listener listener, 59 String id, DocumentStack destination, UrisSupplier srcs, Features features) { 60 super(service, listener, id, OPERATION_MOVE, destination, srcs, features); 61 } 62 63 @Override createProgressBuilder()64 Builder createProgressBuilder() { 65 return super.createProgressBuilder( 66 service.getString(R.string.compress_notification_title), 67 R.drawable.ic_menu_compress, 68 service.getString(android.R.string.cancel), 69 R.drawable.ic_cab_cancel); 70 } 71 72 @Override getSetupNotification()73 public Notification getSetupNotification() { 74 return getSetupNotification(service.getString(R.string.compress_preparing)); 75 } 76 77 @Override getProgressNotification()78 public Notification getProgressNotification() { 79 return getProgressNotification(R.string.copy_remaining); 80 } 81 82 @Override getFailureNotification()83 Notification getFailureNotification() { 84 return getFailureNotification( 85 R.plurals.compress_error_notification_title, R.drawable.ic_menu_compress); 86 } 87 88 @Override setUp()89 public boolean setUp() { 90 if (!super.setUp()) { 91 return false; 92 } 93 94 final ContentResolver resolver = appContext.getContentResolver(); 95 96 // TODO: Move this to DocumentsProvider. 97 98 String displayName; 99 if (mResolvedDocs.size() == 1) { 100 displayName = mResolvedDocs.get(0).displayName + NEW_ARCHIVE_EXTENSION; 101 } else { 102 displayName = service.getString(R.string.new_archive_file_name, NEW_ARCHIVE_EXTENSION); 103 } 104 105 Uri archiveUri; 106 try { 107 archiveUri = DocumentsContract.createDocument( 108 resolver, mDstInfo.derivedUri, "application/zip", displayName); 109 } catch (Exception e) { 110 archiveUri = null; 111 } 112 113 try { 114 mDstInfo = DocumentInfo.fromUri(resolver, ArchivesProvider.buildUriForArchive( 115 archiveUri, ParcelFileDescriptor.MODE_WRITE_ONLY)); 116 ArchivesProvider.acquireArchive(getClient(mDstInfo), mDstInfo.derivedUri); 117 } catch (FileNotFoundException e) { 118 Log.e(TAG, "Failed to create dstInfo.", e); 119 failureCount = mResourceUris.getItemCount(); 120 return false; 121 } catch (RemoteException e) { 122 Log.e(TAG, "Failed to acquire the archive.", e); 123 failureCount = mResourceUris.getItemCount(); 124 return false; 125 } 126 127 return true; 128 } 129 130 @Override finish()131 void finish() { 132 try { 133 ArchivesProvider.releaseArchive(getClient(mDstInfo), mDstInfo.derivedUri); 134 } catch (RemoteException e) { 135 Log.e(TAG, "Failed to release the archive."); 136 } 137 138 // TODO: Remove the archive file in case of an error. 139 140 super.finish(); 141 } 142 143 /** 144 * {@inheritDoc} 145 * 146 * Only check space for moves across authorities. For now we don't know if the doc in 147 * {@link #mSrcs} is in the same root of destination, and if it's optimized move in the same 148 * root it should succeed regardless of free space, but it's for sure a failure if there is no 149 * enough free space if docs are moved from another authority. 150 */ 151 @Override checkSpace()152 boolean checkSpace() { 153 // We're unable to say how much space the archive will take, so assume 154 // it will fit. 155 return true; 156 } 157 processDocument(DocumentInfo src, DocumentInfo dest)158 void processDocument(DocumentInfo src, DocumentInfo dest) throws ResourceException { 159 byteCopyDocument(src, dest); 160 } 161 162 @Override toString()163 public String toString() { 164 return new StringBuilder() 165 .append("CompressJob") 166 .append("{") 167 .append("id=" + id) 168 .append(", uris=" + mResourceUris) 169 .append(", docs=" + mResolvedDocs) 170 .append(", destination=" + stack) 171 .append("}") 172 .toString(); 173 } 174 } 175