1 /*
2  * Copyright (C) 2015 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 package com.android.compatibility.common.deviceinfo;
17 
18 import android.app.Activity;
19 import android.content.Intent;
20 import android.net.Uri;
21 import android.os.Bundle;
22 import android.os.Environment;
23 import android.text.TextUtils;
24 import android.util.JsonWriter;
25 import android.util.Log;
26 
27 import java.io.File;
28 import java.io.FileOutputStream;
29 import java.io.OutputStreamWriter;
30 import java.nio.charset.StandardCharsets;
31 import java.util.Arrays;
32 import java.util.concurrent.CountDownLatch;
33 
34 /**
35  * Collect device information on target device and write to a JSON file.
36  */
37 public abstract class DeviceInfoActivity extends Activity {
38 
39     /** Device info result code: collector failed to complete. */
40     private static final int DEVICE_INFO_RESULT_FAILED = -2;
41     /** Device info result code: collector completed with error. */
42     private static final int DEVICE_INFO_RESULT_ERROR = -1;
43     /** Device info result code: collector has started but not completed. */
44     private static final int DEVICE_INFO_RESULT_STARTED = 0;
45     /** Device info result code: collector completed success. */
46     private static final int DEVICE_INFO_RESULT_OK = 1;
47 
48     private static final int MAX_STRING_VALUE_LENGTH = 1000;
49     private static final int MAX_ARRAY_LENGTH = 1000;
50 
51     private static final String LOG_TAG = "DeviceInfoActivity";
52 
53     private CountDownLatch mDone = new CountDownLatch(1);
54     private JsonWriter mJsonWriter = null;
55     private String mResultFilePath = null;
56     private String mErrorMessage = "Collector has started.";
57     private int mResultCode = DEVICE_INFO_RESULT_STARTED;
58 
59     @Override
onCreate(Bundle savedInstanceState)60     public void onCreate(Bundle savedInstanceState) {
61         super.onCreate(savedInstanceState);
62         if (createFilePath()) {
63             createJsonWriter();
64             startJsonWriter();
65             collectDeviceInfo();
66             closeJsonWriter();
67 
68             if (mResultCode == DEVICE_INFO_RESULT_STARTED) {
69                 mResultCode = DEVICE_INFO_RESULT_OK;
70             }
71         }
72 
73         Intent data = new Intent();
74         if (mResultCode == DEVICE_INFO_RESULT_OK) {
75             data.setData(Uri.parse(mResultFilePath));
76             setResult(RESULT_OK, data);
77         } else {
78             data.setData(Uri.parse(mErrorMessage));
79             setResult(RESULT_CANCELED, data);
80         }
81 
82         mDone.countDown();
83         finish();
84     }
85 
86     /**
87      * Method to collect device information.
88      */
collectDeviceInfo()89     protected abstract void collectDeviceInfo();
90 
waitForActivityToFinish()91     void waitForActivityToFinish() {
92         try {
93             mDone.await();
94         } catch (Exception e) {
95             failed("Exception while waiting for activity to finish: " + e.getMessage());
96         }
97     }
98 
99     /**
100      * Returns the error message if collector did not complete successfully.
101      */
getErrorMessage()102     String getErrorMessage() {
103         if (mResultCode == DEVICE_INFO_RESULT_OK) {
104             return null;
105         }
106         return mErrorMessage;
107     }
108 
109     /**
110      * Returns the path to the json file if collector completed successfully.
111      */
getResultFilePath()112     String getResultFilePath() {
113         if (mResultCode == DEVICE_INFO_RESULT_OK) {
114             return mResultFilePath;
115         }
116         return null;
117     }
118 
error(String message)119     private void error(String message) {
120         mResultCode = DEVICE_INFO_RESULT_ERROR;
121         mErrorMessage = message;
122         Log.e(LOG_TAG, message);
123     }
124 
failed(String message)125     private void failed(String message) {
126         mResultCode = DEVICE_INFO_RESULT_FAILED;
127         mErrorMessage = message;
128         Log.e(LOG_TAG, message);
129     }
130 
createFilePath()131     private boolean createFilePath() {
132         if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
133             failed("External storage is not mounted");
134             return false;
135         }
136         final File dir = new File(Environment.getExternalStorageDirectory(), "device-info-files");
137         if (!dir.mkdirs() && !dir.isDirectory()) {
138             failed("Cannot create directory for device info files");
139             return false;
140         }
141 
142         // Create file at /sdcard/device-info-files/<class_name>.deviceinfo.json
143         final File jsonFile = new File(dir, getClass().getSimpleName() + ".deviceinfo.json");
144         try {
145             jsonFile.createNewFile();
146         } catch (Exception e) {
147             failed("Cannot create file to collect device info");
148             return false;
149         }
150         mResultFilePath = jsonFile.getAbsolutePath();
151         return true;
152     }
153 
createJsonWriter()154     private void createJsonWriter() {
155         try {
156             FileOutputStream out = new FileOutputStream(mResultFilePath);
157             mJsonWriter = new JsonWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8));
158             // TODO(agathaman): remove to make json output less pretty
159             mJsonWriter.setIndent("  ");
160         } catch (Exception e) {
161             failed("Failed to create JSON writer: " + e.getMessage());
162         }
163     }
164 
startJsonWriter()165     private void startJsonWriter() {
166         try {
167             mJsonWriter.beginObject();
168         } catch (Exception e) {
169             failed("Failed to begin JSON object: " + e.getMessage());
170         }
171     }
172 
closeJsonWriter()173     private void closeJsonWriter() {
174         try {
175             mJsonWriter.endObject();
176             mJsonWriter.close();
177         } catch (Exception e) {
178             failed("Failed to close JSON object: " + e.getMessage());
179         }
180     }
181 
182     /**
183      * Start a new group of result.
184      */
startGroup()185     public void startGroup() {
186         try {
187             mJsonWriter.beginObject();
188         } catch (Exception e) {
189             error("Failed to begin JSON group: " + e.getMessage());
190         }
191     }
192 
193     /**
194      * Start a new group of result with specified name.
195      */
startGroup(String name)196     public void startGroup(String name) {
197         try {
198             mJsonWriter.name(name);
199             mJsonWriter.beginObject();
200         } catch (Exception e) {
201             error("Failed to begin JSON group: " + e.getMessage());
202         }
203     }
204 
205     /**
206      * Complete adding result to the last started group.
207      */
endGroup()208     public void endGroup() {
209         try {
210             mJsonWriter.endObject();
211         } catch (Exception e) {
212             error("Failed to end JSON group: " + e.getMessage());
213         }
214     }
215 
216     /**
217      * Start a new array of result.
218      */
startArray()219     public void startArray() {
220         try {
221             mJsonWriter.beginArray();
222         } catch (Exception e) {
223             error("Failed to begin JSON array: " + e.getMessage());
224         }
225     }
226 
227     /**
228      * Start a new array of result with specified name.
229      */
startArray(String name)230     public void startArray(String name) {
231         checkName(name);
232         try {
233             mJsonWriter.name(name);
234             mJsonWriter.beginArray();
235         } catch (Exception e) {
236             error("Failed to begin JSON array: " + e.getMessage());
237         }
238     }
239 
240     /**
241      * Complete adding result to the last started array.
242      */
endArray()243     public void endArray() {
244         try {
245             mJsonWriter.endArray();
246         } catch (Exception e) {
247             error("Failed to end JSON group: " + e.getMessage());
248         }
249     }
250 
251     /**
252      * Add a double value result.
253      */
addResult(String name, double value)254     public void addResult(String name, double value) {
255         checkName(name);
256         try {
257             mJsonWriter.name(name).value(value);
258         } catch (Exception e) {
259             error("Failed to add result for type double: " + e.getMessage());
260         }
261     }
262 
263     /**
264     * Add a long value result.
265     */
addResult(String name, long value)266     public void addResult(String name, long value) {
267         checkName(name);
268         try {
269             mJsonWriter.name(name).value(value);
270         } catch (Exception e) {
271             error("Failed to add result for type long: " + e.getMessage());
272         }
273     }
274 
275     /**
276      * Add an int value result.
277      */
addResult(String name, int value)278     public void addResult(String name, int value) {
279         checkName(name);
280         try {
281             mJsonWriter.name(name).value((Number) value);
282         } catch (Exception e) {
283             error("Failed to add result for type int: " + e.getMessage());
284         }
285     }
286 
287     /**
288      * Add a boolean value result.
289      */
addResult(String name, boolean value)290     public void addResult(String name, boolean value) {
291         checkName(name);
292         try {
293             mJsonWriter.name(name).value(value);
294         } catch (Exception e) {
295             error("Failed to add result for type boolean: " + e.getMessage());
296         }
297     }
298 
299     /**
300      * Add a String value result.
301      */
addResult(String name, String value)302     public void addResult(String name, String value) {
303         checkName(name);
304         try {
305             mJsonWriter.name(name).value(checkString(value));
306         } catch (Exception e) {
307             error("Failed to add result for type String: " + e.getMessage());
308         }
309     }
310 
311     /**
312      * Add a double array result.
313      */
addArray(String name, double[] list)314     public void addArray(String name, double[] list) {
315         checkName(name);
316         try {
317             mJsonWriter.name(name);
318             mJsonWriter.beginArray();
319             for (double value : checkArray(list)) {
320                 mJsonWriter.value(value);
321             }
322             mJsonWriter.endArray();
323         } catch (Exception e) {
324             error("Failed to add result array for type double: " + e.getMessage());
325         }
326     }
327 
328     /**
329      * Add a long array result.
330      */
addArray(String name, long[] list)331     public void addArray(String name, long[] list) {
332         checkName(name);
333         try {
334         mJsonWriter.name(name);
335         mJsonWriter.beginArray();
336         for (long value : checkArray(list)) {
337             mJsonWriter.value(value);
338         }
339             mJsonWriter.endArray();
340         } catch (Exception e) {
341             error("Failed to add result array for type long: " + e.getMessage());
342         }
343     }
344 
345     /**
346      * Add an int array result.
347      */
addArray(String name, int[] list)348     public void addArray(String name, int[] list) {
349         checkName(name);
350         try {
351             mJsonWriter.name(name);
352             mJsonWriter.beginArray();
353             for (int value : checkArray(list)) {
354                 mJsonWriter.value((Number) value);
355             }
356             mJsonWriter.endArray();
357         } catch (Exception e) {
358             error("Failed to add result array for type int: " + e.getMessage());
359         }
360     }
361 
362     /**
363      * Add a boolean array result.
364      */
addArray(String name, boolean[] list)365     public void addArray(String name, boolean[] list) {
366         checkName(name);
367         try {
368             mJsonWriter.name(name);
369             mJsonWriter.beginArray();
370             for (boolean value : checkArray(list)) {
371                 mJsonWriter.value(value);
372             }
373             mJsonWriter.endArray();
374         } catch (Exception e) {
375             error("Failed to add result array for type boolean: " + e.getMessage());
376         }
377     }
378 
379     /**
380      * Add a String array result.
381      */
addArray(String name, String[] list)382     public void addArray(String name, String[] list) {
383         checkName(name);
384         try {
385             mJsonWriter.name(name);
386             mJsonWriter.beginArray();
387             for (String value : checkArray(list)) {
388                 mJsonWriter.value(checkString(value));
389             }
390             mJsonWriter.endArray();
391         } catch (Exception e) {
392             error("Failed to add result array for type Sting: " + e.getMessage());
393         }
394     }
395 
checkArray(boolean[] values)396     private static boolean[] checkArray(boolean[] values) {
397         if (values.length > MAX_ARRAY_LENGTH) {
398             return Arrays.copyOf(values, MAX_ARRAY_LENGTH);
399         } else {
400             return values;
401         }
402     }
403 
checkArray(double[] values)404     private static double[] checkArray(double[] values) {
405         if (values.length > MAX_ARRAY_LENGTH) {
406             return Arrays.copyOf(values, MAX_ARRAY_LENGTH);
407         } else {
408             return values;
409         }
410     }
411 
checkArray(int[] values)412     private static int[] checkArray(int[] values) {
413         if (values.length > MAX_ARRAY_LENGTH) {
414             return Arrays.copyOf(values, MAX_ARRAY_LENGTH);
415         } else {
416             return values;
417         }
418     }
419 
checkArray(long[] values)420     private static long[] checkArray(long[] values) {
421         if (values.length > MAX_ARRAY_LENGTH) {
422             return Arrays.copyOf(values, MAX_ARRAY_LENGTH);
423         } else {
424             return values;
425         }
426     }
427 
checkArray(String[] values)428     private static String[] checkArray(String[] values) {
429         if (values.length > MAX_ARRAY_LENGTH) {
430             return Arrays.copyOf(values, MAX_ARRAY_LENGTH);
431         } else {
432             return values;
433         }
434     }
435 
checkString(String value)436     private static String checkString(String value) {
437         if (value.length() > MAX_STRING_VALUE_LENGTH) {
438             return value.substring(0, MAX_STRING_VALUE_LENGTH);
439         }
440         return value;
441     }
442 
checkName(String value)443     private static String checkName(String value) {
444         if (TextUtils.isEmpty(value)) {
445             throw new NullPointerException();
446         }
447         return value;
448     }
449 }
450 
451