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.

829 lines
33KB

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