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