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
32KB

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