1 /*
2 ==============================================================================
3
4 This file is part of the JUCE library.
5 Copyright (c) 2020 - Raw Material Software Limited
6
7 JUCE is an open source library subject to commercial or open-source
8 licensing.
9
10 The code included in this file is provided under the terms of the ISC license
11 http://www.isc.org/downloads/software-support-policy/isc-license. Permission
12 To use, copy, modify, and/or distribute this software for any purpose with or
13 without fee is hereby granted provided that the above copyright notice and
14 this permission notice appear in all copies.
15
16 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
17 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
18 DISCLAIMED.
19
20 ==============================================================================
21 */
22
23 namespace juce
24 {
25
26 /*
27 Note that a lot of methods that you'd expect to find in this file actually
28 live in juce_posix_SharedCode.h!
29 */
30
31 #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
32 FIELD (activityInfo, "activityInfo", "Landroid/content/pm/ActivityInfo;")
33
34 DECLARE_JNI_CLASS (AndroidResolveInfo, "android/content/pm/ResolveInfo")
35 #undef JNI_CLASS_MEMBERS
36
37 //==============================================================================
38 JavaVM* androidJNIJavaVM = nullptr;
39 jobject androidApkContext = nullptr;
40
41 //==============================================================================
getEnv()42 JNIEnv* getEnv() noexcept
43 {
44 if (androidJNIJavaVM != nullptr)
45 {
46 JNIEnv* env;
47 androidJNIJavaVM->AttachCurrentThread (&env, nullptr);
48
49 return env;
50 }
51
52 // You did not call Thread::initialiseJUCE which must be called at least once in your apk
53 // before using any JUCE APIs. The Projucer will automatically generate java code
54 // which will invoke Thread::initialiseJUCE for you.
55 jassertfalse;
56 return nullptr;
57 }
58
juce_JavainitialiseJUCE(JNIEnv * env,jobject,jobject context)59 void JNICALL juce_JavainitialiseJUCE (JNIEnv* env, jobject /*jclass*/, jobject context)
60 {
61 Thread::initialiseJUCE (env, context);
62 }
63
JNI_OnLoad(JavaVM * vm,void *)64 extern "C" jint JNIEXPORT JNI_OnLoad (JavaVM* vm, void*)
65 {
66 // Huh? JNI_OnLoad was called two times!
67 jassert (androidJNIJavaVM == nullptr);
68
69 androidJNIJavaVM = vm;
70
71 auto* env = getEnv();
72
73 // register the initialisation function
74 auto juceJavaClass = env->FindClass("com/rmsl/juce/Java");
75
76 if (juceJavaClass != nullptr)
77 {
78 JNINativeMethod method {"initialiseJUCE", "(Landroid/content/Context;)V",
79 reinterpret_cast<void*> (juce_JavainitialiseJUCE)};
80
81 auto status = env->RegisterNatives (juceJavaClass, &method, 1);
82 jassert (status == 0);
83 }
84 else
85 {
86 // com.rmsl.juce.Java class not found. Apparently this project is a library
87 // or was not generated by the Projucer. That's ok, the user will have to
88 // call Thread::initialiseJUCE manually
89 env->ExceptionClear();
90 }
91
92 JNIClassBase::initialiseAllClasses (env);
93
94 return JNI_VERSION_1_2;
95 }
96
97 //==============================================================================
98 class JuceActivityWatcher : public ActivityLifecycleCallbacks
99 {
100 public:
JuceActivityWatcher()101 JuceActivityWatcher()
102 {
103 LocalRef<jobject> appContext (getAppContext());
104
105 if (appContext != nullptr)
106 {
107 auto* env = getEnv();
108
109 myself = GlobalRef (CreateJavaInterface (this, "android/app/Application$ActivityLifecycleCallbacks"));
110 env->CallVoidMethod (appContext.get(), AndroidApplication.registerActivityLifecycleCallbacks, myself.get());
111 }
112
113 checkActivityIsMain (androidApkContext);
114 }
115
~JuceActivityWatcher()116 ~JuceActivityWatcher() override
117 {
118 LocalRef<jobject> appContext (getAppContext());
119
120 if (appContext != nullptr && myself != nullptr)
121 {
122 auto* env = getEnv();
123
124 env->CallVoidMethod (appContext.get(), AndroidApplication.unregisterActivityLifecycleCallbacks, myself.get());
125 clear();
126 myself.clear();
127 }
128 }
129
onActivityStarted(jobject activity)130 void onActivityStarted (jobject activity) override
131 {
132 auto* env = getEnv();
133
134 checkActivityIsMain (activity);
135
136 ScopedLock lock (currentActivityLock);
137
138 if (currentActivity != nullptr)
139 {
140 // see Clarification June 2001 in JNI reference for why this is
141 // necessary
142 LocalRef<jobject> localStorage (env->NewLocalRef (currentActivity));
143
144 if (env->IsSameObject (localStorage.get(), activity) != 0)
145 return;
146
147 env->DeleteWeakGlobalRef (currentActivity);
148 currentActivity = nullptr;
149 }
150
151 if (activity != nullptr)
152 currentActivity = env->NewWeakGlobalRef (activity);
153 }
154
onActivityStopped(jobject activity)155 void onActivityStopped (jobject activity) override
156 {
157 auto* env = getEnv();
158
159 ScopedLock lock (currentActivityLock);
160
161 if (currentActivity != nullptr)
162 {
163 // important that the comparison happens in this order
164 // to avoid race condition where the weak reference becomes null
165 // just after the first check
166 if (env->IsSameObject (currentActivity, activity) != 0
167 || env->IsSameObject (currentActivity, nullptr) != 0)
168 {
169 env->DeleteWeakGlobalRef (currentActivity);
170 currentActivity = nullptr;
171 }
172 }
173 }
174
getCurrent()175 LocalRef<jobject> getCurrent()
176 {
177 ScopedLock lock (currentActivityLock);
178 return LocalRef<jobject> (getEnv()->NewLocalRef (currentActivity));
179 }
180
getMain()181 LocalRef<jobject> getMain()
182 {
183 ScopedLock lock (currentActivityLock);
184 return LocalRef<jobject> (getEnv()->NewLocalRef (mainActivity));
185 }
186
getInstance()187 static JuceActivityWatcher& getInstance()
188 {
189 static JuceActivityWatcher activityWatcher;
190 return activityWatcher;
191 }
192
193 private:
checkActivityIsMain(jobject context)194 void checkActivityIsMain (jobject context)
195 {
196 auto* env = getEnv();
197
198 ScopedLock lock (currentActivityLock);
199
200 if (mainActivity != nullptr)
201 {
202 if (env->IsSameObject (mainActivity, nullptr) != 0)
203 {
204 env->DeleteWeakGlobalRef (mainActivity);
205 mainActivity = nullptr;
206 }
207 }
208
209 if (mainActivity == nullptr)
210 {
211 LocalRef<jobject> appContext (getAppContext());
212 auto mainActivityPath = getMainActivityClassPath();
213
214 if (mainActivityPath.isNotEmpty())
215 {
216 auto clasz = env->GetObjectClass (context);
217 auto activityPath = juceString (LocalRef<jstring> ((jstring) env->CallObjectMethod (clasz, JavaClass.getName)));
218
219 // This may be problematic for apps which use several activities with the same type. We just
220 // assume that the very first activity of this type is the main one
221 if (activityPath == mainActivityPath)
222 mainActivity = env->NewWeakGlobalRef (context);
223 }
224 }
225 }
226
getMainActivityClassPath()227 static String getMainActivityClassPath()
228 {
229 static String mainActivityClassPath;
230
231 if (mainActivityClassPath.isEmpty())
232 {
233 LocalRef<jobject> appContext (getAppContext());
234
235 if (appContext != nullptr)
236 {
237 auto* env = getEnv();
238
239 LocalRef<jobject> pkgManager (env->CallObjectMethod (appContext.get(), AndroidContext.getPackageManager));
240 LocalRef<jstring> pkgName ((jstring) env->CallObjectMethod (appContext.get(), AndroidContext.getPackageName));
241
242 LocalRef<jobject> intent (env->NewObject (AndroidIntent, AndroidIntent.constructWithString,
243 javaString ("android.intent.action.MAIN").get()));
244
245 intent = LocalRef<jobject> (env->CallObjectMethod (intent.get(),
246 AndroidIntent.setPackage,
247 pkgName.get()));
248
249 LocalRef<jobject> resolveInfo (env->CallObjectMethod (pkgManager.get(), AndroidPackageManager.resolveActivity, intent.get(), 0));
250
251 if (resolveInfo != nullptr)
252 {
253 LocalRef<jobject> activityInfo (env->GetObjectField (resolveInfo.get(), AndroidResolveInfo.activityInfo));
254 LocalRef<jstring> jName ((jstring) env->GetObjectField (activityInfo.get(), AndroidPackageItemInfo.name));
255 LocalRef<jstring> jPackage ((jstring) env->GetObjectField (activityInfo.get(), AndroidPackageItemInfo.packageName));
256
257 mainActivityClassPath = juceString (jName);
258 }
259 }
260 }
261
262 return mainActivityClassPath;
263 }
264
265 GlobalRef myself;
266 CriticalSection currentActivityLock;
267 jweak currentActivity = nullptr;
268 jweak mainActivity = nullptr;
269 };
270
271 //==============================================================================
272 #if JUCE_MODULE_AVAILABLE_juce_events && JUCE_ANDROID
273 void juce_juceEventsAndroidStartApp();
274 #endif
275
initialiseJUCE(void * jniEnv,void * context)276 void Thread::initialiseJUCE (void* jniEnv, void* context)
277 {
278 static CriticalSection cs;
279 ScopedLock lock (cs);
280
281 // jniEnv and context should not be null!
282 jassert (jniEnv != nullptr && context != nullptr);
283
284 auto* env = static_cast<JNIEnv*> (jniEnv);
285
286 if (androidJNIJavaVM == nullptr)
287 {
288 JavaVM* javaVM = nullptr;
289
290 auto status = env->GetJavaVM (&javaVM);
291 jassert (status == 0 && javaVM != nullptr);
292
293 androidJNIJavaVM = javaVM;
294 }
295
296 static bool firstCall = true;
297
298 if (firstCall)
299 {
300 firstCall = false;
301
302 // if we ever support unloading then this should probably be a weak reference
303 androidApkContext = env->NewGlobalRef (static_cast<jobject> (context));
304 JuceActivityWatcher::getInstance();
305
306 #if JUCE_MODULE_AVAILABLE_juce_events && JUCE_ANDROID
307 juce_juceEventsAndroidStartApp();
308 #endif
309 }
310 }
311
312 //==============================================================================
getAppContext()313 LocalRef<jobject> getAppContext() noexcept
314 {
315 auto* env = getEnv();
316 auto context = androidApkContext;
317
318 // You did not call Thread::initialiseJUCE which must be called at least once in your apk
319 // before using any JUCE APIs. The Projucer will automatically generate java code
320 // which will invoke Thread::initialiseJUCE for you.
321 jassert (env != nullptr && context != nullptr);
322
323 if (context == nullptr)
324 return LocalRef<jobject>();
325
326 if (env->IsInstanceOf (context, AndroidApplication) != 0)
327 return LocalRef<jobject> (env->NewLocalRef (context));
328
329 LocalRef<jobject> applicationContext (env->CallObjectMethod (context, AndroidContext.getApplicationContext));
330
331 if (applicationContext == nullptr)
332 return LocalRef<jobject> (env->NewLocalRef (context));
333
334 return applicationContext;
335 }
336
getCurrentActivity()337 LocalRef<jobject> getCurrentActivity() noexcept
338 {
339 return JuceActivityWatcher::getInstance().getCurrent();
340 }
341
getMainActivity()342 LocalRef<jobject> getMainActivity() noexcept
343 {
344 return JuceActivityWatcher::getInstance().getMain();
345 }
346
347 //==============================================================================
348 // sets the process to 0=low priority, 1=normal, 2=high, 3=realtime
setPriority(ProcessPriority prior)349 JUCE_API void JUCE_CALLTYPE Process::setPriority (ProcessPriority prior)
350 {
351 // TODO
352
353 struct sched_param param;
354 int policy, maxp, minp;
355
356 const int p = (int) prior;
357
358 if (p <= 1)
359 policy = SCHED_OTHER;
360 else
361 policy = SCHED_RR;
362
363 minp = sched_get_priority_min (policy);
364 maxp = sched_get_priority_max (policy);
365
366 if (p < 2)
367 param.sched_priority = 0;
368 else if (p == 2 )
369 // Set to middle of lower realtime priority range
370 param.sched_priority = minp + (maxp - minp) / 4;
371 else
372 // Set to middle of higher realtime priority range
373 param.sched_priority = minp + (3 * (maxp - minp) / 4);
374
375 pthread_setschedparam (pthread_self(), policy, ¶m);
376 }
377
juce_isRunningUnderDebugger()378 JUCE_API bool JUCE_CALLTYPE juce_isRunningUnderDebugger() noexcept
379 {
380 StringArray lines;
381 File ("/proc/self/status").readLines (lines);
382
383 for (int i = lines.size(); --i >= 0;) // (NB - it's important that this runs in reverse order)
384 if (lines[i].upToFirstOccurrenceOf (":", false, false).trim().equalsIgnoreCase ("TracerPid"))
385 return (lines[i].fromFirstOccurrenceOf (":", false, false).trim().getIntValue() > 0);
386
387 return false;
388 }
389
raisePrivilege()390 JUCE_API void JUCE_CALLTYPE Process::raisePrivilege() {}
lowerPrivilege()391 JUCE_API void JUCE_CALLTYPE Process::lowerPrivilege() {}
392
393
394
395 } // namespace juce
396