1 /* 2 * Copyright (C) 2011 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.AlertDialog; 20 import android.content.Context; 21 import android.os.AsyncTask; 22 import android.os.Build; 23 import android.os.Environment; 24 import android.os.FileUtils; 25 import android.os.ParcelFileDescriptor; 26 import android.util.Log; 27 28 import com.android.compatibility.common.util.FileUtil; 29 import com.android.compatibility.common.util.ICaseResult; 30 import com.android.compatibility.common.util.IInvocationResult; 31 import com.android.compatibility.common.util.IModuleResult; 32 import com.android.compatibility.common.util.ITestResult; 33 import com.android.compatibility.common.util.ResultHandler; 34 import com.android.compatibility.common.util.ScreenshotsMetadataHandler; 35 import com.android.compatibility.common.util.ZipUtil; 36 37 import org.xmlpull.v1.XmlPullParserException; 38 39 import java.io.File; 40 import java.io.FileOutputStream; 41 import java.io.IOException; 42 import java.io.InputStream; 43 import java.nio.file.Files; 44 import java.nio.file.Path; 45 import java.nio.file.Paths; 46 import java.nio.file.StandardCopyOption; 47 import java.text.SimpleDateFormat; 48 import java.util.Date; 49 import java.util.Locale; 50 import java.util.logging.Level; 51 import java.util.logging.Logger; 52 53 /** Background task to generate a report and save it to external storage. */ 54 public class ReportExporter extends AsyncTask<Void, Void, String> { 55 private static final String TAG = ReportExporter.class.getSimpleName(); 56 private static final boolean DEBUG = true; 57 58 public static final String REPORT_DIRECTORY = "VerifierReports"; 59 public static final String LOGS_DIRECTORY = "ReportLogFiles"; 60 61 private static final Logger LOG = Logger.getLogger(ReportExporter.class.getName()); 62 private static final String COMMAND_LINE_ARGS = ""; 63 private static final String LOG_URL = null; 64 private static final String REFERENCE_URL = null; 65 private static final String SUITE_NAME_METADATA_KEY = "SuiteName"; 66 // Default CTS-V suite_plan shown in test_result.xml. 67 private static final String DEFAULT_SUITE_PLAN = "verifier"; 68 // CTS Verifier System suite_plan shown in test_result.xml. 69 private static final String SYSTEM_SUITE_PLAN = "verifier-system"; 70 private static final String SUITE_BUILD = "0"; 71 private static final String ZIP_EXTENSION = ".zip"; 72 private final long START_MS = System.currentTimeMillis(); 73 private final long END_MS = START_MS; 74 private final Context mContext; 75 private final TestListAdapter mAdapter; 76 ReportExporter(Context context, TestListAdapter adapter)77 ReportExporter(Context context, TestListAdapter adapter) { 78 this.mContext = context; 79 this.mAdapter = adapter; 80 } 81 82 // 83 // Copy any ReportLog files created by XTS-Verifier tests into the temp report directory 84 // so that they will get ZIPped into the transmitted file. 85 // copyReportFiles(File tempDir)86 private void copyReportFiles(File tempDir) { 87 if (DEBUG) { 88 Log.d(TAG, "copyReportFiles(" + tempDir.getAbsolutePath() + ")"); 89 } 90 91 File reportLogFolder = 92 new File( 93 Environment.getExternalStorageDirectory().getAbsolutePath() 94 + File.separator 95 + LOGS_DIRECTORY); 96 97 copyFilesRecursively(reportLogFolder, tempDir); 98 } 99 copyFilesRecursively(File source, File destFolder)100 private void copyFilesRecursively(File source, File destFolder) { 101 File[] files = source.listFiles(); 102 103 if (files == null) { 104 return; 105 } 106 107 for (File file : files) { 108 Path src = Paths.get(file.getAbsolutePath()); 109 Path dest = Paths.get(destFolder.getAbsolutePath() + File.separator + file.getName()); 110 try { 111 Files.copy(src, dest, StandardCopyOption.REPLACE_EXISTING); 112 } catch (IOException ex) { 113 LOG.log(Level.WARNING, "Error copying ReportLog file. IOException: " + ex); 114 } 115 if (file.isDirectory()) { 116 copyFilesRecursively(file, dest.toFile()); 117 } 118 } 119 } 120 121 @Override doInBackground(Void... params)122 protected String doInBackground(Void... params) { 123 if (!Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) { 124 LOG.log(Level.WARNING, "External storage is not writable."); 125 return mContext.getString(R.string.no_storage); 126 } 127 IInvocationResult result; 128 try { 129 TestResultsReport report = new TestResultsReport(mContext, mAdapter); 130 result = report.generateResult(); 131 } catch (Exception e) { 132 LOG.log(Level.WARNING, "Couldn't create test results report", e); 133 return mContext.getString(R.string.test_results_error); 134 } 135 // create a directory for XTS Verifier reports 136 File externalStorageDirectory = Environment.getExternalStorageDirectory(); 137 File verifierReportsDir = new File(externalStorageDirectory, REPORT_DIRECTORY); 138 verifierReportsDir.mkdirs(); 139 140 String suiteName = Version.getMetadata(mContext, SUITE_NAME_METADATA_KEY); 141 String reportName = getReportName(suiteName); 142 // create a temporary directory for this particular report 143 File tempDir = new File(verifierReportsDir, reportName); 144 tempDir.mkdirs(); 145 146 // Pull in any ReportLogs 147 copyReportFiles(tempDir); 148 149 // create a File object for a report ZIP file 150 File reportZipFile = new File(verifierReportsDir, reportName + ZIP_EXTENSION); 151 152 try { 153 // Serialize the report 154 String versionName = Version.getVersionName(mContext); 155 ResultHandler.writeResults( 156 suiteName, 157 versionName, 158 TestListActivity.getIsSystemEnabled() ? SYSTEM_SUITE_PLAN : DEFAULT_SUITE_PLAN, 159 SUITE_BUILD, 160 result, 161 tempDir, 162 START_MS, 163 END_MS, 164 REFERENCE_URL, 165 LOG_URL, 166 COMMAND_LINE_ARGS, 167 null); 168 169 // Serialize the screenshots metadata if at least one exists 170 if (containsScreenshotMetadata(result)) { 171 ScreenshotsMetadataHandler.writeResults(result, tempDir); 172 } 173 174 // copy formatting files to the temporary report directory 175 copyFormattingFiles(tempDir); 176 177 // create a compressed ZIP file containing the temporary report directory 178 ZipUtil.createZip(tempDir, reportZipFile); 179 } catch (IOException | XmlPullParserException e) { 180 LOG.log(Level.WARNING, "I/O exception writing report to storage.", e); 181 return mContext.getString(R.string.no_storage_io_parser_exception); 182 } finally { 183 // delete the temporary directory and its files made for the report 184 FileUtil.recursiveDelete(tempDir); 185 } 186 saveReportOnInternalStorage(reportZipFile); 187 return mContext.getString(R.string.report_saved, reportZipFile.getPath()); 188 } 189 containsScreenshotMetadata(IInvocationResult result)190 private boolean containsScreenshotMetadata(IInvocationResult result) { 191 for (IModuleResult module : result.getModules()) { 192 for (ICaseResult cr : module.getResults()) { 193 for (ITestResult r : cr.getResults()) { 194 if (r.getResultStatus() == null) { 195 continue; // test was not executed, don't report 196 } 197 if (r.getTestScreenshotsMetadata() != null) { 198 return true; 199 } 200 } 201 } 202 } 203 return false; 204 } 205 saveReportOnInternalStorage(File reportZipFile)206 private void saveReportOnInternalStorage(File reportZipFile) { 207 if (DEBUG) { 208 Log.d(TAG, "---- saveReportOnInternalStorage(" + reportZipFile.getAbsolutePath() + ")"); 209 } 210 try { 211 ParcelFileDescriptor pfd = 212 ParcelFileDescriptor.open(reportZipFile, ParcelFileDescriptor.MODE_READ_ONLY); 213 InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(pfd); 214 215 File verifierDir = mContext.getDir(REPORT_DIRECTORY, Context.MODE_PRIVATE); 216 File verifierReport = new File(verifierDir, reportZipFile.getName()); 217 FileOutputStream fos = new FileOutputStream(verifierReport); 218 219 FileUtils.copy(is, fos); 220 } catch (Exception e) { 221 LOG.log(Level.WARNING, "I/O exception writing report to internal storage.", e); 222 } 223 } 224 225 /** 226 * Copy the XML formatting files stored in the assets directory to the result output. 227 * 228 * @param resultsDir 229 */ copyFormattingFiles(File resultsDir)230 private void copyFormattingFiles(File resultsDir) { 231 for (String resultFileName : ResultHandler.RESULT_RESOURCES) { 232 InputStream rawStream = null; 233 try { 234 rawStream = mContext.getAssets().open(String.format("report/%s", resultFileName)); 235 } catch (IOException e) { 236 LOG.log(Level.WARNING, "Failed to load " + resultFileName + " from assets."); 237 } 238 if (rawStream != null) { 239 File resultFile = new File(resultsDir, resultFileName); 240 try { 241 FileUtil.writeToFile(rawStream, resultFile); 242 } catch (IOException e) { 243 LOG.log(Level.WARNING, "Failed to write " + resultFileName + " to a file."); 244 } 245 } 246 } 247 } 248 getReportName(String suiteName)249 private String getReportName(String suiteName) { 250 SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy.MM.dd_HH.mm.ss", Locale.ENGLISH); 251 String date = dateFormat.format(new Date()); 252 return String.format( 253 "%s-%s-%s-%s-%s-%s", 254 date, suiteName, Build.MANUFACTURER, Build.PRODUCT, Build.DEVICE, Build.ID); 255 } 256 257 @Override onPostExecute(String result)258 protected void onPostExecute(String result) { 259 new AlertDialog.Builder(mContext) 260 .setMessage(result) 261 .setPositiveButton(android.R.string.ok, null) 262 .show(); 263 } 264 } 265