| @@ -1211,36 +1211,8 @@ public class JuceAppActivity extends Activity | |||
| 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) | |||
| { | |||
| 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 (getAndroidSDKVersion, "getAndroidSDKVersion", "()I") \ | |||
| METHOD (audioManagerGetProperty, "audioManagerGetProperty", "(Ljava/lang/String;)Ljava/lang/String;") \ | |||
| METHOD (setCurrentThreadPriority, "setCurrentThreadPriority", "(I)I") \ | |||
| METHOD (hasSystemFeature, "hasSystemFeature", "(Ljava/lang/String;)Z" ) \ | |||
| METHOD (createNewThread, "createNewThread", "(JLjava/lang/String;J)Ljava/lang/Thread;") \ | |||
| METHOD (requestRuntimePermission, "requestRuntimePermission", "(IJ)V" ) \ | |||
| METHOD (isPermissionGranted, "isPermissionGranted", "(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"); | |||
| #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) \ | |||
| 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; | |||
| } | |||
| @@ -127,6 +194,8 @@ AndroidSystem::AndroidSystem() : screenWidth (0), screenHeight (0), dpi (160) | |||
| void AndroidSystem::initialise (JNIEnv* env, jobject act, jstring file, jstring dataDir) | |||
| { | |||
| setEnv (env); | |||
| screenWidth = screenHeight = 0; | |||
| dpi = 160; | |||
| 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::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*); | |||
| #if JUCE_ANDROID | |||
| extern JavaVM* androidJNIJavaVM; | |||
| #endif | |||
| extern "C" void* threadEntryProc (void*); | |||
| 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_threadEntryPoint (userData); | |||
| @@ -999,7 +1012,6 @@ bool Thread::setThreadPriority (void* handle, int priority) | |||
| param.sched_priority = ((maxPriority - minPriority) * priority) / 10 + minPriority; | |||
| return pthread_setschedparam ((pthread_t) handle, policy, ¶m) == 0; | |||
| } | |||
| #endif | |||
| Thread::ThreadID JUCE_CALLTYPE Thread::getCurrentThreadId() | |||
| { | |||
| @@ -1233,7 +1245,6 @@ bool ChildProcess::start (const StringArray& args, int streamFlags) | |||
| } | |||
| //============================================================================== | |||
| #if ! JUCE_ANDROID | |||
| struct HighResolutionTimer::Pimpl | |||
| { | |||
| Pimpl (HighResolutionTimer& t) : owner (t), thread (0), destroyThread (false), isRunning (false) | |||
| @@ -1241,7 +1252,7 @@ struct HighResolutionTimer::Pimpl | |||
| pthread_condattr_t 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); | |||
| #endif | |||
| @@ -1320,7 +1331,13 @@ private: | |||
| static void* timerThread (void* param) | |||
| { | |||
| #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 | |||
| int dummy; | |||
| pthread_setcancelstate (PTHREAD_CANCEL_ENABLE, &dummy); | |||
| @@ -1469,5 +1486,3 @@ private: | |||
| JUCE_DECLARE_NON_COPYABLE (Pimpl) | |||
| }; | |||
| #endif | |||