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, &param);
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