The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

958 lines
42KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2022 - Raw Material Software Limited
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 7 End-User License
  8. Agreement and JUCE Privacy Policy.
  9. End User License Agreement: www.juce.com/juce-7-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. namespace juce
  19. {
  20. //==============================================================================
  21. // This byte-code is generated from native/java/app/com/rmsl/juce/JuceContentProviderCursor.java with min sdk version 16
  22. // See juce_core/native/java/README.txt on how to generate this byte-code.
  23. static const uint8 javaJuceContentProviderCursor[] =
  24. {31,139,8,8,191,114,161,94,0,3,106,97,118,97,74,117,99,101,67,111,110,116,101,110,116,80,114,111,118,105,100,101,114,67,117,
  25. 114,115,111,114,46,100,101,120,0,117,147,177,111,211,64,20,198,223,157,157,148,150,54,164,192,208,14,64,144,16,18,67,235,138,2,
  26. 75,40,162,10,44,150,65,149,2,25,218,233,176,173,198,37,241,69,182,19,121,96,160,21,136,37,19,98,234,80,85,149,152,88,24,248,
  27. 3,24,146,63,130,141,137,129,13,169,99,7,190,203,157,33,18,194,210,207,247,222,229,189,239,157,206,95,130,48,159,91,91,191,75,227,
  28. 60,200,143,134,239,247,151,62,189,43,175,127,249,246,235,241,215,241,112,231,231,193,237,135,22,81,143,136,242,214,157,139,
  29. 100,158,99,78,84,37,189,95,2,159,129,13,70,128,129,83,179,127,102,242,27,120,157,129,71,224,16,156,128,143,96,12,126,128,69,232,
  30. 93,6,75,224,10,184,14,238,129,13,224,130,16,188,4,3,174,245,44,51,79,205,152,53,171,101,206,86,54,241,27,20,206,152,120,136,
  31. 248,156,137,63,32,134,12,45,76,206,166,187,148,230,28,169,125,62,201,249,159,156,209,188,201,23,77,93,241,187,122,134,38,40,225,
  32. 52,42,124,197,245,252,94,141,104,147,182,113,95,21,76,208,83,222,114,125,86,89,101,168,109,162,162,183,134,46,86,249,71,215,
  33. 158,228,54,149,239,71,113,148,61,32,230,210,85,183,239,135,13,25,103,97,156,109,37,114,16,5,97,210,232,39,169,76,86,247,196,64,
  34. 208,53,79,196,65,34,163,192,9,68,38,94,136,52,116,158,136,44,137,114,93,84,167,91,158,47,187,78,210,77,59,206,30,164,156,255,
  35. 234,213,137,181,136,183,92,178,90,174,135,192,163,75,59,158,154,225,116,68,188,235,52,33,26,239,214,169,228,119,100,26,210,121,
  36. 95,118,250,221,248,169,232,134,41,45,251,90,176,217,22,73,33,215,80,101,1,217,109,153,102,52,171,222,207,228,115,52,218,89,
  37. 59,74,233,38,191,48,63,83,217,88,161,85,194,178,141,139,224,184,28,190,255,218,30,113,126,192,201,98,223,249,130,185,27,54,181,
  38. 22,222,227,83,254,43,60,49,50,235,180,15,11,47,150,167,252,200,106,186,95,121,146,85,255,122,134,215,180,190,242,169,101,106,
  39. 212,119,165,154,238,157,124,243,170,142,213,255,224,55,143,234,50,200,64,3,0,0,0,0};
  40. // This byte-code is generated from native/java/app/com/rmsl/juce/JuceContentProviderFileObserver.java with min sdk version 16
  41. // See juce_core/native/java/README.txt on how to generate this byte-code.
  42. static const uint8 javaJuceContentProviderFileObserver[] =
  43. {31,139,8,8,194,122,161,94,0,3,106,97,118,97,74,117,99,101,67,111,110,116,101,110,116,80,114,111,118,105,100,101,114,70,105,
  44. 108,101,79,98,115,101,114,118,101,114,46,100,101,120,0,133,147,205,107,19,65,24,198,223,249,72,98,171,46,105,235,69,16,201,65,81,
  45. 68,221,136,10,66,84,144,250,65,194,130,197,212,32,5,15,155,100,104,182,38,187,97,119,141,241,32,126,30,196,147,23,79,246,216,
  46. 131,120,202,77,169,80,212,191,64,193,66,143,30,60,138,255,130,62,179,51,165,219,147,129,223,188,239,188,239,204,179,179,179,79,
  47. 186,106,60,93,61,123,158,54,159,255,248,112,97,210,120,124,98,237,251,177,7,109,245,115,253,225,198,159,47,243,171,135,198,130,
  48. 104,72,68,227,214,185,89,178,191,45,78,116,128,76,189,8,62,3,169,235,128,129,61,204,204,203,204,204,171,24,142,99,207,2,226,
  49. 4,124,4,159,192,6,248,5,254,130,42,250,87,193,13,224,129,91,224,14,184,11,30,129,23,224,21,120,3,222,130,53,240,158,27,125,110,
  50. 159,95,176,231,41,233,51,216,249,75,44,152,178,249,107,228,211,54,95,69,190,215,230,239,144,11,40,57,153,150,200,222,81,100,
  51. 170,166,190,47,139,68,51,185,200,237,93,8,27,191,218,66,17,138,186,54,225,230,44,195,42,209,149,194,18,238,206,201,58,250,121,
  52. 235,182,215,172,160,191,200,137,159,113,172,158,204,246,50,251,62,38,151,89,103,251,29,139,23,131,48,72,47,19,171,19,107,208,
  53. 145,198,253,142,154,143,194,84,133,233,66,28,141,130,174,138,175,7,125,117,179,157,168,120,164,226,211,43,254,200,167,131,158,
  54. 31,118,227,40,232,186,81,226,230,219,53,114,189,78,52,112,227,65,210,119,87,32,229,254,71,175,70,179,158,150,116,251,126,184,
  55. 236,54,211,56,8,151,107,196,90,36,90,117,143,100,171,97,70,175,142,2,134,195,29,35,213,236,249,241,110,161,107,35,148,169,160,
  56. 178,32,123,81,146,210,148,30,23,163,219,137,34,57,240,147,123,84,138,66,179,76,14,253,180,71,50,237,5,9,29,21,229,185,153,146,
  57. 115,233,20,157,228,206,92,201,89,194,21,113,70,156,61,125,34,191,113,246,12,223,143,253,198,101,237,183,223,133,229,226,182,103,
  58. 121,206,183,34,231,93,153,243,111,129,118,60,92,164,29,31,179,138,217,175,189,204,202,102,141,246,24,175,24,125,237,111,97,
  59. 215,104,15,80,197,236,205,252,81,54,185,254,255,252,3,243,31,208,130,120,3,0,0,0,0};
  60. //==============================================================================
  61. #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
  62. FIELD (authority, "authority", "Ljava/lang/String;")
  63. DECLARE_JNI_CLASS (AndroidProviderInfo, "android/content/pm/ProviderInfo")
  64. #undef JNI_CLASS_MEMBERS
  65. #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
  66. METHOD (constructor, "<init>", "(Landroid/os/ParcelFileDescriptor;JJ)V") \
  67. METHOD (createInputStream, "createInputStream", "()Ljava/io/FileInputStream;") \
  68. METHOD (getLength, "getLength", "()J")
  69. DECLARE_JNI_CLASS (AssetFileDescriptor, "android/content/res/AssetFileDescriptor")
  70. #undef JNI_CLASS_MEMBERS
  71. #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
  72. METHOD (close, "close", "()V")
  73. DECLARE_JNI_CLASS (JavaCloseable, "java/io/Closeable")
  74. #undef JNI_CLASS_MEMBERS
  75. #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
  76. STATICMETHOD (open, "open", "(Ljava/io/File;I)Landroid/os/ParcelFileDescriptor;")
  77. DECLARE_JNI_CLASS (ParcelFileDescriptor, "android/os/ParcelFileDescriptor")
  78. #undef JNI_CLASS_MEMBERS
  79. //==============================================================================
  80. class AndroidContentSharerCursor
  81. {
  82. public:
  83. AndroidContentSharerCursor (JNIEnv* env,
  84. const LocalRef<jobject>& contentProvider,
  85. const LocalRef<jobjectArray>& resultColumns,
  86. std::function<void (AndroidContentSharerCursor&)> onCloseIn)
  87. : onClose (std::move (onCloseIn)),
  88. cursor (GlobalRef (LocalRef<jobject> (env->NewObject (JuceContentProviderCursor,
  89. JuceContentProviderCursor.constructor,
  90. reinterpret_cast<jlong> (this),
  91. resultColumns.get()))))
  92. {
  93. // the content provider must be created first
  94. jassert (contentProvider.get() != nullptr);
  95. }
  96. jobject getNativeCursor() const { return cursor.get(); }
  97. void addRow (LocalRef<jobjectArray>& values)
  98. {
  99. auto* env = getEnv();
  100. env->CallVoidMethod (cursor.get(), JuceContentProviderCursor.addRow, values.get());
  101. }
  102. private:
  103. static void cursorClosed (JNIEnv*, AndroidContentSharerCursor& t)
  104. {
  105. MessageManager::callAsync ([&t]
  106. {
  107. NullCheckedInvocation::invoke (t.onClose, t);
  108. });
  109. }
  110. std::function<void (AndroidContentSharerCursor&)> onClose;
  111. GlobalRef cursor;
  112. //==============================================================================
  113. #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
  114. METHOD (addRow, "addRow", "([Ljava/lang/Object;)V") \
  115. METHOD (constructor, "<init>", "(J[Ljava/lang/String;)V") \
  116. CALLBACK (generatedCallback<&AndroidContentSharerCursor::cursorClosed>, "contentSharerCursorClosed", "(J)V") \
  117. DECLARE_JNI_CLASS_WITH_BYTECODE (JuceContentProviderCursor, "com/rmsl/juce/JuceContentProviderCursor", 16, javaJuceContentProviderCursor)
  118. #undef JNI_CLASS_MEMBERS
  119. };
  120. //==============================================================================
  121. class AndroidContentSharerFileObserver
  122. {
  123. public:
  124. AndroidContentSharerFileObserver (JNIEnv* env,
  125. const LocalRef<jobject>& contentProvider,
  126. const File& filepathToUse,
  127. std::function<void()> onCloseIn)
  128. : onClose (std::move (onCloseIn)),
  129. filepath (filepathToUse),
  130. fileObserver (GlobalRef (LocalRef<jobject> (env->NewObject (JuceContentProviderFileObserver,
  131. JuceContentProviderFileObserver.constructor,
  132. reinterpret_cast<jlong> (this),
  133. javaString (filepath.getFullPathName()).get(),
  134. open | access | closeWrite | closeNoWrite))))
  135. {
  136. // the content provider must be created first
  137. jassert (contentProvider.get() != nullptr);
  138. env->CallVoidMethod (fileObserver, JuceContentProviderFileObserver.startWatching);
  139. }
  140. void onFileEvent (int event, const LocalRef<jstring>&)
  141. {
  142. if (event == open)
  143. {
  144. ++numOpenedHandles;
  145. }
  146. else if (event == access)
  147. {
  148. fileWasRead = true;
  149. }
  150. else if (event == closeNoWrite || event == closeWrite)
  151. {
  152. --numOpenedHandles;
  153. // numOpenedHandles may get negative if we don't receive open handle event.
  154. if (fileWasRead && numOpenedHandles <= 0)
  155. {
  156. MessageManager::callAsync ([fileObserver = fileObserver, onClose = onClose]
  157. {
  158. getEnv()->CallVoidMethod (fileObserver, JuceContentProviderFileObserver.stopWatching);
  159. NullCheckedInvocation::invoke (onClose);
  160. });
  161. }
  162. }
  163. }
  164. private:
  165. static constexpr int open = 32;
  166. static constexpr int access = 1;
  167. static constexpr int closeWrite = 8;
  168. static constexpr int closeNoWrite = 16;
  169. std::function<void()> onClose;
  170. bool fileWasRead = false;
  171. int numOpenedHandles = 0;
  172. File filepath;
  173. GlobalRef fileObserver;
  174. //==============================================================================
  175. #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
  176. METHOD (constructor, "<init>", "(JLjava/lang/String;I)V") \
  177. METHOD (startWatching, "startWatching", "()V") \
  178. METHOD (stopWatching, "stopWatching", "()V") \
  179. CALLBACK (generatedCallback<&AndroidContentSharerFileObserver::onFileEventCallback>, "contentSharerFileObserverEvent", "(JILjava/lang/String;)V") \
  180. DECLARE_JNI_CLASS_WITH_BYTECODE (JuceContentProviderFileObserver, "com/rmsl/juce/JuceContentProviderFileObserver", 16, javaJuceContentProviderFileObserver)
  181. #undef JNI_CLASS_MEMBERS
  182. static void onFileEventCallback (JNIEnv*, AndroidContentSharerFileObserver& t, jint event, jstring path)
  183. {
  184. t.onFileEvent (event, LocalRef<jstring> (path));
  185. }
  186. };
  187. //==============================================================================
  188. class ContentSharerGlobalImpl
  189. {
  190. public:
  191. static ContentSharerGlobalImpl& getInstance()
  192. {
  193. static ContentSharerGlobalImpl result;
  194. return result;
  195. }
  196. const String packageName = juceString (LocalRef<jstring> ((jstring) getEnv()->CallObjectMethod (getAppContext().get(),
  197. AndroidContext.getPackageName)));
  198. const String uriBase = "content://" + packageName + ".sharingcontentprovider/";
  199. std::unique_ptr<ActivityLauncher> sharePreparedFiles (const std::map<String, File>& fileForUriIn,
  200. const StringArray& mimeTypes,
  201. std::function<void (bool)> callback)
  202. {
  203. // This function should be called from the main thread, but must not race with singleton
  204. // access from other threads.
  205. const ScopedLock lock { mutex };
  206. if (! isContentSharingEnabled())
  207. {
  208. // You need to enable "Content Sharing" in Projucer's Android exporter.
  209. jassertfalse;
  210. NullCheckedInvocation::invoke (callback, false);
  211. return {};
  212. }
  213. auto* env = getEnv();
  214. fileForUri.insert (fileForUriIn.begin(), fileForUriIn.end());
  215. LocalRef<jobject> fileUris (env->NewObject (JavaArrayList, JavaArrayList.constructor, fileForUriIn.size()));
  216. for (const auto& pair : fileForUriIn)
  217. {
  218. env->CallBooleanMethod (fileUris,
  219. JavaArrayList.add,
  220. env->CallStaticObjectMethod (AndroidUri,
  221. AndroidUri.parse,
  222. javaString (pair.first).get()));
  223. }
  224. LocalRef<jobject> intent (env->NewObject (AndroidIntent, AndroidIntent.constructor));
  225. env->CallObjectMethod (intent,
  226. AndroidIntent.setAction,
  227. javaString ("android.intent.action.SEND_MULTIPLE").get());
  228. env->CallObjectMethod (intent,
  229. AndroidIntent.setType,
  230. javaString (getCommonMimeType (mimeTypes)).get());
  231. const auto permissions = [&]
  232. {
  233. constexpr int grantReadUriPermission = 1;
  234. constexpr int grantPrefixUriPermission = 128;
  235. if (getAndroidSDKVersion() < 21)
  236. return grantReadUriPermission;
  237. return grantReadUriPermission | grantPrefixUriPermission;
  238. };
  239. env->CallObjectMethod (intent, AndroidIntent.setFlags, permissions);
  240. env->CallObjectMethod (intent,
  241. AndroidIntent.putParcelableArrayListExtra,
  242. javaString ("android.intent.extra.STREAM").get(),
  243. fileUris.get());
  244. return doIntent (intent, callback);
  245. }
  246. std::unique_ptr<ActivityLauncher> shareText (const String& text,
  247. std::function<void (bool)> callback)
  248. {
  249. // This function should be called from the main thread, but must not race with singleton
  250. // access from other threads.
  251. const ScopedLock lock { mutex };
  252. if (! isContentSharingEnabled())
  253. {
  254. // You need to enable "Content Sharing" in Projucer's Android exporter.
  255. jassertfalse;
  256. NullCheckedInvocation::invoke (callback, false);
  257. return {};
  258. }
  259. auto* env = getEnv();
  260. LocalRef<jobject> intent (env->NewObject (AndroidIntent, AndroidIntent.constructor));
  261. env->CallObjectMethod (intent,
  262. AndroidIntent.setAction,
  263. javaString ("android.intent.action.SEND").get());
  264. env->CallObjectMethod (intent,
  265. AndroidIntent.putExtra,
  266. javaString ("android.intent.extra.TEXT").get(),
  267. javaString (text).get());
  268. env->CallObjectMethod (intent, AndroidIntent.setType, javaString ("text/plain").get());
  269. return doIntent (intent, callback);
  270. }
  271. static void onBroadcastResultReceive (JNIEnv*, jobject, int requestCode)
  272. {
  273. getInstance().sharingFinished (requestCode, true);
  274. }
  275. static jobject JNICALL contentSharerQuery (JNIEnv*, jobject contentProvider, jobject uri, jobjectArray projection)
  276. {
  277. return getInstance().query (LocalRef<jobject> (static_cast<jobject> (contentProvider)),
  278. LocalRef<jobject> (static_cast<jobject> (uri)),
  279. LocalRef<jobjectArray> (static_cast<jobjectArray> (projection)));
  280. }
  281. static jobject JNICALL contentSharerOpenFile (JNIEnv*, jobject contentProvider, jobject uri, jstring mode)
  282. {
  283. return getInstance().openFile (LocalRef<jobject> (static_cast<jobject> (contentProvider)),
  284. LocalRef<jobject> (static_cast<jobject> (uri)),
  285. LocalRef<jstring> (static_cast<jstring> (mode)));
  286. }
  287. static jobjectArray JNICALL contentSharerGetStreamTypes (JNIEnv*, jobject /*contentProvider*/, jobject uri, jstring mimeTypeFilter)
  288. {
  289. return getInstance().getStreamTypes (addLocalRefOwner (uri),
  290. addLocalRefOwner (mimeTypeFilter));
  291. }
  292. private:
  293. ContentSharerGlobalImpl() = default;
  294. LocalRef<jobject> makeChooser (const LocalRef<jobject>& intent, int request) const
  295. {
  296. auto* env = getEnv();
  297. const auto text = javaString ("Choose share target");
  298. if (getAndroidSDKVersion() < 22)
  299. return LocalRef<jobject> (env->CallStaticObjectMethod (AndroidIntent,
  300. AndroidIntent.createChooser,
  301. intent.get(),
  302. text.get()));
  303. constexpr jint FLAG_UPDATE_CURRENT = 0x08000000;
  304. constexpr jint FLAG_IMMUTABLE = 0x04000000;
  305. const auto context = getAppContext();
  306. auto* klass = env->FindClass ("com/rmsl/juce/Receiver");
  307. const LocalRef<jobject> replyIntent (env->NewObject (AndroidIntent, AndroidIntent.constructorWithContextAndClass, context.get(), klass));
  308. getEnv()->CallObjectMethod (replyIntent, AndroidIntent.putExtraInt, javaString ("com.rmsl.juce.JUCE_REQUEST_CODE").get(), request);
  309. const auto flags = FLAG_UPDATE_CURRENT | (getAndroidSDKVersion() <= 23 ? 0 : FLAG_IMMUTABLE);
  310. const LocalRef<jobject> pendingIntent (env->CallStaticObjectMethod (AndroidPendingIntent,
  311. AndroidPendingIntent.getBroadcast,
  312. context.get(),
  313. request,
  314. replyIntent.get(),
  315. flags));
  316. return LocalRef<jobject> (env->CallStaticObjectMethod (AndroidIntent22,
  317. AndroidIntent22.createChooser,
  318. intent.get(),
  319. text.get(),
  320. env->CallObjectMethod (pendingIntent,
  321. AndroidPendingIntent.getIntentSender)));
  322. }
  323. //==============================================================================
  324. jobject openFile (const LocalRef<jobject>& contentProvider,
  325. const LocalRef<jobject>& uri,
  326. [[maybe_unused]] const LocalRef<jstring>& mode)
  327. {
  328. // This function can be called from multiple threads.
  329. const ScopedLock lock { mutex };
  330. auto* env = getEnv();
  331. auto uriElements = getContentUriElements (env, uri);
  332. if (uriElements.file == File())
  333. return nullptr;
  334. return getAssetFileDescriptor (env, contentProvider, uriElements.file);
  335. }
  336. jobject query (const LocalRef<jobject>& contentProvider,
  337. const LocalRef<jobject>& uri,
  338. const LocalRef<jobjectArray>& projection)
  339. {
  340. // This function can be called from multiple threads.
  341. const ScopedLock lock { mutex };
  342. StringArray requestedColumns = javaStringArrayToJuce (projection);
  343. StringArray supportedColumns = getSupportedColumns();
  344. StringArray resultColumns;
  345. for (const auto& col : supportedColumns)
  346. {
  347. if (requestedColumns.contains (col))
  348. resultColumns.add (col);
  349. }
  350. // Unsupported columns were queried, file sharing may fail.
  351. if (resultColumns.isEmpty())
  352. return nullptr;
  353. auto resultJavaColumns = juceStringArrayToJava (resultColumns);
  354. auto* env = getEnv();
  355. const auto uriElements = getContentUriElements (env, uri);
  356. const auto callback = [info = uriElements.file] (auto& ref)
  357. {
  358. auto& pimplCursors = ContentSharerGlobalImpl::getInstance().cursors;
  359. const auto iter = std::lower_bound (pimplCursors.begin(), pimplCursors.end(), &ref, [] (const auto& managed, const auto* ptr)
  360. {
  361. return managed.get() == ptr;
  362. });
  363. if (iter != pimplCursors.end() && iter->get() == &ref)
  364. pimplCursors.erase (iter);
  365. };
  366. auto [iter, inserted] = cursors.emplace (new AndroidContentSharerCursor (env,
  367. contentProvider,
  368. resultJavaColumns,
  369. callback));
  370. if (uriElements.file == File())
  371. return (*iter)->getNativeCursor();
  372. LocalRef<jobjectArray> values (env->NewObjectArray ((jsize) resultColumns.size(), JavaObject, nullptr));
  373. for (int i = 0; i < resultColumns.size(); ++i)
  374. {
  375. if (resultColumns.getReference (i) == "_display_name")
  376. {
  377. env->SetObjectArrayElement (values, i, javaString (uriElements.filename).get());
  378. }
  379. else if (resultColumns.getReference (i) == "_size")
  380. {
  381. LocalRef<jobject> javaFile (env->NewObject (JavaFile,
  382. JavaFile.constructor,
  383. javaString (uriElements.file.getFullPathName()).get()));
  384. jlong fileLength = env->CallLongMethod (javaFile, JavaFile.length);
  385. env->SetObjectArrayElement (values, i, env->NewObject (JavaLong, JavaLong.constructor, fileLength));
  386. }
  387. }
  388. (*iter)->addRow (values);
  389. return (*iter)->getNativeCursor();
  390. }
  391. jobjectArray getStreamTypes (const LocalRef<jobject>& uri, const LocalRef<jstring>& mimeTypeFilter)
  392. {
  393. // This function can be called from multiple threads.
  394. const ScopedLock lock { mutex };
  395. auto* env = getEnv();
  396. auto extension = getContentUriElements (env, uri).filename.fromLastOccurrenceOf (".", false, true);
  397. if (extension.isEmpty())
  398. return nullptr;
  399. return juceStringArrayToJava (filterMimeTypes (detail::MimeTypeTable::getMimeTypesForFileExtension (extension),
  400. juceString (mimeTypeFilter.get()))).release();
  401. }
  402. std::unique_ptr<ActivityLauncher> doIntent (const LocalRef<jobject>& intent,
  403. std::function<void (bool)> callback)
  404. {
  405. static std::atomic<int> lastRequest = 1003;
  406. const auto requestCode = lastRequest++;
  407. callbackForRequest.emplace (requestCode, callback);
  408. const auto chooser = makeChooser (intent, requestCode);
  409. auto launcher = std::make_unique<ActivityLauncher> (chooser, requestCode);
  410. launcher->callback = [] (int request, int resultCode, LocalRef<jobject>)
  411. {
  412. ContentSharerGlobalImpl::getInstance().sharingFinished (request, resultCode == -1);
  413. };
  414. launcher->open();
  415. return launcher;
  416. }
  417. void sharingFinished (int request, bool succeeded)
  418. {
  419. // This function should be called from the main thread, but must not race with singleton
  420. // access from other threads.
  421. const ScopedLock lock { mutex };
  422. const auto iter = callbackForRequest.find (request);
  423. if (iter == callbackForRequest.end())
  424. return;
  425. const ScopeGuard scope { [&] { callbackForRequest.erase (iter); } };
  426. if (iter->second == nullptr)
  427. return;
  428. iter->second (succeeded);
  429. }
  430. bool isContentSharingEnabled() const
  431. {
  432. auto* env = getEnv();
  433. LocalRef<jobject> packageManager (env->CallObjectMethod (getAppContext().get(), AndroidContext.getPackageManager));
  434. constexpr int getProviders = 8;
  435. LocalRef<jobject> packageInfo (env->CallObjectMethod (packageManager,
  436. AndroidPackageManager.getPackageInfo,
  437. javaString (packageName).get(),
  438. getProviders));
  439. LocalRef<jobjectArray> providers ((jobjectArray) env->GetObjectField (packageInfo,
  440. AndroidPackageInfo.providers));
  441. if (providers == nullptr)
  442. return false;
  443. auto sharingContentProviderAuthority = packageName + ".sharingcontentprovider";
  444. const int numProviders = env->GetArrayLength (providers.get());
  445. for (int i = 0; i < numProviders; ++i)
  446. {
  447. LocalRef<jobject> providerInfo (env->GetObjectArrayElement (providers, i));
  448. LocalRef<jstring> authority ((jstring) env->GetObjectField (providerInfo, AndroidProviderInfo.authority));
  449. if (juceString (authority) == sharingContentProviderAuthority)
  450. return true;
  451. }
  452. return false;
  453. }
  454. //==============================================================================
  455. struct ContentUriElements
  456. {
  457. String filename;
  458. File file;
  459. };
  460. ContentUriElements getContentUriElements (JNIEnv* env, const LocalRef<jobject>& uri) const
  461. {
  462. const auto fullUri = juceString ((jstring) env->CallObjectMethod (uri.get(), AndroidUri.toString));
  463. const auto filename = fullUri.fromLastOccurrenceOf ("/", false, true);
  464. const auto iter = fileForUri.find (fullUri);
  465. const auto info = iter != fileForUri.end() ? iter->second : File{};
  466. return { filename, info };
  467. }
  468. static StringArray getSupportedColumns()
  469. {
  470. return StringArray ("_display_name", "_size");
  471. }
  472. jobject getAssetFileDescriptor (JNIEnv* env, const LocalRef<jobject>& contentProvider, const File& filepath)
  473. {
  474. if (nonAssetFilePathsPendingShare.find (filepath) == nonAssetFilePathsPendingShare.end())
  475. {
  476. const auto onCloseCallback = [filepath]
  477. {
  478. ContentSharerGlobalImpl::getInstance().nonAssetFilePathsPendingShare.erase (filepath);
  479. };
  480. auto observer = rawToUniquePtr (new AndroidContentSharerFileObserver (env,
  481. contentProvider,
  482. filepath,
  483. onCloseCallback));
  484. nonAssetFilePathsPendingShare.emplace (filepath, std::move (observer));
  485. }
  486. const LocalRef<jobject> javaFile (env->NewObject (JavaFile,
  487. JavaFile.constructor,
  488. javaString (filepath.getFullPathName()).get()));
  489. constexpr int modeReadOnly = 268435456;
  490. LocalRef<jobject> parcelFileDescriptor (env->CallStaticObjectMethod (ParcelFileDescriptor,
  491. ParcelFileDescriptor.open,
  492. javaFile.get(),
  493. modeReadOnly));
  494. if (jniCheckHasExceptionOccurredAndClear())
  495. {
  496. // Failed to create file descriptor. Have you provided a valid file path/resource name?
  497. jassertfalse;
  498. return nullptr;
  499. }
  500. jlong startOffset = 0;
  501. jlong unknownLength = -1;
  502. assetFileDescriptors.add (GlobalRef (LocalRef<jobject> (env->NewObject (AssetFileDescriptor,
  503. AssetFileDescriptor.constructor,
  504. parcelFileDescriptor.get(),
  505. startOffset,
  506. unknownLength))));
  507. return assetFileDescriptors.getReference (assetFileDescriptors.size() - 1).get();
  508. }
  509. StringArray filterMimeTypes (const StringArray& mimeTypes, const String& filter)
  510. {
  511. String filterToUse (filter.removeCharacters ("*"));
  512. if (filterToUse.isEmpty() || filterToUse == "/")
  513. return mimeTypes;
  514. StringArray result;
  515. for (const auto& type : mimeTypes)
  516. if (String (type).contains (filterToUse))
  517. result.add (type);
  518. return result;
  519. }
  520. static String getCommonMimeType (const StringArray& mimeTypes)
  521. {
  522. if (mimeTypes.isEmpty())
  523. return "*/*";
  524. auto commonMime = mimeTypes[0];
  525. bool lookForCommonGroup = false;
  526. for (int i = 1; i < mimeTypes.size(); ++i)
  527. {
  528. if (mimeTypes[i] == commonMime)
  529. continue;
  530. if (! lookForCommonGroup)
  531. {
  532. lookForCommonGroup = true;
  533. commonMime = commonMime.upToFirstOccurrenceOf ("/", true, false);
  534. }
  535. if (! mimeTypes[i].startsWith (commonMime))
  536. return "*/*";
  537. }
  538. return lookForCommonGroup ? commonMime + "*" : commonMime;
  539. }
  540. CriticalSection mutex;
  541. Array<GlobalRef> assetFileDescriptors;
  542. std::map<File, std::unique_ptr<AndroidContentSharerFileObserver>> nonAssetFilePathsPendingShare;
  543. std::set<std::unique_ptr<AndroidContentSharerCursor>> cursors;
  544. std::map<String, File> fileForUri;
  545. std::map<int, std::function<void (bool)>> callbackForRequest;
  546. };
  547. #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
  548. CALLBACK (ContentSharerGlobalImpl::contentSharerQuery, "contentSharerQuery", "(Landroid/net/Uri;[Ljava/lang/String;)Landroid/database/Cursor;") \
  549. CALLBACK (ContentSharerGlobalImpl::contentSharerOpenFile, "contentSharerOpenFile", "(Landroid/net/Uri;Ljava/lang/String;)Landroid/content/res/AssetFileDescriptor;") \
  550. CALLBACK (ContentSharerGlobalImpl::contentSharerGetStreamTypes, "contentSharerGetStreamTypes", "(Landroid/net/Uri;Ljava/lang/String;)[Ljava/lang/String;") \
  551. DECLARE_JNI_CLASS (JuceSharingContentProvider, "com/rmsl/juce/JuceSharingContentProvider")
  552. #undef JNI_CLASS_MEMBERS
  553. #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
  554. CALLBACK (ContentSharerGlobalImpl::onBroadcastResultReceive, "onBroadcastResultNative", "(I)V")
  555. DECLARE_JNI_CLASS (AndroidReceiver, "com/rmsl/juce/Receiver")
  556. #undef JNI_CLASS_MEMBERS
  557. //==============================================================================
  558. class AndroidContentSharerPrepareFilesTask final : private AsyncUpdater
  559. {
  560. public:
  561. AndroidContentSharerPrepareFilesTask (const Array<URL>& fileUrls,
  562. std::function<void (const std::map<String, File>&, const StringArray&)> onCompletionIn)
  563. : onCompletion (std::move (onCompletionIn)),
  564. task (std::async (std::launch::async, [this, fileUrls]
  565. {
  566. run (fileUrls);
  567. triggerAsyncUpdate();
  568. })) {}
  569. ~AndroidContentSharerPrepareFilesTask() override
  570. {
  571. task.wait();
  572. cancelPendingUpdate();
  573. }
  574. private:
  575. const String packageName = ContentSharerGlobalImpl::getInstance().packageName;
  576. const String uriBase = ContentSharerGlobalImpl::getInstance().uriBase;
  577. struct StreamCloser
  578. {
  579. explicit StreamCloser (const LocalRef<jobject>& streamToUse)
  580. : stream (GlobalRef (streamToUse))
  581. {
  582. }
  583. ~StreamCloser()
  584. {
  585. if (stream.get() != nullptr)
  586. getEnv()->CallVoidMethod (stream, JavaCloseable.close);
  587. }
  588. GlobalRef stream;
  589. };
  590. void handleAsyncUpdate() override
  591. {
  592. onCompletion (infoForUri, mimeTypes);
  593. }
  594. void run (const Array<URL>& fileUrls)
  595. {
  596. auto* env = getEnv();
  597. StringArray filePaths;
  598. for (const auto& f : fileUrls)
  599. {
  600. const auto scheme = f.getScheme();
  601. // Only "file://" scheme or no scheme (for files in app bundle) are allowed!
  602. jassert (scheme.isEmpty() || scheme == "file");
  603. const auto fileToPrepare = [&]
  604. {
  605. if (! scheme.isEmpty())
  606. return f;
  607. // Raw resource names need to be all lower case
  608. jassert (f.toString (true).toLowerCase() == f.toString (true));
  609. // This will get us a file with file:// URI
  610. return copyAssetFileToTemporaryFile (env, f.toString (true));
  611. }();
  612. if (fileToPrepare.isEmpty())
  613. continue;
  614. const auto filepath = URL::removeEscapeChars (fileToPrepare.toString (true).fromFirstOccurrenceOf ("file://", false, false));
  615. filePaths.add (filepath);
  616. }
  617. std::vector<String> extensions;
  618. for (const auto& filepath : filePaths)
  619. {
  620. const auto filename = filepath.fromLastOccurrenceOf ("/", false, true);
  621. extensions.push_back (filename.fromLastOccurrenceOf (".", false, true));
  622. }
  623. std::set<String> mimes;
  624. if (std::none_of (extensions.begin(), extensions.end(), [] (const String& s) { return s.isEmpty(); }))
  625. for (const auto& extension : extensions)
  626. for (const auto& mime : detail::MimeTypeTable::getMimeTypesForFileExtension (extension))
  627. mimes.insert (mime);
  628. for (const auto& mime : mimes)
  629. mimeTypes.add (mime);
  630. for (auto it = filePaths.begin(); it != filePaths.end(); ++it)
  631. {
  632. const auto filename = it->fromLastOccurrenceOf ("/", false, true);
  633. const auto contentString = uriBase + String (std::distance (filePaths.begin(), it)) + "/" + filename;
  634. infoForUri.emplace (contentString, *it);
  635. }
  636. }
  637. URL copyAssetFileToTemporaryFile (JNIEnv* env, const String& filename)
  638. {
  639. LocalRef<jobject> resources (env->CallObjectMethod (getAppContext().get(), AndroidContext.getResources));
  640. int fileId = env->CallIntMethod (resources,
  641. AndroidResources.getIdentifier,
  642. javaString (filename).get(),
  643. javaString ("raw").get(),
  644. javaString (packageName).get());
  645. // Raw resource not found. Please make sure that you include your file as a raw resource
  646. // and that you specify just the file name, without an extension.
  647. jassert (fileId != 0);
  648. if (fileId == 0)
  649. return {};
  650. LocalRef<jobject> assetFd (env->CallObjectMethod (resources,
  651. AndroidResources.openRawResourceFd,
  652. fileId));
  653. StreamCloser inputStream (LocalRef<jobject> (env->CallObjectMethod (assetFd, AssetFileDescriptor.createInputStream)));
  654. if (jniCheckHasExceptionOccurredAndClear())
  655. {
  656. // Failed to open file stream for resource
  657. jassertfalse;
  658. return {};
  659. }
  660. auto tempFile = File::createTempFile ({});
  661. tempFile.createDirectory();
  662. tempFile = tempFile.getChildFile (filename);
  663. StreamCloser outputStream (LocalRef<jobject> (env->NewObject (JavaFileOutputStream,
  664. JavaFileOutputStream.constructor,
  665. javaString (tempFile.getFullPathName()).get())));
  666. if (jniCheckHasExceptionOccurredAndClear())
  667. {
  668. // Failed to open file stream for temporary file
  669. jassertfalse;
  670. return {};
  671. }
  672. LocalRef<jbyteArray> buffer (env->NewByteArray (1024));
  673. int bytesRead = 0;
  674. for (;;)
  675. {
  676. bytesRead = env->CallIntMethod (inputStream.stream, JavaFileInputStream.read, buffer.get());
  677. if (jniCheckHasExceptionOccurredAndClear())
  678. {
  679. // Failed to read from resource file.
  680. jassertfalse;
  681. return {};
  682. }
  683. if (bytesRead < 0)
  684. break;
  685. env->CallVoidMethod (outputStream.stream, JavaFileOutputStream.write, buffer.get(), 0, bytesRead);
  686. if (jniCheckHasExceptionOccurredAndClear())
  687. {
  688. // Failed to write to temporary file.
  689. jassertfalse;
  690. return {};
  691. }
  692. }
  693. return URL (tempFile);
  694. }
  695. std::map<String, File> infoForUri;
  696. StringArray mimeTypes;
  697. std::function<void (const std::map<String, File>&, const StringArray&)> onCompletion;
  698. // This task is obtained from std::async(). Its destructor will block until the asynchronous
  699. // task has completed; as a result, we can guarantee that the async task will have finished
  700. // before the lifetimes of the other data members and base class end.
  701. std::future<void> task;
  702. };
  703. auto detail::ScopedContentSharerInterface::shareFiles (const Array<URL>& urls, Component*) -> std::unique_ptr<ScopedContentSharerInterface>
  704. {
  705. class NativeScopedContentSharerInterface final : public detail::ScopedContentSharerInterface
  706. {
  707. public:
  708. explicit NativeScopedContentSharerInterface (Array<URL> f)
  709. : files (std::move (f)) {}
  710. void runAsync (ContentSharer::Callback callback) override
  711. {
  712. // This lambda will only be called if the AndroidContentSharerPrepareFilesTask is still
  713. // alive. We know that our lifetime will end after the
  714. // AndroidContentSharerPrepareFilesTask, so there's no need to check that 'this' is
  715. // still valid inside the lambda.
  716. task.emplace (files, [this, callback] (const std::map<String, File>& infoForUri, const StringArray& mimeTypes)
  717. {
  718. launcher = ContentSharerGlobalImpl::getInstance().sharePreparedFiles (infoForUri, mimeTypes, [callback] (bool success)
  719. {
  720. callback (success, {});
  721. });
  722. });
  723. }
  724. void close() override
  725. {
  726. // dismiss() doesn't close the sharesheet, and there doesn't seem to be any alternative
  727. // Maybe this will work in the future...
  728. launcher.reset();
  729. }
  730. private:
  731. Array<URL> files;
  732. std::optional<AndroidContentSharerPrepareFilesTask> task;
  733. std::unique_ptr<ActivityLauncher> launcher;
  734. };
  735. return std::make_unique<NativeScopedContentSharerInterface> (std::move (urls));
  736. }
  737. auto detail::ScopedContentSharerInterface::shareText (const String& text, Component*) -> std::unique_ptr<ScopedContentSharerInterface>
  738. {
  739. class NativeScopedContentSharerInterface final : public detail::ScopedContentSharerInterface
  740. {
  741. public:
  742. explicit NativeScopedContentSharerInterface (String t)
  743. : text (std::move (t)) {}
  744. void runAsync (ContentSharer::Callback callback) override
  745. {
  746. launcher = ContentSharerGlobalImpl::getInstance().shareText (text, [callback] (bool success)
  747. {
  748. callback (success, {});
  749. });
  750. }
  751. void close() override
  752. {
  753. // dismiss() doesn't close the sharesheet, and there doesn't seem to be any alternative
  754. // Maybe this will work in the future...
  755. launcher.reset();
  756. }
  757. private:
  758. String text;
  759. std::unique_ptr<ActivityLauncher> launcher;
  760. };
  761. return std::make_unique<NativeScopedContentSharerInterface> (std::move (text));
  762. }
  763. } // namespace juce