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 com.android.compatibility.common.util.ReportLog;
37 
38 import java.io.ByteArrayOutputStream;
39 import java.io.File;
40 import java.io.FileInputStream;
41 import java.io.FileNotFoundException;
42 import java.io.IOException;
43 import java.io.ObjectOutputStream;
44 import java.util.Arrays;
45 import java.util.Comparator;
46 
47 import androidx.annotation.NonNull;
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         final String testName = name;
108         return Uri.withAppendedPath(getResultContentUri(context), testName);
109     }
110 
setTestResult(Context context, String testName, int testResult, String testDetails, ReportLog reportLog, TestResultHistoryCollection historyCollection)111     static void setTestResult(Context context, String testName, int testResult,
112         String testDetails, ReportLog reportLog, TestResultHistoryCollection historyCollection) {
113         ContentValues values = new ContentValues(2);
114         values.put(TestResultsProvider.COLUMN_TEST_RESULT, testResult);
115         values.put(TestResultsProvider.COLUMN_TEST_NAME, testName);
116         values.put(TestResultsProvider.COLUMN_TEST_DETAILS, testDetails);
117         values.put(TestResultsProvider.COLUMN_TEST_METRICS, serialize(reportLog));
118         values.put(TestResultsProvider.COLUMN_TEST_RESULT_HISTORY, serialize(historyCollection));
119 
120         final Uri uri = getResultContentUri(context);
121         ContentResolver resolver = context.getContentResolver();
122         int numUpdated = resolver.update(uri, values,
123                 TestResultsProvider.COLUMN_TEST_NAME + " = ?",
124                 new String[]{testName});
125 
126         if (numUpdated == 0) {
127             resolver.insert(uri, values);
128         }
129     }
130 
serialize(Object o)131     private static byte[] serialize(Object o) {
132         ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
133         ObjectOutputStream objectOutput = null;
134         try {
135             objectOutput = new ObjectOutputStream(byteStream);
136             objectOutput.writeObject(o);
137             return byteStream.toByteArray();
138         } catch (IOException e) {
139             return null;
140         } finally {
141             try {
142                 if (objectOutput != null) {
143                     objectOutput.close();
144                 }
145                 byteStream.close();
146             } catch (IOException e) {
147                 // Ignore close exception.
148             }
149         }
150     }
151 
152     @Override
onCreate()153     public boolean onCreate() {
154         final String authority = getContext().getPackageName() + ".testresultsprovider";
155 
156         URI_MATCHER.addURI(authority, RESULTS_PATH, RESULTS_ALL);
157         URI_MATCHER.addURI(authority, RESULTS_PATH + "/#", RESULTS_ID);
158         URI_MATCHER.addURI(authority, RESULTS_PATH + "/*", RESULTS_TEST_NAME);
159         URI_MATCHER.addURI(authority, REPORTS_PATH, REPORT);
160         URI_MATCHER.addURI(authority, REPORTS_PATH + "/latest", REPORT_LATEST);
161         URI_MATCHER.addURI(authority, REPORTS_PATH + "/#", REPORT_ROW);
162         URI_MATCHER.addURI(authority, REPORTS_PATH + "/*", REPORT_FILE_NAME);
163 
164         mOpenHelper = new TestResultsOpenHelper(getContext());
165         mBackupManager = new BackupManager(getContext());
166         return false;
167     }
168 
169     @Override
query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)170     public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
171                         String sortOrder) {
172         SQLiteQueryBuilder query = new SQLiteQueryBuilder();
173         query.setTables(TABLE_NAME);
174 
175         int match = URI_MATCHER.match(uri);
176         switch (match) {
177             case RESULTS_ALL:
178                 break;
179 
180             case RESULTS_ID:
181                 query.appendWhere(_ID);
182                 query.appendWhere("=");
183                 query.appendWhere(uri.getPathSegments().get(1));
184                 break;
185 
186             case RESULTS_TEST_NAME:
187                 query.appendWhere(COLUMN_TEST_NAME);
188                 query.appendWhere("=");
189                 query.appendWhere("\"" + uri.getPathSegments().get(1) + "\"");
190                 break;
191 
192             case REPORT:
193                 final MatrixCursor cursor = new MatrixCursor(new String[]{"filename"});
194                 for (String filename : getFileList()) {
195                     cursor.addRow(new Object[]{filename});
196                 }
197                 return cursor;
198 
199             case REPORT_FILE_NAME:
200             case REPORT_ROW:
201             case REPORT_LATEST:
202                 throw new IllegalArgumentException(
203                         "Report query not supported. Use content read.");
204 
205             default:
206                 throw new IllegalArgumentException("Unknown URI: " + uri);
207         }
208 
209         SQLiteDatabase db = mOpenHelper.getReadableDatabase();
210         return query.query(db, projection, selection, selectionArgs, null, null, sortOrder);
211     }
212 
213     @Override
insert(Uri uri, ContentValues values)214     public Uri insert(Uri uri, ContentValues values) {
215         int match = URI_MATCHER.match(uri);
216         switch (match) {
217             case REPORT:
218                 throw new IllegalArgumentException(
219                         "Report insert not supported. Use content query.");
220             case REPORT_FILE_NAME:
221             case REPORT_ROW:
222             case REPORT_LATEST:
223                 throw new IllegalArgumentException(
224                         "Report insert not supported. Use content read.");
225             default:
226                 break;
227         }
228         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
229         long id = db.insert(TABLE_NAME, null, values);
230         getContext().getContentResolver().notifyChange(uri, null);
231         mBackupManager.dataChanged();
232         return Uri.withAppendedPath(getResultContentUri(getContext()), "" + id);
233     }
234 
235     @Override
update(Uri uri, ContentValues values, String selection, String[] selectionArgs)236     public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
237         int match = URI_MATCHER.match(uri);
238         switch (match) {
239             case RESULTS_ALL:
240                 break;
241 
242             case RESULTS_ID:
243                 String idSelection = _ID + "=" + uri.getPathSegments().get(1);
244                 if (selection != null && selection.length() > 0) {
245                     selection = idSelection + " AND " + selection;
246                 } else {
247                     selection = idSelection;
248                 }
249                 break;
250 
251             case RESULTS_TEST_NAME:
252                 String testNameSelection = COLUMN_TEST_NAME + "=\""
253                         + uri.getPathSegments().get(1) + "\"";
254                 if (selection != null && selection.length() > 0) {
255                     selection = testNameSelection + " AND " + selection;
256                 } else {
257                     selection = testNameSelection;
258                 }
259                 break;
260             case REPORT:
261                 throw new IllegalArgumentException(
262                         "Report update not supported. Use content query.");
263             case REPORT_FILE_NAME:
264             case REPORT_ROW:
265             case REPORT_LATEST:
266                 throw new IllegalArgumentException(
267                         "Report update not supported. Use content read.");
268             default:
269                 throw new IllegalArgumentException("Unknown URI: " + uri);
270         }
271 
272         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
273         int numUpdated = db.update(TABLE_NAME, values, selection, selectionArgs);
274         if (numUpdated > 0) {
275             getContext().getContentResolver().notifyChange(uri, null);
276             mBackupManager.dataChanged();
277         }
278         return numUpdated;
279     }
280 
281     @Override
delete(Uri uri, String selection, String[] selectionArgs)282     public int delete(Uri uri, String selection, String[] selectionArgs) {
283         int match = URI_MATCHER.match(uri);
284         switch (match) {
285             case REPORT:
286                 throw new IllegalArgumentException(
287                         "Report delete not supported. Use content query.");
288             case REPORT_FILE_NAME:
289             case REPORT_ROW:
290             case REPORT_LATEST:
291                 throw new IllegalArgumentException(
292                         "Report delete not supported. Use content read.");
293             default:
294                 break;
295         }
296 
297         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
298         int numDeleted = db.delete(TABLE_NAME, selection, selectionArgs);
299         if (numDeleted > 0) {
300             getContext().getContentResolver().notifyChange(uri, null);
301             mBackupManager.dataChanged();
302         }
303         return numDeleted;
304     }
305 
306     @Override
getType(Uri uri)307     public String getType(Uri uri) {
308         return null;
309     }
310 
311     @Override
openFile(@onNull Uri uri, @NonNull String mode)312     public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode)
313             throws FileNotFoundException {
314         String fileName;
315         String[] fileList;
316         File file;
317         int match = URI_MATCHER.match(uri);
318         switch (match) {
319             case REPORT_ROW:
320                 int rowId = Integer.parseInt(uri.getPathSegments().get(1));
321                 file = getFileByIndex(rowId);
322                 break;
323 
324             case REPORT_FILE_NAME:
325                 fileName = uri.getPathSegments().get(1);
326                 file = getFileByName(fileName);
327                 break;
328 
329             case REPORT_LATEST:
330                 file = getLatestFile();
331                 break;
332 
333             case REPORT:
334                 throw new IllegalArgumentException("Read not supported. Use content query.");
335 
336             case RESULTS_ALL:
337             case RESULTS_ID:
338             case RESULTS_TEST_NAME:
339                 throw new IllegalArgumentException("Read not supported for URI: " + uri);
340 
341             default:
342                 throw new IllegalArgumentException("Unknown URI: " + uri);
343         }
344         try {
345             FileInputStream fis = new FileInputStream(file);
346             return ParcelFileDescriptor.dup(fis.getFD());
347         } catch (IOException e) {
348             throw new IllegalArgumentException("Cannot open file.");
349         }
350     }
351 
352 
getFileByIndex(int index)353     private File getFileByIndex(int index) {
354         File[] files = getFiles();
355         if (files.length == 0) {
356             throw new IllegalArgumentException("No report saved at " + index + ".");
357         }
358         return files[index];
359     }
360 
getFileByName(String fileName)361     private File getFileByName(String fileName) {
362         File[] files = getFiles();
363         if (files.length == 0) {
364             throw new IllegalArgumentException("No reports saved.");
365         }
366         for (File file : files) {
367             if (fileName.equals(file.getName())) {
368                 return file;
369             }
370         }
371         throw new IllegalArgumentException(fileName + " not found.");
372     }
373 
getLatestFile()374     private File getLatestFile() {
375         File[] files = getFiles();
376         if (files.length == 0) {
377             throw new IllegalArgumentException("No reports saved.");
378         }
379         return files[files.length - 1];
380     }
381 
getFileList()382     private String[] getFileList() {
383         return Arrays.stream(getFiles()).map(File::getName).toArray(String[]::new);
384     }
385 
getFiles()386     private File[] getFiles() {
387         File dir = getContext().getDir(ReportExporter.REPORT_DIRECTORY, Context.MODE_PRIVATE);
388         File[] files = dir.listFiles();
389         Arrays.sort(files, Comparator.comparingLong(File::lastModified));
390         return files;
391     }
392 
393     private static class TestResultsOpenHelper extends SQLiteOpenHelper {
394 
395         private static final String DATABASE_NAME = "results.db";
396 
397         private static final int DATABASE_VERSION = 6;
398 
TestResultsOpenHelper(Context context)399         TestResultsOpenHelper(Context context) {
400             super(context, DATABASE_NAME, null, DATABASE_VERSION);
401         }
402 
403         @Override
onCreate(SQLiteDatabase db)404         public void onCreate(SQLiteDatabase db) {
405             db.execSQL("CREATE TABLE " + TABLE_NAME + " ("
406                     + _ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
407                     + COLUMN_TEST_NAME + " TEXT, "
408                     + COLUMN_TEST_RESULT + " INTEGER,"
409                     + COLUMN_TEST_INFO_SEEN + " INTEGER DEFAULT 0,"
410                     + COLUMN_TEST_DETAILS + " TEXT,"
411                     + COLUMN_TEST_METRICS + " BLOB,"
412                     + COLUMN_TEST_RESULT_HISTORY + " BLOB);");
413         }
414 
415         @Override
onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)416         public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
417             db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
418             onCreate(db);
419         }
420     }
421 }
422