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) 2020 - 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 6 End-User License
  8. Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
  9. End User License Agreement: www.juce.com/juce-6-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. class Owner
  84. {
  85. public:
  86. virtual ~Owner() {}
  87. virtual void cursorClosed (const AndroidContentSharerCursor&) = 0;
  88. };
  89. AndroidContentSharerCursor (Owner& ownerToUse, JNIEnv* env,
  90. const LocalRef<jobject>& contentProvider,
  91. const LocalRef<jobjectArray>& resultColumns)
  92. : owner (ownerToUse),
  93. cursor (GlobalRef (LocalRef<jobject> (env->NewObject (JuceContentProviderCursor,
  94. JuceContentProviderCursor.constructor,
  95. reinterpret_cast<jlong> (this),
  96. resultColumns.get()))))
  97. {
  98. // the content provider must be created first
  99. jassert (contentProvider.get() != nullptr);
  100. }
  101. jobject getNativeCursor() { return cursor.get(); }
  102. void cursorClosed()
  103. {
  104. MessageManager::callAsync ([this] { owner.cursorClosed (*this); });
  105. }
  106. void addRow (LocalRef<jobjectArray>& values)
  107. {
  108. auto* env = getEnv();
  109. env->CallVoidMethod (cursor.get(), JuceContentProviderCursor.addRow, values.get());
  110. }
  111. private:
  112. Owner& owner;
  113. GlobalRef cursor;
  114. //==============================================================================
  115. #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
  116. METHOD (addRow, "addRow", "([Ljava/lang/Object;)V") \
  117. METHOD (constructor, "<init>", "(J[Ljava/lang/String;)V") \
  118. CALLBACK (contentSharerCursorClosed, "contentSharerCursorClosed", "(J)V") \
  119. DECLARE_JNI_CLASS_WITH_BYTECODE (JuceContentProviderCursor, "com/rmsl/juce/JuceContentProviderCursor", 16, javaJuceContentProviderCursor, sizeof (javaJuceContentProviderCursor))
  120. #undef JNI_CLASS_MEMBERS
  121. static void JNICALL contentSharerCursorClosed (JNIEnv*, jobject, jlong host)
  122. {
  123. if (auto* myself = reinterpret_cast<AndroidContentSharerCursor*> (host))
  124. myself->cursorClosed();
  125. }
  126. };
  127. AndroidContentSharerCursor::JuceContentProviderCursor_Class AndroidContentSharerCursor::JuceContentProviderCursor;
  128. //==============================================================================
  129. class AndroidContentSharerFileObserver
  130. {
  131. public:
  132. class Owner
  133. {
  134. public:
  135. virtual ~Owner() {}
  136. virtual void fileHandleClosed (const AndroidContentSharerFileObserver&) = 0;
  137. };
  138. AndroidContentSharerFileObserver (Owner& ownerToUse, JNIEnv* env,
  139. const LocalRef<jobject>& contentProvider,
  140. const String& filepathToUse)
  141. : owner (ownerToUse),
  142. filepath (filepathToUse),
  143. fileObserver (GlobalRef (LocalRef<jobject> (env->NewObject (JuceContentProviderFileObserver,
  144. JuceContentProviderFileObserver.constructor,
  145. reinterpret_cast<jlong> (this),
  146. javaString (filepath).get(),
  147. open | access | closeWrite | closeNoWrite))))
  148. {
  149. // the content provider must be created first
  150. jassert (contentProvider.get() != nullptr);
  151. env->CallVoidMethod (fileObserver, JuceContentProviderFileObserver.startWatching);
  152. }
  153. void onFileEvent (int event, const LocalRef<jstring>& path)
  154. {
  155. ignoreUnused (path);
  156. if (event == open)
  157. {
  158. ++numOpenedHandles;
  159. }
  160. else if (event == access)
  161. {
  162. fileWasRead = true;
  163. }
  164. else if (event == closeNoWrite || event == closeWrite)
  165. {
  166. --numOpenedHandles;
  167. // numOpenedHandles may get negative if we don't receive open handle event.
  168. if (fileWasRead && numOpenedHandles <= 0)
  169. {
  170. MessageManager::callAsync ([this]
  171. {
  172. getEnv()->CallVoidMethod (fileObserver, JuceContentProviderFileObserver.stopWatching);
  173. owner.fileHandleClosed (*this);
  174. });
  175. }
  176. }
  177. }
  178. private:
  179. static constexpr int open = 32;
  180. static constexpr int access = 1;
  181. static constexpr int closeWrite = 8;
  182. static constexpr int closeNoWrite = 16;
  183. bool fileWasRead = false;
  184. int numOpenedHandles = 0;
  185. Owner& owner;
  186. String filepath;
  187. GlobalRef fileObserver;
  188. //==============================================================================
  189. #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
  190. METHOD (constructor, "<init>", "(JLjava/lang/String;I)V") \
  191. METHOD (startWatching, "startWatching", "()V") \
  192. METHOD (stopWatching, "stopWatching", "()V") \
  193. CALLBACK (contentSharerFileObserverEvent, "contentSharerFileObserverEvent", "(JILjava/lang/String;)V") \
  194. DECLARE_JNI_CLASS_WITH_BYTECODE (JuceContentProviderFileObserver, "com/rmsl/juce/JuceContentProviderFileObserver", 16, javaJuceContentProviderFileObserver, sizeof (javaJuceContentProviderFileObserver))
  195. #undef JNI_CLASS_MEMBERS
  196. static void JNICALL contentSharerFileObserverEvent (JNIEnv*, jobject /*fileObserver*/, jlong host, int event, jstring path)
  197. {
  198. if (auto* myself = reinterpret_cast<AndroidContentSharerFileObserver*> (host))
  199. myself->onFileEvent (event, LocalRef<jstring> (path));
  200. }
  201. };
  202. AndroidContentSharerFileObserver::JuceContentProviderFileObserver_Class AndroidContentSharerFileObserver::JuceContentProviderFileObserver;
  203. //==============================================================================
  204. class AndroidContentSharerPrepareFilesThread : private Thread
  205. {
  206. public:
  207. AndroidContentSharerPrepareFilesThread (AsyncUpdater& ownerToUse,
  208. const Array<URL>& fileUrlsToUse,
  209. const String& packageNameToUse,
  210. const String& uriBaseToUse)
  211. : Thread ("AndroidContentSharerPrepareFilesThread"),
  212. owner (ownerToUse),
  213. fileUrls (fileUrlsToUse),
  214. resultFileUris (GlobalRef (LocalRef<jobject> (getEnv()->NewObject (JavaArrayList,
  215. JavaArrayList.constructor,
  216. fileUrls.size())))),
  217. packageName (packageNameToUse),
  218. uriBase (uriBaseToUse)
  219. {
  220. startThread();
  221. }
  222. ~AndroidContentSharerPrepareFilesThread() override
  223. {
  224. signalThreadShouldExit();
  225. waitForThreadToExit (10000);
  226. for (auto& f : temporaryFilesFromAssetFiles)
  227. f.deleteFile();
  228. }
  229. jobject getResultFileUris() { return resultFileUris.get(); }
  230. const StringArray& getMimeTypes() const { return mimeTypes; }
  231. const StringArray& getFilePaths() const { return filePaths; }
  232. private:
  233. struct StreamCloser
  234. {
  235. StreamCloser (const LocalRef<jobject>& streamToUse)
  236. : stream (GlobalRef (streamToUse))
  237. {
  238. }
  239. ~StreamCloser()
  240. {
  241. if (stream.get() != nullptr)
  242. getEnv()->CallVoidMethod (stream, JavaCloseable.close);
  243. }
  244. GlobalRef stream;
  245. };
  246. void run() override
  247. {
  248. auto* env = getEnv();
  249. bool canSpecifyMimeTypes = true;
  250. for (auto f : fileUrls)
  251. {
  252. auto scheme = f.getScheme();
  253. // Only "file://" scheme or no scheme (for files in app bundle) are allowed!
  254. jassert (scheme.isEmpty() || scheme == "file");
  255. if (scheme.isEmpty())
  256. {
  257. // Raw resource names need to be all lower case
  258. jassert (f.toString (true).toLowerCase() == f.toString (true));
  259. // This will get us a file with file:// URI
  260. f = copyAssetFileToTemporaryFile (env, f.toString (true));
  261. if (f.isEmpty())
  262. continue;
  263. }
  264. if (threadShouldExit())
  265. return;
  266. auto filepath = URL::removeEscapeChars (f.toString (true).fromFirstOccurrenceOf ("file://", false, false));
  267. filePaths.add (filepath);
  268. auto filename = filepath.fromLastOccurrenceOf ("/", false, true);
  269. auto fileExtension = filename.fromLastOccurrenceOf (".", false, true);
  270. auto contentString = uriBase + String (filePaths.size() - 1) + "/" + filename;
  271. auto uri = LocalRef<jobject> (env->CallStaticObjectMethod (AndroidUri, AndroidUri.parse,
  272. javaString (contentString).get()));
  273. if (canSpecifyMimeTypes)
  274. canSpecifyMimeTypes = fileExtension.isNotEmpty();
  275. if (canSpecifyMimeTypes)
  276. mimeTypes.addArray (getMimeTypesForFileExtension (fileExtension));
  277. else
  278. mimeTypes.clear();
  279. env->CallBooleanMethod (resultFileUris, JavaArrayList.add, uri.get());
  280. }
  281. owner.triggerAsyncUpdate();
  282. }
  283. URL copyAssetFileToTemporaryFile (JNIEnv* env, const String& filename)
  284. {
  285. auto resources = LocalRef<jobject> (env->CallObjectMethod (getAppContext().get(), AndroidContext.getResources));
  286. int fileId = env->CallIntMethod (resources, AndroidResources.getIdentifier, javaString (filename).get(),
  287. javaString ("raw").get(), javaString (packageName).get());
  288. // Raw resource not found. Please make sure that you include your file as a raw resource
  289. // and that you specify just the file name, without an extension.
  290. jassert (fileId != 0);
  291. if (fileId == 0)
  292. return {};
  293. auto assetFd = LocalRef<jobject> (env->CallObjectMethod (resources,
  294. AndroidResources.openRawResourceFd,
  295. fileId));
  296. auto inputStream = StreamCloser (LocalRef<jobject> (env->CallObjectMethod (assetFd,
  297. AssetFileDescriptor.createInputStream)));
  298. if (jniCheckHasExceptionOccurredAndClear())
  299. {
  300. // Failed to open file stream for resource
  301. jassertfalse;
  302. return {};
  303. }
  304. auto tempFile = File::createTempFile ({});
  305. tempFile.createDirectory();
  306. tempFile = tempFile.getChildFile (filename);
  307. auto outputStream = StreamCloser (LocalRef<jobject> (env->NewObject (JavaFileOutputStream,
  308. JavaFileOutputStream.constructor,
  309. javaString (tempFile.getFullPathName()).get())));
  310. if (jniCheckHasExceptionOccurredAndClear())
  311. {
  312. // Failed to open file stream for temporary file
  313. jassertfalse;
  314. return {};
  315. }
  316. auto buffer = LocalRef<jbyteArray> (env->NewByteArray (1024));
  317. int bytesRead = 0;
  318. for (;;)
  319. {
  320. if (threadShouldExit())
  321. return {};
  322. bytesRead = env->CallIntMethod (inputStream.stream, JavaFileInputStream.read, buffer.get());
  323. if (jniCheckHasExceptionOccurredAndClear())
  324. {
  325. // Failed to read from resource file.
  326. jassertfalse;
  327. return {};
  328. }
  329. if (bytesRead < 0)
  330. break;
  331. env->CallVoidMethod (outputStream.stream, JavaFileOutputStream.write, buffer.get(), 0, bytesRead);
  332. if (jniCheckHasExceptionOccurredAndClear())
  333. {
  334. // Failed to write to temporary file.
  335. jassertfalse;
  336. return {};
  337. }
  338. }
  339. temporaryFilesFromAssetFiles.add (tempFile);
  340. return URL (tempFile);
  341. }
  342. AsyncUpdater& owner;
  343. Array<URL> fileUrls;
  344. GlobalRef resultFileUris;
  345. String packageName;
  346. String uriBase;
  347. StringArray filePaths;
  348. Array<File> temporaryFilesFromAssetFiles;
  349. StringArray mimeTypes;
  350. };
  351. //==============================================================================
  352. class ContentSharer::ContentSharerNativeImpl : public ContentSharer::Pimpl,
  353. public AndroidContentSharerFileObserver::Owner,
  354. public AndroidContentSharerCursor::Owner,
  355. public AsyncUpdater,
  356. private Timer
  357. {
  358. public:
  359. ContentSharerNativeImpl (ContentSharer& cs)
  360. : owner (cs),
  361. packageName (juceString (LocalRef<jstring> ((jstring) getEnv()->CallObjectMethod (getAppContext().get(), AndroidContext.getPackageName)))),
  362. uriBase ("content://" + packageName + ".sharingcontentprovider/")
  363. {
  364. }
  365. ~ContentSharerNativeImpl() override
  366. {
  367. masterReference.clear();
  368. }
  369. void shareFiles (const Array<URL>& files) override
  370. {
  371. if (! isContentSharingEnabled())
  372. {
  373. // You need to enable "Content Sharing" in Projucer's Android exporter.
  374. jassertfalse;
  375. owner.sharingFinished (false, {});
  376. }
  377. prepareFilesThread.reset (new AndroidContentSharerPrepareFilesThread (*this, files, packageName, uriBase));
  378. }
  379. void shareText (const String& text) override
  380. {
  381. if (! isContentSharingEnabled())
  382. {
  383. // You need to enable "Content Sharing" in Projucer's Android exporter.
  384. jassertfalse;
  385. owner.sharingFinished (false, {});
  386. }
  387. auto* env = getEnv();
  388. auto intent = LocalRef<jobject> (env->NewObject (AndroidIntent, AndroidIntent.constructor));
  389. env->CallObjectMethod (intent, AndroidIntent.setAction,
  390. javaString ("android.intent.action.SEND").get());
  391. env->CallObjectMethod (intent, AndroidIntent.putExtra,
  392. javaString ("android.intent.extra.TEXT").get(),
  393. javaString (text).get());
  394. env->CallObjectMethod (intent, AndroidIntent.setType, javaString ("text/plain").get());
  395. auto chooserIntent = LocalRef<jobject> (env->CallStaticObjectMethod (AndroidIntent, AndroidIntent.createChooser,
  396. intent.get(), javaString ("Choose share target").get()));
  397. startAndroidActivityForResult (chooserIntent, 1003,
  398. [weakRef = WeakReference<ContentSharerNativeImpl> { this }] (int /*requestCode*/,
  399. int resultCode,
  400. 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. startAndroidActivityForResult (chooserIntent, 1003,
  541. [weakRef = WeakReference<ContentSharerNativeImpl> { this }] (int /*requestCode*/,
  542. int resultCode,
  543. LocalRef<jobject> /*intentData*/) mutable
  544. {
  545. if (weakRef != nullptr)
  546. weakRef->sharingFinished (resultCode);
  547. });
  548. }
  549. void decrementPendingFileCountAndNotifyOwnerIfReady()
  550. {
  551. --nonAssetFilesPendingShare;
  552. notifyOwnerIfReady();
  553. }
  554. void notifyOwnerIfReady()
  555. {
  556. if (sharingActivityDidFinish && nonAssetFilesPendingShare.get() == 0)
  557. owner.sharingFinished (succeeded, {});
  558. }
  559. void timerCallback() override
  560. {
  561. stopTimer();
  562. notifyOwnerIfReady();
  563. }
  564. //==============================================================================
  565. struct ContentUriElements
  566. {
  567. String index;
  568. String filename;
  569. String filepath;
  570. };
  571. ContentUriElements getContentUriElements (JNIEnv* env, const LocalRef<jobject>& uri) const
  572. {
  573. jassert (prepareFilesThread != nullptr);
  574. if (prepareFilesThread == nullptr)
  575. return {};
  576. auto fullUri = juceString ((jstring) env->CallObjectMethod (uri.get(), AndroidUri.toString));
  577. auto index = fullUri.fromFirstOccurrenceOf (uriBase, false, false)
  578. .upToFirstOccurrenceOf ("/", false, true);
  579. auto filename = fullUri.fromLastOccurrenceOf ("/", false, true);
  580. return { index, filename, prepareFilesThread->getFilePaths()[index.getIntValue()] };
  581. }
  582. static StringArray getSupportedColumns()
  583. {
  584. return StringArray ("_display_name", "_size");
  585. }
  586. jobject getAssetFileDescriptor (JNIEnv* env, const LocalRef<jobject>& contentProvider,
  587. const String& filepath)
  588. {
  589. // This function can be called from multiple threads.
  590. {
  591. const ScopedLock sl (nonAssetFileOpenLock);
  592. if (! nonAssetFilePathsPendingShare.contains (filepath))
  593. {
  594. nonAssetFilePathsPendingShare.add (filepath);
  595. ++nonAssetFilesPendingShare;
  596. nonAssetFileObservers.add (new AndroidContentSharerFileObserver (*this, env,
  597. contentProvider,
  598. filepath));
  599. }
  600. }
  601. auto javaFile = LocalRef<jobject> (env->NewObject (JavaFile, JavaFile.constructor,
  602. javaString (filepath).get()));
  603. constexpr int modeReadOnly = 268435456;
  604. auto parcelFileDescriptor = LocalRef<jobject> (env->CallStaticObjectMethod (ParcelFileDescriptor,
  605. ParcelFileDescriptor.open,
  606. javaFile.get(), modeReadOnly));
  607. if (jniCheckHasExceptionOccurredAndClear())
  608. {
  609. // Failed to create file descriptor. Have you provided a valid file path/resource name?
  610. jassertfalse;
  611. return nullptr;
  612. }
  613. jlong startOffset = 0;
  614. jlong unknownLength = -1;
  615. assetFileDescriptors.add (GlobalRef (LocalRef<jobject> (env->NewObject (AssetFileDescriptor,
  616. AssetFileDescriptor.constructor,
  617. parcelFileDescriptor.get(),
  618. startOffset, unknownLength))));
  619. return assetFileDescriptors.getReference (assetFileDescriptors.size() - 1).get();
  620. }
  621. StringArray filterMimeTypes (const StringArray& mimeTypes, const String& filter)
  622. {
  623. String filterToUse (filter.removeCharacters ("*"));
  624. if (filterToUse.isEmpty() || filterToUse == "/")
  625. return mimeTypes;
  626. StringArray result;
  627. for (const auto& type : mimeTypes)
  628. if (String (type).contains (filterToUse))
  629. result.add (type);
  630. return result;
  631. }
  632. String getCommonMimeType (const StringArray& mimeTypes)
  633. {
  634. if (mimeTypes.isEmpty())
  635. return "*/*";
  636. auto commonMime = mimeTypes[0];
  637. bool lookForCommonGroup = false;
  638. for (int i = 1; i < mimeTypes.size(); ++i)
  639. {
  640. if (mimeTypes[i] == commonMime)
  641. continue;
  642. if (! lookForCommonGroup)
  643. {
  644. lookForCommonGroup = true;
  645. commonMime = commonMime.upToFirstOccurrenceOf ("/", true, false);
  646. }
  647. if (! mimeTypes[i].startsWith (commonMime))
  648. return "*/*";
  649. }
  650. return lookForCommonGroup ? commonMime + "*" : commonMime;
  651. }
  652. ContentSharer& owner;
  653. String packageName;
  654. String uriBase;
  655. std::unique_ptr<AndroidContentSharerPrepareFilesThread> prepareFilesThread;
  656. bool succeeded = false;
  657. String errorDescription;
  658. bool sharingActivityDidFinish = false;
  659. OwnedArray<AndroidContentSharerCursor> cursors;
  660. Array<GlobalRef> assetFileDescriptors;
  661. CriticalSection nonAssetFileOpenLock;
  662. StringArray nonAssetFilePathsPendingShare;
  663. Atomic<int> nonAssetFilesPendingShare { 0 };
  664. OwnedArray<AndroidContentSharerFileObserver> nonAssetFileObservers;
  665. WeakReference<ContentSharerNativeImpl>::Master masterReference;
  666. friend class WeakReference<ContentSharerNativeImpl>;
  667. //==============================================================================
  668. #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
  669. CALLBACK (contentSharerQuery, "contentSharerQuery", "(Landroid/net/Uri;[Ljava/lang/String;)Landroid/database/Cursor;") \
  670. CALLBACK (contentSharerOpenFile, "contentSharerOpenFile", "(Landroid/net/Uri;Ljava/lang/String;)Landroid/content/res/AssetFileDescriptor;") \
  671. CALLBACK (contentSharerGetStreamTypes, "contentSharerGetStreamTypes", "(Landroid/net/Uri;Ljava/lang/String;)[Ljava/lang/String;") \
  672. DECLARE_JNI_CLASS_WITH_MIN_SDK (JuceSharingContentProvider, "com/rmsl/juce/JuceSharingContentProvider", 16)
  673. #undef JNI_CLASS_MEMBERS
  674. static jobject JNICALL contentSharerQuery (JNIEnv*, jobject contentProvider, jobject uri, jobjectArray projection)
  675. {
  676. if (auto *pimpl = (ContentSharer::ContentSharerNativeImpl *) ContentSharer::getInstance ()->pimpl.get ())
  677. return pimpl->query (LocalRef<jobject> (static_cast<jobject> (contentProvider)),
  678. LocalRef<jobject> (static_cast<jobject> (uri)),
  679. LocalRef<jobjectArray> (static_cast<jobjectArray> (projection)));
  680. return nullptr;
  681. }
  682. static jobject JNICALL contentSharerOpenFile (JNIEnv*, jobject contentProvider, jobject uri, jstring mode)
  683. {
  684. if (auto* pimpl = (ContentSharer::ContentSharerNativeImpl*) ContentSharer::getInstance()->pimpl.get())
  685. return pimpl->openFile (LocalRef<jobject> (static_cast<jobject> (contentProvider)),
  686. LocalRef<jobject> (static_cast<jobject> (uri)),
  687. LocalRef<jstring> (static_cast<jstring> (mode)));
  688. return nullptr;
  689. }
  690. static jobjectArray JNICALL contentSharerGetStreamTypes (JNIEnv*, jobject /*contentProvider*/, jobject uri, jstring mimeTypeFilter)
  691. {
  692. if (auto* pimpl = (ContentSharer::ContentSharerNativeImpl*) ContentSharer::getInstance()->pimpl.get())
  693. return pimpl->getStreamTypes (LocalRef<jobject> (static_cast<jobject> (uri)),
  694. LocalRef<jstring> (static_cast<jstring> (mimeTypeFilter)));
  695. return nullptr;
  696. }
  697. };
  698. //==============================================================================
  699. ContentSharer::Pimpl* ContentSharer::createPimpl()
  700. {
  701. return new ContentSharerNativeImpl (*this);
  702. }
  703. ContentSharer::ContentSharerNativeImpl::JuceSharingContentProvider_Class ContentSharer::ContentSharerNativeImpl::JuceSharingContentProvider;
  704. } // namespace juce