1{
2 composeAndroidPackages,
3 stdenv,
4 lib,
5 runtimeShell,
6 meta,
7}:
8{
9 name,
10 app ? null,
11 platformVersion ? "35",
12 abiVersion ? "x86",
13 systemImageType ? "default",
14 enableGPU ? false, # Enable GPU acceleration. It's deprecated, instead use `configOptions` below.
15 configOptions ? (
16 # List of options to add in config.ini
17 lib.optionalAttrs enableGPU (
18 lib.warn "enableGPU argument is deprecated and will be removed; use configOptions instead" {
19 "hw.gpu.enabled" = "yes";
20 }
21 )
22 ),
23 extraAVDFiles ? [ ],
24 package ? null,
25 activity ? null,
26 androidUserHome ? null,
27 avdHomeDir ? null, # Support old variable with non-standard naming!
28 androidAvdHome ? avdHomeDir,
29 deviceName ? "device",
30 sdkExtraArgs ? { },
31 androidAvdFlags ? null,
32 androidEmulatorFlags ? null,
33}:
34
35let
36 sdkArgs = {
37 includeEmulator = true;
38 includeSystemImages = true;
39 }
40 // sdkExtraArgs
41 // {
42 cmdLineToolsVersion = "8.0";
43 platformVersions = [ platformVersion ];
44 systemImageTypes = [ systemImageType ];
45 abiVersions = [ abiVersion ];
46 };
47
48 sdk = (composeAndroidPackages sdkArgs).androidsdk;
49in
50stdenv.mkDerivation {
51 inherit name;
52
53 buildCommand = ''
54 mkdir -p $out/bin
55
56 cat > $out/bin/run-test-emulator << "EOF"
57 #!${runtimeShell} -e
58
59 # We need a TMPDIR
60 if [ "$TMPDIR" = "" ]
61 then
62 export TMPDIR=/tmp
63 fi
64
65 ${
66 if androidUserHome == null then
67 ''
68 # Store the virtual devices somewhere else, instead of polluting a user's HOME directory
69 export ANDROID_USER_HOME=$(mktemp -d $TMPDIR/nix-android-user-home-XXXX)
70 ''
71 else
72 ''
73 mkdir -p "${androidUserHome}"
74 export ANDROID_USER_HOME="${androidUserHome}"
75 ''
76 }
77
78 ${
79 if androidAvdHome == null then
80 ''
81 export ANDROID_AVD_HOME=$ANDROID_USER_HOME/avd
82 ''
83 else
84 ''
85 mkdir -p "${androidAvdHome}"
86 export ANDROID_AVD_HOME="${androidAvdHome}"
87 ''
88 }
89
90 # We need to specify the location of the Android SDK root folder
91 export ANDROID_SDK_ROOT=${sdk}/libexec/android-sdk
92
93 ${lib.optionalString (androidAvdFlags != null) ''
94 # If NIX_ANDROID_AVD_FLAGS is empty
95 if [[ -z "$NIX_ANDROID_AVD_FLAGS" ]]; then
96 NIX_ANDROID_AVD_FLAGS="${androidAvdFlags}"
97 fi
98 ''}
99
100 ${lib.optionalString (androidEmulatorFlags != null) ''
101 # If NIX_ANDROID_EMULATOR_FLAGS is empty
102 if [[ -z "$NIX_ANDROID_EMULATOR_FLAGS" ]]; then
103 NIX_ANDROID_EMULATOR_FLAGS="${androidEmulatorFlags}"
104 fi
105 ''}
106
107 # We have to look for a free TCP port
108
109 echo "Looking for a free TCP port in range 5554-5584" >&2
110
111 for i in $(seq 5554 2 5584)
112 do
113 if [ -z "$(${sdk}/bin/adb devices | grep emulator-$i)" ]
114 then
115 port=$i
116 break
117 fi
118 done
119
120 if [ -z "$port" ]
121 then
122 echo "Unfortunately, the emulator port space is exhausted!" >&2
123 exit 1
124 else
125 echo "We have a free TCP port: $port" >&2
126 fi
127
128 export ANDROID_SERIAL="emulator-$port"
129
130 # Create a virtual android device for testing if it does not exist
131 if [ "$(${sdk}/bin/avdmanager list avd | grep 'Name: ${deviceName}')" = "" ]
132 then
133 # Create a virtual android device
134 yes "" | ${sdk}/bin/avdmanager create avd --force -n ${deviceName} -k "system-images;android-${platformVersion};${systemImageType};${abiVersion}" -p $ANDROID_AVD_HOME/${deviceName}.avd $NIX_ANDROID_AVD_FLAGS
135
136 ${builtins.concatStringsSep "\n" (
137 lib.mapAttrsToList (configKey: configValue: ''
138 echo "${configKey} = ${configValue}" >> $ANDROID_AVD_HOME/${deviceName}.avd/config.ini
139 '') configOptions
140 )}
141
142 ${lib.concatMapStrings (extraAVDFile: ''
143 ln -sf ${extraAVDFile} $ANDROID_AVD_HOME/${deviceName}.avd
144 '') extraAVDFiles}
145 fi
146
147 # Launch the emulator
148 echo "\nLaunch the emulator"
149 $ANDROID_SDK_ROOT/emulator/emulator -avd ${deviceName} -no-boot-anim -port $port $NIX_ANDROID_EMULATOR_FLAGS &
150
151 # Wait until the device has completely booted
152 echo "Waiting until the emulator has booted the ${deviceName} and the package manager is ready..." >&2
153
154 ${sdk}/libexec/android-sdk/platform-tools/adb -s emulator-$port wait-for-device
155
156 echo "Device state has been reached" >&2
157
158 while [ -z "$(${sdk}/libexec/android-sdk/platform-tools/adb -s emulator-$port shell getprop dev.bootcomplete | grep 1)" ]
159 do
160 sleep 5
161 done
162
163 echo "dev.bootcomplete property is 1" >&2
164
165 #while [ -z "$(${sdk}/libexec/android-sdk/platform-tools/adb -s emulator-$port shell getprop sys.boot_completed | grep 1)" ]
166 #do
167 #sleep 5
168 #done
169
170 #echo "sys.boot_completed property is 1" >&2
171
172 echo "ready" >&2
173
174 ${lib.optionalString (app != null) ''
175 # Install the App through the debugger, if it has not been installed yet
176
177 if [ -z "${package}" ] || [ "$(${sdk}/libexec/android-sdk/platform-tools/adb -s emulator-$port shell pm list packages | grep package:${package})" = "" ]
178 then
179 if [ -d "${app}" ]
180 then
181 appPath="$(echo ${app}/*.apk)"
182 else
183 appPath="${app}"
184 fi
185
186 ${sdk}/libexec/android-sdk/platform-tools/adb -s emulator-$port install "$appPath"
187 fi
188
189 # Start the application
190 ${lib.optionalString (package != null && activity != null) ''
191 ${sdk}/libexec/android-sdk/platform-tools/adb -s emulator-$port shell am start -a android.intent.action.MAIN -n ${package}/${activity}
192 ''}
193 ''}
194 EOF
195 chmod +x $out/bin/run-test-emulator
196 '';
197
198 meta = meta // {
199 mainProgram = "run-test-emulator";
200 };
201}