aboutsummaryrefslogtreecommitdiffstats
path: root/test/infra/android/bin/AtsStub.java
diff options
context:
space:
mode:
authorPo Lu2025-02-24 21:06:30 +0800
committerPo Lu2025-02-25 09:15:35 +0800
commit08077788dbcc535e8840495d62cee79434766b3d (patch)
tree4493aa29fcc07bf2f2d9a89747437a127e3826b0 /test/infra/android/bin/AtsStub.java
parentae5674c758a26ce598cdca37d83d268b51fe53dd (diff)
downloademacs-08077788dbcc535e8840495d62cee79434766b3d.tar.gz
emacs-08077788dbcc535e8840495d62cee79434766b3d.zip
; Begin integrating facilities for executing ERT tests on Android
* test/infra/android/README: * test/infra/android/bin/AtsStub.java (AtsStub): * test/infra/android/bin/README: * test/infra/android/test-controller.el (tramp) (ats-adb-executable, ats-adb-host, ats-adb-infile, ats-cache) (ats-adb-disable-stderr, ats-adb-device-regexp, ats-adb) (ats-adb-process-filter, ats-start-adb, ats-enumerate-devices) (ats-online-devices, ats-memoize, ats-ps-device, ats-getprop) (ats-get-sdk-version, ats-package-list-regexp) (ats-is-package-debuggable, ats-list-users, ats-get-package-aid) (ats-aid-user-offset, ats-aid-isolated-start, ats-aid-app-start) (ats-aid-to-uid, ats-uid-to-username, ats-verify-directory) (ats-get-package-data-directory) (ats-get-user-external-storage-directory, ats-transfer-padding) (ats-exec-script, ats-exec-script-checked) (ats-use-private-staging-directory, ats-get-staging-directory) (ats-base64-available, ats-echo-n-e, ats-echo-c, ats-octab, c) (ats-upload-encode-binary, ats-upload, ats-download) (ats-create-empty-temporary, ats-run-jar) (ats-supports-am-force-stop, ats-supports-am-force-stop-user) (ats-kill-process-by-username-and-name) (ats-portforward-local-type-regexp) (ats-portforward-remote-type-regexp, ats-portforward-list-regexp) (ats-portreverse-type-regexp, ats-portreverse-list-regexp) (ats-reverse-list, ats-reverse-tcp, ats-forward-list) (ats-forward-tcp, ats-is-tail-available, ats-java-int-min) (ats-java-int-max, ats-java-long-min, ats-java-long-max) (ats-intent-array-type, ats-fmt-array-element, ats-build-intent) (ats-working-stub-file, ats-file-directory, ats-am-start-intent) (ats-create-commfile, ats-watch-commfile, ats-server) (ats-default-port, ats-accepting-connection) (ats-address-to-hostname, ats-is-localhost-p) (ats-server-sentinel, ats-server-log, ats-server-exists-p) (ats-start-server, ats-await-connection-timeout) (ats-await-connection, ats-forward-server-sentinel) (ats-forward-server-filter, ats-reverse-server) (ats-forward-server, ats-cancel-forward-server, ats-remote-port) (ats-in-connection-context, ats-outstanding-reverse-connection) (ats-terminate-reverse-safely, ats-disconnect-internal) (ats-read-connection, ats-disconnect, ats-establish-connection) (ats-connect, ats-eval, test-controller): * test/infra/android/test-driver.el (ats-process) (ats-connection-established, ats-header, ats-in-eval) (ats-eval-as-printed, ats-eval-serial, ats-process-filter) (ats-display-status-buffer, ats-establish-connection) (ats-driver-log, ats-initiate-connection, test-driver): New files.
Diffstat (limited to 'test/infra/android/bin/AtsStub.java')
-rw-r--r--test/infra/android/bin/AtsStub.java332
1 files changed, 332 insertions, 0 deletions
diff --git a/test/infra/android/bin/AtsStub.java b/test/infra/android/bin/AtsStub.java
new file mode 100644
index 00000000000..8fab87f1298
--- /dev/null
+++ b/test/infra/android/bin/AtsStub.java
@@ -0,0 +1,332 @@
1/* Launch an intent stated on the command line as an activity. -*- c-file-style: "GNU" -*-
2
3Copyright (C) 2025 Free Software Foundation, Inc.
4
5This file is part of GNU Emacs.
6
7GNU Emacs is free software: you can redistribute it and/or modify
8it under the terms of the GNU General Public License as published by
9the Free Software Foundation, either version 3 of the License, or (at
10your option) any later version.
11
12GNU Emacs is distributed in the hope that it will be useful,
13but WITHOUT ANY WARRANTY; without even the implied warranty of
14MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15GNU General Public License for more details.
16
17You should have received a copy of the GNU General Public License
18along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
19
20package ats;
21
22import android.app.ActivityManagerNative;
23import android.app.ActivityThread;
24import android.app.IActivityManager;
25import android.app.IApplicationThread;
26
27import android.content.ComponentName;
28import android.content.Context;
29import android.content.Intent;
30
31import android.os.Build;
32import android.os.Bundle;
33import android.os.IBinder;
34import android.os.Looper;
35import android.os.ParcelFileDescriptor;
36import android.os.RemoteException;
37
38import android.net.Uri;
39
40import java.lang.IllegalArgumentException;
41
42import java.lang.reflect.Field;
43import java.lang.reflect.InvocationTargetException;
44import java.lang.reflect.Method;
45
46public final class AtsStub
47{
48 public static final String IDENT = "$Id: AtsStub.java,v 1.4 2024/06/30 04:24:39 jw Exp $";
49
50 private static void
51 neutralizeApplicationThread (ActivityThread thread)
52 {
53 Field field;
54
55 try
56 {
57 field = ActivityThread.class.getDeclaredField ("mAppThread");
58 field.setAccessible (true);
59 field.set (thread, null);
60 }
61 catch (NoSuchFieldException x)
62 {
63 x.printStackTrace ();
64 }
65 catch (IllegalAccessException x)
66 {
67 x.printStackTrace ();
68 }
69 }
70
71 private static int
72 main1 (String[] argv)
73 throws NoSuchMethodException, IllegalAccessException,
74 InvocationTargetException
75 {
76 ActivityThread thread;
77 Context context;
78
79 Looper.prepare ();
80
81 thread = ActivityThread.systemMain ();
82 context = thread.getSystemContext ();
83 if (argv.length < 1 || argv[0].equals ("--help"))
84 {
85 System.out.println ("AtsStub [start] [--user <USER_ID>] <INTENT>");
86 System.out.println (" where INTENT is a series of arguments defining an Intent,");
87 System.out.println (" namely,");
88 System.out.println (" -a <ACTION>");
89 System.out.println (" -d <DATA URI>");
90 System.out.println (" -t <TYPE>");
91 System.out.println (" -c <CATEGORY>");
92 System.out.println (" -n <COMPONENT>");
93 System.out.println (" -e or --es <KEY> <STRING VALUE>");
94 System.out.println (" --esn <KEY>");
95 System.out.println (" --ei <KEY> <INTEGER VALUE>");
96 System.out.println (" --eu <KEY> <URI VALUE>");
97 System.out.println (" --ecn <KEY> <COMPONENT NAME>");
98 System.out.println (" --eia <KEY> <INTEGER>, ...");
99 System.out.println (" --el <KEY> <LONG>");
100 System.out.println (" --ela <KEY> <LONG>, ...");
101 System.out.println (" --ef <KEY> <FLOAT>");
102 System.out.println (" --efa <KEY> <FLOAT ARRAY>");
103 System.out.println (" --esa <KEY> <STRING>, ...");
104 System.out.println (" --ez <KEY> <BOOLEAN>");
105 System.out.println (" -f <KEY> <FLAGS>");
106 return 0;
107 }
108 else if (argv[0].equals ("start"))
109 {
110 Intent intent;
111 int i, userID = 0;
112 String token, type;
113 Uri data;
114 boolean debug;
115
116 intent = new Intent ();
117 debug = false;
118 data = null;
119 type = null;
120
121 for (i = 1; i < argv.length; ++i)
122 {
123 int j;
124
125 token = argv[i];
126
127 if (token.equals ("-a"))
128 intent.setAction (argv[++i]);
129 else if (token.equals ("-d"))
130 data = Uri.parse (argv[++i]);
131 else if (token.equals ("-t"))
132 type = argv[++i];
133 else if (token.equals ("-c"))
134 intent.addCategory (argv[++i]);
135 else if (token.equals ("-e") || token.equals ("--es"))
136 {
137 intent.putExtra (argv[i + 1], argv[i + 2]);
138 i += 2;
139 }
140 else if (token.equals ("--esn"))
141 intent.putExtra (argv[++i], (String) null);
142 else if (token.equals ("--ei"))
143 {
144 int value = Integer.valueOf (argv[i + 2]);
145 intent.putExtra (argv[i + 1], value);
146 i += 2;
147 }
148 else if (token.equals ("--eu"))
149 {
150 Uri value = Uri.parse (argv[i + 2]);
151 intent.putExtra (argv[i + 1], value);
152 i += 2;
153 }
154 else if (token.equals ("--ecn"))
155 {
156 ComponentName value
157 = ComponentName.unflattenFromString (argv[i + 2]);
158 intent.putExtra (argv[i + 1], value);
159 i += 2;
160 }
161 else if (token.equals ("--eia"))
162 {
163 String values[] = argv[i + 2].split (",");
164 int array[] = new int[values.length];
165
166 for (j = 0; j < values.length; ++j)
167 array[j] = Integer.valueOf (values[j]);
168 intent.putExtra (argv[i + 1], array);
169 i += 2;
170 }
171 else if (token.equals ("--el"))
172 {
173 long value = Long.valueOf (argv[i + 2]);
174 intent.putExtra (argv[i + 1], value);
175 i += 2;
176 }
177 else if (token.equals ("--ela"))
178 {
179 String values[] = argv[i + 2].split (",");
180 long array[] = new long[values.length];
181
182 for (j = 0; j < values.length; ++j)
183 array[j] = Long.valueOf (values[j]);
184 intent.putExtra (argv[i + 1], array);
185 i += 2;
186 }
187 else if (token.equals ("--ef"))
188 {
189 float value = Float.valueOf (argv[i + 2]);
190 intent.putExtra (argv[i + 1], value);
191 i += 2;
192 }
193 else if (token.equals ("--efa"))
194 {
195 String values[] = argv[i + 2].split (",");
196 float array[] = new float[values.length];
197
198 for (j = 0; j < values.length; ++j)
199 array[j] = Float.valueOf (values[j]);
200 intent.putExtra (argv[i + 1], array);
201 i += 2;
202 }
203 else if (token.equals ("--esa"))
204 {
205 String[] strings;
206
207 strings = argv[i + 2].split ("(?<!\\\\),");
208 intent.putExtra (argv[i + 1], strings);
209 i += 2;
210 }
211 else if (token.equals ("--ez"))
212 {
213 boolean value = Boolean.valueOf (argv[i + 2]);
214 intent.putExtra (argv[i + 1], value);
215 i += 2;
216 }
217 else if (token.equals ("-n"))
218 {
219 ComponentName value
220 = ComponentName.unflattenFromString (argv[++i]);
221 if (value == null)
222 throw new IllegalArgumentException ("Invalid component name: " + argv[i]);
223 intent.setComponent (value);
224 }
225 else if (token.equals ("-f"))
226 intent.addFlags (Integer.decode (argv[++i]).intValue ());
227 else if (token.equals ("--user"))
228 {
229 int value = Integer.valueOf (argv[++i]);
230 if (value != 0
231 && (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1))
232 throw new IllegalArgumentException ("Invalid user: " + value);
233 userID = value;
234 }
235 else
236 throw new IllegalArgumentException ("Option not understood: " + argv[i]);
237 }
238
239 intent.addFlags (Intent.FLAG_ACTIVITY_NEW_TASK);
240 intent.setDataAndType (data, type);
241
242 if ((Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1)
243 || userID == 0)
244 {
245 /* mAppThread must be neutralized, or the ActivityManager
246 service will attempt and fail to locate a matching app
247 record when it is passed as the caller argument to the
248 startActivity RPC routine. */
249 neutralizeApplicationThread (thread);
250 context.startActivity (intent);
251 }
252 else
253 {
254 /* Otherwise, there are two revisions of startActivityAsUser
255 this utility must support, whose signatures follow:
256
257 (IApplicationThread, Intent, String, IBinder, String,
258 int, int, String, ParcelFileDescriptor, Bundle, int)
259
260 (IApplicationThread, String, Intent, String, IBinder, String,
261 int, int, String, ParcelFileDescriptor, Bundle, int) */
262
263 Method method;
264 IActivityManager am = ActivityManagerNative.getDefault ();
265 int rc;
266 Class klass = IActivityManager.class;
267
268 /* Attempt to resolve the first variant which is mostly
269 observed on Jelly Bean MR1 systems. */
270 try
271 {
272 method = klass.getMethod ("startActivityAsUser",
273 IApplicationThread.class,
274 Intent.class, String.class,
275 IBinder.class, String.class,
276 int.class, int.class,
277 String.class,
278 ParcelFileDescriptor.class,
279 Bundle.class, int.class);
280 }
281 catch (NoSuchMethodException e)
282 {
283 method = null;
284 }
285
286 if (method != null)
287 rc = (Integer) method.invoke (am, null, intent, intent.getType (),
288 null, null, 0, 0, null, null, null,
289 userID);
290 else
291 {
292 /* Now the modern `IActivityManager#startActivityAsUser'. */
293 method = klass.getMethod ("startActivityAsUser",
294 IApplicationThread.class,
295 String.class, Intent.class,
296 String.class, IBinder.class,
297 String.class, int.class,
298 int.class, String.class,
299 ParcelFileDescriptor.class,
300 Bundle.class, int.class);
301
302 rc = (Integer) method.invoke (am, null, null, intent,
303 intent.getType (),
304 null, null, 0, 0, null,
305 null, null, userID);
306 }
307
308 if (rc != 0)
309 {
310 System.err.println ("Failed to start activity as user: " + rc);
311 return 1;
312 }
313 }
314 return 0;
315 }
316 return 1;
317 }
318
319 public static void
320 main (String arg[])
321 {
322 try
323 {
324 System.exit (main1 (arg));
325 }
326 catch (Throwable e)
327 {
328 e.printStackTrace ();
329 System.exit (0);
330 }
331 }
332};