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.

881 lines
34KB

  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. auto exception = LocalRef<jobject> (env->ExceptionOccurred());
  244. if (exception != 0)
  245. {
  246. // Failed to open file stream for resource
  247. jassertfalse;
  248. env->ExceptionClear();
  249. return {};
  250. }
  251. auto tempFile = File::createTempFile ({});
  252. tempFile.createDirectory();
  253. tempFile = tempFile.getChildFile (filename);
  254. auto outputStream = StreamCloser (LocalRef<jobject> (env->NewObject (JavaFileOutputStream,
  255. JavaFileOutputStream.constructor,
  256. javaString (tempFile.getFullPathName()).get())));
  257. exception = LocalRef<jobject> (env->ExceptionOccurred());
  258. if (exception != 0)
  259. {
  260. // Failed to open file stream for temporary file
  261. jassertfalse;
  262. env->ExceptionClear();
  263. return {};
  264. }
  265. auto buffer = LocalRef<jbyteArray> (env->NewByteArray (1024));
  266. int bytesRead = 0;
  267. while (true)
  268. {
  269. if (threadShouldExit())
  270. return {};
  271. bytesRead = env->CallIntMethod (inputStream.stream, JavaFileInputStream.read, buffer.get());
  272. exception = LocalRef<jobject> (env->ExceptionOccurred());
  273. if (exception != 0)
  274. {
  275. // Failed to read from resource file.
  276. jassertfalse;
  277. env->ExceptionClear();
  278. return {};
  279. }
  280. if (bytesRead < 0)
  281. break;
  282. env->CallVoidMethod (outputStream.stream, JavaFileOutputStream.write, buffer.get(), 0, bytesRead);
  283. if (exception != 0)
  284. {
  285. // Failed to write to temporary file.
  286. jassertfalse;
  287. env->ExceptionClear();
  288. return {};
  289. }
  290. }
  291. temporaryFilesFromAssetFiles.add (tempFile);
  292. return URL (tempFile);
  293. }
  294. AsyncUpdater& owner;
  295. Array<URL> fileUrls;
  296. GlobalRef resultFileUris;
  297. String packageName;
  298. String uriBase;
  299. StringArray filePaths;
  300. Array<File> temporaryFilesFromAssetFiles;
  301. StringArray mimeTypes;
  302. };
  303. //==============================================================================
  304. class ContentSharer::ContentSharerNativeImpl : public ContentSharer::Pimpl,
  305. public AndroidContentSharerFileObserver::Owner,
  306. public AndroidContentSharerCursor::Owner,
  307. public AsyncUpdater,
  308. private Timer
  309. {
  310. public:
  311. ContentSharerNativeImpl (ContentSharer& cs)
  312. : owner (cs),
  313. packageName (juceString (LocalRef<jstring> ((jstring) getEnv()->CallObjectMethod (android.activity,
  314. JuceAppActivity.getPackageName)))),
  315. uriBase ("content://" + packageName + ".sharingcontentprovider/")
  316. {
  317. }
  318. ~ContentSharerNativeImpl()
  319. {
  320. masterReference.clear();
  321. }
  322. void shareFiles (const Array<URL>& files) override
  323. {
  324. if (! isContentSharingEnabled())
  325. {
  326. // You need to enable "Content Sharing" in Projucer's Android exporter.
  327. jassertfalse;
  328. owner.sharingFinished (false, {});
  329. }
  330. prepareFilesThread = new AndroidContentSharerPrepareFilesThread (*this, files, packageName, uriBase);
  331. }
  332. void shareText (const String& text) 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. auto* env = getEnv();
  341. auto intent = LocalRef<jobject> (env->NewObject (AndroidIntent, AndroidIntent.constructor));
  342. env->CallObjectMethod (intent, AndroidIntent.setAction,
  343. javaString ("android.intent.action.SEND").get());
  344. env->CallObjectMethod (intent, AndroidIntent.putExtra,
  345. javaString ("android.intent.extra.TEXT").get(),
  346. javaString (text).get());
  347. env->CallObjectMethod (intent, AndroidIntent.setType, javaString ("text/plain").get());
  348. auto chooserIntent = LocalRef<jobject> (env->CallStaticObjectMethod (AndroidIntent, AndroidIntent.createChooser,
  349. intent.get(), javaString ("Choose share target").get()));
  350. env->CallVoidMethod (android.activity, JuceAppActivity.startActivityForResult, chooserIntent.get(), 1003);
  351. }
  352. //==============================================================================
  353. void cursorClosed (const AndroidContentSharerCursor& cursor) override
  354. {
  355. cursors.removeObject (&cursor);
  356. }
  357. void fileHandleClosed (const AndroidContentSharerFileObserver&) override
  358. {
  359. decrementPendingFileCountAndNotifyOwnerIfReady();
  360. }
  361. //==============================================================================
  362. void* openFile (const LocalRef<jobject>& contentProvider,
  363. const LocalRef<jobject>& uri, const LocalRef<jstring>& mode)
  364. {
  365. ignoreUnused (mode);
  366. WeakReference<ContentSharerNativeImpl> weakRef (this);
  367. if (weakRef == nullptr)
  368. return nullptr;
  369. auto* env = getEnv();
  370. auto uriElements = getContentUriElements (env, uri);
  371. if (uriElements.filepath.isEmpty())
  372. return nullptr;
  373. return getAssetFileDescriptor (env, contentProvider, uriElements.filepath);
  374. }
  375. void* query (const LocalRef<jobject>& contentProvider, const LocalRef<jobject>& uri,
  376. const LocalRef<jobjectArray>& projection, const LocalRef<jobject>& selection,
  377. const LocalRef<jobjectArray>& selectionArgs, const LocalRef<jobject>& sortOrder)
  378. {
  379. ignoreUnused (selection, selectionArgs, sortOrder);
  380. StringArray requestedColumns = javaStringArrayToJuceStringArray (projection);
  381. StringArray supportedColumns = getSupportedColumns();
  382. StringArray resultColumns;
  383. for (const auto& col : supportedColumns)
  384. {
  385. if (requestedColumns.contains (col))
  386. resultColumns.add (col);
  387. }
  388. // Unsupported columns were queried, file sharing may fail.
  389. if (resultColumns.isEmpty())
  390. return nullptr;
  391. auto resultJavaColumns = juceStringArrayToJavaStringArray (resultColumns);
  392. auto* env = getEnv();
  393. auto cursor = cursors.add (new AndroidContentSharerCursor (*this, env, contentProvider,
  394. resultJavaColumns));
  395. auto uriElements = getContentUriElements (env, uri);
  396. if (uriElements.filepath.isEmpty())
  397. return cursor->getNativeCursor();
  398. auto values = LocalRef<jobjectArray> (env->NewObjectArray ((jsize) resultColumns.size(),
  399. JavaObject, 0));
  400. for (int i = 0; i < resultColumns.size(); ++i)
  401. {
  402. if (resultColumns.getReference (i) == "_display_name")
  403. {
  404. env->SetObjectArrayElement (values, i, javaString (uriElements.filename).get());
  405. }
  406. else if (resultColumns.getReference (i) == "_size")
  407. {
  408. auto javaFile = LocalRef<jobject> (env->NewObject (JavaFile, JavaFile.constructor,
  409. javaString (uriElements.filepath).get()));
  410. jlong fileLength = env->CallLongMethod (javaFile, JavaFile.length);
  411. env->SetObjectArrayElement (values, i, env->NewObject (JavaLong,
  412. JavaLong.constructor,
  413. fileLength));
  414. }
  415. }
  416. auto nativeCursor = cursor->getNativeCursor();
  417. env->CallVoidMethod (nativeCursor, JuceContentProviderFileObserverCursor.addRow, values.get());
  418. return nativeCursor;
  419. }
  420. void* getStreamTypes (const LocalRef<jobject>& uri, const LocalRef<jstring>& mimeTypeFilter)
  421. {
  422. auto* env = getEnv();
  423. auto extension = getContentUriElements (env, uri).filename.fromLastOccurrenceOf (".", false, true);
  424. if (extension.isEmpty())
  425. return nullptr;
  426. return juceStringArrayToJavaStringArray (filterMimeTypes (getMimeTypesForFileExtension (extension),
  427. juceString (mimeTypeFilter.get())));
  428. }
  429. void sharingFinished (int resultCode)
  430. {
  431. sharingActivityDidFinish = true;
  432. succeeded = resultCode == -1;
  433. // Give content sharer a chance to request file access.
  434. if (nonAssetFilesPendingShare.get() == 0)
  435. startTimer (2000);
  436. else
  437. notifyOwnerIfReady();
  438. }
  439. private:
  440. bool isContentSharingEnabled() const
  441. {
  442. auto* env = getEnv();
  443. auto packageManager = LocalRef<jobject> (env->CallObjectMethod (android.activity,
  444. JuceAppActivity.getPackageManager));
  445. constexpr int getProviders = 8;
  446. auto packageInfo = LocalRef<jobject> (env->CallObjectMethod (packageManager,
  447. AndroidPackageManager.getPackageInfo,
  448. javaString (packageName).get(),
  449. getProviders));
  450. auto providers = LocalRef<jobjectArray> ((jobjectArray) env->GetObjectField (packageInfo,
  451. AndroidPackageInfo.providers));
  452. if (providers == nullptr)
  453. return false;
  454. auto sharingContentProviderAuthority = packageName + ".sharingcontentprovider";
  455. const int numProviders = env->GetArrayLength (providers.get());
  456. for (int i = 0; i < numProviders; ++i)
  457. {
  458. auto providerInfo = LocalRef<jobject> (env->GetObjectArrayElement (providers, i));
  459. auto authority = LocalRef<jstring> ((jstring) env->GetObjectField (providerInfo,
  460. AndroidProviderInfo.authority));
  461. if (juceString (authority) == sharingContentProviderAuthority)
  462. return true;
  463. }
  464. return false;
  465. }
  466. void handleAsyncUpdate() override
  467. {
  468. jassert (prepareFilesThread != nullptr);
  469. if (prepareFilesThread == nullptr)
  470. return;
  471. filesPrepared (prepareFilesThread->getResultFileUris(), prepareFilesThread->getMimeTypes());
  472. }
  473. void filesPrepared (jobject fileUris, const StringArray& mimeTypes)
  474. {
  475. auto* env = getEnv();
  476. auto intent = LocalRef<jobject> (env->NewObject (AndroidIntent, AndroidIntent.constructor));
  477. env->CallObjectMethod (intent, AndroidIntent.setAction,
  478. javaString ("android.intent.action.SEND_MULTIPLE").get());
  479. env->CallObjectMethod (intent, AndroidIntent.setType,
  480. javaString (getCommonMimeType (mimeTypes)).get());
  481. constexpr int grantReadPermission = 1;
  482. env->CallObjectMethod (intent, AndroidIntent.setFlags, grantReadPermission);
  483. env->CallObjectMethod (intent, AndroidIntent.putParcelableArrayListExtra,
  484. javaString ("android.intent.extra.STREAM").get(),
  485. fileUris);
  486. auto chooserIntent = LocalRef<jobject> (env->CallStaticObjectMethod (AndroidIntent,
  487. AndroidIntent.createChooser,
  488. intent.get(),
  489. javaString ("Choose share target").get()));
  490. env->CallVoidMethod (android.activity, JuceAppActivity.startActivityForResult, chooserIntent.get(), 1003);
  491. }
  492. void decrementPendingFileCountAndNotifyOwnerIfReady()
  493. {
  494. --nonAssetFilesPendingShare;
  495. notifyOwnerIfReady();
  496. }
  497. void notifyOwnerIfReady()
  498. {
  499. if (sharingActivityDidFinish && nonAssetFilesPendingShare.get() == 0)
  500. owner.sharingFinished (succeeded, {});
  501. }
  502. void timerCallback() override
  503. {
  504. stopTimer();
  505. notifyOwnerIfReady();
  506. }
  507. //==============================================================================
  508. struct ContentUriElements
  509. {
  510. String index;
  511. String filename;
  512. String filepath;
  513. };
  514. ContentUriElements getContentUriElements (JNIEnv* env, const LocalRef<jobject>& uri) const
  515. {
  516. jassert (prepareFilesThread != nullptr);
  517. if (prepareFilesThread == nullptr)
  518. return {};
  519. auto fullUri = juceString ((jstring) env->CallObjectMethod (uri.get(), AndroidUri.toString));
  520. auto index = fullUri.fromFirstOccurrenceOf (uriBase, false, false)
  521. .upToFirstOccurrenceOf ("/", false, true);
  522. auto filename = fullUri.fromLastOccurrenceOf ("/", false, true);
  523. return { index, filename, prepareFilesThread->getFilePaths()[index.getIntValue()] };
  524. }
  525. static LocalRef<jobjectArray> juceStringArrayToJavaStringArray (const StringArray& juceArray)
  526. {
  527. auto* env = getEnv();
  528. auto javaArray = LocalRef<jobjectArray> (env->NewObjectArray ((jsize) juceArray.size(),
  529. JavaString,
  530. javaString ("").get()));
  531. for (int i = 0; i < juceArray.size(); ++i)
  532. env->SetObjectArrayElement (javaArray, i, javaString (juceArray [i]).get());
  533. return javaArray;
  534. }
  535. static StringArray javaStringArrayToJuceStringArray (const LocalRef<jobjectArray>& javaArray)
  536. {
  537. if (javaArray.get() == 0)
  538. return {};
  539. auto* env = getEnv();
  540. const int size = env->GetArrayLength (javaArray.get());
  541. StringArray juceArray;
  542. for (int i = 0; i < size; ++i)
  543. {
  544. auto javaString = LocalRef<jstring> ((jstring) env->GetObjectArrayElement (javaArray.get(), i));
  545. juceArray.add (juceString (javaString.get()));
  546. }
  547. return juceArray;
  548. }
  549. static StringArray getSupportedColumns()
  550. {
  551. return StringArray ("_display_name", "_size");
  552. }
  553. void* getAssetFileDescriptor (JNIEnv* env, const LocalRef<jobject>& contentProvider,
  554. const String& filepath)
  555. {
  556. // This function can be called from multiple threads.
  557. {
  558. const ScopedLock sl (nonAssetFileOpenLock);
  559. if (! nonAssetFilePathsPendingShare.contains (filepath))
  560. {
  561. nonAssetFilePathsPendingShare.add (filepath);
  562. ++nonAssetFilesPendingShare;
  563. nonAssetFileObservers.add (new AndroidContentSharerFileObserver (*this, env,
  564. contentProvider,
  565. filepath));
  566. }
  567. }
  568. auto javaFile = LocalRef<jobject> (env->NewObject (JavaFile, JavaFile.constructor,
  569. javaString (filepath).get()));
  570. constexpr int modeReadOnly = 268435456;
  571. auto parcelFileDescriptor = LocalRef<jobject> (env->CallStaticObjectMethod (ParcelFileDescriptor,
  572. ParcelFileDescriptor.open,
  573. javaFile.get(), modeReadOnly));
  574. auto exception = LocalRef<jobject> (env->ExceptionOccurred());
  575. if (exception != 0)
  576. {
  577. // Failed to create file descriptor. Have you provided a valid file path/resource name?
  578. jassertfalse;
  579. env->ExceptionClear();
  580. return nullptr;
  581. }
  582. jlong startOffset = 0;
  583. jlong unknownLength = -1;
  584. assetFileDescriptors.add (GlobalRef (LocalRef<jobject> (env->NewObject (AssetFileDescriptor,
  585. AssetFileDescriptor.constructor,
  586. parcelFileDescriptor.get(),
  587. startOffset, unknownLength))));
  588. return assetFileDescriptors.getReference (assetFileDescriptors.size() - 1).get();
  589. }
  590. ContentSharer& owner;
  591. String packageName;
  592. String uriBase;
  593. ScopedPointer<AndroidContentSharerPrepareFilesThread> prepareFilesThread;
  594. bool succeeded = false;
  595. String errorDescription;
  596. bool sharingActivityDidFinish = false;
  597. OwnedArray<AndroidContentSharerCursor> cursors;
  598. Array<GlobalRef> assetFileDescriptors;
  599. CriticalSection nonAssetFileOpenLock;
  600. StringArray nonAssetFilePathsPendingShare;
  601. Atomic<int> nonAssetFilesPendingShare { 0 };
  602. OwnedArray<AndroidContentSharerFileObserver> nonAssetFileObservers;
  603. WeakReference<ContentSharerNativeImpl>::Master masterReference;
  604. friend class WeakReference<ContentSharerNativeImpl>;
  605. };
  606. //==============================================================================
  607. ContentSharer::Pimpl* ContentSharer::createPimpl()
  608. {
  609. return new ContentSharerNativeImpl (*this);
  610. }
  611. //==============================================================================
  612. void* juce_contentSharerQuery (void* contentProvider, void* uri, void* projection,
  613. void* selection, void* selectionArgs, void* sortOrder)
  614. {
  615. auto* pimpl = (ContentSharer::ContentSharerNativeImpl*) ContentSharer::getInstance()->pimpl.get();
  616. return pimpl->query (LocalRef<jobject> (static_cast<jobject> (contentProvider)),
  617. LocalRef<jobject> (static_cast<jobject> (uri)),
  618. LocalRef<jobjectArray> (static_cast<jobjectArray> (projection)),
  619. LocalRef<jobject> (static_cast<jobject> (selection)),
  620. LocalRef<jobjectArray> (static_cast<jobjectArray> (selectionArgs)),
  621. LocalRef<jobject> (static_cast<jobject> (sortOrder)));
  622. }
  623. void* juce_contentSharerOpenFile (void* contentProvider, void* uri, void* mode)
  624. {
  625. auto* pimpl = (ContentSharer::ContentSharerNativeImpl*) ContentSharer::getInstance()->pimpl.get();
  626. return pimpl->openFile (LocalRef<jobject> (static_cast<jobject> (contentProvider)),
  627. LocalRef<jobject> (static_cast<jobject> (uri)),
  628. LocalRef<jstring> (static_cast<jstring> (mode)));
  629. }
  630. void juce_contentSharingCompleted (int resultCode)
  631. {
  632. auto* pimpl = (ContentSharer::ContentSharerNativeImpl*) ContentSharer::getInstance()->pimpl.get();
  633. return pimpl->sharingFinished (resultCode);
  634. }
  635. void* juce_contentSharerGetStreamTypes (void* uri, void* mimeTypeFilter)
  636. {
  637. auto* pimpl = (ContentSharer::ContentSharerNativeImpl*) ContentSharer::getInstance()->pimpl.get();
  638. return pimpl->getStreamTypes (LocalRef<jobject> (static_cast<jobject> (uri)),
  639. LocalRef<jstring> (static_cast<jstring> (mimeTypeFilter)));
  640. }
  641. //==============================================================================
  642. JUCE_JNI_CALLBACK (JUCE_ANDROID_SHARING_CONTENT_PROVIDER_CLASSNAME, contentSharerFileObserverEvent, void,
  643. (JNIEnv* env, jobject /*fileObserver*/, jlong host, int event, jstring path))
  644. {
  645. setEnv (env);
  646. reinterpret_cast<AndroidContentSharerFileObserver*> (host)->onFileEvent (event, LocalRef<jstring> (path));
  647. }
  648. //==============================================================================
  649. JUCE_JNI_CALLBACK (JUCE_ANDROID_SHARING_CONTENT_PROVIDER_CLASSNAME, contentSharerQuery, jobject,
  650. (JNIEnv* env, jobject contentProvider, jobject uri, jobjectArray projection,
  651. jobject selection, jobjectArray selectionArgs, jobject sortOrder))
  652. {
  653. setEnv (env);
  654. return (jobject) juce_contentSharerQuery (contentProvider, uri, projection, selection, selectionArgs, sortOrder);
  655. }
  656. JUCE_JNI_CALLBACK (JUCE_ANDROID_SHARING_CONTENT_PROVIDER_CLASSNAME, contentSharerCursorClosed, void,
  657. (JNIEnv* env, jobject /*cursor*/, jlong host))
  658. {
  659. setEnv (env);
  660. reinterpret_cast<AndroidContentSharerCursor*> (host)->cursorClosed();
  661. }
  662. JUCE_JNI_CALLBACK (JUCE_ANDROID_SHARING_CONTENT_PROVIDER_CLASSNAME, contentSharerOpenFile, jobject,
  663. (JNIEnv* env, jobject contentProvider, jobject uri, jstring mode))
  664. {
  665. setEnv (env);
  666. return (jobject) juce_contentSharerOpenFile ((void*) contentProvider, (void*) uri, (void*) mode);
  667. }
  668. JUCE_JNI_CALLBACK (JUCE_ANDROID_SHARING_CONTENT_PROVIDER_CLASSNAME, contentSharerGetStreamTypes, jobject,
  669. (JNIEnv* env, jobject /*contentProvider*/, jobject uri, jstring mimeTypeFilter))
  670. {
  671. setEnv (env);
  672. return (jobject) juce_contentSharerGetStreamTypes ((void*) uri, (void*) mimeTypeFilter);
  673. }
  674. } // namespace juce