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.dirlist; 18 19 import static com.android.documentsui.base.DocumentInfo.getCursorInt; 20 import static com.android.documentsui.base.DocumentInfo.getCursorString; 21 22 import android.database.Cursor; 23 import android.provider.DocumentsContract.Document; 24 import android.util.Log; 25 26 import androidx.recyclerview.selection.SelectionTracker.SelectionObserver; 27 28 import com.android.documentsui.MenuManager; 29 import com.android.documentsui.archives.ArchivesProvider; 30 import com.android.documentsui.base.MimeTypes; 31 import com.android.documentsui.roots.RootCursorWrapper; 32 33 import java.util.function.Function; 34 35 /** 36 * A class that aggregates document metadata describing the selection. It can answer questions 37 * like: Can the selection be deleted? and Does the selection contain a folder? 38 * 39 * <p>By collecting information in real-time as the selection changes the need to 40 * traverse the entire selection in order to answer questions is eliminated. 41 */ 42 public class SelectionMetadata extends SelectionObserver<String> 43 implements MenuManager.SelectionDetails { 44 45 private static final String TAG = "SelectionMetadata"; 46 private final static int FLAG_CAN_DELETE = 47 Document.FLAG_SUPPORTS_REMOVE | Document.FLAG_SUPPORTS_DELETE; 48 49 private final Function<String, Cursor> mDocFinder; 50 51 private int mDirectoryCount = 0; 52 private int mFileCount = 0; 53 54 // Partial files are files that haven't been fully downloaded. 55 private int mPartialCount = 0; 56 private int mWritableDirectoryCount = 0; 57 private int mNoDeleteCount = 0; 58 private int mNoRenameCount = 0; 59 private int mInArchiveCount = 0; 60 private boolean mSupportsSettings = false; 61 SelectionMetadata(Function<String, Cursor> docFinder)62 public SelectionMetadata(Function<String, Cursor> docFinder) { 63 mDocFinder = docFinder; 64 } 65 66 @Override onItemStateChanged(String modelId, boolean selected)67 public void onItemStateChanged(String modelId, boolean selected) { 68 final Cursor cursor = mDocFinder.apply(modelId); 69 if (cursor == null) { 70 Log.w(TAG, "Model returned null cursor for document: " + modelId 71 + ". Ignoring state changed event."); 72 return; 73 } 74 75 final int delta = selected ? 1 : -1; 76 77 final String mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE); 78 if (MimeTypes.isDirectoryType(mimeType)) { 79 mDirectoryCount += delta; 80 } else { 81 mFileCount += delta; 82 } 83 84 final int docFlags = getCursorInt(cursor, Document.COLUMN_FLAGS); 85 if ((docFlags & Document.FLAG_PARTIAL) != 0) { 86 mPartialCount += delta; 87 } 88 if ((docFlags & Document.FLAG_DIR_SUPPORTS_CREATE) != 0) { 89 mWritableDirectoryCount += delta; 90 } 91 if ((docFlags & FLAG_CAN_DELETE) == 0) { 92 mNoDeleteCount += delta; 93 } 94 if ((docFlags & Document.FLAG_SUPPORTS_RENAME) == 0) { 95 mNoRenameCount += delta; 96 } 97 if ((docFlags & Document.FLAG_PARTIAL) != 0) { 98 mPartialCount += delta; 99 } 100 mSupportsSettings = (docFlags & Document.FLAG_SUPPORTS_SETTINGS) != 0 && 101 (mFileCount + mDirectoryCount) == 1; 102 103 104 final String authority = getCursorString(cursor, RootCursorWrapper.COLUMN_AUTHORITY); 105 if (ArchivesProvider.AUTHORITY.equals(authority)) { 106 mInArchiveCount += delta; 107 } 108 } 109 110 @Override onSelectionRefresh()111 public void onSelectionRefresh() { 112 mFileCount = 0; 113 mDirectoryCount = 0; 114 mPartialCount = 0; 115 mWritableDirectoryCount = 0; 116 mNoDeleteCount = 0; 117 mNoRenameCount = 0; 118 } 119 120 @Override containsDirectories()121 public boolean containsDirectories() { 122 return mDirectoryCount > 0; 123 } 124 125 @Override containsFiles()126 public boolean containsFiles() { 127 return mFileCount > 0; 128 } 129 130 @Override size()131 public int size() { 132 return mDirectoryCount + mFileCount; 133 } 134 135 @Override containsPartialFiles()136 public boolean containsPartialFiles() { 137 return mPartialCount > 0; 138 } 139 140 @Override containsFilesInArchive()141 public boolean containsFilesInArchive() { 142 return mInArchiveCount > 0; 143 } 144 145 @Override canDelete()146 public boolean canDelete() { 147 return size() > 0 && mNoDeleteCount == 0; 148 } 149 150 @Override canExtract()151 public boolean canExtract() { 152 return size() > 0 && mInArchiveCount == size(); 153 } 154 155 @Override canRename()156 public boolean canRename() { 157 return mNoRenameCount == 0 && size() == 1; 158 } 159 160 @Override canViewInOwner()161 public boolean canViewInOwner() { 162 return mSupportsSettings; 163 } 164 165 @Override canPasteInto()166 public boolean canPasteInto() { 167 return mDirectoryCount == 1 && mWritableDirectoryCount == 1 && size() == 1; 168 } 169 170 @Override canOpenWith()171 public boolean canOpenWith() { 172 return size() == 1 && mDirectoryCount == 0 && mInArchiveCount == 0 && mPartialCount == 0; 173 } 174 } 175