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