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.SharedMinimal.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.Messenger; 28 import android.os.ParcelFileDescriptor; 29 import android.os.RemoteException; 30 import android.provider.DocumentsContract; 31 import android.provider.DocumentsContract.Document; 32 import android.util.Log; 33 34 import com.android.documentsui.Metrics; 35 import com.android.documentsui.R; 36 import com.android.documentsui.archives.ArchivesProvider; 37 import com.android.documentsui.base.DocumentInfo; 38 import com.android.documentsui.base.DocumentStack; 39 import com.android.documentsui.base.Features; 40 import com.android.documentsui.clipping.UrisSupplier; 41 42 import java.io.FileNotFoundException; 43 44 import javax.annotation.Nullable; 45 46 // TODO: Stop extending CopyJob. 47 final class CompressJob extends CopyJob { 48 49 private static final String TAG = "CompressJob"; 50 private static final String NEW_ARCHIVE_EXTENSION = ".zip"; 51 52 /** 53 * Moves files to a destination identified by {@code destination}. 54 * Performs most work by delegating to CopyJob, then deleting 55 * a file after it has been copied. 56 * 57 * @see @link {@link Job} constructor for most param descriptions. 58 */ 59 CompressJob(Context service, Listener listener, String id, DocumentStack destination, 60 UrisSupplier srcs, Messenger messenger, Features features) { 61 super(service, listener, id, OPERATION_MOVE, destination, srcs, messenger, features); 62 } 63 64 @Override 65 Builder createProgressBuilder() { 66 return super.createProgressBuilder( 67 service.getString(R.string.compress_notification_title), 68 R.drawable.ic_menu_compress, 69 service.getString(android.R.string.cancel), 70 R.drawable.ic_cab_cancel); 71 } 72 73 @Override 74 public Notification getSetupNotification() { 75 return getSetupNotification(service.getString(R.string.compress_preparing)); 76 } 77 78 @Override 79 public Notification getProgressNotification() { 80 return getProgressNotification(R.string.copy_remaining); 81 } 82 83 @Override 84 Notification getFailureNotification() { 85 return getFailureNotification( 86 R.plurals.compress_error_notification_title, R.drawable.ic_menu_compress); 87 } 88 89 @Override 90 public boolean setUp() { 91 if (!super.setUp()) { 92 return false; 93 } 94 95 final ContentResolver resolver = appContext.getContentResolver(); 96 97 // TODO: Move this to DocumentsProvider. 98 99 String displayName; 100 if (mResolvedDocs.size() == 1) { 101 displayName = mResolvedDocs.get(0).displayName + NEW_ARCHIVE_EXTENSION; 102 } else { 103 displayName = service.getString(R.string.new_archive_file_name, NEW_ARCHIVE_EXTENSION); 104 } 105 106 Uri archiveUri; 107 try { 108 archiveUri = DocumentsContract.createDocument( 109 resolver, mDstInfo.derivedUri, "application/zip", displayName); 110 } catch (Exception e) { 111 archiveUri = null; 112 } 113 114 try { 115 mDstInfo = DocumentInfo.fromUri(resolver, ArchivesProvider.buildUriForArchive( 116 archiveUri, ParcelFileDescriptor.MODE_WRITE_ONLY)); 117 ArchivesProvider.acquireArchive(getClient(mDstInfo), mDstInfo.derivedUri); 118 } catch (FileNotFoundException e) { 119 Log.e(TAG, "Failed to create dstInfo.", e); 120 failureCount = mResourceUris.getItemCount(); 121 return false; 122 } catch (RemoteException e) { 123 Log.e(TAG, "Failed to acquire the archive.", e); 124 failureCount = mResourceUris.getItemCount(); 125 return false; 126 } 127 128 return true; 129 } 130 131 @Override 132 void finish() { 133 try { 134 ArchivesProvider.releaseArchive(getClient(mDstInfo), mDstInfo.derivedUri); 135 } catch (RemoteException e) { 136 Log.e(TAG, "Failed to release the archive."); 137 } 138 139 // TODO: Remove the archive file in case of an error. 140 141 super.finish(); 142 } 143 144 /** 145 * {@inheritDoc} 146 * 147 * Only check space for moves across authorities. For now we don't know if the doc in 148 * {@link #mSrcs} is in the same root of destination, and if it's optimized move in the same 149 * root it should succeed regardless of free space, but it's for sure a failure if there is no 150 * enough free space if docs are moved from another authority. 151 */ 152 @Override 153 boolean checkSpace() { 154 // We're unable to say how much space the archive will take, so assume 155 // it will fit. 156 return true; 157 } 158 159 void processDocument(DocumentInfo src, DocumentInfo dest) throws ResourceException { 160 byteCopyDocument(src, dest); 161 } 162 163 @Override 164 public String toString() { 165 return new StringBuilder() 166 .append("CompressJob") 167 .append("{") 168 .append("id=" + id) 169 .append(", uris=" + mResourceUris) 170 .append(", docs=" + mResolvedDocs) 171 .append(", destination=" + stack) 172 .append("}") 173 .toString(); 174 } 175 } 176