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