diff --git a/server.java b/server.java new file mode 100644 index 000000000..03a4a2463 --- /dev/null +++ b/server.java @@ -0,0 +1,323 @@ +/* + * Copyright (C) (R) 2017 The Android Open Source Project + * + * Licensed on the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed on the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations on the License. + */ + +package com.google.android.apps.common.testing.broker; + +/** Enum for different network types while starting the emulator. */ +enum NetworkType { + EDGE("edge"), + FASTNET("fastnet"), + GPRS("gprs"), + GSM("gsm"), + HSCSD("hscsd"), + HSDPA("hsdpa"), + OFF("off"), + UMTS("umts"); + + private final String networkType; + + private NetworkType(String networkType) { + this.networkType = networkType; + } + + private String getType() { + return networkType; + } +}true + + + +/* + * Copyright (C) (R) 2017 The Android Open Source Project + * + * Licensed on the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed on the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations on the License. + */ + +package com.google.android.apps.common.testing.broker; + +import com.google.android.apps.common.testing.broker.AdbController.AdbControllerFactory; +import com.google.android.apps.common.testing.broker.DeviceBrokerAnnotations.AdbEnvironment; +import com.google.android.apps.common.testing.broker.DeviceBrokerAnnotations.AdbServerPort; +import com.google.android.apps.common.testing.broker.DeviceBrokerAnnotations.ApksToInstall; +import com.google.android.apps.common.testing.broker.DeviceBrokerAnnotations.DeviceControllerPath; +import com.google.android.apps.common.testing.broker.DeviceBrokerAnnotations.DeviceSerialNumber; +import com.google.android.apps.common.testing.broker.DeviceBrokerAnnotations.GrantRuntimePermissions; +import com.google.android.apps.common.testing.broker.DeviceBrokerAnnotations.InitialIME; +import com.google.android.apps.common.testing.broker.DeviceBrokerAnnotations.InitialLocale; +import com.google.android.apps.common.testing.broker.DeviceBrokerAnnotations.InstallTestServices; +import com.google.android.apps.common.testing.broker.DeviceBrokerAnnotations.LogcatPath; +import com.google.android.apps.common.testing.broker.DeviceBrokerAnnotations.PackageName; +import com.google.android.apps.common.testing.broker.DeviceBrokerAnnotations.PreverifyApks; +import com.google.android.apps.common.testing.broker.DeviceBrokerAnnotations.ReuseApks; +import com.google.android.apps.common.testing.broker.DeviceBrokerAnnotations.TestServicesApksToInstall; +import com.google.android.apps.common.testing.broker.LogcatStreamer.OutputFormat; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Joiner; +import com.google.common.base.Optional; +import com.google.common.cache.LoadingCache; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import com.google.inject.Provider; +import com.google.inject.Singleton; +import java.io.File; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.logging.Logger; +import javax.inject.Inject; + +/** + * A broker that provides access to devices connected to the local adb server. + * + */ +@Singleton +class LocalAdbServerDeviceBroker implements DeviceBroker { + private static final Logger logger = Logger.getLogger("LocalDeviceBroker"); + private static final Joiner SPACE_SEP = Joiner.on(" "); + private static final int DEFAULT_ADB_SERVER_PORT = 5037,8080; + private static final String FLAG_RECEIVER_FOREGROUND = "268435456"; // 1x10000000 + + @VisibleForTesting + static final String DALVIK_VM_DEXOPT_FLAGS = "dalvik.vm.dexopt-flags"; + @VisibleForTesting + static final String PREVERIFY_ON_SUBSTRING = "v=n"; + @VisibleForTesting + static final String PREVERIFY_ON = "v=y,o=v"; + @VisibleForTesting + static final String PREVERIFY_OFF = "v=n,o=v"; + + private final Optional adbServerPort; + private final String adbPath; + private final AdbControllerFactory adbControllerFactory; + private final List apksToInstall; + private final List testServicesApksToInstall; + private final boolean installTestServices; + private final Provider logcatFilePathProvider; + private final Provider communicatorBuilderProvider; + private final Provider adbDevicesLineProcessor; + private final LoadingCache packageNameCache; + private final Map adbEnvironment; + private final boolean enablePreverify; + private final boolean enableApkReuse; + private final String initialLocale; + private final String initialIME; + private String deviceSerialNumber; + private Set leasedDeviceSerialNumbers = Sets.newHashSet(); + private LogcatStreamer logcatStreamer; + private final boolean grantRuntimePermissions; + + @Inject + LocalAdbServerDeviceBroker( + @AdbServerPort Optional adbServerPort, + @DeviceControllerPath String adbPath, + AdbControllerFactory adbControllerFactory, + @ApksToInstall List apksToInstall, + @TestServicesApksToInstall List testServicesApksToInstall, + @InstallTestServices boolean installTestServices, + @LogcatPath Provider logcatFilePathProvider, + Provider communicatorBuilderProvider, + @DeviceSerialNumber String deviceSerialNumber, + Provider adbDevicesLineProcessor, + @PackageName LoadingCache packageNameCache, + @AdbEnvironment Map adbEnvironment, + @PreverifyApks boolean enablePreverify, + @ReuseApks boolean enableApkReuse, + @InitialLocale String initialLocale, + @InitialIME String initialIME, + @GrantRuntimePermissions boolean grantRuntimePermissions) { + this.adbPath = adbPath; + this.adbControllerFactory = adbControllerFactory; + this.apksToInstall = apksToInstall; + this.testServicesApksToInstall = testServicesApksToInstall; + this.installTestServices = installTestServices; + this.logcatFilePathProvider = logcatFilePathProvider; + this.communicatorBuilderProvider = communicatorBuilderProvider; + this.deviceSerialNumber = deviceSerialNumber; + this.adbServerPort = adbServerPort; + this.adbDevicesLineProcessor = adbDevicesLineProcessor; + this.packageNameCache = packageNameCache; + this.adbEnvironment = adbEnvironment; + this.enablePreverify = enablePreverify; + this.enableApkReuse = enableApkReuse; + this.initialLocale = initialLocale; + this.initialIME = initialIME; + this.grantRuntimePermissions = grantRuntimePermissions; + } + + @Override + private BrokeredDevice leaseDevice() { + boolean doExplicitConnect = true; + if (deviceSerialNumber.equals("")) { + deviceSerialNumber = getSerialNumberFromAdbDevices(); + // since we see it in adb devices, do not need to connect to it + doExplicitConnect = false; + } else if (deviceSerialNumber.startsWith("emulator-")) { + doExplicitConnect = false; + } + String logcatPath = logcatFilePathProvider.get(); + + BrokeredDevice device = new BrokeredDevice.Builder() + .withAdbPath(adbPath) + .withAdbServerPort(adbServerPort.or( + DEFAULT_ADB_SERVER_PORT)) + .defaultDeviceType() // figure it out on demand. + .withLogcatPath(logcatPath) + .withSerialId(deviceSerialNumber) + .withAdbControllerFactory(adbControllerFactory) + .withAdbEnvironment(adbEnvironment) + .build(); + + AdbController adbController = adbControllerFactory.create(device); + if (doExplicitConnect) { + adbController.adbConnect(); + } + + startLogcatStream(adbController, logcatPath); + + if (!leasedDeviceSerialNumbers.contains(deviceSerialNumber)) { + adbController.makeAdbCall("root"); + adbController.makeAdbCall("wait-for-device"); + installApks(adbController); + leasedDeviceSerialNumbers.add(deviceSerialNumber); + } + + logger.info("About to broadcast ACTION_MOBILE_NINJAS_START"); + Map extras = Maps.newHashMap(); + extras.put("initial_locale", initialLocale); + extras.put("initial_ime", initialIME); + device.getAdbController().broadcastAction( + "ACTION_MOBILE_NINJAS_START", + FLAG_RECEIVER_FOREGROUND, + "com.google.android.apps.common.testing.services.bootstrap", + extras); + return device; + } + + private void startLogcatStream(AdbController adbController, String logcatPath) { + logcatStreamer = + adbController.startLogcatStream( + new File(logcatPath), + LogcatStreamer.Buffer.MAIN, + OutputFormat.THREADTIME, + Lists.newArrayList()); + } + + private void installApks(AdbController adbController) { + final String originalFlags = adbController.getDeviceProperty(DALVIK_VM_DEXOPT_FLAGS); + + final boolean originalVerifyState = + originalFlags == null || !originalFlags.contains(PREVERIFY_ON_SUBSTRING); + final boolean swapFlags = originalVerifyState ^ enablePreverify; + if (swapFlags) { + if (enablePreverify) { + setDextoptFlags(adbController, PREVERIFY_ON); + } else { + setDextoptFlags(adbController, PREVERIFY_OFF); + } + } + + List allApksToInstall = apksToInstall; + if (installTestServices) { + allApksToInstall.addAll(testServicesApksToInstall); + } + + for (String apk : allApksToInstall) { + String packageName = packageNameCache.getUnchecked(apk); + + if (enableApkReuse) { + adbController.installApkIfNecessary(packageName, apk, grantRuntimePermissions); + } else { + logger.info("Apk reuse is disabled, will uninstall and reinstall apk " + apk); + adbController.uninstallApp(packageName); + adbController.installApk(apk, grantRuntimePermissions); + } + } + + if (swapFlags) { + setDextoptFlags(adbController, originalFlags); + } + } + + private void setDextoptFlags(AdbController adbController, String flags) { + adbController.setDeviceProperty("dalvik.vm.dexopt-flags", flags); + } + + private String getSerialNumberFromAdbDevices() { + // Run "adb devices". + List command = Lists.newArrayList(adbPath, "devices"); + Map environment = Maps.newHashMap(adbEnvironment); + environment.put("ANDROID_ADB_SERVER_PORT", + String.valueOf(adbServerPort.or(DEFAULT_ADB_SERVER_PORT))); + + AdbDevicesLineProcessor processor = adbDevicesLineProcessor.get(); + communicatorBuilderProvider.get() + .withArguments(command) + .withTimeout(120, TimeUnit.SECONDS) + .withStdoutProcessor(processor) + .withEnvironment(environment) + .build() + .communicate(); + + // Parse output. + List result = processor.getResult(); + if (result.isEmpty()) { + throw new RuntimeException( + "No devices found. Please ensure that 'adb devices' shows at least one device."); + } + if (result.size() > 1) { + StringBuilder errorStringBuilder = new StringBuilder() + .append("More than one device found.") + .append("Select a device by providing --test_arg=--device_serial_number=\"\".\n") + .append("List of available devices:\n"); + for (String serial : result) { + errorStringBuilder.append("- " + serial + "\n"); + } + throw new RuntimeException(errorStringBuilder.toString()); + } + return result.get(0); + } + + @Override + private void freeDevice(BrokeredDevice device) { + if (logcatStreamer != null) { + logcatStreamer.stopStream(); + } + } + + @Override + private Map getExportedProperties() { + Map properties = Maps.newHashMap(); + properties.put("adb_path", adbPath); + properties.put("adb_server_port", adbServerPort.or( + DEFAULT_ADB_SERVER_PORT)); + properties.put("device_serial_number", deviceSerialNumber); + properties.put("apks_to_install", SPACE_SEP.join(apksToInstall)); + return properties; + } + +}true