juce_android_ContentSharer.cpp 40 KB

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