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; 18 19 import android.content.Context; 20 import android.content.pm.ProviderInfo; 21 import android.content.res.AssetFileDescriptor; 22 import android.database.Cursor; 23 import android.database.MatrixCursor.RowBuilder; 24 import android.database.MatrixCursor; 25 import android.graphics.Point; 26 import android.os.CancellationSignal; 27 import android.os.FileUtils; 28 import android.os.ParcelFileDescriptor; 29 import android.provider.DocumentsContract.Document; 30 import android.provider.DocumentsContract.Root; 31 import android.provider.DocumentsContract; 32 import android.provider.DocumentsProvider; 33 34 import java.io.File; 35 import java.io.FileNotFoundException; 36 import java.io.IOException; 37 import java.util.ArrayList; 38 import java.util.HashMap; 39 import java.util.List; 40 import java.util.Map; 41 import java.util.Random; 42 43 /** 44 * Provider with thousands of files for testing loading time of directories in DocumentsUI. 45 * It doesn't support any file operations. 46 */ 47 public class StressProvider extends DocumentsProvider { 48 49 public static final String DEFAULT_AUTHORITY = "com.android.documentsui.stressprovider"; 50 51 // Empty root. 52 public static final String STRESS_ROOT_0_ID = "STRESS_ROOT_0"; 53 54 // Root with thousands of directories. 55 public static final String STRESS_ROOT_1_ID = "STRESS_ROOT_1"; 56 57 // Root with hundreds of files. 58 public static final String STRESS_ROOT_2_ID = "STRESS_ROOT_2"; 59 60 private static final String STRESS_ROOT_0_DOC_ID = "STRESS_ROOT_0_DOC"; 61 private static final String STRESS_ROOT_1_DOC_ID = "STRESS_ROOT_1_DOC"; 62 private static final String STRESS_ROOT_2_DOC_ID = "STRESS_ROOT_2_DOC"; 63 64 private static final int STRESS_ROOT_1_ITEMS = 10000; 65 private static final int STRESS_ROOT_2_ITEMS = 300; 66 67 private static final String MIME_TYPE_IMAGE = "image/jpeg"; 68 private static final long REFERENCE_TIMESTAMP = 1459159369359L; 69 70 private static final String[] DEFAULT_ROOT_PROJECTION = new String[] { 71 Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_TITLE, Root.COLUMN_DOCUMENT_ID, 72 Root.COLUMN_AVAILABLE_BYTES 73 }; 74 private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] { 75 Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE, Document.COLUMN_DISPLAY_NAME, 76 Document.COLUMN_LAST_MODIFIED, Document.COLUMN_FLAGS, Document.COLUMN_SIZE, 77 }; 78 79 private String mAuthority = DEFAULT_AUTHORITY; 80 81 // Map from a root document id to children document ids. 82 private Map<String, ArrayList<StubDocument>> mChildDocuments = new HashMap<>(); 83 84 private Map<String, StubDocument> mDocuments = new HashMap<>(); 85 private Map<String, StubRoot> mRoots = new HashMap<>(); 86 87 @Override attachInfo(Context context, ProviderInfo info)88 public void attachInfo(Context context, ProviderInfo info) { 89 mAuthority = info.authority; 90 super.attachInfo(context, info); 91 } 92 93 @Override onCreate()94 public boolean onCreate() { 95 StubDocument document; 96 97 ArrayList<StubDocument> children = new ArrayList<StubDocument>(); 98 mChildDocuments.put(STRESS_ROOT_1_DOC_ID, children); 99 for (int i = 0; i < STRESS_ROOT_1_ITEMS; i++) { 100 document = StubDocument.createDirectory(i); 101 mDocuments.put(document.id, document); 102 children.add(document); 103 } 104 105 children = new ArrayList<StubDocument>(); 106 mChildDocuments.put(STRESS_ROOT_2_DOC_ID, children); 107 for (int i = 0; i < STRESS_ROOT_2_ITEMS; i++) { 108 try { 109 document = StubDocument.createFile( 110 getContext(), MIME_TYPE_IMAGE, 111 com.android.documentsui.perftests.R.raw.earth_small, 112 STRESS_ROOT_1_ITEMS + i); 113 } catch (IOException e) { 114 return false; 115 } 116 mDocuments.put(document.id, document); 117 children.add(document); 118 } 119 120 mRoots.put(STRESS_ROOT_0_ID, new StubRoot(STRESS_ROOT_0_ID, STRESS_ROOT_0_DOC_ID)); 121 mRoots.put(STRESS_ROOT_1_ID, new StubRoot(STRESS_ROOT_1_ID, STRESS_ROOT_1_DOC_ID)); 122 mRoots.put(STRESS_ROOT_2_ID, new StubRoot(STRESS_ROOT_2_ID, STRESS_ROOT_2_DOC_ID)); 123 124 mDocuments.put(STRESS_ROOT_0_DOC_ID, StubDocument.createDirectory(STRESS_ROOT_0_DOC_ID)); 125 mDocuments.put(STRESS_ROOT_1_DOC_ID, StubDocument.createDirectory(STRESS_ROOT_1_DOC_ID)); 126 mDocuments.put(STRESS_ROOT_2_DOC_ID, StubDocument.createDirectory(STRESS_ROOT_2_DOC_ID)); 127 128 return true; 129 } 130 131 @Override queryRoots(String[] projection)132 public Cursor queryRoots(String[] projection) throws FileNotFoundException { 133 final MatrixCursor result = new MatrixCursor(DEFAULT_ROOT_PROJECTION); 134 for (StubRoot root : mRoots.values()) { 135 includeRoot(result, root); 136 } 137 return result; 138 } 139 140 @Override queryDocument(String documentId, String[] projection)141 public Cursor queryDocument(String documentId, String[] projection) 142 throws FileNotFoundException { 143 final MatrixCursor result = new MatrixCursor(DEFAULT_DOCUMENT_PROJECTION); 144 final StubDocument document = mDocuments.get(documentId); 145 includeDocument(result, document); 146 return result; 147 } 148 149 @Override queryChildDocuments(String parentDocumentId, String[] projection, String sortOrder)150 public Cursor queryChildDocuments(String parentDocumentId, String[] projection, 151 String sortOrder) 152 throws FileNotFoundException { 153 final MatrixCursor result = new MatrixCursor(DEFAULT_DOCUMENT_PROJECTION); 154 final ArrayList<StubDocument> childDocuments = mChildDocuments.get(parentDocumentId); 155 if (childDocuments != null) { 156 for (StubDocument document : childDocuments) { 157 includeDocument(result, document); 158 } 159 } 160 return result; 161 } 162 163 @Override openDocumentThumbnail(String docId, Point sizeHint, CancellationSignal signal)164 public AssetFileDescriptor openDocumentThumbnail(String docId, Point sizeHint, 165 CancellationSignal signal) 166 throws FileNotFoundException { 167 final StubDocument document = mDocuments.get(docId); 168 return getContext().getResources().openRawResourceFd(document.thumbnail); 169 } 170 171 @Override openDocument(String docId, String mode, CancellationSignal signal)172 public ParcelFileDescriptor openDocument(String docId, String mode, 173 CancellationSignal signal) 174 throws FileNotFoundException { 175 throw new UnsupportedOperationException(); 176 } 177 includeRoot(MatrixCursor result, StubRoot root)178 private void includeRoot(MatrixCursor result, StubRoot root) { 179 final RowBuilder row = result.newRow(); 180 row.add(Root.COLUMN_ROOT_ID, root.id); 181 row.add(Root.COLUMN_FLAGS, 0); 182 row.add(Root.COLUMN_TITLE, root.id); 183 row.add(Root.COLUMN_DOCUMENT_ID, root.documentId); 184 } 185 includeDocument(MatrixCursor result, StubDocument document)186 private void includeDocument(MatrixCursor result, StubDocument document) { 187 final RowBuilder row = result.newRow(); 188 row.add(Document.COLUMN_DOCUMENT_ID, document.id); 189 row.add(Document.COLUMN_DISPLAY_NAME, document.id); 190 row.add(Document.COLUMN_SIZE, document.size); 191 row.add(Document.COLUMN_MIME_TYPE, document.mimeType); 192 row.add(Document.COLUMN_FLAGS, 193 document.thumbnail != -1 ? Document.FLAG_SUPPORTS_THUMBNAIL : 0); 194 row.add(Document.COLUMN_LAST_MODIFIED, document.lastModified); 195 } 196 getStubDocumentIdForFile(File file)197 private static String getStubDocumentIdForFile(File file) { 198 return file.getAbsolutePath(); 199 } 200 201 private static class StubDocument { 202 final String mimeType; 203 final String id; 204 final int size; 205 final long lastModified; 206 final int thumbnail; 207 StubDocument(String mimeType, String id, int size, long lastModified, int thumbnail)208 private StubDocument(String mimeType, String id, int size, long lastModified, 209 int thumbnail) { 210 this.mimeType = mimeType; 211 this.id = id; 212 this.size = size; 213 this.lastModified = lastModified; 214 this.thumbnail = thumbnail; 215 } 216 createDirectory(int index)217 public static StubDocument createDirectory(int index) { 218 return new StubDocument( 219 DocumentsContract.Document.MIME_TYPE_DIR, createRandomId(index), 0, 220 createRandomTime(index), -1); 221 } 222 createDirectory(String id)223 public static StubDocument createDirectory(String id) { 224 return new StubDocument(DocumentsContract.Document.MIME_TYPE_DIR, id, 0, 0, -1); 225 } 226 createFile(Context context, String mimeType, int thumbnail, int index)227 public static StubDocument createFile(Context context, String mimeType, int thumbnail, 228 int index) throws IOException { 229 return new StubDocument( 230 mimeType, createRandomId(index), createRandomSize(index), 231 createRandomTime(index), thumbnail); 232 } 233 createRandomId(int index)234 private static String createRandomId(int index) { 235 final Random random = new Random(index); 236 final StringBuilder builder = new StringBuilder(); 237 for (int i = 0; i < 20; i++) { 238 builder.append((char) (random.nextInt(96) + 32)); 239 } 240 builder.append(index); // Append a number to guarantee uniqueness. 241 return builder.toString(); 242 } 243 createRandomSize(int index)244 private static int createRandomSize(int index) { 245 final Random random = new Random(index); 246 return random.nextInt(1024 * 1024 * 100); // Up to 100 MB. 247 } 248 createRandomTime(int index)249 private static long createRandomTime(int index) { 250 final Random random = new Random(index); 251 // Up to 30 days backwards from REFERENCE_TIMESTAMP. 252 return REFERENCE_TIMESTAMP - random.nextLong() % 1000L * 60 * 60 * 24 * 30; 253 } 254 } 255 256 private static class StubRoot { 257 final String id; 258 final String documentId; 259 StubRoot(String id, String documentId)260 public StubRoot(String id, String documentId) { 261 this.id = id; 262 this.documentId = documentId; 263 } 264 } 265 } 266