|  | /*
  ==============================================================================
   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.
   JUCE is an open source library subject to commercial or open-source
   licensing.
   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).
   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy
   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).
   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
{
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
 FIELD (providers, "providers", "[Landroid/content/pm/ProviderInfo;")
DECLARE_JNI_CLASS (AndroidPackageInfo, "android/content/pm/PackageInfo");
#undef JNI_CLASS_MEMBERS
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
 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) \
 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) \
 METHOD (close, "close", "()V")
DECLARE_JNI_CLASS (JavaCloseable, "java/io/Closeable");
#undef JNI_CLASS_MEMBERS
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
 METHOD (constructor,   "<init>",        "(L" JUCE_ANDROID_SHARING_CONTENT_PROVIDER_CLASSPATH ";JLjava/lang/String;I)V") \
 METHOD (startWatching, "startWatching", "()V") \
 METHOD (stopWatching,  "stopWatching",  "()V")
DECLARE_JNI_CLASS (JuceContentProviderFileObserver, JUCE_ANDROID_SHARING_CONTENT_PROVIDER_CLASSPATH "$ProviderFileObserver");
#undef JNI_CLASS_MEMBERS
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
 METHOD (addRow,      "addRow", "([Ljava/lang/Object;)V") \
 METHOD (constructor, "<init>", "(L" JUCE_ANDROID_SHARING_CONTENT_PROVIDER_CLASSPATH ";J[Ljava/lang/String;)V")
DECLARE_JNI_CLASS (JuceContentProviderFileObserverCursor, JUCE_ANDROID_SHARING_CONTENT_PROVIDER_CLASSPATH "$ProviderCursor");
#undef JNI_CLASS_MEMBERS
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
 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 (JuceContentProviderFileObserverCursor,
                                                                JuceContentProviderFileObserverCursor.constructor,
                                                                contentProvider.get(),
                                                                reinterpret_cast<jlong> (this),
                                                                resultColumns.get()))))
    {
        // the content provider must be created first
        jassert (contentProvider.get() != 0);
    }
    jobject getNativeCursor() { return cursor.get(); }
    void cursorClosed()
    {
        MessageManager::callAsync ([this] { owner.cursorClosed (*this); });
    }
private:
    Owner& owner;
    GlobalRef cursor;
};
//==============================================================================
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,
                                                                      contentProvider.get(),
                                                                      reinterpret_cast<jlong> (this),
                                                                      javaString (filepath).get(),
                                                                      open | access | closeWrite | closeNoWrite))))
    {
        // the content provider must be created first
        jassert (contentProvider.get() != 0);
        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;
};
//==============================================================================
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()
    {
        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 (jobject streamToUse)
            : stream (GlobalRef (streamToUse))
        {
        }
        ~StreamCloser()
        {
            if (stream.get() != 0)
                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 (android.activity, JuceAppActivity.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 extention.
        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;
        while (true)
        {
            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 (android.activity,
                                                                                            JuceAppActivity.getPackageName)))),
          uriBase ("content://" + packageName + ".sharingcontentprovider/")
    {
    }
    ~ContentSharerNativeImpl()
    {
        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()));
        env->CallVoidMethod (android.activity, JuceAppActivity.startActivityForResult, chooserIntent.get(), 1003);
    }
    //==============================================================================
    void cursorClosed (const AndroidContentSharerCursor& cursor) override
    {
        cursors.removeObject (&cursor);
    }
    void fileHandleClosed (const AndroidContentSharerFileObserver&) override
    {
        decrementPendingFileCountAndNotifyOwnerIfReady();
    }
    //==============================================================================
    void* 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);
    }
    void* query (const LocalRef<jobject>& contentProvider, const LocalRef<jobject>& uri,
                 const LocalRef<jobjectArray>& projection, const LocalRef<jobject>& selection,
                 const LocalRef<jobjectArray>& selectionArgs, const LocalRef<jobject>& sortOrder)
    {
        ignoreUnused (selection, selectionArgs, sortOrder);
        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, 0));
        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));
            }
        }
        auto nativeCursor = cursor->getNativeCursor();
        env->CallVoidMethod (nativeCursor, JuceContentProviderFileObserverCursor.addRow, values.get());
        return nativeCursor;
    }
    void* 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();
        auto packageManager = LocalRef<jobject> (env->CallObjectMethod (android.activity,
                                                                        JuceAppActivity.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()));
        env->CallVoidMethod (android.activity, JuceAppActivity.startActivityForResult, chooserIntent.get(), 1003);
    }
    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");
    }
    void* 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();
    }
    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>;
};
//==============================================================================
ContentSharer::Pimpl* ContentSharer::createPimpl()
{
    return new ContentSharerNativeImpl (*this);
}
//==============================================================================
void* juce_contentSharerQuery (void* contentProvider, void* uri, void* projection,
                               void* selection, void* selectionArgs, void* sortOrder)
{
    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)),
                         LocalRef<jobject>      (static_cast<jobject> (selection)),
                         LocalRef<jobjectArray> (static_cast<jobjectArray> (selectionArgs)),
                         LocalRef<jobject>      (static_cast<jobject> (sortOrder)));
}
void* juce_contentSharerOpenFile (void* contentProvider, void* uri, void* mode)
{
    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)));
}
void juce_contentSharingCompleted (int resultCode)
{
    auto* pimpl = (ContentSharer::ContentSharerNativeImpl*) ContentSharer::getInstance()->pimpl.get();
    return pimpl->sharingFinished (resultCode);
}
void* juce_contentSharerGetStreamTypes (void* uri, void* mimeTypeFilter)
{
    auto* pimpl = (ContentSharer::ContentSharerNativeImpl*) ContentSharer::getInstance()->pimpl.get();
    return pimpl->getStreamTypes (LocalRef<jobject> (static_cast<jobject> (uri)),
                                  LocalRef<jstring> (static_cast<jstring> (mimeTypeFilter)));
}
//==============================================================================
JUCE_JNI_CALLBACK (JUCE_ANDROID_SHARING_CONTENT_PROVIDER_CLASSNAME, contentSharerFileObserverEvent, void,
                   (JNIEnv* env, jobject /*fileObserver*/, jlong host, int event, jstring path))
{
    setEnv (env);
    reinterpret_cast<AndroidContentSharerFileObserver*> (host)->onFileEvent (event, LocalRef<jstring> (path));
}
//==============================================================================
JUCE_JNI_CALLBACK (JUCE_ANDROID_SHARING_CONTENT_PROVIDER_CLASSNAME, contentSharerQuery, jobject,
                   (JNIEnv* env, jobject contentProvider, jobject uri, jobjectArray projection,
                    jobject selection, jobjectArray selectionArgs, jobject sortOrder))
{
    setEnv (env);
    return (jobject) juce_contentSharerQuery (contentProvider, uri, projection, selection, selectionArgs, sortOrder);
}
JUCE_JNI_CALLBACK (JUCE_ANDROID_SHARING_CONTENT_PROVIDER_CLASSNAME, contentSharerCursorClosed, void,
                   (JNIEnv* env, jobject /*cursor*/, jlong host))
{
    setEnv (env);
    reinterpret_cast<AndroidContentSharerCursor*> (host)->cursorClosed();
}
JUCE_JNI_CALLBACK (JUCE_ANDROID_SHARING_CONTENT_PROVIDER_CLASSNAME, contentSharerOpenFile, jobject,
                   (JNIEnv* env, jobject contentProvider, jobject uri, jstring mode))
{
    setEnv (env);
    return (jobject) juce_contentSharerOpenFile ((void*) contentProvider, (void*) uri, (void*) mode);
}
JUCE_JNI_CALLBACK (JUCE_ANDROID_SHARING_CONTENT_PROVIDER_CLASSNAME, contentSharerGetStreamTypes, jobject,
                   (JNIEnv* env, jobject /*contentProvider*/, jobject uri, jstring mimeTypeFilter))
{
    setEnv (env);
    return (jobject) juce_contentSharerGetStreamTypes ((void*) uri, (void*) mimeTypeFilter);
}
} // namespace juce
 |