1 /*
2  * Copyright (C) 2014 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.camera.its;
18 
19 import android.content.BroadcastReceiver;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.IntentFilter;
23 import android.content.res.Configuration;
24 import android.hardware.camera2.CameraAccessException;
25 import android.hardware.camera2.CameraCharacteristics;
26 import android.hardware.camera2.CameraManager;
27 import android.os.Bundle;
28 import android.text.method.ScrollingMovementMethod;
29 import android.util.Log;
30 import android.view.WindowManager;
31 import android.widget.TextView;
32 import android.widget.Toast;
33 
34 import java.util.ArrayList;
35 import java.util.Arrays;
36 import java.util.Comparator;
37 import java.util.HashSet;
38 import java.util.HashMap;
39 import java.util.Iterator;
40 import java.util.List;
41 import java.util.Set;
42 import java.util.TreeSet;
43 import java.io.BufferedReader;
44 import java.io.FileReader;
45 import java.io.FileNotFoundException;
46 import java.io.IOException;
47 
48 import com.android.compatibility.common.util.ResultType;
49 import com.android.compatibility.common.util.ResultUnit;
50 import com.android.cts.verifier.ArrayTestListAdapter;
51 import com.android.cts.verifier.DialogTestListActivity;
52 import com.android.cts.verifier.R;
53 import com.android.cts.verifier.TestResult;
54 
55 import org.json.JSONArray;
56 import org.json.JSONObject;
57 
58 /**
59  * Test for Camera features that require that the camera be aimed at a specific test scene.
60  * This test activity requires a USB connection to a computer, and a corresponding host-side run of
61  * the python scripts found in the CameraITS directory.
62  */
63 public class ItsTestActivity extends DialogTestListActivity {
64     private static final String TAG = "ItsTestActivity";
65     private static final String EXTRA_CAMERA_ID = "camera.its.extra.CAMERA_ID";
66     private static final String EXTRA_RESULTS = "camera.its.extra.RESULTS";
67     private static final String EXTRA_VERSION = "camera.its.extra.VERSION";
68     private static final String CURRENT_VERSION = "1.0";
69     private static final String ACTION_ITS_RESULT =
70             "com.android.cts.verifier.camera.its.ACTION_ITS_RESULT";
71 
72     private static final String RESULT_PASS = "PASS";
73     private static final String RESULT_FAIL = "FAIL";
74     private static final String RESULT_NOT_EXECUTED = "NOT_EXECUTED";
75     private static final Set<String> RESULT_VALUES = new HashSet<String>(
76             Arrays.asList(new String[] {RESULT_PASS, RESULT_FAIL, RESULT_NOT_EXECUTED}));
77     private static final int MAX_SUMMARY_LEN = 200;
78 
79     private final ResultReceiver mResultsReceiver = new ResultReceiver();
80 
81     // Initialized in onCreate
82     ArrayList<String> mNonLegacyCameraIds = null;
83 
84     // Scenes
85     private static final ArrayList<String> mSceneIds = new ArrayList<String> () { {
86             add("scene0");
87             add("scene1");
88             add("scene2");
89             add("scene3");
90             add("scene4");
91             add("scene5");
92         } };
93 
94     // TODO: cache the following in saved bundle
95     private Set<ResultKey> mAllScenes = null;
96     // (camera, scene) -> (pass, fail)
97     private final HashMap<ResultKey, Boolean> mExecutedScenes = new HashMap<>();
98     // map camera id to ITS summary report path
99     private final HashMap<ResultKey, String> mSummaryMap = new HashMap<>();
100 
101     final class ResultKey {
102         public final String cameraId;
103         public final String sceneId;
104 
ResultKey(String cameraId, String sceneId)105         public ResultKey(String cameraId, String sceneId) {
106             this.cameraId = cameraId;
107             this.sceneId = sceneId;
108         }
109 
110         @Override
equals(final Object o)111         public boolean equals(final Object o) {
112             if (o == null) return false;
113             if (this == o) return true;
114             if (o instanceof ResultKey) {
115                 final ResultKey other = (ResultKey) o;
116                 return cameraId.equals(other.cameraId) && sceneId.equals(other.sceneId);
117             }
118             return false;
119         }
120 
121         @Override
hashCode()122         public int hashCode() {
123             int h = cameraId.hashCode();
124             h = ((h << 5) - h) ^ sceneId.hashCode();
125             return h;
126         }
127     }
128 
ItsTestActivity()129     public ItsTestActivity() {
130         super(R.layout.its_main,
131                 R.string.camera_its_test,
132                 R.string.camera_its_test_info,
133                 R.string.camera_its_test);
134     }
135 
136     private final Comparator<ResultKey> mComparator = new Comparator<ResultKey>() {
137         @Override
138         public int compare(ResultKey k1, ResultKey k2) {
139             if (k1.cameraId.equals(k2.cameraId))
140                 return k1.sceneId.compareTo(k2.sceneId);
141             return k1.cameraId.compareTo(k2.cameraId);
142         }
143     };
144 
145     class ResultReceiver extends BroadcastReceiver {
146         @Override
onReceive(Context context, Intent intent)147         public void onReceive(Context context, Intent intent) {
148             Log.i(TAG, "Received result for Camera ITS tests");
149             if (ACTION_ITS_RESULT.equals(intent.getAction())) {
150                 String version = intent.getStringExtra(EXTRA_VERSION);
151                 if (version == null || !version.equals(CURRENT_VERSION)) {
152                     Log.e(TAG, "Its result version mismatch: expect " + CURRENT_VERSION +
153                             ", got " + ((version == null) ? "null" : version));
154                     ItsTestActivity.this.showToast(R.string.its_version_mismatch);
155                     return;
156                 }
157 
158                 String cameraId = intent.getStringExtra(EXTRA_CAMERA_ID);
159                 String results = intent.getStringExtra(EXTRA_RESULTS);
160                 if (cameraId == null || results == null) {
161                     Log.e(TAG, "cameraId = " + ((cameraId == null) ? "null" : cameraId) +
162                             ", results = " + ((results == null) ? "null" : results));
163                     return;
164                 }
165 
166                 if (!mNonLegacyCameraIds.contains(cameraId)) {
167                     Log.e(TAG, "Unknown camera id " + cameraId + " reported to ITS");
168                     return;
169                 }
170 
171                 try {
172                     /* Sample JSON results string
173                     {
174                        "scene0":{
175                           "result":"PASS",
176                           "summary":"/sdcard/cam0_scene0.txt"
177                        },
178                        "scene1":{
179                           "result":"NOT_EXECUTED"
180                        },
181                        "scene2":{
182                           "result":"FAIL",
183                           "summary":"/sdcard/cam0_scene2.txt"
184                        }
185                     }
186                     */
187                     JSONObject jsonResults = new JSONObject(results);
188                     Set<String> scenes = new HashSet<>();
189                     Iterator<String> keys = jsonResults.keys();
190                     while (keys.hasNext()) {
191                         scenes.add(keys.next());
192                     }
193                     boolean newScenes = false;
194                     if (mAllScenes == null) {
195                         mAllScenes = new TreeSet<>(mComparator);
196                         newScenes = true;
197                     } else { // See if scene lists changed
198                         for (String scene : scenes) {
199                             if (!mAllScenes.contains(new ResultKey(cameraId, scene))) {
200                                 // Scene list changed. Cleanup previous test results
201                                 newScenes = true;
202                                 break;
203                             }
204                         }
205                         for (ResultKey k : mAllScenes) {
206                             if (!scenes.contains(k.sceneId)) {
207                                 newScenes = true;
208                                 break;
209                             }
210                         }
211                     }
212                     if (newScenes) {
213                         mExecutedScenes.clear();
214                         mAllScenes.clear();
215                         for (String scene : scenes) {
216                             for (String c : mNonLegacyCameraIds) {
217                                 mAllScenes.add(new ResultKey(c, scene));
218                             }
219                         }
220                     }
221 
222                     // Update test execution results
223                     for (String scene : scenes) {
224                         JSONObject sceneResult = jsonResults.getJSONObject(scene);
225                         String result = sceneResult.getString("result");
226                         if (result == null) {
227                             Log.e(TAG, "Result for " + scene + " is null");
228                             return;
229                         }
230                         Log.i(TAG, "ITS camera" + cameraId + " " + scene + ": result:" + result);
231                         if (!RESULT_VALUES.contains(result)) {
232                             Log.e(TAG, "Unknown result for " + scene + ": " + result);
233                             return;
234                         }
235                         ResultKey key = new ResultKey(cameraId, scene);
236                         if (result.equals(RESULT_PASS) || result.equals(RESULT_FAIL)) {
237                             boolean pass = result.equals(RESULT_PASS);
238                             mExecutedScenes.put(key, pass);
239                             setTestResult(testId(cameraId, scene), pass ?
240                                     TestResult.TEST_RESULT_PASSED : TestResult.TEST_RESULT_FAILED);
241                             Log.e(TAG, "setTestResult for " + testId(cameraId, scene) + ": " + result);
242                             String summary = sceneResult.optString("summary");
243                             if (!summary.equals("")) {
244                                 mSummaryMap.put(key, summary);
245                             }
246                         } // do nothing for NOT_EXECUTED scenes
247                     }
248                 } catch (org.json.JSONException e) {
249                     Log.e(TAG, "Error reading json result string:" + results , e);
250                     return;
251                 }
252 
253                 // Set summary if all scenes reported
254                 if (mSummaryMap.keySet().containsAll(mAllScenes)) {
255                     StringBuilder summary = new StringBuilder();
256                     for (String path : mSummaryMap.values()) {
257                         appendFileContentToSummary(summary, path);
258                     }
259                     if (summary.length() > MAX_SUMMARY_LEN) {
260                         Log.w(TAG, "ITS summary report too long: len: " + summary.length());
261                     }
262                     ItsTestActivity.this.getReportLog().setSummary(
263                             summary.toString(), 1.0, ResultType.NEUTRAL, ResultUnit.NONE);
264                 }
265 
266                 // Display current progress
267                 StringBuilder progress = new StringBuilder();
268                 for (ResultKey k : mAllScenes) {
269                     String status = RESULT_NOT_EXECUTED;
270                     if (mExecutedScenes.containsKey(k)) {
271                         status = mExecutedScenes.get(k) ? RESULT_PASS : RESULT_FAIL;
272                     }
273                     progress.append(String.format("Cam %s, %s: %s\n",
274                             k.cameraId, k.sceneId, status));
275                 }
276                 TextView progressView = (TextView) findViewById(R.id.its_progress);
277                 progressView.setMovementMethod(new ScrollingMovementMethod());
278                 progressView.setText(progress.toString());
279 
280 
281                 // Enable pass button if all scenes pass
282                 boolean allScenesPassed = true;
283                 for (ResultKey k : mAllScenes) {
284                     Boolean pass = mExecutedScenes.get(k);
285                     if (pass == null || pass == false) {
286                         allScenesPassed = false;
287                         break;
288                     }
289                 }
290                 if (allScenesPassed) {
291                     // Enable pass button
292                     ItsTestActivity.this.showToast(R.string.its_test_passed);
293                     ItsTestActivity.this.getPassButton().setEnabled(true);
294                     ItsTestActivity.this.setTestResultAndFinish(true);
295                 } else {
296                     ItsTestActivity.this.getPassButton().setEnabled(false);
297                 }
298             }
299         }
300 
appendFileContentToSummary(StringBuilder summary, String path)301         private void appendFileContentToSummary(StringBuilder summary, String path) {
302             BufferedReader reader = null;
303             try {
304                 reader = new BufferedReader(new FileReader(path));
305                 String line = null;
306                 do {
307                     line = reader.readLine();
308                     if (line != null) {
309                         summary.append(line);
310                     }
311                 } while (line != null);
312             } catch (FileNotFoundException e) {
313                 Log.e(TAG, "Cannot find ITS summary file at " + path);
314                 summary.append("Cannot find ITS summary file at " + path);
315             } catch (IOException e) {
316                 Log.e(TAG, "IO exception when trying to read " + path);
317                 summary.append("IO exception when trying to read " + path);
318             } finally {
319                 if (reader != null) {
320                     try {
321                         reader.close();
322                     } catch (IOException e) {
323                     }
324                 }
325             }
326         }
327     }
328 
329     @Override
onCreate(Bundle savedInstanceState)330     protected void onCreate(Bundle savedInstanceState) {
331         // Hide the test if all camera devices are legacy
332         CameraManager manager = (CameraManager) this.getSystemService(Context.CAMERA_SERVICE);
333         try {
334             String[] cameraIds = manager.getCameraIdList();
335             mNonLegacyCameraIds = new ArrayList<String>();
336             boolean allCamerasAreLegacy = true;
337             for (String id : cameraIds) {
338                 CameraCharacteristics characteristics = manager.getCameraCharacteristics(id);
339                 if (characteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL)
340                         != CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) {
341                     mNonLegacyCameraIds.add(id);
342                     allCamerasAreLegacy = false;
343                 }
344             }
345             if (allCamerasAreLegacy) {
346                 showToast(R.string.all_legacy_devices);
347                 ItsTestActivity.this.getReportLog().setSummary(
348                         "PASS: all cameras on this device are LEGACY"
349                         , 1.0, ResultType.NEUTRAL, ResultUnit.NONE);
350                 setTestResultAndFinish(true);
351             }
352         } catch (CameraAccessException e) {
353             Toast.makeText(ItsTestActivity.this,
354                     "Received error from camera service while checking device capabilities: "
355                             + e, Toast.LENGTH_SHORT).show();
356         }
357 
358         super.onCreate(savedInstanceState);
359         getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
360     }
361 
362     @Override
showManualTestDialog(final DialogTestListItem test, final DialogTestListItem.TestCallback callback)363     public void showManualTestDialog(final DialogTestListItem test,
364             final DialogTestListItem.TestCallback callback) {
365         //Nothing todo for ITS
366     }
367 
testTitle(String cam, String scene)368     protected String testTitle(String cam, String scene) {
369         return "Camera: " + cam + ", " + scene;
370     }
371 
testId(String cam, String scene)372     protected String testId(String cam, String scene) {
373         return "Camera_ITS_" + cam + "_" + scene;
374     }
375 
setupItsTests(ArrayTestListAdapter adapter)376     protected void setupItsTests(ArrayTestListAdapter adapter) {
377         for (String cam : mNonLegacyCameraIds) {
378             for (String scene : mSceneIds) {
379                 adapter.add(new DialogTestListItem(this,
380                 testTitle(cam, scene),
381                 testId(cam, scene)));
382             }
383         }
384     }
385 
386     @Override
setupTests(ArrayTestListAdapter adapter)387     protected void setupTests(ArrayTestListAdapter adapter) {
388         setupItsTests(adapter);
389     }
390 
391     @Override
onResume()392     protected void onResume() {
393         super.onResume();
394         CameraManager manager = (CameraManager) this.getSystemService(Context.CAMERA_SERVICE);
395         if (manager == null) {
396             showToast(R.string.no_camera_manager);
397         } else {
398             Log.d(TAG, "register ITS result receiver");
399             IntentFilter filter = new IntentFilter(ACTION_ITS_RESULT);
400             registerReceiver(mResultsReceiver, filter);
401         }
402     }
403 
404     @Override
onPause()405     protected void onPause() {
406         super.onPause();
407         Log.d(TAG, "unregister ITS result receiver");
408         unregisterReceiver(mResultsReceiver);
409     }
410 
411     @Override
onConfigurationChanged(Configuration newConfig)412     public void onConfigurationChanged(Configuration newConfig) {
413         super.onConfigurationChanged(newConfig);
414         setContentView(R.layout.its_main);
415         setInfoResources(R.string.camera_its_test, R.string.camera_its_test_info, -1);
416         setPassFailButtonClickListeners();
417     }
418 }
419