diff options
| author | Po Lu | 2025-02-24 21:06:30 +0800 |
|---|---|---|
| committer | Po Lu | 2025-02-25 09:15:35 +0800 |
| commit | 08077788dbcc535e8840495d62cee79434766b3d (patch) | |
| tree | 4493aa29fcc07bf2f2d9a89747437a127e3826b0 /test/infra/android/bin/AtsStub.java | |
| parent | ae5674c758a26ce598cdca37d83d268b51fe53dd (diff) | |
| download | emacs-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.java | 332 |
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 | |||
| 3 | Copyright (C) 2025 Free Software Foundation, Inc. | ||
| 4 | |||
| 5 | This file is part of GNU Emacs. | ||
| 6 | |||
| 7 | GNU Emacs is free software: you can redistribute it and/or modify | ||
| 8 | it under the terms of the GNU General Public License as published by | ||
| 9 | the Free Software Foundation, either version 3 of the License, or (at | ||
| 10 | your option) any later version. | ||
| 11 | |||
| 12 | GNU Emacs is distributed in the hope that it will be useful, | ||
| 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 15 | GNU General Public License for more details. | ||
| 16 | |||
| 17 | You should have received a copy of the GNU General Public License | ||
| 18 | along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */ | ||
| 19 | |||
| 20 | package ats; | ||
| 21 | |||
| 22 | import android.app.ActivityManagerNative; | ||
| 23 | import android.app.ActivityThread; | ||
| 24 | import android.app.IActivityManager; | ||
| 25 | import android.app.IApplicationThread; | ||
| 26 | |||
| 27 | import android.content.ComponentName; | ||
| 28 | import android.content.Context; | ||
| 29 | import android.content.Intent; | ||
| 30 | |||
| 31 | import android.os.Build; | ||
| 32 | import android.os.Bundle; | ||
| 33 | import android.os.IBinder; | ||
| 34 | import android.os.Looper; | ||
| 35 | import android.os.ParcelFileDescriptor; | ||
| 36 | import android.os.RemoteException; | ||
| 37 | |||
| 38 | import android.net.Uri; | ||
| 39 | |||
| 40 | import java.lang.IllegalArgumentException; | ||
| 41 | |||
| 42 | import java.lang.reflect.Field; | ||
| 43 | import java.lang.reflect.InvocationTargetException; | ||
| 44 | import java.lang.reflect.Method; | ||
| 45 | |||
| 46 | public 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 | }; | ||