1 /* 2 * Copyright (C) 2016 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_DELETE; 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.icu.text.MessageFormat; 27 import android.net.Uri; 28 import android.text.BidiFormatter; 29 import android.util.Log; 30 31 import com.android.documentsui.MetricConsts; 32 import com.android.documentsui.Metrics; 33 import com.android.documentsui.R; 34 import com.android.documentsui.base.DocumentInfo; 35 import com.android.documentsui.base.DocumentStack; 36 import com.android.documentsui.base.Features; 37 import com.android.documentsui.base.UserId; 38 import com.android.documentsui.clipping.UrisSupplier; 39 40 import java.io.FileNotFoundException; 41 import java.util.HashMap; 42 import java.util.Locale; 43 import java.util.Map; 44 45 import javax.annotation.Nullable; 46 47 final class DeleteJob extends ResolvedResourcesJob { 48 49 private static final String TAG = "DeleteJob"; 50 51 private final Uri mParentUri; 52 53 private volatile int mDocsProcessed = 0; 54 55 /** 56 * Moves files to a destination identified by {@code destination}. 57 * Performs most work by delegating to CopyJob, then deleting 58 * a file after it has been copied. 59 * 60 * @see @link {@link Job} constructor for most param descriptions. 61 */ DeleteJob(Context service, Listener listener, String id, DocumentStack stack, UrisSupplier srcs, @Nullable Uri srcParent, Features features)62 DeleteJob(Context service, Listener listener, String id, DocumentStack stack, 63 UrisSupplier srcs, @Nullable Uri srcParent, Features features) { 64 super(service, listener, id, OPERATION_DELETE, stack, srcs, features); 65 mParentUri = srcParent; 66 } 67 68 @Override createProgressBuilder()69 Builder createProgressBuilder() { 70 return super.createProgressBuilder( 71 service.getString(R.string.delete_notification_title), 72 R.drawable.ic_menu_delete, 73 service.getString(android.R.string.cancel), 74 R.drawable.ic_cab_cancel); 75 } 76 77 @Override getSetupNotification()78 public Notification getSetupNotification() { 79 return getSetupNotification(service.getString(R.string.delete_preparing)); 80 } 81 82 @Override getProgressNotification()83 public Notification getProgressNotification() { 84 mProgressBuilder.setProgress(mResourceUris.getItemCount(), mDocsProcessed, false); 85 String format = service.getString(R.string.delete_progress); 86 mProgressBuilder.setSubText( 87 String.format(format, mDocsProcessed, mResourceUris.getItemCount())); 88 89 mProgressBuilder.setContentText(null); 90 91 return mProgressBuilder.build(); 92 } 93 94 @Override getFailureNotification()95 Notification getFailureNotification() { 96 return getFailureNotification( 97 R.plurals.delete_error_notification_title, R.drawable.ic_menu_delete); 98 } 99 100 @Override getWarningNotification()101 Notification getWarningNotification() { 102 throw new UnsupportedOperationException(); 103 } 104 getProgressMessage()105 private String getProgressMessage() { 106 switch (getState()) { 107 case Job.STATE_SET_UP: 108 case Job.STATE_COMPLETED: 109 case Job.STATE_CANCELED: 110 Map<String, Object> formatArgs = new HashMap<>(); 111 formatArgs.put("count", mResolvedDocs.size()); 112 if (mResolvedDocs.size() == 1) { 113 formatArgs.put("filename", BidiFormatter.getInstance().unicodeWrap( 114 mResolvedDocs.get(0).displayName)); 115 } 116 return (new MessageFormat( 117 service.getString(R.string.delete_in_progress), Locale.getDefault())) 118 .format(formatArgs); 119 default: 120 return ""; 121 } 122 } 123 124 @Override getJobProgress()125 JobProgress getJobProgress() { 126 return new JobProgress( 127 id, 128 getState(), 129 getProgressMessage(), 130 hasFailures()); 131 } 132 133 @Override start()134 void start() { 135 ContentResolver resolver = appContext.getContentResolver(); 136 137 DocumentInfo parentDoc; 138 try { 139 parentDoc = mParentUri != null 140 ? DocumentInfo.fromUri(resolver, mParentUri, UserId.DEFAULT_USER) 141 : null; 142 } catch (FileNotFoundException e) { 143 Log.e(TAG, "Failed to resolve parent from Uri: " + mParentUri + ". Cannot continue.", e); 144 failureCount += this.mResourceUris.getItemCount(); 145 return; 146 } 147 148 for (DocumentInfo doc : mResolvedDocs) { 149 if (DEBUG) { 150 Log.d(TAG, "Deleting document @ " + doc.derivedUri); 151 } 152 try { 153 deleteDocument(doc, parentDoc); 154 } catch (ResourceException e) { 155 Metrics.logFileOperationFailure( 156 appContext, MetricConsts.SUBFILEOP_DELETE_DOCUMENT, doc.derivedUri); 157 Log.e(TAG, "Failed to delete document @ " + doc.derivedUri, e); 158 onFileFailed(doc); 159 } 160 161 mDocsProcessed++; 162 if (isCanceled()) { 163 return; 164 } 165 } 166 167 Metrics.logFileOperation(operationType, mResolvedDocs, null); 168 } 169 170 @Override toString()171 public String toString() { 172 return new StringBuilder() 173 .append("DeleteJob") 174 .append("{") 175 .append("id=" + id) 176 .append(", uris=" + mResourceUris) 177 .append(", docs=" + mResolvedDocs) 178 .append(", srcParent=" + mParentUri) 179 .append(", location=" + stack) 180 .append("}") 181 .toString(); 182 } 183 } 184