1 /*
2  * Copyright (C) 2020 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 android.device.collectors;
18 
19 import android.device.collectors.annotations.OptionClass;
20 import android.os.Bundle;
21 import android.os.SystemClock;
22 import android.util.Log;
23 
24 import androidx.annotation.Nullable;
25 
26 import com.google.common.annotations.VisibleForTesting;
27 import com.google.common.base.Strings;
28 
29 import org.junit.runner.Description;
30 import org.junit.runner.Result;
31 import org.junit.runner.notification.Failure;
32 
33 import java.io.File;
34 import java.nio.charset.StandardCharsets;
35 import java.util.HashMap;
36 import java.util.concurrent.TimeUnit;
37 
38 /**
39  * A {@link GcaEventLogCollector} that captures Google Camera App (GCA) on-device event log protos
40  * and save them to external storage at run_listeners/camera_events directory. This collector
41  * disables selinux at the start of the test run and recover at the end. So if your tests require
42  * selinux enforcing, please do not use this collector.
43  */
44 @OptionClass(alias = "gca-proto-log-collector")
45 public class GcaEventLogCollector extends BaseMetricListener {
46 
47     private static final String TAG = GcaEventLogCollector.class.getSimpleName();
48     private static final String DEST_DIR = "run_listeners/camera_events";
49     private static final String CAMERA_EVENT_LOG_PATTERN =
50             "/data/user/0/%s/files/camera_events/session.pb";
51 
52     @VisibleForTesting
53     static final String COLLECT_TEST_FAILURE_CAMERA_LOGS = "collect_test_failure_camera_logs";
54 
55     @VisibleForTesting
56     static final String COLLECT_CAMERA_LOGS_PER_RUN = "collect_camera_logs_per_run";
57 
58     private static final String GOOGLE_CAMERA_APP_PACKAGE = "google_camera_app_package";
59     private static final long WAIT_AFTER_APP_FORCE_STOP_MILLIS = TimeUnit.SECONDS.toMillis(2);
60 
61     enum SeLinuxEnforceProperty {
62         ENFORCING("1"),
63         PERMISSIVE("0");
64 
65         private String property;
66 
getProperty()67         public String getProperty() {
68             return property;
69         }
70 
SeLinuxEnforceProperty(String property)71         private SeLinuxEnforceProperty(String property) {
72             this.property = property;
73         }
74     }
75 
76     private String mGcaPkg = "com.google.android.GoogleCamera";
77     private boolean mIsCollectPerRun = false;
78     private boolean mCollectMetricsForFailedTest = false;
79     private boolean mIsTestFailed;
80     private File mDestDir;
81     private String mEventLogPath;
82     private SeLinuxEnforceProperty mOrigSeLinuxEnforceProp;
83     private String mOrigCameraEventProp;
84 
85     // Map to keep track of test iterations for multiple test iterations.
86     private HashMap<Description, Integer> mTestIterations = new HashMap<>();
87 
GcaEventLogCollector()88     public GcaEventLogCollector() {
89         super();
90     }
91 
92     @VisibleForTesting
GcaEventLogCollector(Bundle args)93     GcaEventLogCollector(Bundle args) {
94         super(args);
95     }
96 
97     @Override
onTestRunStart(DataRecord runData, Description description)98     public void onTestRunStart(DataRecord runData, Description description) {
99         mDestDir = createAndEmptyDirectory(DEST_DIR);
100         mEventLogPath = String.format(CAMERA_EVENT_LOG_PATTERN, mGcaPkg);
101         cleanUpEventLog();
102         mOrigSeLinuxEnforceProp = getSeLinuxEnforceProperty();
103         mOrigCameraEventProp = getCameraEventLogProperty();
104         setSeLinuxEnforceProperty(SeLinuxEnforceProperty.PERMISSIVE);
105         setCameraEventLogProperty("1");
106         killGoogleCameraApp();
107     }
108 
109     @Override
onTestRunEnd(DataRecord runData, Result result)110     public void onTestRunEnd(DataRecord runData, Result result) {
111         try {
112             if (mIsCollectPerRun) {
113                 collectEventLog(runData, null);
114             }
115             setCameraEventLogProperty(mOrigCameraEventProp);
116             killGoogleCameraApp();
117         } finally {
118             setSeLinuxEnforceProperty(mOrigSeLinuxEnforceProp);
119         }
120     }
121 
122     @Override
onTestStart(DataRecord runData, Description description)123     public void onTestStart(DataRecord runData, Description description) {
124         mIsTestFailed = false;
125         if (!mIsCollectPerRun) {
126             killGoogleCameraApp();
127             cleanUpEventLog();
128         }
129         // Keep track of test iterations.
130         mTestIterations.computeIfPresent(description, (desc, iteration) -> iteration + 1);
131         mTestIterations.computeIfAbsent(description, desc -> 1);
132     }
133 
134     @Override
onTestEnd(DataRecord testData, Description description)135     public void onTestEnd(DataRecord testData, Description description) {
136         if (mIsCollectPerRun) {
137             return;
138         }
139         if (!mCollectMetricsForFailedTest && mIsTestFailed) {
140             return;
141         }
142         collectEventLog(testData, description);
143     }
144 
145     @Override
onTestFail(DataRecord runData, Description description, Failure failure)146     public void onTestFail(DataRecord runData, Description description, Failure failure) {
147         mIsTestFailed = true;
148     }
149 
150     @Override
setupAdditionalArgs()151     public void setupAdditionalArgs() {
152         Bundle args = getArgsBundle();
153         if (!Strings.isNullOrEmpty(args.getString(GOOGLE_CAMERA_APP_PACKAGE))) {
154             mGcaPkg = args.getString(GOOGLE_CAMERA_APP_PACKAGE);
155         }
156         mIsCollectPerRun =
157                 Boolean.parseBoolean(args.getString(COLLECT_CAMERA_LOGS_PER_RUN, "false"));
158         mCollectMetricsForFailedTest =
159                 Boolean.parseBoolean(args.getString(COLLECT_TEST_FAILURE_CAMERA_LOGS, "false"));
160     }
161 
collectEventLog(DataRecord testData, Description description)162     private void collectEventLog(DataRecord testData, Description description) {
163         logListResult(mEventLogPath);
164         String fileName = buildCollectedEventLogFileName(description);
165         Log.d(TAG, String.format("Destination event log file name: %s", fileName));
166         copyEventLogToSharedStorage(fileName);
167         testData.addFileMetric(fileName, new File(mDestDir, fileName));
168     }
169 
170     @VisibleForTesting
copyEventLogToSharedStorage(String fileName)171     protected void copyEventLogToSharedStorage(String fileName) {
172         String cmd =
173                 String.format("cp %s %s/%s", mEventLogPath, mDestDir.getAbsolutePath(), fileName);
174         Log.d(TAG, "Copy command: " + cmd);
175         executeCommandBlocking(cmd);
176         logListResult(mDestDir.getAbsolutePath());
177     }
178 
cleanUpEventLog()179     private void cleanUpEventLog() {
180         executeCommandBlocking("rm " + mEventLogPath);
181         logListResult(mEventLogPath);
182     }
183 
getCameraEventLogProperty()184     private String getCameraEventLogProperty() {
185         String res =
186                 new String(
187                                 executeCommandBlocking("getprop camera.use_local_logger"),
188                                 StandardCharsets.UTF_8)
189                         .trim();
190         return res.isEmpty() ? "0" : res;
191     }
192 
setCameraEventLogProperty(String value)193     private void setCameraEventLogProperty(String value) {
194         if (!getCameraEventLogProperty().equals(value)) {
195             Log.d(TAG, "Setting property camera.use_local_logger to " + value);
196             executeCommandBlocking("setprop camera.use_local_logger " + value);
197         }
198     }
199 
killGoogleCameraApp()200     private void killGoogleCameraApp() {
201         executeCommandBlocking("am force-stop " + mGcaPkg);
202         // Add delay after force stop GCA to avoid new instance start before the app fully die.
203         // (https://b.corp.google.com/issues/181777896#comment27)
204         SystemClock.sleep(WAIT_AFTER_APP_FORCE_STOP_MILLIS);
205     }
206 
getSeLinuxEnforceProperty()207     private SeLinuxEnforceProperty getSeLinuxEnforceProperty() {
208         String res =
209                 new String(executeCommandBlocking("getenforce"), StandardCharsets.UTF_8).trim();
210         return SeLinuxEnforceProperty.valueOf(res.toUpperCase());
211     }
212 
setSeLinuxEnforceProperty(SeLinuxEnforceProperty value)213     private void setSeLinuxEnforceProperty(SeLinuxEnforceProperty value) {
214         if (getSeLinuxEnforceProperty() != value) {
215             Log.d(TAG, "Setting SeLinux enforcing to " + value.getProperty());
216             executeCommandBlocking("setenforce " + value.getProperty());
217         }
218     }
219 
buildCollectedEventLogFileName(@ullable Description description)220     private String buildCollectedEventLogFileName(@Nullable Description description) {
221         long timestamp = System.currentTimeMillis();
222         if (description == null) {
223             return String.format("session_run_%d.pb", timestamp);
224         }
225         int iteration = mTestIterations.get(description);
226         return String.format(
227                 "session_%s_%s%s_%d.pb",
228                 description.getClassName(),
229                 description.getMethodName(),
230                 iteration == 1 ? "" : ("_" + String.valueOf(iteration)),
231                 timestamp);
232     }
233 
logListResult(String targetPath)234     private void logListResult(String targetPath) {
235         byte[] lsResult = executeCommandBlocking("ls " + targetPath);
236         Log.d(
237                 TAG,
238                 String.format(
239                         "List path %s result: %s",
240                         targetPath, new String(lsResult, StandardCharsets.UTF_8)));
241     }
242 }
243