1 /*
2  * Copyright (C) 2010 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.cts.verifier;
18 
19 import static com.android.cts.verifier.TestListActivity.sCurrentDisplayMode;
20 import static com.android.cts.verifier.TestListAdapter.setTestNameSuffix;
21 
22 import android.app.backup.BackupManager;
23 import android.content.ContentProvider;
24 import android.content.ContentResolver;
25 import android.content.ContentValues;
26 import android.content.Context;
27 import android.content.UriMatcher;
28 import android.database.Cursor;
29 import android.database.MatrixCursor;
30 import android.database.sqlite.SQLiteDatabase;
31 import android.database.sqlite.SQLiteOpenHelper;
32 import android.database.sqlite.SQLiteQueryBuilder;
33 import android.net.Uri;
34 import android.os.ParcelFileDescriptor;
35 
36 import androidx.annotation.NonNull;
37 
38 import com.android.compatibility.common.util.ReportLog;
39 
40 import java.io.ByteArrayOutputStream;
41 import java.io.File;
42 import java.io.FileInputStream;
43 import java.io.FileNotFoundException;
44 import java.io.IOException;
45 import java.io.ObjectOutputStream;
46 import java.util.Arrays;
47 import java.util.Comparator;
48 
49 /**
50  * {@link ContentProvider} that provides read and write access to the test results.
51  */
52 public class TestResultsProvider extends ContentProvider {
53 
54     static final String _ID = "_id";
55     /** String name of the test like "com.android.cts.verifier.foo.FooTestActivity" */
56     static final String COLUMN_TEST_NAME = "testname";
57     /** Integer test result corresponding to constants in {@link TestResult}. */
58     static final String COLUMN_TEST_RESULT = "testresult";
59     /** Boolean indicating whether the test info has been seen. */
60     static final String COLUMN_TEST_INFO_SEEN = "testinfoseen";
61     /** String containing the test's details. */
62     static final String COLUMN_TEST_DETAILS = "testdetails";
63     /** ReportLog containing the test result metrics. */
64     static final String COLUMN_TEST_METRICS = "testmetrics";
65     /** TestResultHistory containing the test run histories. */
66     static final String COLUMN_TEST_RESULT_HISTORY = "testresulthistory";
67 
68     /**
69      * Report saved location
70      */
71     private static final String REPORTS_PATH = "reports";
72     private static final String RESULTS_PATH = "results";
73     private static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
74     private static final int RESULTS_ALL = 1;
75     private static final int RESULTS_ID = 2;
76     private static final int RESULTS_TEST_NAME = 3;
77     private static final int REPORT = 4;
78     private static final int REPORT_ROW = 5;
79     private static final int REPORT_FILE_NAME = 6;
80     private static final int REPORT_LATEST = 7;
81     private static final String TABLE_NAME = "results";
82     private SQLiteOpenHelper mOpenHelper;
83     private BackupManager mBackupManager;
84 
85     /**
86      * Get the URI from the result content.
87      *
88      * @param context
89      * @return Uri
90      */
getResultContentUri(Context context)91     public static Uri getResultContentUri(Context context) {
92         final String packageName = context.getPackageName();
93         final Uri contentUri = Uri.parse("content://" + packageName + ".testresultsprovider");
94         return Uri.withAppendedPath(contentUri, RESULTS_PATH);
95     }
96 
97     /**
98      * Get the URI from the test name.
99      *
100      * @param context
101      * @param testName
102      * @return Uri
103      */
getTestNameUri(Context context)104     public static Uri getTestNameUri(Context context) {
105         String name = context.getClass().getName();
106         name = setTestNameSuffix(sCurrentDisplayMode, name);
107         return getTestNameUri(context, name);
108     }
109 
110     /**
111      * Gets the URI from the context and test name.
112      * @param context current context
113      * @param testName name of the test which needs to get the URI
114      * @return the URI for the test result
115      */
getTestNameUri(Context context, String testName)116     public static Uri getTestNameUri(Context context, String testName) {
117         return Uri.withAppendedPath(getResultContentUri(context), testName);
118     }
119 
setTestResult(Context context, String testName, int testResult, String testDetails, ReportLog reportLog, TestResultHistoryCollection historyCollection)120     static void setTestResult(Context context, String testName, int testResult,
121         String testDetails, ReportLog reportLog, TestResultHistoryCollection historyCollection) {
122         ContentValues values = new ContentValues(2);
123         values.put(TestResultsProvider.COLUMN_TEST_RESULT, testResult);
124         values.put(TestResultsProvider.COLUMN_TEST_NAME, testName);
125         values.put(TestResultsProvider.COLUMN_TEST_DETAILS, testDetails);
126         values.put(TestResultsProvider.COLUMN_TEST_METRICS, serialize(reportLog));
127         values.put(TestResultsProvider.COLUMN_TEST_RESULT_HISTORY, serialize(historyCollection));
128 
129         final Uri uri = getResultContentUri(context);
130         ContentResolver resolver = context.getContentResolver();
131         int numUpdated = resolver.update(uri, values,
132                 TestResultsProvider.COLUMN_TEST_NAME + " = ?",
133                 new String[]{testName});
134 
135         if (numUpdated == 0) {
136             resolver.insert(uri, values);
137         }
138     }
139 
serialize(Object o)140     private static byte[] serialize(Object o) {
141         ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
142         ObjectOutputStream objectOutput = null;
143         try {
144             objectOutput = new ObjectOutputStream(byteStream);
145             objectOutput.writeObject(o);
146             return byteStream.toByteArray();
147         } catch (IOException e) {
148             return null;
149         } finally {
150             try {
151                 if (objectOutput != null) {
152                     objectOutput.close();
153                 }
154                 byteStream.close();
155             } catch (IOException e) {
156                 // Ignore close exception.
157             }
158         }
159     }
160 
161     @Override
onCreate()162     public boolean onCreate() {
163         final String authority = getContext().getPackageName() + ".testresultsprovider";
164 
165         URI_MATCHER.addURI(authority, RESULTS_PATH, RESULTS_ALL);
166         URI_MATCHER.addURI(authority, RESULTS_PATH + "/#", RESULTS_ID);
167         URI_MATCHER.addURI(authority, RESULTS_PATH + "/*", RESULTS_TEST_NAME);
168         URI_MATCHER.addURI(authority, REPORTS_PATH, REPORT);
169         URI_MATCHER.addURI(authority, REPORTS_PATH + "/latest", REPORT_LATEST);
170         URI_MATCHER.addURI(authority, REPORTS_PATH + "/#", REPORT_ROW);
171         URI_MATCHER.addURI(authority, REPORTS_PATH + "/*", REPORT_FILE_NAME);
172 
173         mOpenHelper = new TestResultsOpenHelper(getContext());
174         mBackupManager = new BackupManager(getContext());
175         return false;
176     }
177 
178     @Override
query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)179     public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
180                         String sortOrder) {
181         SQLiteQueryBuilder query = new SQLiteQueryBuilder();
182         query.setTables(TABLE_NAME);
183 
184         int match = URI_MATCHER.match(uri);
185         switch (match) {
186             case RESULTS_ALL:
187                 break;
188 
189             case RESULTS_ID:
190                 query.appendWhere(_ID);
191                 query.appendWhere("=");
192                 query.appendWhere(uri.getPathSegments().get(1));
193                 break;
194 
195             case RESULTS_TEST_NAME:
196                 query.appendWhere(COLUMN_TEST_NAME);
197                 query.appendWhere("=");
198                 query.appendWhere("\"" + uri.getPathSegments().get(1) + "\"");
199                 break;
200 
201             case REPORT:
202                 final MatrixCursor cursor = new MatrixCursor(new String[]{"filename"});
203                 for (String filename : getFileList()) {
204                     cursor.addRow(new Object[]{filename});
205                 }
206                 return cursor;
207 
208             case REPORT_FILE_NAME:
209             case REPORT_ROW:
210             case REPORT_LATEST:
211                 throw new IllegalArgumentException(
212                         "Report query not supported. Use content read.");
213 
214             default:
215                 throw new IllegalArgumentException("Unknown URI: " + uri);
216         }
217 
218         SQLiteDatabase db = mOpenHelper.getReadableDatabase();
219         return query.query(db, projection, selection, selectionArgs, null, null, sortOrder);
220     }
221 
222     @Override
insert(Uri uri, ContentValues values)223     public Uri insert(Uri uri, ContentValues values) {
224         int match = URI_MATCHER.match(uri);
225         switch (match) {
226             case REPORT:
227                 throw new IllegalArgumentException(
228                         "Report insert not supported. Use content query.");
229             case REPORT_FILE_NAME:
230             case REPORT_ROW:
231             case REPORT_LATEST:
232                 throw new IllegalArgumentException(
233                         "Report insert not supported. Use content read.");
234             default:
235                 break;
236         }
237         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
238         long id = db.insert(TABLE_NAME, null, values);
239         getContext().getContentResolver().notifyChange(uri, null);
240         mBackupManager.dataChanged();
241         return Uri.withAppendedPath(getResultContentUri(getContext()), "" + id);
242     }
243 
244     @Override
update(Uri uri, ContentValues values, String selection, String[] selectionArgs)245     public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
246         int match = URI_MATCHER.match(uri);
247         switch (match) {
248             case RESULTS_ALL:
249                 break;
250 
251             case RESULTS_ID:
252                 String idSelection = _ID + "=" + uri.getPathSegments().get(1);
253                 if (selection != null && selection.length() > 0) {
254                     selection = idSelection + " AND " + selection;
255                 } else {
256                     selection = idSelection;
257                 }
258                 break;
259 
260             case RESULTS_TEST_NAME:
261                 String testNameSelection = COLUMN_TEST_NAME + "=\""
262                         + uri.getPathSegments().get(1) + "\"";
263                 if (selection != null && selection.length() > 0) {
264                     selection = testNameSelection + " AND " + selection;
265                 } else {
266                     selection = testNameSelection;
267                 }
268                 break;
269             case REPORT:
270                 throw new IllegalArgumentException(
271                         "Report update not supported. Use content query.");
272             case REPORT_FILE_NAME:
273             case REPORT_ROW:
274             case REPORT_LATEST:
275                 throw new IllegalArgumentException(
276                         "Report update not supported. Use content read.");
277             default:
278                 throw new IllegalArgumentException("Unknown URI: " + uri);
279         }
280 
281         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
282         int numUpdated = db.update(TABLE_NAME, values, selection, selectionArgs);
283         if (numUpdated > 0) {
284             getContext().getContentResolver().notifyChange(uri, null);
285             mBackupManager.dataChanged();
286         }
287         return numUpdated;
288     }
289 
290     @Override
delete(Uri uri, String selection, String[] selectionArgs)291     public int delete(Uri uri, String selection, String[] selectionArgs) {
292         int match = URI_MATCHER.match(uri);
293         switch (match) {
294             case REPORT:
295                 throw new IllegalArgumentException(
296                         "Report delete not supported. Use content query.");
297             case REPORT_FILE_NAME:
298             case REPORT_ROW:
299             case REPORT_LATEST:
300                 throw new IllegalArgumentException(
301                         "Report delete not supported. Use content read.");
302             default:
303                 break;
304         }
305 
306         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
307         int numDeleted = db.delete(TABLE_NAME, selection, selectionArgs);
308         if (numDeleted > 0) {
309             getContext().getContentResolver().notifyChange(uri, null);
310             mBackupManager.dataChanged();
311         }
312         return numDeleted;
313     }
314 
315     @Override
getType(Uri uri)316     public String getType(Uri uri) {
317         return null;
318     }
319 
320     @Override
openFile(@onNull Uri uri, @NonNull String mode)321     public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode)
322             throws FileNotFoundException {
323         String fileName;
324         String[] fileList;
325         File file;
326         int match = URI_MATCHER.match(uri);
327         switch (match) {
328             case REPORT_ROW:
329                 int rowId = Integer.parseInt(uri.getPathSegments().get(1));
330                 file = getFileByIndex(rowId);
331                 break;
332 
333             case REPORT_FILE_NAME:
334                 fileName = uri.getPathSegments().get(1);
335                 file = getFileByName(fileName);
336                 break;
337 
338             case REPORT_LATEST:
339                 file = getLatestFile();
340                 break;
341 
342             case REPORT:
343                 throw new IllegalArgumentException("Read not supported. Use content query.");
344 
345             case RESULTS_ALL:
346             case RESULTS_ID:
347             case RESULTS_TEST_NAME:
348                 throw new IllegalArgumentException("Read not supported for URI: " + uri);
349 
350             default:
351                 throw new IllegalArgumentException("Unknown URI: " + uri);
352         }
353         try {
354             FileInputStream fis = new FileInputStream(file);
355             return ParcelFileDescriptor.dup(fis.getFD());
356         } catch (IOException e) {
357             throw new IllegalArgumentException("Cannot open file.");
358         }
359     }
360 
361 
getFileByIndex(int index)362     private File getFileByIndex(int index) {
363         File[] files = getFiles();
364         if (files.length == 0) {
365             throw new IllegalArgumentException("No report saved at " + index + ".");
366         }
367         return files[index];
368     }
369 
getFileByName(String fileName)370     private File getFileByName(String fileName) {
371         File[] files = getFiles();
372         if (files.length == 0) {
373             throw new IllegalArgumentException("No reports saved.");
374         }
375         for (File file : files) {
376             if (fileName.equals(file.getName())) {
377                 return file;
378             }
379         }
380         throw new IllegalArgumentException(fileName + " not found.");
381     }
382 
getLatestFile()383     private File getLatestFile() {
384         File[] files = getFiles();
385         if (files.length == 0) {
386             throw new IllegalArgumentException("No reports saved.");
387         }
388         return files[files.length - 1];
389     }
390 
getFileList()391     private String[] getFileList() {
392         return Arrays.stream(getFiles()).map(File::getName).toArray(String[]::new);
393     }
394 
getFiles()395     private File[] getFiles() {
396         File dir = getContext().getDir(ReportExporter.REPORT_DIRECTORY, Context.MODE_PRIVATE);
397         File[] files = dir.listFiles();
398         Arrays.sort(files, Comparator.comparingLong(File::lastModified));
399         return files;
400     }
401 
402     private static class TestResultsOpenHelper extends SQLiteOpenHelper {
403 
404         private static final String DATABASE_NAME = "results.db";
405 
406         private static final int DATABASE_VERSION = 6;
407 
TestResultsOpenHelper(Context context)408         TestResultsOpenHelper(Context context) {
409             super(context, DATABASE_NAME, null, DATABASE_VERSION);
410         }
411 
412         @Override
onCreate(SQLiteDatabase db)413         public void onCreate(SQLiteDatabase db) {
414             db.execSQL("CREATE TABLE " + TABLE_NAME + " ("
415                     + _ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
416                     + COLUMN_TEST_NAME + " TEXT, "
417                     + COLUMN_TEST_RESULT + " INTEGER,"
418                     + COLUMN_TEST_INFO_SEEN + " INTEGER DEFAULT 0,"
419                     + COLUMN_TEST_DETAILS + " TEXT,"
420                     + COLUMN_TEST_METRICS + " BLOB,"
421                     + COLUMN_TEST_RESULT_HISTORY + " BLOB);");
422         }
423 
424         @Override
onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)425         public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
426             db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
427             onCreate(db);
428         }
429     }
430 }
431