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