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.

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