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