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