|
- /*
- ==============================================================================
-
- This file is part of the JUCE 6 technical preview.
- Copyright (c) 2017 - ROLI Ltd.
-
- You may use this code under the terms of the GPL v3
- (see www.gnu.org/licenses).
-
- For this technical preview, this file is not subject to commercial licensing.
-
- JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
- EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
- DISCLAIMED.
-
- ==============================================================================
- */
- namespace juce
- {
-
- //==============================================================================
- // This byte-code is generated from native/java/app/com/roli/juce/JuceContentProviderCursor.java with min sdk version 16
- // See juce_core/native/java/README.txt on how to generate this byte-code.
- static const uint8 javaJuceContentProviderCursor[] =
- { 31,139,8,8,138,122,94,94,0,3,74,117,99,101,67,111,110,116,101,110,116,80,114,111,118,105,100,101,114,67,117,114,115,111,114,
- 46,100,101,120,0,117,147,177,111,211,64,20,198,223,157,157,148,150,52,184,144,1,6,32,160,46,29,138,43,209,178,132,34,80,144,42,
- 89,6,85,10,164,82,59,93,109,211,184,77,124,145,237,68,17,98,0,4,91,6,6,38,38,64,133,153,255,0,33,133,255,131,137,149,133,177,3,
- 223,229,206,37,18,194,210,207,247,222,229,189,239,157,206,95,194,104,180,176,118,115,131,142,7,95,175,127,216,250,229,236,28,
- 61,169,85,222,124,219,126,234,140,155,159,157,142,184,107,17,245,137,104,212,94,63,79,230,121,207,137,28,210,251,37,240,5,216,
- 96,2,24,248,109,246,79,76,190,140,215,9,184,15,222,129,143,224,19,248,14,126,130,37,232,213,192,69,112,25,92,3,183,192,38,240,
- 64,4,142,192,144,107,61,203,204,83,51,230,205,106,153,179,149,77,252,10,133,115,38,30,35,62,99,226,183,136,33,67,139,211,179,
- 233,46,165,185,64,106,159,79,115,126,154,51,170,152,124,201,212,21,191,171,103,108,130,18,78,163,194,103,92,207,239,215,137,238,
- 209,46,238,171,138,9,122,202,107,174,207,42,29,134,218,22,42,250,107,232,98,213,127,116,237,105,110,83,249,118,156,196,249,29,
- 98,30,93,241,6,65,212,148,73,30,37,249,118,42,135,113,24,165,205,65,154,201,244,198,161,24,10,186,234,139,36,76,101,28,186,161,
- 200,197,190,200,34,247,129,200,211,120,164,139,26,180,226,7,178,231,166,178,27,187,135,144,114,255,171,215,32,214,38,222,246,
- 200,106,123,62,2,159,46,236,249,106,134,219,21,201,129,219,130,104,114,208,160,82,208,149,89,68,103,3,217,29,244,146,135,162,
- 23,101,116,41,208,130,173,142,72,11,185,166,42,11,201,238,200,44,167,121,245,126,36,31,163,209,206,59,113,70,21,126,174,50,87,
- 221,92,165,26,97,217,197,69,112,92,14,127,241,220,158,112,254,146,147,197,126,240,69,115,55,108,102,45,188,199,103,252,87,120,
- 98,98,214,89,31,22,94,44,207,248,145,213,117,191,242,36,115,254,122,134,215,181,190,242,169,101,106,212,119,165,186,238,157,126,
- 115,71,199,234,127,240,7,124,122,243,207,64,3,0,0,0,0 };
-
- // This byte-code is generated from native/java/app/com/roli/juce/JuceContentProviderFileObserver.java with min sdk version 16
- // See juce_core/native/java/README.txt on how to generate this byte-code.
- static const uint8 javaJuceContentProviderFileObserver[] =
- { 31,139,8,8,123,122,94,94,0,3,74,117,99,101,67,111,110,116,101,110,116,80,114,111,118,105,100,101,114,70,105,108,101,79,98,115,
- 101,114,118,101,114,46,100,101,120,0,133,147,205,107,19,65,24,198,223,249,72,98,91,93,210,84,4,17,36,130,130,32,186,145,218,
- 131,196,10,197,47,18,22,44,166,6,41,120,216,38,75,119,107,186,19,118,215,144,131,5,45,61,148,158,188,244,164,71,65,241,212,163,
- 40,138,136,224,213,91,253,3,60,250,7,120,211,103,118,166,116,61,25,248,205,251,206,251,206,60,59,59,251,164,31,140,39,27,179,
- 115,116,110,243,212,238,147,208,189,112,245,227,236,183,157,19,103,216,220,215,223,91,251,175,247,249,88,16,13,137,104,220,189,
- 82,35,251,251,193,137,142,147,169,151,193,103,32,117,29,48,112,132,153,121,149,153,121,3,195,121,236,89,68,220,3,239,192,7,
- 240,9,252,4,127,64,3,253,155,224,14,240,192,61,240,0,60,4,27,96,11,236,128,93,240,2,188,2,111,185,209,231,246,249,37,123,158,138,
- 62,131,157,111,99,193,132,205,159,35,159,180,249,75,228,83,54,127,131,92,64,201,201,181,68,254,142,34,87,53,245,163,121,36,
- 154,46,68,110,239,66,216,248,197,22,202,80,212,181,61,110,206,50,108,16,45,148,150,113,119,78,222,209,207,123,111,123,157,58,
- 250,75,156,248,101,199,234,201,124,47,179,239,99,114,153,119,14,222,177,124,45,138,163,236,58,177,22,177,54,157,109,63,238,5,
- 55,84,156,5,113,182,152,168,81,212,15,146,219,209,32,184,187,146,6,201,40,72,46,173,249,35,159,78,122,126,220,79,84,212,119,85,
- 234,22,219,77,114,189,158,90,119,19,53,136,220,53,72,185,255,209,107,82,205,211,146,238,192,143,87,221,78,150,68,241,106,147,
- 88,151,68,183,229,145,236,182,205,232,181,80,192,112,186,103,164,58,161,159,252,43,116,107,132,50,149,130,60,200,80,165,25,77,
- 232,113,73,221,79,3,146,235,126,250,136,42,42,54,203,228,208,207,66,146,89,24,165,52,37,170,51,211,21,103,254,34,213,184,51,
- 83,113,150,113,69,156,17,103,207,158,202,239,156,109,226,251,177,95,184,172,99,246,187,176,66,60,240,44,47,248,86,20,188,43,11,
- 254,45,209,161,135,203,116,232,99,86,55,251,181,151,89,213,172,209,30,227,117,163,175,253,45,236,26,237,1,170,155,189,185,63,
- 170,38,215,255,159,191,29,121,169,41,120,3,0,0,0,0 };
-
- //==============================================================================
- #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
- FIELD (authority, "authority", "Ljava/lang/String;")
-
- DECLARE_JNI_CLASS (AndroidProviderInfo, "android/content/pm/ProviderInfo")
- #undef JNI_CLASS_MEMBERS
-
- #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
- METHOD (constructor, "<init>", "(Landroid/os/ParcelFileDescriptor;JJ)V") \
- METHOD (createInputStream, "createInputStream", "()Ljava/io/FileInputStream;") \
- METHOD (getLength, "getLength", "()J")
-
- DECLARE_JNI_CLASS (AssetFileDescriptor, "android/content/res/AssetFileDescriptor")
- #undef JNI_CLASS_MEMBERS
-
- #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
- METHOD (close, "close", "()V")
-
- DECLARE_JNI_CLASS (JavaCloseable, "java/io/Closeable")
- #undef JNI_CLASS_MEMBERS
-
- #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
- STATICMETHOD (open, "open", "(Ljava/io/File;I)Landroid/os/ParcelFileDescriptor;")
-
- DECLARE_JNI_CLASS (ParcelFileDescriptor, "android/os/ParcelFileDescriptor")
- #undef JNI_CLASS_MEMBERS
-
- //==============================================================================
- class AndroidContentSharerCursor
- {
- public:
- class Owner
- {
- public:
- virtual ~Owner() {}
-
- virtual void cursorClosed (const AndroidContentSharerCursor&) = 0;
- };
-
- AndroidContentSharerCursor (Owner& ownerToUse, JNIEnv* env,
- const LocalRef<jobject>& contentProvider,
- const LocalRef<jobjectArray>& resultColumns)
- : owner (ownerToUse),
- cursor (GlobalRef (LocalRef<jobject> (env->NewObject (JuceContentProviderCursor,
- JuceContentProviderCursor.constructor,
- reinterpret_cast<jlong> (this),
- resultColumns.get()))))
- {
- // the content provider must be created first
- jassert (contentProvider.get() != nullptr);
- }
-
- jobject getNativeCursor() { return cursor.get(); }
-
- void cursorClosed()
- {
- MessageManager::callAsync ([this] { owner.cursorClosed (*this); });
- }
-
- void addRow (LocalRef<jobjectArray>& values)
- {
- auto* env = getEnv();
-
- env->CallVoidMethod (cursor.get(), JuceContentProviderCursor.addRow, values.get());
- }
-
- private:
- Owner& owner;
- GlobalRef cursor;
-
- //==============================================================================
- #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
- METHOD (addRow, "addRow", "([Ljava/lang/Object;)V") \
- METHOD (constructor, "<init>", "(J[Ljava/lang/String;)V") \
- CALLBACK (contentSharerCursorClosed, "contentSharerCursorClosed", "(J)V") \
-
- DECLARE_JNI_CLASS_WITH_BYTECODE (JuceContentProviderCursor, "com/roli/juce/JuceContentProviderCursor", 16, javaJuceContentProviderCursor, sizeof (javaJuceContentProviderCursor))
- #undef JNI_CLASS_MEMBERS
-
- static void JNICALL contentSharerCursorClosed (JNIEnv*, jobject, jlong host)
- {
- if (auto* myself = reinterpret_cast<AndroidContentSharerCursor*> (host))
- myself->cursorClosed();
- }
- };
-
- AndroidContentSharerCursor::JuceContentProviderCursor_Class AndroidContentSharerCursor::JuceContentProviderCursor;
-
- //==============================================================================
- class AndroidContentSharerFileObserver
- {
- public:
- class Owner
- {
- public:
- virtual ~Owner() {}
-
- virtual void fileHandleClosed (const AndroidContentSharerFileObserver&) = 0;
- };
-
- AndroidContentSharerFileObserver (Owner& ownerToUse, JNIEnv* env,
- const LocalRef<jobject>& contentProvider,
- const String& filepathToUse)
- : owner (ownerToUse),
- filepath (filepathToUse),
- fileObserver (GlobalRef (LocalRef<jobject> (env->NewObject (JuceContentProviderFileObserver,
- JuceContentProviderFileObserver.constructor,
- reinterpret_cast<jlong> (this),
- javaString (filepath).get(),
- open | access | closeWrite | closeNoWrite))))
- {
- // the content provider must be created first
- jassert (contentProvider.get() != nullptr);
-
- env->CallVoidMethod (fileObserver, JuceContentProviderFileObserver.startWatching);
- }
-
- void onFileEvent (int event, const LocalRef<jstring>& path)
- {
- ignoreUnused (path);
-
- if (event == open)
- {
- ++numOpenedHandles;
- }
- else if (event == access)
- {
- fileWasRead = true;
- }
- else if (event == closeNoWrite || event == closeWrite)
- {
- --numOpenedHandles;
-
- // numOpenedHandles may get negative if we don't receive open handle event.
- if (fileWasRead && numOpenedHandles <= 0)
- {
- MessageManager::callAsync ([this]
- {
- getEnv()->CallVoidMethod (fileObserver, JuceContentProviderFileObserver.stopWatching);
- owner.fileHandleClosed (*this);
- });
- }
- }
- }
-
- private:
- static constexpr int open = 32;
- static constexpr int access = 1;
- static constexpr int closeWrite = 8;
- static constexpr int closeNoWrite = 16;
-
- bool fileWasRead = false;
- int numOpenedHandles = 0;
-
- Owner& owner;
- String filepath;
- GlobalRef fileObserver;
-
- //==============================================================================
- #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
- METHOD (constructor, "<init>", "(JLjava/lang/String;I)V") \
- METHOD (startWatching, "startWatching", "()V") \
- METHOD (stopWatching, "stopWatching", "()V") \
- CALLBACK (contentSharerFileObserverEvent, "contentSharerFileObserverEvent", "(JILjava/lang/String;)V") \
-
- DECLARE_JNI_CLASS_WITH_BYTECODE (JuceContentProviderFileObserver, "com/roli/juce/JuceContentProviderFileObserver", 16, javaJuceContentProviderFileObserver, sizeof (javaJuceContentProviderFileObserver))
- #undef JNI_CLASS_MEMBERS
-
- static void JNICALL contentSharerFileObserverEvent (JNIEnv*, jobject /*fileObserver*/, jlong host, int event, jstring path)
- {
- if (auto* myself = reinterpret_cast<AndroidContentSharerFileObserver*> (host))
- myself->onFileEvent (event, LocalRef<jstring> (path));
- }
- };
-
- AndroidContentSharerFileObserver::JuceContentProviderFileObserver_Class AndroidContentSharerFileObserver::JuceContentProviderFileObserver;
-
- //==============================================================================
- class AndroidContentSharerPrepareFilesThread : private Thread
- {
- public:
- AndroidContentSharerPrepareFilesThread (AsyncUpdater& ownerToUse,
- const Array<URL>& fileUrlsToUse,
- const String& packageNameToUse,
- const String& uriBaseToUse)
- : Thread ("AndroidContentSharerPrepareFilesThread"),
- owner (ownerToUse),
- fileUrls (fileUrlsToUse),
- resultFileUris (GlobalRef (LocalRef<jobject> (getEnv()->NewObject (JavaArrayList,
- JavaArrayList.constructor,
- fileUrls.size())))),
- packageName (packageNameToUse),
- uriBase (uriBaseToUse)
- {
- startThread();
- }
-
- ~AndroidContentSharerPrepareFilesThread() override
- {
- signalThreadShouldExit();
- waitForThreadToExit (10000);
-
- for (auto& f : temporaryFilesFromAssetFiles)
- f.deleteFile();
- }
-
- jobject getResultFileUris() { return resultFileUris.get(); }
- const StringArray& getMimeTypes() const { return mimeTypes; }
- const StringArray& getFilePaths() const { return filePaths; }
-
- private:
- struct StreamCloser
- {
- StreamCloser (const LocalRef<jobject>& streamToUse)
- : stream (GlobalRef (streamToUse))
- {
- }
-
- ~StreamCloser()
- {
- if (stream.get() != nullptr)
- getEnv()->CallVoidMethod (stream, JavaCloseable.close);
- }
-
- GlobalRef stream;
- };
-
- void run() override
- {
- auto* env = getEnv();
-
- bool canSpecifyMimeTypes = true;
-
- for (auto f : fileUrls)
- {
- auto scheme = f.getScheme();
-
- // Only "file://" scheme or no scheme (for files in app bundle) are allowed!
- jassert (scheme.isEmpty() || scheme == "file");
-
- if (scheme.isEmpty())
- {
- // Raw resource names need to be all lower case
- jassert (f.toString (true).toLowerCase() == f.toString (true));
-
- // This will get us a file with file:// URI
- f = copyAssetFileToTemporaryFile (env, f.toString (true));
-
- if (f.isEmpty())
- continue;
- }
-
- if (threadShouldExit())
- return;
-
- auto filepath = URL::removeEscapeChars (f.toString (true).fromFirstOccurrenceOf ("file://", false, false));
-
- filePaths.add (filepath);
-
- auto filename = filepath.fromLastOccurrenceOf ("/", false, true);
- auto fileExtension = filename.fromLastOccurrenceOf (".", false, true);
- auto contentString = uriBase + String (filePaths.size() - 1) + "/" + filename;
-
- auto uri = LocalRef<jobject> (env->CallStaticObjectMethod (AndroidUri, AndroidUri.parse,
- javaString (contentString).get()));
-
- if (canSpecifyMimeTypes)
- canSpecifyMimeTypes = fileExtension.isNotEmpty();
-
- if (canSpecifyMimeTypes)
- mimeTypes.addArray (getMimeTypesForFileExtension (fileExtension));
- else
- mimeTypes.clear();
-
- env->CallBooleanMethod (resultFileUris, JavaArrayList.add, uri.get());
- }
-
- owner.triggerAsyncUpdate();
- }
-
- URL copyAssetFileToTemporaryFile (JNIEnv* env, const String& filename)
- {
- auto resources = LocalRef<jobject> (env->CallObjectMethod (getAppContext().get(), AndroidContext.getResources));
- int fileId = env->CallIntMethod (resources, AndroidResources.getIdentifier, javaString (filename).get(),
- javaString ("raw").get(), javaString (packageName).get());
-
- // Raw resource not found. Please make sure that you include your file as a raw resource
- // and that you specify just the file name, without an extension.
- jassert (fileId != 0);
-
- if (fileId == 0)
- return {};
-
- auto assetFd = LocalRef<jobject> (env->CallObjectMethod (resources,
- AndroidResources.openRawResourceFd,
- fileId));
-
- auto inputStream = StreamCloser (LocalRef<jobject> (env->CallObjectMethod (assetFd,
- AssetFileDescriptor.createInputStream)));
-
- if (jniCheckHasExceptionOccurredAndClear())
- {
- // Failed to open file stream for resource
- jassertfalse;
- return {};
- }
-
- auto tempFile = File::createTempFile ({});
- tempFile.createDirectory();
- tempFile = tempFile.getChildFile (filename);
-
- auto outputStream = StreamCloser (LocalRef<jobject> (env->NewObject (JavaFileOutputStream,
- JavaFileOutputStream.constructor,
- javaString (tempFile.getFullPathName()).get())));
-
- if (jniCheckHasExceptionOccurredAndClear())
- {
- // Failed to open file stream for temporary file
- jassertfalse;
- return {};
- }
-
- auto buffer = LocalRef<jbyteArray> (env->NewByteArray (1024));
- int bytesRead = 0;
-
- for (;;)
- {
- if (threadShouldExit())
- return {};
-
- bytesRead = env->CallIntMethod (inputStream.stream, JavaFileInputStream.read, buffer.get());
-
- if (jniCheckHasExceptionOccurredAndClear())
- {
- // Failed to read from resource file.
- jassertfalse;
- return {};
- }
-
- if (bytesRead < 0)
- break;
-
- env->CallVoidMethod (outputStream.stream, JavaFileOutputStream.write, buffer.get(), 0, bytesRead);
-
- if (jniCheckHasExceptionOccurredAndClear())
- {
- // Failed to write to temporary file.
- jassertfalse;
- return {};
- }
- }
-
- temporaryFilesFromAssetFiles.add (tempFile);
-
- return URL (tempFile);
- }
-
- AsyncUpdater& owner;
- Array<URL> fileUrls;
-
- GlobalRef resultFileUris;
- String packageName;
- String uriBase;
-
- StringArray filePaths;
- Array<File> temporaryFilesFromAssetFiles;
- StringArray mimeTypes;
- };
-
- //==============================================================================
- class ContentSharer::ContentSharerNativeImpl : public ContentSharer::Pimpl,
- public AndroidContentSharerFileObserver::Owner,
- public AndroidContentSharerCursor::Owner,
- public AsyncUpdater,
- private Timer
- {
- public:
- ContentSharerNativeImpl (ContentSharer& cs)
- : owner (cs),
- packageName (juceString (LocalRef<jstring> ((jstring) getEnv()->CallObjectMethod (getAppContext().get(), AndroidContext.getPackageName)))),
- uriBase ("content://" + packageName + ".sharingcontentprovider/")
- {
- }
-
- ~ContentSharerNativeImpl() override
- {
- masterReference.clear();
- }
-
- void shareFiles (const Array<URL>& files) override
- {
- if (! isContentSharingEnabled())
- {
- // You need to enable "Content Sharing" in Projucer's Android exporter.
- jassertfalse;
- owner.sharingFinished (false, {});
- }
-
- prepareFilesThread.reset (new AndroidContentSharerPrepareFilesThread (*this, files, packageName, uriBase));
- }
-
- void shareText (const String& text) override
- {
- if (! isContentSharingEnabled())
- {
- // You need to enable "Content Sharing" in Projucer's Android exporter.
- jassertfalse;
- owner.sharingFinished (false, {});
- }
-
- auto* env = getEnv();
-
- auto intent = LocalRef<jobject> (env->NewObject (AndroidIntent, AndroidIntent.constructor));
- env->CallObjectMethod (intent, AndroidIntent.setAction,
- javaString ("android.intent.action.SEND").get());
- env->CallObjectMethod (intent, AndroidIntent.putExtra,
- javaString ("android.intent.extra.TEXT").get(),
- javaString (text).get());
- env->CallObjectMethod (intent, AndroidIntent.setType, javaString ("text/plain").get());
-
- auto chooserIntent = LocalRef<jobject> (env->CallStaticObjectMethod (AndroidIntent, AndroidIntent.createChooser,
- intent.get(), javaString ("Choose share target").get()));
-
- WeakReference<ContentSharerNativeImpl> weakRef (this);
-
- startAndroidActivityForResult (chooserIntent, 1003,
- [weakRef] (int /*requestCode*/, int resultCode, LocalRef<jobject> /*intentData*/) mutable
- {
- if (weakRef != nullptr)
- weakRef->sharingFinished (resultCode);
- });
- }
-
- //==============================================================================
- void cursorClosed (const AndroidContentSharerCursor& cursor) override
- {
- cursors.removeObject (&cursor);
- }
-
- void fileHandleClosed (const AndroidContentSharerFileObserver&) override
- {
- decrementPendingFileCountAndNotifyOwnerIfReady();
- }
-
- //==============================================================================
- jobject openFile (const LocalRef<jobject>& contentProvider,
- const LocalRef<jobject>& uri, const LocalRef<jstring>& mode)
- {
- ignoreUnused (mode);
-
- WeakReference<ContentSharerNativeImpl> weakRef (this);
-
- if (weakRef == nullptr)
- return nullptr;
-
- auto* env = getEnv();
-
- auto uriElements = getContentUriElements (env, uri);
-
- if (uriElements.filepath.isEmpty())
- return nullptr;
-
- return getAssetFileDescriptor (env, contentProvider, uriElements.filepath);
- }
-
- jobject query (const LocalRef<jobject>& contentProvider, const LocalRef<jobject>& uri,
- const LocalRef<jobjectArray>& projection)
- {
- StringArray requestedColumns = javaStringArrayToJuce (projection);
- StringArray supportedColumns = getSupportedColumns();
-
- StringArray resultColumns;
-
- for (const auto& col : supportedColumns)
- {
- if (requestedColumns.contains (col))
- resultColumns.add (col);
- }
-
- // Unsupported columns were queried, file sharing may fail.
- if (resultColumns.isEmpty())
- return nullptr;
-
- auto resultJavaColumns = juceStringArrayToJava (resultColumns);
-
- auto* env = getEnv();
-
- auto cursor = cursors.add (new AndroidContentSharerCursor (*this, env, contentProvider,
- resultJavaColumns));
-
- auto uriElements = getContentUriElements (env, uri);
-
- if (uriElements.filepath.isEmpty())
- return cursor->getNativeCursor();
-
- auto values = LocalRef<jobjectArray> (env->NewObjectArray ((jsize) resultColumns.size(),
- JavaObject, nullptr));
-
- for (int i = 0; i < resultColumns.size(); ++i)
- {
- if (resultColumns.getReference (i) == "_display_name")
- {
- env->SetObjectArrayElement (values, i, javaString (uriElements.filename).get());
- }
- else if (resultColumns.getReference (i) == "_size")
- {
- auto javaFile = LocalRef<jobject> (env->NewObject (JavaFile, JavaFile.constructor,
- javaString (uriElements.filepath).get()));
-
- jlong fileLength = env->CallLongMethod (javaFile, JavaFile.length);
-
- env->SetObjectArrayElement (values, i, env->NewObject (JavaLong,
- JavaLong.constructor,
- fileLength));
- }
- }
-
- cursor->addRow (values);
- return cursor->getNativeCursor();
- }
-
- jobjectArray getStreamTypes (const LocalRef<jobject>& uri, const LocalRef<jstring>& mimeTypeFilter)
- {
- auto* env = getEnv();
-
- auto extension = getContentUriElements (env, uri).filename.fromLastOccurrenceOf (".", false, true);
-
- if (extension.isEmpty())
- return nullptr;
-
- return juceStringArrayToJava (filterMimeTypes (getMimeTypesForFileExtension (extension),
- juceString (mimeTypeFilter.get())));
- }
-
- void sharingFinished (int resultCode)
- {
- sharingActivityDidFinish = true;
-
- succeeded = resultCode == -1;
-
- // Give content sharer a chance to request file access.
- if (nonAssetFilesPendingShare.get() == 0)
- startTimer (2000);
- else
- notifyOwnerIfReady();
- }
-
- private:
- bool isContentSharingEnabled() const
- {
- auto* env = getEnv();
-
- LocalRef<jobject> packageManager (env->CallObjectMethod (getAppContext().get(), AndroidContext.getPackageManager));
-
- constexpr int getProviders = 8;
- auto packageInfo = LocalRef<jobject> (env->CallObjectMethod (packageManager,
- AndroidPackageManager.getPackageInfo,
- javaString (packageName).get(),
- getProviders));
- auto providers = LocalRef<jobjectArray> ((jobjectArray) env->GetObjectField (packageInfo,
- AndroidPackageInfo.providers));
-
- if (providers == nullptr)
- return false;
-
- auto sharingContentProviderAuthority = packageName + ".sharingcontentprovider";
- const int numProviders = env->GetArrayLength (providers.get());
-
- for (int i = 0; i < numProviders; ++i)
- {
- auto providerInfo = LocalRef<jobject> (env->GetObjectArrayElement (providers, i));
- auto authority = LocalRef<jstring> ((jstring) env->GetObjectField (providerInfo,
- AndroidProviderInfo.authority));
-
- if (juceString (authority) == sharingContentProviderAuthority)
- return true;
- }
-
- return false;
- }
-
- void handleAsyncUpdate() override
- {
- jassert (prepareFilesThread != nullptr);
-
- if (prepareFilesThread == nullptr)
- return;
-
- filesPrepared (prepareFilesThread->getResultFileUris(), prepareFilesThread->getMimeTypes());
- }
-
- void filesPrepared (jobject fileUris, const StringArray& mimeTypes)
- {
- auto* env = getEnv();
-
- auto intent = LocalRef<jobject> (env->NewObject (AndroidIntent, AndroidIntent.constructor));
- env->CallObjectMethod (intent, AndroidIntent.setAction,
- javaString ("android.intent.action.SEND_MULTIPLE").get());
-
- env->CallObjectMethod (intent, AndroidIntent.setType,
- javaString (getCommonMimeType (mimeTypes)).get());
-
- constexpr int grantReadPermission = 1;
- env->CallObjectMethod (intent, AndroidIntent.setFlags, grantReadPermission);
-
- env->CallObjectMethod (intent, AndroidIntent.putParcelableArrayListExtra,
- javaString ("android.intent.extra.STREAM").get(),
- fileUris);
-
- auto chooserIntent = LocalRef<jobject> (env->CallStaticObjectMethod (AndroidIntent,
- AndroidIntent.createChooser,
- intent.get(),
- javaString ("Choose share target").get()));
- WeakReference<ContentSharerNativeImpl> weakRef (this);
-
- startAndroidActivityForResult (chooserIntent, 1003,
- [weakRef] (int /*requestCode*/, int resultCode, LocalRef<jobject> /*intentData*/) mutable
- {
- if (weakRef != nullptr)
- weakRef->sharingFinished (resultCode);
- });
- }
-
- void decrementPendingFileCountAndNotifyOwnerIfReady()
- {
- --nonAssetFilesPendingShare;
-
- notifyOwnerIfReady();
- }
-
- void notifyOwnerIfReady()
- {
- if (sharingActivityDidFinish && nonAssetFilesPendingShare.get() == 0)
- owner.sharingFinished (succeeded, {});
- }
-
- void timerCallback() override
- {
- stopTimer();
-
- notifyOwnerIfReady();
- }
-
- //==============================================================================
- struct ContentUriElements
- {
- String index;
- String filename;
- String filepath;
- };
-
- ContentUriElements getContentUriElements (JNIEnv* env, const LocalRef<jobject>& uri) const
- {
- jassert (prepareFilesThread != nullptr);
-
- if (prepareFilesThread == nullptr)
- return {};
-
- auto fullUri = juceString ((jstring) env->CallObjectMethod (uri.get(), AndroidUri.toString));
-
- auto index = fullUri.fromFirstOccurrenceOf (uriBase, false, false)
- .upToFirstOccurrenceOf ("/", false, true);
-
- auto filename = fullUri.fromLastOccurrenceOf ("/", false, true);
-
- return { index, filename, prepareFilesThread->getFilePaths()[index.getIntValue()] };
- }
-
- static StringArray getSupportedColumns()
- {
- return StringArray ("_display_name", "_size");
- }
-
- jobject getAssetFileDescriptor (JNIEnv* env, const LocalRef<jobject>& contentProvider,
- const String& filepath)
- {
- // This function can be called from multiple threads.
- {
- const ScopedLock sl (nonAssetFileOpenLock);
-
- if (! nonAssetFilePathsPendingShare.contains (filepath))
- {
- nonAssetFilePathsPendingShare.add (filepath);
- ++nonAssetFilesPendingShare;
-
- nonAssetFileObservers.add (new AndroidContentSharerFileObserver (*this, env,
- contentProvider,
- filepath));
- }
- }
-
- auto javaFile = LocalRef<jobject> (env->NewObject (JavaFile, JavaFile.constructor,
- javaString (filepath).get()));
-
- constexpr int modeReadOnly = 268435456;
- auto parcelFileDescriptor = LocalRef<jobject> (env->CallStaticObjectMethod (ParcelFileDescriptor,
- ParcelFileDescriptor.open,
- javaFile.get(), modeReadOnly));
-
- if (jniCheckHasExceptionOccurredAndClear())
- {
- // Failed to create file descriptor. Have you provided a valid file path/resource name?
- jassertfalse;
- return nullptr;
- }
-
- jlong startOffset = 0;
- jlong unknownLength = -1;
-
- assetFileDescriptors.add (GlobalRef (LocalRef<jobject> (env->NewObject (AssetFileDescriptor,
- AssetFileDescriptor.constructor,
- parcelFileDescriptor.get(),
- startOffset, unknownLength))));
-
- return assetFileDescriptors.getReference (assetFileDescriptors.size() - 1).get();
- }
-
- StringArray filterMimeTypes (const StringArray& mimeTypes, const String& filter)
- {
- String filterToUse (filter.removeCharacters ("*"));
-
- if (filterToUse.isEmpty() || filterToUse == "/")
- return mimeTypes;
-
- StringArray result;
-
- for (const auto& type : mimeTypes)
- if (String (type).contains (filterToUse))
- result.add (type);
-
- return result;
- }
-
- String getCommonMimeType (const StringArray& mimeTypes)
- {
- if (mimeTypes.isEmpty())
- return "*/*";
-
- auto commonMime = mimeTypes[0];
- bool lookForCommonGroup = false;
-
- for (int i = 1; i < mimeTypes.size(); ++i)
- {
- if (mimeTypes[i] == commonMime)
- continue;
-
- if (! lookForCommonGroup)
- {
- lookForCommonGroup = true;
- commonMime = commonMime.upToFirstOccurrenceOf ("/", true, false);
- }
-
- if (! mimeTypes[i].startsWith (commonMime))
- return "*/*";
- }
-
- return lookForCommonGroup ? commonMime + "*" : commonMime;
- }
-
- ContentSharer& owner;
- String packageName;
- String uriBase;
-
- std::unique_ptr<AndroidContentSharerPrepareFilesThread> prepareFilesThread;
-
- bool succeeded = false;
- String errorDescription;
-
- bool sharingActivityDidFinish = false;
-
- OwnedArray<AndroidContentSharerCursor> cursors;
-
- Array<GlobalRef> assetFileDescriptors;
-
- CriticalSection nonAssetFileOpenLock;
- StringArray nonAssetFilePathsPendingShare;
- Atomic<int> nonAssetFilesPendingShare { 0 };
- OwnedArray<AndroidContentSharerFileObserver> nonAssetFileObservers;
-
- WeakReference<ContentSharerNativeImpl>::Master masterReference;
- friend class WeakReference<ContentSharerNativeImpl>;
-
- //==============================================================================
- #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
- CALLBACK (contentSharerQuery, "contentSharerQuery", "(Landroid/net/Uri;[Ljava/lang/String;)Landroid/database/Cursor;") \
- CALLBACK (contentSharerOpenFile, "contentSharerOpenFile", "(Landroid/net/Uri;Ljava/lang/String;)Landroid/content/res/AssetFileDescriptor;") \
- CALLBACK (contentSharerGetStreamTypes, "contentSharerGetStreamTypes", "(Landroid/net/Uri;Ljava/lang/String;)[Ljava/lang/String;") \
-
- DECLARE_JNI_CLASS_WITH_MIN_SDK (JuceSharingContentProvider, "com/roli/juce/JuceSharingContentProvider", 16)
- #undef JNI_CLASS_MEMBERS
-
- static jobject JNICALL contentSharerQuery (JNIEnv*, jobject contentProvider, jobject uri, jobjectArray projection)
- {
- if (auto *pimpl = (ContentSharer::ContentSharerNativeImpl *) ContentSharer::getInstance ()->pimpl.get ())
- return pimpl->query (LocalRef<jobject> (static_cast<jobject> (contentProvider)),
- LocalRef<jobject> (static_cast<jobject> (uri)),
- LocalRef<jobjectArray> (static_cast<jobjectArray> (projection)));
-
- return nullptr;
- }
-
- static jobject JNICALL contentSharerOpenFile (JNIEnv*, jobject contentProvider, jobject uri, jstring mode)
- {
- if (auto* pimpl = (ContentSharer::ContentSharerNativeImpl*) ContentSharer::getInstance()->pimpl.get())
- return pimpl->openFile (LocalRef<jobject> (static_cast<jobject> (contentProvider)),
- LocalRef<jobject> (static_cast<jobject> (uri)),
- LocalRef<jstring> (static_cast<jstring> (mode)));
-
- return nullptr;
- }
-
- static jobjectArray JNICALL contentSharerGetStreamTypes (JNIEnv*, jobject /*contentProvider*/, jobject uri, jstring mimeTypeFilter)
- {
- if (auto* pimpl = (ContentSharer::ContentSharerNativeImpl*) ContentSharer::getInstance()->pimpl.get())
- return pimpl->getStreamTypes (LocalRef<jobject> (static_cast<jobject> (uri)),
- LocalRef<jstring> (static_cast<jstring> (mimeTypeFilter)));
-
- return nullptr;
- }
- };
-
- //==============================================================================
- ContentSharer::Pimpl* ContentSharer::createPimpl()
- {
- return new ContentSharerNativeImpl (*this);
- }
-
- ContentSharer::ContentSharerNativeImpl::JuceSharingContentProvider_Class ContentSharer::ContentSharerNativeImpl::JuceSharingContentProvider;
-
- } // namespace juce
|