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