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.

901 lines
38KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  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 5 End-User License
  8. Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
  9. 27th April 2017).
  10. End User License Agreement: www.juce.com/juce-5-licence
  11. Privacy Policy: www.juce.com/juce-5-privacy-policy
  12. Or: You may also use this code under the terms of the GPL v3 (see
  13. www.gnu.org/licenses).
  14. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  15. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  16. DISCLAIMED.
  17. ==============================================================================
  18. */
  19. namespace juce
  20. {
  21. //==============================================================================
  22. // This byte-code is generated from native/java/app/com/roli/juce/JuceContentProviderCursor.java with min sdk version 16
  23. // See juce_core/native/java/README.txt on how to generate this byte-code.
  24. static const uint8 javaJuceContentProviderCursor[] =
  25. { 31,139,8,8,138,122,94,94,0,3,74,117,99,101,67,111,110,116,101,110,116,80,114,111,118,105,100,101,114,67,117,114,115,111,114,
  26. 46,100,101,120,0,117,147,177,111,211,64,20,198,223,157,157,148,150,52,184,144,1,6,32,160,46,29,138,43,209,178,132,34,80,144,42,
  27. 89,6,85,10,164,82,59,93,109,211,184,77,124,145,237,68,17,98,0,4,91,6,6,38,38,64,133,153,255,0,33,133,255,131,137,149,133,177,3,
  28. 223,229,206,37,18,194,210,207,247,222,229,189,239,157,206,95,194,104,180,176,118,115,131,142,7,95,175,127,216,250,229,236,28,
  29. 61,169,85,222,124,219,126,234,140,155,159,157,142,184,107,17,245,137,104,212,94,63,79,230,121,207,137,28,210,251,37,240,5,216,
  30. 96,2,24,248,109,246,79,76,190,140,215,9,184,15,222,129,143,224,19,248,14,126,130,37,232,213,192,69,112,25,92,3,183,192,38,240,
  31. 64,4,142,192,144,107,61,203,204,83,51,230,205,106,153,179,149,77,252,10,133,115,38,30,35,62,99,226,183,136,33,67,139,211,179,
  32. 233,46,165,185,64,106,159,79,115,126,154,51,170,152,124,201,212,21,191,171,103,108,130,18,78,163,194,103,92,207,239,215,137,238,
  33. 209,46,238,171,138,9,122,202,107,174,207,42,29,134,218,22,42,250,107,232,98,213,127,116,237,105,110,83,249,118,156,196,249,29,
  34. 98,30,93,241,6,65,212,148,73,30,37,249,118,42,135,113,24,165,205,65,154,201,244,198,161,24,10,186,234,139,36,76,101,28,186,161,
  35. 200,197,190,200,34,247,129,200,211,120,164,139,26,180,226,7,178,231,166,178,27,187,135,144,114,255,171,215,32,214,38,222,246,
  36. 200,106,123,62,2,159,46,236,249,106,134,219,21,201,129,219,130,104,114,208,160,82,208,149,89,68,103,3,217,29,244,146,135,162,
  37. 23,101,116,41,208,130,173,142,72,11,185,166,42,11,201,238,200,44,167,121,245,126,36,31,163,209,206,59,113,70,21,126,174,50,87,
  38. 221,92,165,26,97,217,197,69,112,92,14,127,241,220,158,112,254,146,147,197,126,240,69,115,55,108,102,45,188,199,103,252,87,120,
  39. 98,98,214,89,31,22,94,44,207,248,145,213,117,191,242,36,115,254,122,134,215,181,190,242,169,101,106,212,119,165,186,238,157,126,
  40. 115,71,199,234,127,240,7,124,122,243,207,64,3,0,0,0,0 };
  41. // This byte-code is generated from native/java/app/com/roli/juce/JuceContentProviderFileObserver.java with min sdk version 16
  42. // See juce_core/native/java/README.txt on how to generate this byte-code.
  43. static const uint8 javaJuceContentProviderFileObserver[] =
  44. { 31,139,8,8,123,122,94,94,0,3,74,117,99,101,67,111,110,116,101,110,116,80,114,111,118,105,100,101,114,70,105,108,101,79,98,115,
  45. 101,114,118,101,114,46,100,101,120,0,133,147,205,107,19,65,24,198,223,249,72,98,91,93,210,84,4,17,36,130,130,32,186,145,218,
  46. 131,196,10,197,47,18,22,44,166,6,41,120,216,38,75,119,107,186,19,118,215,144,131,5,45,61,148,158,188,244,164,71,65,241,212,163,
  47. 40,138,136,224,213,91,253,3,60,250,7,120,211,103,118,166,116,61,25,248,205,251,206,251,206,60,59,59,251,164,31,140,39,27,179,
  48. 115,116,110,243,212,238,147,208,189,112,245,227,236,183,157,19,103,216,220,215,223,91,251,175,247,249,88,16,13,137,104,220,189,
  49. 82,35,251,251,193,137,142,147,169,151,193,103,32,117,29,48,112,132,153,121,149,153,121,3,195,121,236,89,68,220,3,239,192,7,
  50. 240,9,252,4,127,64,3,253,155,224,14,240,192,61,240,0,60,4,27,96,11,236,128,93,240,2,188,2,111,185,209,231,246,249,37,123,158,138,
  51. 62,131,157,111,99,193,132,205,159,35,159,180,249,75,228,83,54,127,131,92,64,201,201,181,68,254,142,34,87,53,245,163,121,36,
  52. 154,46,68,110,239,66,216,248,197,22,202,80,212,181,61,110,206,50,108,16,45,148,150,113,119,78,222,209,207,123,111,123,157,58,
  53. 250,75,156,248,101,199,234,201,124,47,179,239,99,114,153,119,14,222,177,124,45,138,163,236,58,177,22,177,54,157,109,63,238,5,
  54. 55,84,156,5,113,182,152,168,81,212,15,146,219,209,32,184,187,146,6,201,40,72,46,173,249,35,159,78,122,126,220,79,84,212,119,85,
  55. 234,22,219,77,114,189,158,90,119,19,53,136,220,53,72,185,255,209,107,82,205,211,146,238,192,143,87,221,78,150,68,241,106,147,
  56. 88,151,68,183,229,145,236,182,205,232,181,80,192,112,186,103,164,58,161,159,252,43,116,107,132,50,149,130,60,200,80,165,25,77,
  57. 232,113,73,221,79,3,146,235,126,250,136,42,42,54,203,228,208,207,66,146,89,24,165,52,37,170,51,211,21,103,254,34,213,184,51,
  58. 83,113,150,113,69,156,17,103,207,158,202,239,156,109,226,251,177,95,184,172,99,246,187,176,66,60,240,44,47,248,86,20,188,43,11,
  59. 254,45,209,161,135,203,116,232,99,86,55,251,181,151,89,213,172,209,30,227,117,163,175,253,45,236,26,237,1,170,155,189,185,63,
  60. 170,38,215,255,159,191,29,121,169,41,120,3,0,0,0,0 };
  61. //==============================================================================
  62. #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
  63. FIELD (authority, "authority", "Ljava/lang/String;")
  64. DECLARE_JNI_CLASS (AndroidProviderInfo, "android/content/pm/ProviderInfo")
  65. #undef JNI_CLASS_MEMBERS
  66. #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
  67. METHOD (constructor, "<init>", "(Landroid/os/ParcelFileDescriptor;JJ)V") \
  68. METHOD (createInputStream, "createInputStream", "()Ljava/io/FileInputStream;") \
  69. METHOD (getLength, "getLength", "()J")
  70. DECLARE_JNI_CLASS (AssetFileDescriptor, "android/content/res/AssetFileDescriptor")
  71. #undef JNI_CLASS_MEMBERS
  72. #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
  73. METHOD (close, "close", "()V")
  74. DECLARE_JNI_CLASS (JavaCloseable, "java/io/Closeable")
  75. #undef JNI_CLASS_MEMBERS
  76. #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
  77. STATICMETHOD (open, "open", "(Ljava/io/File;I)Landroid/os/ParcelFileDescriptor;")
  78. DECLARE_JNI_CLASS (ParcelFileDescriptor, "android/os/ParcelFileDescriptor")
  79. #undef JNI_CLASS_MEMBERS
  80. //==============================================================================
  81. class AndroidContentSharerCursor
  82. {
  83. public:
  84. class Owner
  85. {
  86. public:
  87. virtual ~Owner() {}
  88. virtual void cursorClosed (const AndroidContentSharerCursor&) = 0;
  89. };
  90. AndroidContentSharerCursor (Owner& ownerToUse, JNIEnv* env,
  91. const LocalRef<jobject>& contentProvider,
  92. const LocalRef<jobjectArray>& resultColumns)
  93. : owner (ownerToUse),
  94. cursor (GlobalRef (LocalRef<jobject> (env->NewObject (JuceContentProviderCursor,
  95. JuceContentProviderCursor.constructor,
  96. reinterpret_cast<jlong> (this),
  97. resultColumns.get()))))
  98. {
  99. // the content provider must be created first
  100. jassert (contentProvider.get() != nullptr);
  101. }
  102. jobject getNativeCursor() { return cursor.get(); }
  103. void cursorClosed()
  104. {
  105. MessageManager::callAsync ([this] { owner.cursorClosed (*this); });
  106. }
  107. void addRow (LocalRef<jobjectArray>& values)
  108. {
  109. auto* env = getEnv();
  110. env->CallVoidMethod (cursor.get(), JuceContentProviderCursor.addRow, values.get());
  111. }
  112. private:
  113. Owner& owner;
  114. GlobalRef cursor;
  115. //==============================================================================
  116. #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
  117. METHOD (addRow, "addRow", "([Ljava/lang/Object;)V") \
  118. METHOD (constructor, "<init>", "(J[Ljava/lang/String;)V") \
  119. CALLBACK (contentSharerCursorClosed, "contentSharerCursorClosed", "(J)V") \
  120. DECLARE_JNI_CLASS_WITH_BYTECODE (JuceContentProviderCursor, "com/roli/juce/JuceContentProviderCursor", 16, javaJuceContentProviderCursor, sizeof (javaJuceContentProviderCursor))
  121. #undef JNI_CLASS_MEMBERS
  122. static void JNICALL contentSharerCursorClosed (JNIEnv*, jobject, jlong host)
  123. {
  124. if (auto* myself = reinterpret_cast<AndroidContentSharerCursor*> (host))
  125. myself->cursorClosed();
  126. }
  127. };
  128. AndroidContentSharerCursor::JuceContentProviderCursor_Class AndroidContentSharerCursor::JuceContentProviderCursor;
  129. //==============================================================================
  130. class AndroidContentSharerFileObserver
  131. {
  132. public:
  133. class Owner
  134. {
  135. public:
  136. virtual ~Owner() {}
  137. virtual void fileHandleClosed (const AndroidContentSharerFileObserver&) = 0;
  138. };
  139. AndroidContentSharerFileObserver (Owner& ownerToUse, JNIEnv* env,
  140. const LocalRef<jobject>& contentProvider,
  141. const String& filepathToUse)
  142. : owner (ownerToUse),
  143. filepath (filepathToUse),
  144. fileObserver (GlobalRef (LocalRef<jobject> (env->NewObject (JuceContentProviderFileObserver,
  145. JuceContentProviderFileObserver.constructor,
  146. reinterpret_cast<jlong> (this),
  147. javaString (filepath).get(),
  148. open | access | closeWrite | closeNoWrite))))
  149. {
  150. // the content provider must be created first
  151. jassert (contentProvider.get() != nullptr);
  152. env->CallVoidMethod (fileObserver, JuceContentProviderFileObserver.startWatching);
  153. }
  154. void onFileEvent (int event, const LocalRef<jstring>& path)
  155. {
  156. ignoreUnused (path);
  157. if (event == open)
  158. {
  159. ++numOpenedHandles;
  160. }
  161. else if (event == access)
  162. {
  163. fileWasRead = true;
  164. }
  165. else if (event == closeNoWrite || event == closeWrite)
  166. {
  167. --numOpenedHandles;
  168. // numOpenedHandles may get negative if we don't receive open handle event.
  169. if (fileWasRead && numOpenedHandles <= 0)
  170. {
  171. MessageManager::callAsync ([this]
  172. {
  173. getEnv()->CallVoidMethod (fileObserver, JuceContentProviderFileObserver.stopWatching);
  174. owner.fileHandleClosed (*this);
  175. });
  176. }
  177. }
  178. }
  179. private:
  180. static constexpr int open = 32;
  181. static constexpr int access = 1;
  182. static constexpr int closeWrite = 8;
  183. static constexpr int closeNoWrite = 16;
  184. bool fileWasRead = false;
  185. int numOpenedHandles = 0;
  186. Owner& owner;
  187. String filepath;
  188. GlobalRef fileObserver;
  189. //==============================================================================
  190. #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
  191. METHOD (constructor, "<init>", "(JLjava/lang/String;I)V") \
  192. METHOD (startWatching, "startWatching", "()V") \
  193. METHOD (stopWatching, "stopWatching", "()V") \
  194. CALLBACK (contentSharerFileObserverEvent, "contentSharerFileObserverEvent", "(JILjava/lang/String;)V") \
  195. DECLARE_JNI_CLASS_WITH_BYTECODE (JuceContentProviderFileObserver, "com/roli/juce/JuceContentProviderFileObserver", 16, javaJuceContentProviderFileObserver, sizeof (javaJuceContentProviderFileObserver))
  196. #undef JNI_CLASS_MEMBERS
  197. static void JNICALL contentSharerFileObserverEvent (JNIEnv*, jobject /*fileObserver*/, jlong host, int event, jstring path)
  198. {
  199. if (auto* myself = reinterpret_cast<AndroidContentSharerFileObserver*> (host))
  200. myself->onFileEvent (event, LocalRef<jstring> (path));
  201. }
  202. };
  203. AndroidContentSharerFileObserver::JuceContentProviderFileObserver_Class AndroidContentSharerFileObserver::JuceContentProviderFileObserver;
  204. //==============================================================================
  205. class AndroidContentSharerPrepareFilesThread : private Thread
  206. {
  207. public:
  208. AndroidContentSharerPrepareFilesThread (AsyncUpdater& ownerToUse,
  209. const Array<URL>& fileUrlsToUse,
  210. const String& packageNameToUse,
  211. const String& uriBaseToUse)
  212. : Thread ("AndroidContentSharerPrepareFilesThread"),
  213. owner (ownerToUse),
  214. fileUrls (fileUrlsToUse),
  215. resultFileUris (GlobalRef (LocalRef<jobject> (getEnv()->NewObject (JavaArrayList,
  216. JavaArrayList.constructor,
  217. fileUrls.size())))),
  218. packageName (packageNameToUse),
  219. uriBase (uriBaseToUse)
  220. {
  221. startThread();
  222. }
  223. ~AndroidContentSharerPrepareFilesThread() override
  224. {
  225. signalThreadShouldExit();
  226. waitForThreadToExit (10000);
  227. for (auto& f : temporaryFilesFromAssetFiles)
  228. f.deleteFile();
  229. }
  230. jobject getResultFileUris() { return resultFileUris.get(); }
  231. const StringArray& getMimeTypes() const { return mimeTypes; }
  232. const StringArray& getFilePaths() const { return filePaths; }
  233. private:
  234. struct StreamCloser
  235. {
  236. StreamCloser (const LocalRef<jobject>& streamToUse)
  237. : stream (GlobalRef (streamToUse))
  238. {
  239. }
  240. ~StreamCloser()
  241. {
  242. if (stream.get() != nullptr)
  243. getEnv()->CallVoidMethod (stream, JavaCloseable.close);
  244. }
  245. GlobalRef stream;
  246. };
  247. void run() override
  248. {
  249. auto* env = getEnv();
  250. bool canSpecifyMimeTypes = true;
  251. for (auto f : fileUrls)
  252. {
  253. auto scheme = f.getScheme();
  254. // Only "file://" scheme or no scheme (for files in app bundle) are allowed!
  255. jassert (scheme.isEmpty() || scheme == "file");
  256. if (scheme.isEmpty())
  257. {
  258. // Raw resource names need to be all lower case
  259. jassert (f.toString (true).toLowerCase() == f.toString (true));
  260. // This will get us a file with file:// URI
  261. f = copyAssetFileToTemporaryFile (env, f.toString (true));
  262. if (f.isEmpty())
  263. continue;
  264. }
  265. if (threadShouldExit())
  266. return;
  267. auto filepath = URL::removeEscapeChars (f.toString (true).fromFirstOccurrenceOf ("file://", false, false));
  268. filePaths.add (filepath);
  269. auto filename = filepath.fromLastOccurrenceOf ("/", false, true);
  270. auto fileExtension = filename.fromLastOccurrenceOf (".", false, true);
  271. auto contentString = uriBase + String (filePaths.size() - 1) + "/" + filename;
  272. auto uri = LocalRef<jobject> (env->CallStaticObjectMethod (AndroidUri, AndroidUri.parse,
  273. javaString (contentString).get()));
  274. if (canSpecifyMimeTypes)
  275. canSpecifyMimeTypes = fileExtension.isNotEmpty();
  276. if (canSpecifyMimeTypes)
  277. mimeTypes.addArray (getMimeTypesForFileExtension (fileExtension));
  278. else
  279. mimeTypes.clear();
  280. env->CallBooleanMethod (resultFileUris, JavaArrayList.add, uri.get());
  281. }
  282. owner.triggerAsyncUpdate();
  283. }
  284. URL copyAssetFileToTemporaryFile (JNIEnv* env, const String& filename)
  285. {
  286. auto resources = LocalRef<jobject> (env->CallObjectMethod (getAppContext().get(), AndroidContext.getResources));
  287. int fileId = env->CallIntMethod (resources, AndroidResources.getIdentifier, javaString (filename).get(),
  288. javaString ("raw").get(), javaString (packageName).get());
  289. // Raw resource not found. Please make sure that you include your file as a raw resource
  290. // and that you specify just the file name, without an extension.
  291. jassert (fileId != 0);
  292. if (fileId == 0)
  293. return {};
  294. auto assetFd = LocalRef<jobject> (env->CallObjectMethod (resources,
  295. AndroidResources.openRawResourceFd,
  296. fileId));
  297. auto inputStream = StreamCloser (LocalRef<jobject> (env->CallObjectMethod (assetFd,
  298. AssetFileDescriptor.createInputStream)));
  299. if (jniCheckHasExceptionOccurredAndClear())
  300. {
  301. // Failed to open file stream for resource
  302. jassertfalse;
  303. return {};
  304. }
  305. auto tempFile = File::createTempFile ({});
  306. tempFile.createDirectory();
  307. tempFile = tempFile.getChildFile (filename);
  308. auto outputStream = StreamCloser (LocalRef<jobject> (env->NewObject (JavaFileOutputStream,
  309. JavaFileOutputStream.constructor,
  310. javaString (tempFile.getFullPathName()).get())));
  311. if (jniCheckHasExceptionOccurredAndClear())
  312. {
  313. // Failed to open file stream for temporary file
  314. jassertfalse;
  315. return {};
  316. }
  317. auto buffer = LocalRef<jbyteArray> (env->NewByteArray (1024));
  318. int bytesRead = 0;
  319. for (;;)
  320. {
  321. if (threadShouldExit())
  322. return {};
  323. bytesRead = env->CallIntMethod (inputStream.stream, JavaFileInputStream.read, buffer.get());
  324. if (jniCheckHasExceptionOccurredAndClear())
  325. {
  326. // Failed to read from resource file.
  327. jassertfalse;
  328. return {};
  329. }
  330. if (bytesRead < 0)
  331. break;
  332. env->CallVoidMethod (outputStream.stream, JavaFileOutputStream.write, buffer.get(), 0, bytesRead);
  333. if (jniCheckHasExceptionOccurredAndClear())
  334. {
  335. // Failed to write to temporary file.
  336. jassertfalse;
  337. return {};
  338. }
  339. }
  340. temporaryFilesFromAssetFiles.add (tempFile);
  341. return URL (tempFile);
  342. }
  343. AsyncUpdater& owner;
  344. Array<URL> fileUrls;
  345. GlobalRef resultFileUris;
  346. String packageName;
  347. String uriBase;
  348. StringArray filePaths;
  349. Array<File> temporaryFilesFromAssetFiles;
  350. StringArray mimeTypes;
  351. };
  352. //==============================================================================
  353. class ContentSharer::ContentSharerNativeImpl : public ContentSharer::Pimpl,
  354. public AndroidContentSharerFileObserver::Owner,
  355. public AndroidContentSharerCursor::Owner,
  356. public AsyncUpdater,
  357. private Timer
  358. {
  359. public:
  360. ContentSharerNativeImpl (ContentSharer& cs)
  361. : owner (cs),
  362. packageName (juceString (LocalRef<jstring> ((jstring) getEnv()->CallObjectMethod (getAppContext().get(), AndroidContext.getPackageName)))),
  363. uriBase ("content://" + packageName + ".sharingcontentprovider/")
  364. {
  365. }
  366. ~ContentSharerNativeImpl() override
  367. {
  368. masterReference.clear();
  369. }
  370. void shareFiles (const Array<URL>& files) override
  371. {
  372. if (! isContentSharingEnabled())
  373. {
  374. // You need to enable "Content Sharing" in Projucer's Android exporter.
  375. jassertfalse;
  376. owner.sharingFinished (false, {});
  377. }
  378. prepareFilesThread.reset (new AndroidContentSharerPrepareFilesThread (*this, files, packageName, uriBase));
  379. }
  380. void shareText (const String& text) override
  381. {
  382. if (! isContentSharingEnabled())
  383. {
  384. // You need to enable "Content Sharing" in Projucer's Android exporter.
  385. jassertfalse;
  386. owner.sharingFinished (false, {});
  387. }
  388. auto* env = getEnv();
  389. auto intent = LocalRef<jobject> (env->NewObject (AndroidIntent, AndroidIntent.constructor));
  390. env->CallObjectMethod (intent, AndroidIntent.setAction,
  391. javaString ("android.intent.action.SEND").get());
  392. env->CallObjectMethod (intent, AndroidIntent.putExtra,
  393. javaString ("android.intent.extra.TEXT").get(),
  394. javaString (text).get());
  395. env->CallObjectMethod (intent, AndroidIntent.setType, javaString ("text/plain").get());
  396. auto chooserIntent = LocalRef<jobject> (env->CallStaticObjectMethod (AndroidIntent, AndroidIntent.createChooser,
  397. intent.get(), javaString ("Choose share target").get()));
  398. WeakReference<ContentSharerNativeImpl> weakRef (this);
  399. startAndroidActivityForResult (chooserIntent, 1003,
  400. [weakRef] (int /*requestCode*/, int resultCode, LocalRef<jobject> /*intentData*/) mutable
  401. {
  402. if (weakRef != nullptr)
  403. weakRef->sharingFinished (resultCode);
  404. });
  405. }
  406. //==============================================================================
  407. void cursorClosed (const AndroidContentSharerCursor& cursor) override
  408. {
  409. cursors.removeObject (&cursor);
  410. }
  411. void fileHandleClosed (const AndroidContentSharerFileObserver&) override
  412. {
  413. decrementPendingFileCountAndNotifyOwnerIfReady();
  414. }
  415. //==============================================================================
  416. jobject openFile (const LocalRef<jobject>& contentProvider,
  417. const LocalRef<jobject>& uri, const LocalRef<jstring>& mode)
  418. {
  419. ignoreUnused (mode);
  420. WeakReference<ContentSharerNativeImpl> weakRef (this);
  421. if (weakRef == nullptr)
  422. return nullptr;
  423. auto* env = getEnv();
  424. auto uriElements = getContentUriElements (env, uri);
  425. if (uriElements.filepath.isEmpty())
  426. return nullptr;
  427. return getAssetFileDescriptor (env, contentProvider, uriElements.filepath);
  428. }
  429. jobject query (const LocalRef<jobject>& contentProvider, const LocalRef<jobject>& uri,
  430. const LocalRef<jobjectArray>& projection)
  431. {
  432. StringArray requestedColumns = javaStringArrayToJuce (projection);
  433. StringArray supportedColumns = getSupportedColumns();
  434. StringArray resultColumns;
  435. for (const auto& col : supportedColumns)
  436. {
  437. if (requestedColumns.contains (col))
  438. resultColumns.add (col);
  439. }
  440. // Unsupported columns were queried, file sharing may fail.
  441. if (resultColumns.isEmpty())
  442. return nullptr;
  443. auto resultJavaColumns = juceStringArrayToJava (resultColumns);
  444. auto* env = getEnv();
  445. auto cursor = cursors.add (new AndroidContentSharerCursor (*this, env, contentProvider,
  446. resultJavaColumns));
  447. auto uriElements = getContentUriElements (env, uri);
  448. if (uriElements.filepath.isEmpty())
  449. return cursor->getNativeCursor();
  450. auto values = LocalRef<jobjectArray> (env->NewObjectArray ((jsize) resultColumns.size(),
  451. JavaObject, nullptr));
  452. for (int i = 0; i < resultColumns.size(); ++i)
  453. {
  454. if (resultColumns.getReference (i) == "_display_name")
  455. {
  456. env->SetObjectArrayElement (values, i, javaString (uriElements.filename).get());
  457. }
  458. else if (resultColumns.getReference (i) == "_size")
  459. {
  460. auto javaFile = LocalRef<jobject> (env->NewObject (JavaFile, JavaFile.constructor,
  461. javaString (uriElements.filepath).get()));
  462. jlong fileLength = env->CallLongMethod (javaFile, JavaFile.length);
  463. env->SetObjectArrayElement (values, i, env->NewObject (JavaLong,
  464. JavaLong.constructor,
  465. fileLength));
  466. }
  467. }
  468. cursor->addRow (values);
  469. return cursor->getNativeCursor();
  470. }
  471. jobjectArray getStreamTypes (const LocalRef<jobject>& uri, const LocalRef<jstring>& mimeTypeFilter)
  472. {
  473. auto* env = getEnv();
  474. auto extension = getContentUriElements (env, uri).filename.fromLastOccurrenceOf (".", false, true);
  475. if (extension.isEmpty())
  476. return nullptr;
  477. return juceStringArrayToJava (filterMimeTypes (getMimeTypesForFileExtension (extension),
  478. juceString (mimeTypeFilter.get())));
  479. }
  480. void sharingFinished (int resultCode)
  481. {
  482. sharingActivityDidFinish = true;
  483. succeeded = resultCode == -1;
  484. // Give content sharer a chance to request file access.
  485. if (nonAssetFilesPendingShare.get() == 0)
  486. startTimer (2000);
  487. else
  488. notifyOwnerIfReady();
  489. }
  490. private:
  491. bool isContentSharingEnabled() const
  492. {
  493. auto* env = getEnv();
  494. LocalRef<jobject> packageManager (env->CallObjectMethod (getAppContext().get(), AndroidContext.getPackageManager));
  495. constexpr int getProviders = 8;
  496. auto packageInfo = LocalRef<jobject> (env->CallObjectMethod (packageManager,
  497. AndroidPackageManager.getPackageInfo,
  498. javaString (packageName).get(),
  499. getProviders));
  500. auto providers = LocalRef<jobjectArray> ((jobjectArray) env->GetObjectField (packageInfo,
  501. AndroidPackageInfo.providers));
  502. if (providers == nullptr)
  503. return false;
  504. auto sharingContentProviderAuthority = packageName + ".sharingcontentprovider";
  505. const int numProviders = env->GetArrayLength (providers.get());
  506. for (int i = 0; i < numProviders; ++i)
  507. {
  508. auto providerInfo = LocalRef<jobject> (env->GetObjectArrayElement (providers, i));
  509. auto authority = LocalRef<jstring> ((jstring) env->GetObjectField (providerInfo,
  510. AndroidProviderInfo.authority));
  511. if (juceString (authority) == sharingContentProviderAuthority)
  512. return true;
  513. }
  514. return false;
  515. }
  516. void handleAsyncUpdate() override
  517. {
  518. jassert (prepareFilesThread != nullptr);
  519. if (prepareFilesThread == nullptr)
  520. return;
  521. filesPrepared (prepareFilesThread->getResultFileUris(), prepareFilesThread->getMimeTypes());
  522. }
  523. void filesPrepared (jobject fileUris, const StringArray& mimeTypes)
  524. {
  525. auto* env = getEnv();
  526. auto intent = LocalRef<jobject> (env->NewObject (AndroidIntent, AndroidIntent.constructor));
  527. env->CallObjectMethod (intent, AndroidIntent.setAction,
  528. javaString ("android.intent.action.SEND_MULTIPLE").get());
  529. env->CallObjectMethod (intent, AndroidIntent.setType,
  530. javaString (getCommonMimeType (mimeTypes)).get());
  531. constexpr int grantReadPermission = 1;
  532. env->CallObjectMethod (intent, AndroidIntent.setFlags, grantReadPermission);
  533. env->CallObjectMethod (intent, AndroidIntent.putParcelableArrayListExtra,
  534. javaString ("android.intent.extra.STREAM").get(),
  535. fileUris);
  536. auto chooserIntent = LocalRef<jobject> (env->CallStaticObjectMethod (AndroidIntent,
  537. AndroidIntent.createChooser,
  538. intent.get(),
  539. javaString ("Choose share target").get()));
  540. WeakReference<ContentSharerNativeImpl> weakRef (this);
  541. startAndroidActivityForResult (chooserIntent, 1003,
  542. [weakRef] (int /*requestCode*/, int resultCode, LocalRef<jobject> /*intentData*/) mutable
  543. {
  544. if (weakRef != nullptr)
  545. weakRef->sharingFinished (resultCode);
  546. });
  547. }
  548. void decrementPendingFileCountAndNotifyOwnerIfReady()
  549. {
  550. --nonAssetFilesPendingShare;
  551. notifyOwnerIfReady();
  552. }
  553. void notifyOwnerIfReady()
  554. {
  555. if (sharingActivityDidFinish && nonAssetFilesPendingShare.get() == 0)
  556. owner.sharingFinished (succeeded, {});
  557. }
  558. void timerCallback() override
  559. {
  560. stopTimer();
  561. notifyOwnerIfReady();
  562. }
  563. //==============================================================================
  564. struct ContentUriElements
  565. {
  566. String index;
  567. String filename;
  568. String filepath;
  569. };
  570. ContentUriElements getContentUriElements (JNIEnv* env, const LocalRef<jobject>& uri) const
  571. {
  572. jassert (prepareFilesThread != nullptr);
  573. if (prepareFilesThread == nullptr)
  574. return {};
  575. auto fullUri = juceString ((jstring) env->CallObjectMethod (uri.get(), AndroidUri.toString));
  576. auto index = fullUri.fromFirstOccurrenceOf (uriBase, false, false)
  577. .upToFirstOccurrenceOf ("/", false, true);
  578. auto filename = fullUri.fromLastOccurrenceOf ("/", false, true);
  579. return { index, filename, prepareFilesThread->getFilePaths()[index.getIntValue()] };
  580. }
  581. static StringArray getSupportedColumns()
  582. {
  583. return StringArray ("_display_name", "_size");
  584. }
  585. jobject getAssetFileDescriptor (JNIEnv* env, const LocalRef<jobject>& contentProvider,
  586. const String& filepath)
  587. {
  588. // This function can be called from multiple threads.
  589. {
  590. const ScopedLock sl (nonAssetFileOpenLock);
  591. if (! nonAssetFilePathsPendingShare.contains (filepath))
  592. {
  593. nonAssetFilePathsPendingShare.add (filepath);
  594. ++nonAssetFilesPendingShare;
  595. nonAssetFileObservers.add (new AndroidContentSharerFileObserver (*this, env,
  596. contentProvider,
  597. filepath));
  598. }
  599. }
  600. auto javaFile = LocalRef<jobject> (env->NewObject (JavaFile, JavaFile.constructor,
  601. javaString (filepath).get()));
  602. constexpr int modeReadOnly = 268435456;
  603. auto parcelFileDescriptor = LocalRef<jobject> (env->CallStaticObjectMethod (ParcelFileDescriptor,
  604. ParcelFileDescriptor.open,
  605. javaFile.get(), modeReadOnly));
  606. if (jniCheckHasExceptionOccurredAndClear())
  607. {
  608. // Failed to create file descriptor. Have you provided a valid file path/resource name?
  609. jassertfalse;
  610. return nullptr;
  611. }
  612. jlong startOffset = 0;
  613. jlong unknownLength = -1;
  614. assetFileDescriptors.add (GlobalRef (LocalRef<jobject> (env->NewObject (AssetFileDescriptor,
  615. AssetFileDescriptor.constructor,
  616. parcelFileDescriptor.get(),
  617. startOffset, unknownLength))));
  618. return assetFileDescriptors.getReference (assetFileDescriptors.size() - 1).get();
  619. }
  620. StringArray filterMimeTypes (const StringArray& mimeTypes, const String& filter)
  621. {
  622. String filterToUse (filter.removeCharacters ("*"));
  623. if (filterToUse.isEmpty() || filterToUse == "/")
  624. return mimeTypes;
  625. StringArray result;
  626. for (const auto& type : mimeTypes)
  627. if (String (type).contains (filterToUse))
  628. result.add (type);
  629. return result;
  630. }
  631. String getCommonMimeType (const StringArray& mimeTypes)
  632. {
  633. if (mimeTypes.isEmpty())
  634. return "*/*";
  635. auto commonMime = mimeTypes[0];
  636. bool lookForCommonGroup = false;
  637. for (int i = 1; i < mimeTypes.size(); ++i)
  638. {
  639. if (mimeTypes[i] == commonMime)
  640. continue;
  641. if (! lookForCommonGroup)
  642. {
  643. lookForCommonGroup = true;
  644. commonMime = commonMime.upToFirstOccurrenceOf ("/", true, false);
  645. }
  646. if (! mimeTypes[i].startsWith (commonMime))
  647. return "*/*";
  648. }
  649. return lookForCommonGroup ? commonMime + "*" : commonMime;
  650. }
  651. ContentSharer& owner;
  652. String packageName;
  653. String uriBase;
  654. std::unique_ptr<AndroidContentSharerPrepareFilesThread> prepareFilesThread;
  655. bool succeeded = false;
  656. String errorDescription;
  657. bool sharingActivityDidFinish = false;
  658. OwnedArray<AndroidContentSharerCursor> cursors;
  659. Array<GlobalRef> assetFileDescriptors;
  660. CriticalSection nonAssetFileOpenLock;
  661. StringArray nonAssetFilePathsPendingShare;
  662. Atomic<int> nonAssetFilesPendingShare { 0 };
  663. OwnedArray<AndroidContentSharerFileObserver> nonAssetFileObservers;
  664. WeakReference<ContentSharerNativeImpl>::Master masterReference;
  665. friend class WeakReference<ContentSharerNativeImpl>;
  666. //==============================================================================
  667. #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
  668. CALLBACK (contentSharerQuery, "contentSharerQuery", "(Landroid/net/Uri;[Ljava/lang/String;)Landroid/database/Cursor;") \
  669. CALLBACK (contentSharerOpenFile, "contentSharerOpenFile", "(Landroid/net/Uri;Ljava/lang/String;)Landroid/content/res/AssetFileDescriptor;") \
  670. CALLBACK (contentSharerGetStreamTypes, "contentSharerGetStreamTypes", "(Landroid/net/Uri;Ljava/lang/String;)[Ljava/lang/String;") \
  671. DECLARE_JNI_CLASS_WITH_MIN_SDK (JuceSharingContentProvider, "com/roli/juce/JuceSharingContentProvider", 16)
  672. #undef JNI_CLASS_MEMBERS
  673. static jobject JNICALL contentSharerQuery (JNIEnv*, jobject contentProvider, jobject uri, jobjectArray projection)
  674. {
  675. if (auto *pimpl = (ContentSharer::ContentSharerNativeImpl *) ContentSharer::getInstance ()->pimpl.get ())
  676. return pimpl->query (LocalRef<jobject> (static_cast<jobject> (contentProvider)),
  677. LocalRef<jobject> (static_cast<jobject> (uri)),
  678. LocalRef<jobjectArray> (static_cast<jobjectArray> (projection)));
  679. return nullptr;
  680. }
  681. static jobject JNICALL contentSharerOpenFile (JNIEnv*, jobject contentProvider, jobject uri, jstring mode)
  682. {
  683. if (auto* pimpl = (ContentSharer::ContentSharerNativeImpl*) ContentSharer::getInstance()->pimpl.get())
  684. return pimpl->openFile (LocalRef<jobject> (static_cast<jobject> (contentProvider)),
  685. LocalRef<jobject> (static_cast<jobject> (uri)),
  686. LocalRef<jstring> (static_cast<jstring> (mode)));
  687. return nullptr;
  688. }
  689. static jobjectArray JNICALL contentSharerGetStreamTypes (JNIEnv*, jobject /*contentProvider*/, jobject uri, jstring mimeTypeFilter)
  690. {
  691. if (auto* pimpl = (ContentSharer::ContentSharerNativeImpl*) ContentSharer::getInstance()->pimpl.get())
  692. return pimpl->getStreamTypes (LocalRef<jobject> (static_cast<jobject> (uri)),
  693. LocalRef<jstring> (static_cast<jstring> (mimeTypeFilter)));
  694. return nullptr;
  695. }
  696. };
  697. //==============================================================================
  698. ContentSharer::Pimpl* ContentSharer::createPimpl()
  699. {
  700. return new ContentSharerNativeImpl (*this);
  701. }
  702. ContentSharer::ContentSharerNativeImpl::JuceSharingContentProvider_Class ContentSharer::ContentSharerNativeImpl::JuceSharingContentProvider;
  703. } // namespace juce