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 package com.android.documentsui.ui; 17 18 import android.app.Activity; 19 import android.app.AlertDialog; 20 import android.app.FragmentManager; 21 import android.content.DialogInterface; 22 import android.support.design.widget.Snackbar; 23 import android.widget.Button; 24 import android.widget.TextView; 25 26 import com.android.documentsui.R; 27 import com.android.documentsui.base.ConfirmationCallback; 28 import com.android.documentsui.base.DocumentInfo; 29 import com.android.documentsui.base.Features; 30 import com.android.documentsui.picker.OverwriteConfirmFragment; 31 import com.android.documentsui.services.FileOperation; 32 import com.android.documentsui.services.FileOperationService.OpType; 33 import com.android.documentsui.services.FileOperationService; 34 import com.android.documentsui.services.FileOperations.Callback.Status; 35 import com.android.documentsui.services.FileOperations; 36 37 import java.util.List; 38 import javax.annotation.Nullable; 39 40 public interface DialogController { 41 42 // Dialogs used in FilesActivity 43 void confirmDelete(List<DocumentInfo> docs, ConfirmationCallback callback); 44 void showFileOperationStatus(int status, int opType, int docCount); 45 46 /** 47 * There can be only one progress dialog active at the time. Each call to this 48 * method will discard any previously created progress dialogs. 49 */ 50 void showProgressDialog(String jobId, FileOperation operation); 51 52 void showNoApplicationFound(); 53 void showOperationUnsupported(); 54 void showViewInArchivesUnsupported(); 55 void showDocumentsClipped(int size); 56 57 // Dialogs used in PickActivity 58 void confirmOverwrite(FragmentManager fm, DocumentInfo overwriteTarget); 59 60 // Should be private, but Java doesn't like me treating an interface like a mini-package. 61 public static final class RuntimeDialogController implements DialogController { 62 63 private final Activity mActivity; 64 private final MessageBuilder mMessages; 65 private final Features mFeatures; 66 private OperationProgressDialog mCurrentProgressDialog = null; 67 68 public RuntimeDialogController(Features features, Activity activity, MessageBuilder messages) { 69 mFeatures = features; 70 mActivity = activity; 71 mMessages = messages; 72 } 73 74 @Override 75 public void confirmDelete(List<DocumentInfo> docs, ConfirmationCallback callback) { 76 assert(!docs.isEmpty()); 77 78 TextView message = 79 (TextView) mActivity.getLayoutInflater().inflate( 80 R.layout.dialog_delete_confirmation, null); 81 message.setText(mMessages.generateDeleteMessage(docs)); 82 83 // For now, we implement this dialog NOT 84 // as a fragment (which can survive rotation and have its own state), 85 // but as a simple runtime dialog. So rotating a device with an 86 // active delete dialog...results in that dialog disappearing. 87 // We can do better, but don't have cycles for it now. 88 final AlertDialog alertDialog = new AlertDialog.Builder(mActivity) 89 .setView(message) 90 .setPositiveButton( 91 android.R.string.ok, 92 new DialogInterface.OnClickListener() { 93 @Override 94 public void onClick(DialogInterface dialog, int id) { 95 callback.accept(ConfirmationCallback.CONFIRM); 96 } 97 }) 98 .setNegativeButton(android.R.string.cancel, null) 99 .create(); 100 101 alertDialog.setOnShowListener( 102 (DialogInterface) -> { 103 Button positive = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE); 104 positive.setFocusable(true); 105 positive.requestFocus(); 106 }); 107 alertDialog.show(); 108 } 109 110 @Override 111 public void showFileOperationStatus(@Status int status, @OpType int opType, 112 int docCount) { 113 if (status == FileOperations.Callback.STATUS_REJECTED) { 114 showOperationUnsupported(); 115 return; 116 } 117 if (status == FileOperations.Callback.STATUS_FAILED) { 118 Snackbars.showOperationFailed(mActivity); 119 return; 120 } 121 122 if (docCount == 0) { 123 // Nothing has been pasted, so there is no need to show a snackbar. 124 return; 125 } 126 127 if (shouldShowProgressDialogForOperation(opType)) { 128 // The operation has a progress dialog created, so do not show a snackbar 129 // for operation start, as it would duplicate the UI. 130 return; 131 } 132 133 switch (opType) { 134 case FileOperationService.OPERATION_MOVE: 135 Snackbars.showMove(mActivity, docCount); 136 break; 137 case FileOperationService.OPERATION_COPY: 138 Snackbars.showCopy(mActivity, docCount); 139 break; 140 case FileOperationService.OPERATION_COMPRESS: 141 Snackbars.showCompress(mActivity, docCount); 142 break; 143 case FileOperationService.OPERATION_EXTRACT: 144 Snackbars.showExtract(mActivity, docCount); 145 break; 146 case FileOperationService.OPERATION_DELETE: 147 Snackbars.showDelete(mActivity, docCount); 148 break; 149 default: 150 throw new UnsupportedOperationException("Unsupported Operation: " + opType); 151 } 152 } 153 154 private boolean shouldShowProgressDialogForOperation(@OpType int opType) { 155 // TODO: Hook up progress dialog to the delete operation. 156 if (opType == FileOperationService.OPERATION_DELETE) { 157 return false; 158 } 159 160 return mFeatures.isJobProgressDialogEnabled(); 161 } 162 163 @Override 164 public void showProgressDialog(String jobId, FileOperation operation) { 165 assert(operation.getOpType() != FileOperationService.OPERATION_UNKNOWN); 166 167 if (!shouldShowProgressDialogForOperation(operation.getOpType())) { 168 return; 169 } 170 171 if (mCurrentProgressDialog != null) { 172 mCurrentProgressDialog.dismiss(); 173 } 174 175 mCurrentProgressDialog = OperationProgressDialog.create(mActivity, jobId, operation); 176 mCurrentProgressDialog.show(); 177 } 178 179 @Override 180 public void showNoApplicationFound() { 181 Snackbars.makeSnackbar( 182 mActivity, R.string.toast_no_application, Snackbar.LENGTH_SHORT).show(); 183 } 184 185 @Override 186 public void showOperationUnsupported() { 187 Snackbars.showOperationRejected(mActivity); 188 } 189 190 @Override 191 public void showViewInArchivesUnsupported() { 192 Snackbars.makeSnackbar(mActivity, R.string.toast_view_in_archives_unsupported, 193 Snackbar.LENGTH_SHORT).show(); 194 } 195 196 @Override 197 public void showDocumentsClipped(int size) { 198 Snackbars.showDocumentsClipped(mActivity, size); 199 } 200 201 @Override 202 public void confirmOverwrite(FragmentManager fm, DocumentInfo overwriteTarget) { 203 OverwriteConfirmFragment.show(fm, overwriteTarget); 204 } 205 } 206 207 static DialogController create(Features features, Activity activity, MessageBuilder messages) { 208 return new RuntimeDialogController(features, activity, messages); 209 } 210 } 211