1 /* Communication module for Android terminals. -*- c-file-style: "GNU" -*-
2
3 Copyright (C) 2023 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 org.gnu.emacs;
21
22 import android.os.Looper;
23 import android.os.Build;
24
25 import android.content.Context;
26 import android.content.res.AssetManager;
27
28 import java.lang.reflect.Constructor;
29 import java.lang.reflect.Method;
30
31 /* Noninteractive Emacs.
32
33 This is the class that libandroid-emacs.so starts.
34 libandroid-emacs.so figures out the system classpath, then starts
35 dalvikvm with the framework jars.
36
37 At that point, dalvikvm calls main, which sets up the main looper,
38 creates an ActivityThread and attaches it to the main thread.
39
40 Then, it obtains an application context for the LoadedApk in the
41 application thread.
42
43 Finally, it obtains the necessary context specific objects and
44 initializes Emacs. */
45
46 @SuppressWarnings ("unchecked")
47 public final class EmacsNoninteractive
48 {
49 public static void
50 main (String[] args)
51 {
52 Object activityThread, loadedApk;
53 Class activityThreadClass, loadedApkClass, contextImplClass;
54 Class compatibilityInfoClass;
55 Method method;
56 Context context;
57 AssetManager assets;
58 String filesDir, libDir, cacheDir;
59
60 Looper.prepare ();
61 context = null;
62 assets = null;
63 filesDir = libDir = cacheDir = null;
64
65 try
66 {
67 /* Get the activity thread. */
68 activityThreadClass = Class.forName ("android.app.ActivityThread");
69
70 /* Get the systemMain method. */
71 method = activityThreadClass.getMethod ("systemMain");
72
73 /* Create and attach the activity thread. */
74 activityThread = method.invoke (null);
75 context = null;
76
77 /* Now get an LoadedApk. */
78
79 try
80 {
81 loadedApkClass = Class.forName ("android.app.LoadedApk");
82 }
83 catch (ClassNotFoundException exception)
84 {
85 /* Android 2.2 has no LoadedApk class, but fortunately it
86 does not need to be used, since contexts can be
87 directly created. */
88
89 loadedApkClass = null;
90 contextImplClass = Class.forName ("android.app.ContextImpl");
91
92 method = activityThreadClass.getDeclaredMethod ("getSystemContext");
93 context = (Context) method.invoke (activityThread);
94 method = contextImplClass.getDeclaredMethod ("createPackageContext",
95 String.class,
96 int.class);
97 method.setAccessible (true);
98 context = (Context) method.invoke (context, "org.gnu.emacs",
99 0);
100 }
101
102 /* If the context has not already been created, then do what
103 is appropriate for newer versions of Android. */
104
105 if (context == null)
106 {
107 /* Get a LoadedApk. How to do this varies by Android version.
108 On Android 2.3.3 and earlier, there is no
109 ``compatibilityInfo'' argument to getPackageInfo. */
110
111 if (Build.VERSION.SDK_INT
112 <= Build.VERSION_CODES.GINGERBREAD_MR1)
113 {
114 method
115 = activityThreadClass.getMethod ("getPackageInfo",
116 String.class,
117 int.class);
118 loadedApk = method.invoke (activityThread, "org.gnu.emacs",
119 0);
120 }
121 else
122 {
123 compatibilityInfoClass
124 = Class.forName ("android.content.res.CompatibilityInfo");
125
126 method
127 = activityThreadClass.getMethod ("getPackageInfo",
128 String.class,
129 compatibilityInfoClass,
130 int.class);
131 loadedApk = method.invoke (activityThread, "org.gnu.emacs",
132 null, 0);
133 }
134
135 if (loadedApk == null)
136 throw new RuntimeException ("getPackageInfo returned NULL");
137
138 /* Now, get a context. */
139 contextImplClass = Class.forName ("android.app.ContextImpl");
140
141 try
142 {
143 method
144 = contextImplClass.getDeclaredMethod ("createAppContext",
145 activityThreadClass,
146 loadedApkClass);
147 method.setAccessible (true);
148 context = (Context) method.invoke (null, activityThread,
149 loadedApk);
150 }
151 catch (NoSuchMethodException exception)
152 {
153 /* Older Android versions don't have createAppContext, but
154 instead require creating a ContextImpl, and then
155 calling createPackageContext. */
156 method
157 = activityThreadClass.getDeclaredMethod ("getSystemContext");
158 context = (Context) method.invoke (activityThread);
159 method
160 = contextImplClass.getDeclaredMethod ("createPackageContext",
161 String.class,
162 int.class);
163 method.setAccessible (true);
164 context = (Context) method.invoke (context, "org.gnu.emacs",
165 0);
166 }
167 }
168
169 /* Don't actually start the looper or anything. Instead, obtain
170 an AssetManager. */
171 assets = context.getAssets ();
172
173 /* Now configure Emacs. The class path should already be set. */
174
175 filesDir = context.getFilesDir ().getCanonicalPath ();
176 libDir = EmacsService.getLibraryDirectory (context);
177 cacheDir = context.getCacheDir ().getCanonicalPath ();
178 }
179 catch (Exception e)
180 {
181 System.err.println ("Internal error: " + e);
182 System.err.println ("This means that the Android platform changed,");
183 System.err.println ("and that Emacs needs adjustments in order to");
184 System.err.println ("obtain required system internal resources.");
185 System.err.println ("Please report this bug to bug-gnu-emacs@gnu.org.");
186 e.printStackTrace ();
187
188 System.exit (1);
189 }
190
191 EmacsNative.setEmacsParams (assets, filesDir,
192 libDir, cacheDir, 0.0f,
193 0.0f, 0.0f, null, null,
194 Build.VERSION.SDK_INT);
195
196 /* Now find the dump file that Emacs should use, if it has already
197 been dumped. */
198 EmacsApplication.findDumpFile (context);
199
200 /* Start Emacs. */
201 EmacsNative.initEmacs (args, EmacsApplication.dumpFileName);
202 }
203 };