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