| @@ -1211,36 +1211,8 @@ public class JuceAppActivity extends Activity | |||||
| return null; | return null; | ||||
| } | } | ||||
| public final int setCurrentThreadPriority (int priority) | |||||
| { | |||||
| android.os.Process.setThreadPriority (android.os.Process.myTid(), priority); | |||||
| return android.os.Process.getThreadPriority (android.os.Process.myTid()); | |||||
| } | |||||
| public final boolean hasSystemFeature (String property) | public final boolean hasSystemFeature (String property) | ||||
| { | { | ||||
| return getPackageManager().hasSystemFeature (property); | return getPackageManager().hasSystemFeature (property); | ||||
| } | } | ||||
| private static class JuceThread extends Thread | |||||
| { | |||||
| public JuceThread (long host, String threadName, long threadStackSize) | |||||
| { | |||||
| super (null, null, threadName, threadStackSize); | |||||
| _this = host; | |||||
| } | |||||
| public void run() | |||||
| { | |||||
| runThread(_this); | |||||
| } | |||||
| private native void runThread (long host); | |||||
| private long _this; | |||||
| } | |||||
| public final Thread createNewThread(long host, String threadName, long threadStackSize) | |||||
| { | |||||
| return new JuceThread(host, threadName, threadStackSize); | |||||
| } | |||||
| } | } | ||||
| @@ -293,9 +293,7 @@ extern AndroidSystem android; | |||||
| METHOD (getAndroidBluetoothManager, "getAndroidBluetoothManager", "()L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$BluetoothManager;") \ | METHOD (getAndroidBluetoothManager, "getAndroidBluetoothManager", "()L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$BluetoothManager;") \ | ||||
| METHOD (getAndroidSDKVersion, "getAndroidSDKVersion", "()I") \ | METHOD (getAndroidSDKVersion, "getAndroidSDKVersion", "()I") \ | ||||
| METHOD (audioManagerGetProperty, "audioManagerGetProperty", "(Ljava/lang/String;)Ljava/lang/String;") \ | METHOD (audioManagerGetProperty, "audioManagerGetProperty", "(Ljava/lang/String;)Ljava/lang/String;") \ | ||||
| METHOD (setCurrentThreadPriority, "setCurrentThreadPriority", "(I)I") \ | |||||
| METHOD (hasSystemFeature, "hasSystemFeature", "(Ljava/lang/String;)Z" ) \ | METHOD (hasSystemFeature, "hasSystemFeature", "(Ljava/lang/String;)Z" ) \ | ||||
| METHOD (createNewThread, "createNewThread", "(JLjava/lang/String;J)Ljava/lang/Thread;") \ | |||||
| METHOD (requestRuntimePermission, "requestRuntimePermission", "(IJ)V" ) \ | METHOD (requestRuntimePermission, "requestRuntimePermission", "(IJ)V" ) \ | ||||
| METHOD (isPermissionGranted, "isPermissionGranted", "(I)Z" ) \ | METHOD (isPermissionGranted, "isPermissionGranted", "(I)Z" ) \ | ||||
| METHOD (isPermissionDeclaredInManifest, "isPermissionDeclaredInManifest", "(I)Z" ) \ | METHOD (isPermissionDeclaredInManifest, "isPermissionDeclaredInManifest", "(I)Z" ) \ | ||||
| @@ -329,19 +327,6 @@ DECLARE_JNI_CLASS (Paint, "android/graphics/Paint"); | |||||
| DECLARE_JNI_CLASS (Matrix, "android/graphics/Matrix"); | DECLARE_JNI_CLASS (Matrix, "android/graphics/Matrix"); | ||||
| #undef JNI_CLASS_MEMBERS | #undef JNI_CLASS_MEMBERS | ||||
| //============================================================================== | |||||
| #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ | |||||
| METHOD (start, "start", "()V") \ | |||||
| METHOD (stop, "stop", "()V") \ | |||||
| METHOD (setName, "setName", "(Ljava/lang/String;)V") \ | |||||
| METHOD (getName, "getName", "()Ljava/lang/String;") \ | |||||
| METHOD (getId, "getId", "()J") \ | |||||
| STATICMETHOD (currentThread, "currentThread", "()Ljava/lang/Thread;") \ | |||||
| METHOD (setPriority, "setPriority", "(I)V") \ | |||||
| DECLARE_JNI_CLASS (JuceThread, "java/lang/Thread"); | |||||
| #undef JNI_CLASS_MEMBERS | |||||
| //============================================================================== | //============================================================================== | ||||
| #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ | #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ | ||||
| METHOD (constructor, "<init>", "(IIII)V") \ | METHOD (constructor, "<init>", "(IIII)V") \ | ||||
| @@ -100,23 +100,90 @@ jfieldID JNIClassBase::resolveStaticField (JNIEnv* env, const char* fieldName, c | |||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| ThreadLocalValue<JNIEnv*> androidJNIEnv; | |||||
| JavaVM* androidJNIJavaVM = nullptr; | |||||
| JNIEnv* getEnv() noexcept | |||||
| class JniEnvThreadHolder | |||||
| { | { | ||||
| JNIEnv* env = androidJNIEnv.get(); | |||||
| jassert (env != nullptr); | |||||
| public: | |||||
| static JniEnvThreadHolder& getInstance() noexcept | |||||
| { | |||||
| // You cann only use JNI functions AFTER JNI_OnLoad was called | |||||
| jassert (androidJNIJavaVM != nullptr); | |||||
| try | |||||
| { | |||||
| if (instance == nullptr) | |||||
| instance = new JniEnvThreadHolder; | |||||
| } | |||||
| catch (...) | |||||
| { | |||||
| jassertfalse; | |||||
| std::terminate(); | |||||
| } | |||||
| return *instance; | |||||
| } | |||||
| return env; | |||||
| } | |||||
| static JNIEnv* getEnv() | |||||
| { | |||||
| JNIEnv* env = reinterpret_cast<JNIEnv*> (pthread_getspecific (getInstance().threadKey)); | |||||
| void setEnv (JNIEnv* env) noexcept | |||||
| { | |||||
| androidJNIEnv.get() = env; | |||||
| } | |||||
| // You are trying to use a JUCE function on a thread that was not created by JUCE. | |||||
| // You need to first call setEnv on this thread before using JUCE | |||||
| jassert (env != nullptr); | |||||
| return env; | |||||
| } | |||||
| static void setEnv (JNIEnv* env) | |||||
| { | |||||
| // env must not be a nullptr | |||||
| jassert (env != nullptr); | |||||
| #if JUCE_DEBUG | |||||
| JNIEnv* oldenv = reinterpret_cast<JNIEnv*> (pthread_getspecific (getInstance().threadKey)); | |||||
| // This thread is already attached to the JavaVM and you trying to attach | |||||
| // it to a different instance of the VM. | |||||
| jassert (oldenv == nullptr || oldenv == env); | |||||
| #endif | |||||
| pthread_setspecific (getInstance().threadKey, env); | |||||
| } | |||||
| extern "C" jint JNI_OnLoad (JavaVM*, void*) | |||||
| private: | |||||
| pthread_key_t threadKey; | |||||
| static void threadDetach (void* p) | |||||
| { | |||||
| if (JNIEnv* env = reinterpret_cast<JNIEnv*> (p)) | |||||
| { | |||||
| ignoreUnused (env); | |||||
| androidJNIJavaVM->DetachCurrentThread(); | |||||
| } | |||||
| } | |||||
| JniEnvThreadHolder() | |||||
| { | |||||
| pthread_key_create (&threadKey, threadDetach); | |||||
| } | |||||
| static JniEnvThreadHolder* instance; | |||||
| }; | |||||
| JniEnvThreadHolder* JniEnvThreadHolder::instance = nullptr; | |||||
| //============================================================================== | |||||
| JNIEnv* getEnv() noexcept { return JniEnvThreadHolder::getEnv(); } | |||||
| void setEnv (JNIEnv* env) noexcept { JniEnvThreadHolder::setEnv (env); } | |||||
| extern "C" jint JNI_OnLoad (JavaVM* vm, void*) | |||||
| { | { | ||||
| // Huh? JNI_OnLoad was called two times! | |||||
| jassert (androidJNIJavaVM == nullptr); | |||||
| androidJNIJavaVM = vm; | |||||
| return JNI_VERSION_1_2; | return JNI_VERSION_1_2; | ||||
| } | } | ||||
| @@ -127,6 +194,8 @@ AndroidSystem::AndroidSystem() : screenWidth (0), screenHeight (0), dpi (160) | |||||
| void AndroidSystem::initialise (JNIEnv* env, jobject act, jstring file, jstring dataDir) | void AndroidSystem::initialise (JNIEnv* env, jobject act, jstring file, jstring dataDir) | ||||
| { | { | ||||
| setEnv (env); | |||||
| screenWidth = screenHeight = 0; | screenWidth = screenHeight = 0; | ||||
| dpi = 160; | dpi = 160; | ||||
| JNIClassBase::initialiseAllClasses (env); | JNIClassBase::initialiseAllClasses (env); | ||||
| @@ -78,234 +78,3 @@ JUCE_API bool JUCE_CALLTYPE juce_isRunningUnderDebugger() noexcept | |||||
| JUCE_API void JUCE_CALLTYPE Process::raisePrivilege() {} | JUCE_API void JUCE_CALLTYPE Process::raisePrivilege() {} | ||||
| JUCE_API void JUCE_CALLTYPE Process::lowerPrivilege() {} | JUCE_API void JUCE_CALLTYPE Process::lowerPrivilege() {} | ||||
| struct AndroidThreadData | |||||
| { | |||||
| AndroidThreadData (Thread* thread) noexcept | |||||
| : owner (thread), tId (0) | |||||
| { | |||||
| } | |||||
| Thread* owner; | |||||
| Thread::ThreadID tId; | |||||
| WaitableEvent eventSet, eventGet; | |||||
| }; | |||||
| void JUCE_API juce_threadEntryPoint (void*); | |||||
| void* threadEntryProc (AndroidThreadData* priv) | |||||
| { | |||||
| priv->tId = (Thread::ThreadID) pthread_self(); | |||||
| priv->eventSet.signal(); | |||||
| priv->eventGet.wait (-1); | |||||
| juce_threadEntryPoint (priv->owner); | |||||
| return nullptr; | |||||
| } | |||||
| JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024JuceThread), runThread, | |||||
| void, (JNIEnv* env, jobject /*device*/, jlong host)) | |||||
| { | |||||
| // This thread does not have a JNIEnv assigned to it yet. So assign it now. | |||||
| setEnv (env); | |||||
| if (AndroidThreadData* thread = reinterpret_cast<AndroidThreadData*> (host)) | |||||
| threadEntryProc (thread); | |||||
| } | |||||
| void Thread::launchThread() | |||||
| { | |||||
| threadHandle = 0; | |||||
| ScopedPointer<AndroidThreadData> threadPrivateData = new AndroidThreadData (this); | |||||
| const LocalRef<jstring> jName (javaString (threadName)); | |||||
| jobject juceNewThread = android.activity.callObjectMethod (JuceAppActivity.createNewThread, | |||||
| (jlong) threadPrivateData.get(), | |||||
| jName.get(), (jlong) threadStackSize); | |||||
| if (jobject juceThread = getEnv()->NewGlobalRef (juceNewThread)) | |||||
| { | |||||
| AndroidThreadData* priv = threadPrivateData.release(); | |||||
| threadHandle = (void*) juceThread; | |||||
| getEnv()->CallVoidMethod (juceThread, JuceThread.start); | |||||
| priv->eventSet.wait (-1); | |||||
| threadId = priv->tId; | |||||
| priv->eventGet.signal(); | |||||
| } | |||||
| } | |||||
| void Thread::closeThreadHandle() | |||||
| { | |||||
| if (threadHandle != 0) | |||||
| { | |||||
| jobject juceThread = reinterpret_cast<jobject> (threadHandle); | |||||
| getEnv()->DeleteGlobalRef (juceThread); | |||||
| threadHandle = 0; | |||||
| } | |||||
| threadId = 0; | |||||
| } | |||||
| void Thread::killThread() | |||||
| { | |||||
| if (threadHandle != 0) | |||||
| { | |||||
| jobject juceThread = reinterpret_cast<jobject> (threadHandle); | |||||
| getEnv()->CallVoidMethod (juceThread, JuceThread.stop); | |||||
| } | |||||
| } | |||||
| void JUCE_CALLTYPE Thread::setCurrentThreadName (const String& name) | |||||
| { | |||||
| LocalRef<jobject> juceThread (getEnv()->CallStaticObjectMethod (JuceThread, JuceThread.currentThread)); | |||||
| if (jobject t = juceThread.get()) | |||||
| getEnv()->CallVoidMethod (t, JuceThread.setName, javaString (name).get()); | |||||
| } | |||||
| bool Thread::setThreadPriority (void* handle, int priority) | |||||
| { | |||||
| if (handle == nullptr) | |||||
| { | |||||
| LocalRef<jobject> juceThread (getEnv()->CallStaticObjectMethod (JuceThread, JuceThread.currentThread)); | |||||
| if (jobject t = juceThread.get()) | |||||
| return setThreadPriority (t, priority); | |||||
| return false; | |||||
| } | |||||
| jobject juceThread = reinterpret_cast<jobject> (handle); | |||||
| const int minPriority = 1; | |||||
| const int maxPriority = 10; | |||||
| jint javaPriority = ((maxPriority - minPriority) * priority) / 10 + minPriority; | |||||
| getEnv()->CallVoidMethod (juceThread, JuceThread.setPriority, javaPriority); | |||||
| return true; | |||||
| } | |||||
| //============================================================================== | |||||
| struct HighResolutionTimer::Pimpl | |||||
| { | |||||
| struct HighResolutionThread : public Thread | |||||
| { | |||||
| HighResolutionThread (HighResolutionTimer::Pimpl& parent) | |||||
| : Thread ("High Resolution Timer"), pimpl (parent) | |||||
| { | |||||
| startThread(); | |||||
| } | |||||
| void run() override | |||||
| { | |||||
| pimpl.timerThread(); | |||||
| } | |||||
| private: | |||||
| HighResolutionTimer::Pimpl& pimpl; | |||||
| }; | |||||
| //============================================================================== | |||||
| Pimpl (HighResolutionTimer& t) : owner (t) {} | |||||
| ~Pimpl() | |||||
| { | |||||
| stop(); | |||||
| } | |||||
| void start (int newPeriod) | |||||
| { | |||||
| if (periodMs != newPeriod) | |||||
| { | |||||
| if (thread.get() == nullptr | |||||
| || thread->getThreadId() != Thread::getCurrentThreadId() | |||||
| || thread->threadShouldExit()) | |||||
| { | |||||
| stop(); | |||||
| periodMs = newPeriod; | |||||
| thread = new HighResolutionThread (*this); | |||||
| } | |||||
| else | |||||
| { | |||||
| periodMs = newPeriod; | |||||
| } | |||||
| } | |||||
| } | |||||
| void stop() | |||||
| { | |||||
| if (thread.get() != nullptr) | |||||
| { | |||||
| thread->signalThreadShouldExit(); | |||||
| if (thread->getThreadId() != Thread::getCurrentThreadId()) | |||||
| { | |||||
| thread->waitForThreadToExit (-1); | |||||
| thread = nullptr; | |||||
| } | |||||
| } | |||||
| } | |||||
| HighResolutionTimer& owner; | |||||
| int volatile periodMs; | |||||
| private: | |||||
| ScopedPointer<Thread> thread; | |||||
| void timerThread() | |||||
| { | |||||
| jassert (thread.get() != nullptr); | |||||
| int lastPeriod = periodMs; | |||||
| Clock clock (lastPeriod); | |||||
| while (! thread->threadShouldExit()) | |||||
| { | |||||
| clock.wait(); | |||||
| owner.hiResTimerCallback(); | |||||
| if (lastPeriod != periodMs) | |||||
| { | |||||
| lastPeriod = periodMs; | |||||
| clock = Clock (lastPeriod); | |||||
| } | |||||
| } | |||||
| periodMs = 0; | |||||
| } | |||||
| struct Clock | |||||
| { | |||||
| Clock (double millis) noexcept : delta ((uint64) (millis * 1000000)) | |||||
| { | |||||
| } | |||||
| void wait() noexcept | |||||
| { | |||||
| struct timespec t; | |||||
| t.tv_sec = (time_t) (delta / 1000000000); | |||||
| t.tv_nsec = (long) (delta % 1000000000); | |||||
| nanosleep (&t, nullptr); | |||||
| } | |||||
| uint64 delta; | |||||
| }; | |||||
| static bool setThreadToRealtime (pthread_t thread, uint64 periodMs) | |||||
| { | |||||
| ignoreUnused (periodMs); | |||||
| struct sched_param param; | |||||
| param.sched_priority = sched_get_priority_max (SCHED_RR); | |||||
| return pthread_setschedparam (thread, SCHED_RR, ¶m) == 0; | |||||
| } | |||||
| JUCE_DECLARE_NON_COPYABLE (Pimpl) | |||||
| }; | |||||
| @@ -906,12 +906,25 @@ void InterProcessLock::exit() | |||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| #if ! JUCE_ANDROID | |||||
| void JUCE_API juce_threadEntryPoint (void*); | void JUCE_API juce_threadEntryPoint (void*); | ||||
| #if JUCE_ANDROID | |||||
| extern JavaVM* androidJNIJavaVM; | |||||
| #endif | |||||
| extern "C" void* threadEntryProc (void*); | extern "C" void* threadEntryProc (void*); | ||||
| extern "C" void* threadEntryProc (void* userData) | extern "C" void* threadEntryProc (void* userData) | ||||
| { | { | ||||
| #if JUCE_ANDROID | |||||
| // JNI_OnLoad was not called - make sure you load the JUCE shared library | |||||
| // using System.load inside of Java | |||||
| jassert (androidJNIJavaVM != nullptr); | |||||
| JNIEnv* env; | |||||
| androidJNIJavaVM->AttachCurrentThread (&env, nullptr); | |||||
| setEnv (env); | |||||
| #endif | |||||
| JUCE_AUTORELEASEPOOL | JUCE_AUTORELEASEPOOL | ||||
| { | { | ||||
| juce_threadEntryPoint (userData); | juce_threadEntryPoint (userData); | ||||
| @@ -999,7 +1012,6 @@ bool Thread::setThreadPriority (void* handle, int priority) | |||||
| param.sched_priority = ((maxPriority - minPriority) * priority) / 10 + minPriority; | param.sched_priority = ((maxPriority - minPriority) * priority) / 10 + minPriority; | ||||
| return pthread_setschedparam ((pthread_t) handle, policy, ¶m) == 0; | return pthread_setschedparam ((pthread_t) handle, policy, ¶m) == 0; | ||||
| } | } | ||||
| #endif | |||||
| Thread::ThreadID JUCE_CALLTYPE Thread::getCurrentThreadId() | Thread::ThreadID JUCE_CALLTYPE Thread::getCurrentThreadId() | ||||
| { | { | ||||
| @@ -1233,7 +1245,6 @@ bool ChildProcess::start (const StringArray& args, int streamFlags) | |||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| #if ! JUCE_ANDROID | |||||
| struct HighResolutionTimer::Pimpl | struct HighResolutionTimer::Pimpl | ||||
| { | { | ||||
| Pimpl (HighResolutionTimer& t) : owner (t), thread (0), destroyThread (false), isRunning (false) | Pimpl (HighResolutionTimer& t) : owner (t), thread (0), destroyThread (false), isRunning (false) | ||||
| @@ -1241,7 +1252,7 @@ struct HighResolutionTimer::Pimpl | |||||
| pthread_condattr_t attr; | pthread_condattr_t attr; | ||||
| pthread_condattr_init (&attr); | pthread_condattr_init (&attr); | ||||
| #if ! (JUCE_MAC || JUCE_IOS) | |||||
| #if JUCE_LINUX || (JUCE_ANDROID && defined(__ANDROID_API__) && __ANDROID_API__ >= 21) | |||||
| pthread_condattr_setclock(&attr, CLOCK_MONOTONIC); | pthread_condattr_setclock(&attr, CLOCK_MONOTONIC); | ||||
| #endif | #endif | ||||
| @@ -1320,7 +1331,13 @@ private: | |||||
| static void* timerThread (void* param) | static void* timerThread (void* param) | ||||
| { | { | ||||
| #if JUCE_ANDROID | #if JUCE_ANDROID | ||||
| const AndroidThreadScope androidEnv; | |||||
| // JNI_OnLoad was not called - make sure you load the JUCE shared library | |||||
| // using System.load inside of Java | |||||
| jassert (androidJNIJavaVM != nullptr); | |||||
| JNIEnv* env; | |||||
| androidJNIJavaVM->AttachCurrentThread (&env, nullptr); | |||||
| setEnv (env); | |||||
| #else | #else | ||||
| int dummy; | int dummy; | ||||
| pthread_setcancelstate (PTHREAD_CANCEL_ENABLE, &dummy); | pthread_setcancelstate (PTHREAD_CANCEL_ENABLE, &dummy); | ||||
| @@ -1469,5 +1486,3 @@ private: | |||||
| JUCE_DECLARE_NON_COPYABLE (Pimpl) | JUCE_DECLARE_NON_COPYABLE (Pimpl) | ||||
| }; | }; | ||||
| #endif | |||||