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