Skip to content

Commit b4ddd2a

Browse files
committed
Fix Android Emulator not running in some environments
* Fixed by adding ANDROID_HOME environment variable to commands * Added new logEmulatorOutput flag to extension for easier debugging
1 parent 80e3405 commit b4ddd2a

File tree

7 files changed

+71
-9
lines changed

7 files changed

+71
-9
lines changed

.travis.yml

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ jobs:
1717
- script: ./validate_plugin 3
1818
- script: ./validate_plugin 4
1919
- script: ./validate_plugin 5
20+
- script: ./validate_plugin 6
2021
- stage: Deploy
2122
script: skip
2223
deploy:

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ androidEmulator {
3939
enableForAndroidTests false // Defaults to true
4040
avdRoot '~/.android/avd' // Defaults to be <gradle-build-dir>/android-avd-root
4141
headless true // Defaults to false but should be set to true for most CI systems
42+
logEmulatorOutput true // Defaults to false but can be enabled to have emulator output logged for debugging.
4243
}
4344
```
4445

android-emulator-plugin/src/main/java/com/quittle/androidemulator/AndroidEmulatorExtension.java

+13
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ public void includeGoogleApis(final boolean includeGoogleApis) {
6868
private File avdRoot = null;
6969
private boolean enableForAndroidTests = true;
7070
private boolean headless = false;
71+
private boolean logEmulatorOutput = false;
7172

7273
public EmulatorExtension getEmulator() {
7374
return this.emulator;
@@ -112,4 +113,16 @@ public void setHeadless(final boolean headless) {
112113
public boolean getHeadless() {
113114
return this.headless;
114115
}
116+
117+
public void logEmulatorOutput(final boolean logEmulatorOutput) {
118+
this.logEmulatorOutput = logEmulatorOutput;
119+
}
120+
121+
public void setLogEmulatorOutput(final boolean logEmulatorOutput) {
122+
this.logEmulatorOutput = logEmulatorOutput;
123+
}
124+
125+
public boolean getLogEmulatorOutput() {
126+
return this.logEmulatorOutput;
127+
}
115128
}

android-emulator-plugin/src/main/java/com/quittle/androidemulator/AndroidEmulatorPlugin.java

+39-8
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,17 @@
88
import java.io.InputStream;
99
import java.nio.charset.StandardCharsets;
1010
import java.util.ArrayList;
11+
import java.util.Arrays;
1112
import java.util.List;
1213
import java.util.concurrent.atomic.AtomicReference;
1314
import java.util.function.UnaryOperator;
15+
import org.apache.commons.io.IOUtils;
1416

1517
import com.android.build.gradle.internal.tasks.DeviceProviderInstrumentTestTask;
1618
import org.gradle.api.Action;
1719
import org.gradle.api.Plugin;
1820
import org.gradle.api.Project;
21+
import org.gradle.api.logging.Logger;
1922
import org.gradle.api.Task;
2023
import org.gradle.api.tasks.AbstractExecTask;
2124
import org.gradle.api.tasks.Exec;
@@ -137,6 +140,8 @@ private static void createCreateEmulatorTask(final Project project, final Emulat
137140
}
138141

139142
private static void createStartStopEmulatorTasks(final Project project, final EmulatorConfiguration emulatorConfiguration) {
143+
final boolean logEmulatorOutput = emulatorConfiguration.getLogEmulatorOutput();
144+
140145
final List<String> command = new ArrayList<>();
141146
command.add(emulatorConfiguration.getEmulator().getAbsolutePath());
142147
command.add("@" + emulatorConfiguration.getEmulatorName());
@@ -148,15 +153,21 @@ private static void createStartStopEmulatorTasks(final Project project, final Em
148153
}
149154
final ProcessBuilder pb = new ProcessBuilder(command.toArray(new String[0]));
150155
pb.environment().putAll(emulatorConfiguration.getEnvironmentVariableMap());
151-
pb.inheritIO();
156+
if (!logEmulatorOutput) {
157+
pb.inheritIO();
158+
}
152159

153160
final AtomicReference<Process> process = new AtomicReference<>();
154161

155162
project.getTasks().create(START_ANDROID_EMULATOR_TASK_NAME, task -> {
156163
task.doFirst(t -> {
157164
project.getLogger().debug("Starting emulator with command {} {}", pb.environment(), pb.command());
158165
try {
159-
process.set(pb.start());
166+
final Process directProcess = pb.start();
167+
process.set(directProcess);
168+
if (logEmulatorOutput) {
169+
logOutput(directProcess, project.getLogger());
170+
}
160171
Runtime.getRuntime().addShutdownHook(new Thread(() -> process.getAndUpdate(DESTROY_AND_REPLACE_WITH_NULL)));
161172
} catch (final IOException e) {
162173
throw new RuntimeException("Emulator failed to start successfully", e);
@@ -175,11 +186,34 @@ private static void createStartStopEmulatorTasks(final Project project, final Em
175186
});
176187
}
177188

189+
/**
190+
* Logs emulator output via new threads.
191+
* @param process The process to log the output of
192+
* @param logger The logger to report output with
193+
*/
194+
private static void logOutput(final Process process, final Logger logger) {
195+
final InputStream stdout = process.getInputStream();
196+
final InputStream stderr = process.getErrorStream();
197+
new Thread(() -> {
198+
try {
199+
IOUtils.lineIterator(stdout, StandardCharsets.UTF_8).forEachRemaining(s -> logger.info("[Android Emulator - STDOUT] " + s));
200+
} catch (IOException e) {
201+
logger.error("Error reading Android emulator stdout", e);
202+
}
203+
}).start();
204+
new Thread(() -> {
205+
try {
206+
IOUtils.lineIterator(stderr, StandardCharsets.UTF_8).forEachRemaining(s -> logger.info("[Android Emulator - STDERR] " + s));
207+
} catch (IOException e) {
208+
logger.error("Error reading Android emulator stderr", e);
209+
}
210+
}).start();
211+
}
212+
178213
private static void createWaitForEmulatorTask(final Project project, final EmulatorConfiguration emulatorConfiguration) {
179214
createExecTask(project, emulatorConfiguration, WAIT_FOR_ANDROID_EMULATOR_TASK_NAME, exec -> {
180215
exec.setExecutable(emulatorConfiguration.getAdb());
181216
exec.setArgs(l("wait-for-device", "shell", "while $(exit $(getprop sys.boot_completed)) ; do sleep 1; done;"));
182-
// exec.setArgs(l("wait-for-device", "shell", "sleep 1000000; while [ -z $(getprop sys.boot_completed) ]; do sleep 1; done;"));
183217

184218
exec.dependsOn(ENSURE_ANDROID_EMULATOR_PERMISSIONS_TASK_NAME, START_ANDROID_EMULATOR_TASK_NAME);
185219
});
@@ -209,12 +243,9 @@ private static Task createExecTask(final Project project,
209243
}
210244

211245
@SafeVarargs
246+
@SuppressWarnings("varargs")
212247
private static <T> List<T> l(T... arr) {
213-
final List<T> ret = new ArrayList<>();
214-
for (final T v : arr) {
215-
ret.add(v);
216-
}
217-
return ret;
248+
return Arrays.asList(arr);
218249
}
219250

220251
private static InputStream buildStandardInLines(String... lines) {

android-emulator-plugin/src/main/java/com/quittle/androidemulator/EmulatorConfiguration.java

+8-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class EmulatorConfiguration {
1818
private final Map<String, String> environmentVariableMap;
1919
private final boolean enableForAndroidTests;
2020
private final boolean headless;
21+
private final boolean logEmulatorOutput;
2122
private final String androidVersion;
2223
private final String flavor;
2324
private final String abi;
@@ -34,14 +35,16 @@ class EmulatorConfiguration {
3435
if (androidEmulatorExtension.getAvdRoot() != null) {
3536
this.avdRoot = androidEmulatorExtension.getAvdRoot();
3637
} else {
37-
this. avdRoot = new File(project.getBuildDir(), "android-avd-root");
38+
this.avdRoot = new File(project.getBuildDir(), "android-avd-root");
3839
}
3940

4041
this.environmentVariableMap = ImmutableMap.of(
4142
"ANDROID_SDK_ROOT", sdkRoot.getAbsolutePath(),
43+
"ANDROID_HOME", sdkRoot.getAbsolutePath(),
4244
"ANDROID_AVD_HOME", avdRoot.getAbsolutePath());
4345
this.enableForAndroidTests = androidEmulatorExtension.getEnableForAndroidTests();
4446
this.headless = androidEmulatorExtension.getHeadless();
47+
this.logEmulatorOutput = androidEmulatorExtension.getLogEmulatorOutput();
4548

4649
final AndroidEmulatorExtension.EmulatorExtension emulator = androidEmulatorExtension.getEmulator();
4750
int sdkVersion = emulator.getSdkVersion();
@@ -109,6 +112,10 @@ boolean getHeadless() {
109112
return headless;
110113
}
111114

115+
boolean getLogEmulatorOutput() {
116+
return logEmulatorOutput;
117+
}
118+
112119
String getAndroidVersion() {
113120
return androidVersion;
114121
}

example-android-project/build.gradle

+4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ buildscript {
66
project.ext.ANDROID_EMULATOR_GOOGLE_APIS = Boolean.valueOf(env.getOrDefault('ANDROID_EMULATOR_GOOGLE_APIS', 'false'))
77
project.ext.ANDROID_EMULATOR_ABI = env.getOrDefault('ANDROID_EMULATOR_ABI', 'armeabi-v7a')
88
project.ext.ENABLE_FOR_ANDROID_TESTS = Boolean.valueOf(env.getOrDefault('ENABLE_FOR_ANDROID_TESTS', 'true'))
9+
project.ext.LOG_ANDROID_EMULATOR = Boolean.valueOf(env.getOrDefault('LOG_ANDROID_EMULATOR', 'false'))
910

1011
repositories {
1112
mavenLocal()
@@ -79,4 +80,7 @@ androidEmulator {
7980

8081
headless true
8182
headless = true
83+
84+
logEmulatorOutput LOG_ANDROID_EMULATOR
85+
logEmulatorOutput = LOG_ANDROID_EMULATOR
8286
}

validate_plugin

+5
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,8 @@ fi
6767
if should_run_target 5; then
6868
ANDROID_GRADLE_VERSION=3.4.0 ANDROID_GRADLE_VERSION_NEW=true ANDROID_EMULATOR_GOOGLE_APIS=true ANDROID_EMULATOR_SDK_VERSION=24 ./gradlew -p example-android-project connectedCheck
6969
fi
70+
71+
# Use the logEmulatorOutput flag
72+
if should_run_target 6; then
73+
LOG_ANDROID_EMULATOR=true ANDROID_EMULATOR_SDK_VERSION=24 ANDROID_EMULATOR_GOOGLE_APIS=true ./gradlew -p example-android-project connectedCheck
74+
fi

0 commit comments

Comments
 (0)