1 /* 2 * Copyright (C) 2013 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.base; 18 19 import static com.android.documentsui.base.DocumentInfo.getCursorLong; 20 import static com.android.documentsui.base.DocumentInfo.getCursorString; 21 import static com.android.documentsui.base.SharedMinimal.DEBUG; 22 import static com.android.documentsui.base.SharedMinimal.TAG; 23 24 import android.database.AbstractCursor; 25 import android.database.ContentObserver; 26 import android.database.Cursor; 27 import android.os.Bundle; 28 import android.provider.DocumentsContract.Document; 29 import android.util.Log; 30 31 /** 32 * Cursor wrapper that filters cursor results by given conditions. 33 */ 34 public class FilteringCursorWrapper extends AbstractCursor { 35 private final Cursor mCursor; 36 37 private int[] mPositions; 38 private int mCount; 39 FilteringCursorWrapper(Cursor cursor)40 public FilteringCursorWrapper(Cursor cursor) { 41 mCursor = cursor; 42 mCount = cursor.getCount(); 43 mPositions = new int[mCount]; 44 for (int i = 0; i < mCount; i++) { 45 mPositions[i] = i; 46 } 47 } 48 49 /** 50 * Filters cursor according to mimes. If both lists are empty, all mimes will be rejected. 51 * 52 * @param acceptMimes allowed list of mimes 53 * @param rejectMimes blocked list of mimes 54 */ filterMimes(String[] acceptMimes, String[] rejectMimes)55 public void filterMimes(String[] acceptMimes, String[] rejectMimes) { 56 filterByCondition((cursor) -> { 57 final String mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE); 58 if (rejectMimes != null && MimeTypes.mimeMatches(rejectMimes, mimeType)) { 59 return false; 60 } 61 return MimeTypes.mimeMatches(acceptMimes, mimeType); 62 }); 63 } 64 65 /** Filters cursor according to last modified time, and reject earlier than given timestamp. */ filterLastModified(long rejectBeforeTimestamp)66 public void filterLastModified(long rejectBeforeTimestamp) { 67 filterByCondition((cursor) -> { 68 final long lastModified = getCursorLong(cursor, Document.COLUMN_LAST_MODIFIED); 69 return lastModified >= rejectBeforeTimestamp; 70 }); 71 } 72 73 /** Filter hidden files based on preference. */ filterHiddenFiles(boolean showHiddenFiles)74 public void filterHiddenFiles(boolean showHiddenFiles) { 75 if (showHiddenFiles) { 76 return; 77 } 78 79 filterByCondition((cursor) -> { 80 // Judge by name and documentId separately because for some providers 81 // e.g. DownloadProvider, documentId may not contain file name. 82 final String name = getCursorString(cursor, Document.COLUMN_DISPLAY_NAME); 83 final String documentId = getCursorString(cursor, Document.COLUMN_DOCUMENT_ID); 84 boolean documentIdHidden = documentId != null && documentId.contains("/."); 85 boolean fileNameHidden = name != null && name.startsWith("."); 86 return !(documentIdHidden || fileNameHidden); 87 }); 88 } 89 90 @Override getExtras()91 public Bundle getExtras() { 92 return mCursor.getExtras(); 93 } 94 95 @Override close()96 public void close() { 97 super.close(); 98 mCursor.close(); 99 } 100 101 @Override onMove(int oldPosition, int newPosition)102 public boolean onMove(int oldPosition, int newPosition) { 103 return mCursor.moveToPosition(mPositions[newPosition]); 104 } 105 106 @Override getColumnNames()107 public String[] getColumnNames() { 108 return mCursor.getColumnNames(); 109 } 110 111 @Override getCount()112 public int getCount() { 113 return mCount; 114 } 115 116 @Override getDouble(int column)117 public double getDouble(int column) { 118 return mCursor.getDouble(column); 119 } 120 121 @Override getFloat(int column)122 public float getFloat(int column) { 123 return mCursor.getFloat(column); 124 } 125 126 @Override getInt(int column)127 public int getInt(int column) { 128 return mCursor.getInt(column); 129 } 130 131 @Override getLong(int column)132 public long getLong(int column) { 133 return mCursor.getLong(column); 134 } 135 136 @Override getShort(int column)137 public short getShort(int column) { 138 return mCursor.getShort(column); 139 } 140 141 @Override getString(int column)142 public String getString(int column) { 143 return mCursor.getString(column); 144 } 145 146 @Override getType(int column)147 public int getType(int column) { 148 return mCursor.getType(column); 149 } 150 151 @Override isNull(int column)152 public boolean isNull(int column) { 153 return mCursor.isNull(column); 154 } 155 156 @Override registerContentObserver(ContentObserver observer)157 public void registerContentObserver(ContentObserver observer) { 158 mCursor.registerContentObserver(observer); 159 } 160 161 @Override unregisterContentObserver(ContentObserver observer)162 public void unregisterContentObserver(ContentObserver observer) { 163 mCursor.unregisterContentObserver(observer); 164 } 165 166 private interface FilteringCondition { accept(Cursor cursor)167 boolean accept(Cursor cursor); 168 } 169 filterByCondition(FilteringCondition condition)170 private void filterByCondition(FilteringCondition condition) { 171 final int oldCount = this.getCount(); 172 int[] newPositions = new int[oldCount]; 173 int newCount = 0; 174 175 this.moveToPosition(-1); 176 while (this.moveToNext() && newCount < oldCount) { 177 if (condition.accept(mCursor)) { 178 newPositions[newCount++] = mPositions[this.getPosition()]; 179 } 180 } 181 182 if (DEBUG && newCount != this.getCount()) { 183 Log.d(TAG, "Before filtering " + oldCount + ", after " + newCount); 184 } 185 mCount = newCount; 186 mPositions = newPositions; 187 } 188 } 189