From 51b95a0117ef5f669866f665ff7ab3d2aa2a9637 Mon Sep 17 00:00:00 2001 From: Meghan Date: Mon, 27 Sep 2021 11:58:12 +0200 Subject: [PATCH] New Open File and File Chooser service Not tested on IOS. Adapted from other Gluon Attach Services. Requires pull request to gluonfx-maven-plugin to also be approved where OPEN and FILE_CHOOSER, have been added to the enum list in gluonfx-maven-plugin-master\src\main\java\com\gluonhq\attach\AttachService.java --- modules/file-chooser/build.gradle | 6 + .../filechooser/FileChooserService.java | 127 ++++++++ .../impl/AndroidFileChooserService.java | 133 ++++++++ .../impl/DummyFileChooserService.java | 33 ++ .../impl/IOSFileChooserService.java | 93 ++++++ .../attach/filechooser/package-info.java | 33 ++ .../src/main/java/module-info.java | 34 +++ .../src/main/native/android/c/filechooser.c | 102 +++++++ .../dalvik/DalvikFileChooserService.java | 167 +++++++++++ .../src/main/native/ios/FileChooser.h | 38 +++ .../src/main/native/ios/FileChooser.m | 283 ++++++++++++++++++ .../config/jniconfig-aarch64-android.json | 6 + .../substrate/config/jniconfig-arm64-ios.json | 6 + .../reflectionconfig-aarch64-android.json | 6 + .../config/reflectionconfig-arm64-ios.json | 6 + .../substrate/dalvik/AndroidManifest.xml | 15 + .../dalvik/res/xml/file_provider_paths.xml | 5 + modules/open/build.gradle | 6 + .../com/gluonhq/attach/open/OpenService.java | 143 +++++++++ .../attach/open/impl/AndroidOpenService.java | 135 +++++++++ .../attach/open/impl/DummyOpenService.java | 34 +++ .../attach/open/impl/IOSOpenService.java | 76 +++++ .../com/gluonhq/attach/open/package-info.java | 33 ++ modules/open/src/main/java/module-info.java | 34 +++ modules/open/src/main/native/android/c/open.c | 102 +++++++ .../android/dalvik/DalvikOpenService.java | 112 +++++++ modules/open/src/main/native/ios/Open.h | 39 +++ modules/open/src/main/native/ios/Open.m | 179 +++++++++++ .../reflectionconfig-aarch64-android.json | 6 + .../config/reflectionconfig-arm64-ios.json | 6 + .../substrate/dalvik/AndroidManifest.xml | 14 + .../dalvik/res/xml/file_provider_paths.xml | 4 + settings.gradle | 5 + 33 files changed, 2021 insertions(+) create mode 100644 modules/file-chooser/build.gradle create mode 100644 modules/file-chooser/src/main/java/com/gluonhq/attach/filechooser/FileChooserService.java create mode 100644 modules/file-chooser/src/main/java/com/gluonhq/attach/filechooser/impl/AndroidFileChooserService.java create mode 100644 modules/file-chooser/src/main/java/com/gluonhq/attach/filechooser/impl/DummyFileChooserService.java create mode 100644 modules/file-chooser/src/main/java/com/gluonhq/attach/filechooser/impl/IOSFileChooserService.java create mode 100644 modules/file-chooser/src/main/java/com/gluonhq/attach/filechooser/package-info.java create mode 100644 modules/file-chooser/src/main/java/module-info.java create mode 100644 modules/file-chooser/src/main/native/android/c/filechooser.c create mode 100644 modules/file-chooser/src/main/native/android/dalvik/DalvikFileChooserService.java create mode 100644 modules/file-chooser/src/main/native/ios/FileChooser.h create mode 100644 modules/file-chooser/src/main/native/ios/FileChooser.m create mode 100644 modules/file-chooser/src/main/resources/META-INF/substrate/config/jniconfig-aarch64-android.json create mode 100644 modules/file-chooser/src/main/resources/META-INF/substrate/config/jniconfig-arm64-ios.json create mode 100644 modules/file-chooser/src/main/resources/META-INF/substrate/config/reflectionconfig-aarch64-android.json create mode 100644 modules/file-chooser/src/main/resources/META-INF/substrate/config/reflectionconfig-arm64-ios.json create mode 100644 modules/file-chooser/src/main/resources/META-INF/substrate/dalvik/AndroidManifest.xml create mode 100644 modules/file-chooser/src/main/resources/META-INF/substrate/dalvik/res/xml/file_provider_paths.xml create mode 100644 modules/open/build.gradle create mode 100644 modules/open/src/main/java/com/gluonhq/attach/open/OpenService.java create mode 100644 modules/open/src/main/java/com/gluonhq/attach/open/impl/AndroidOpenService.java create mode 100644 modules/open/src/main/java/com/gluonhq/attach/open/impl/DummyOpenService.java create mode 100644 modules/open/src/main/java/com/gluonhq/attach/open/impl/IOSOpenService.java create mode 100644 modules/open/src/main/java/com/gluonhq/attach/open/package-info.java create mode 100644 modules/open/src/main/java/module-info.java create mode 100644 modules/open/src/main/native/android/c/open.c create mode 100644 modules/open/src/main/native/android/dalvik/DalvikOpenService.java create mode 100644 modules/open/src/main/native/ios/Open.h create mode 100644 modules/open/src/main/native/ios/Open.m create mode 100644 modules/open/src/main/resources/META-INF/substrate/config/reflectionconfig-aarch64-android.json create mode 100644 modules/open/src/main/resources/META-INF/substrate/config/reflectionconfig-arm64-ios.json create mode 100644 modules/open/src/main/resources/META-INF/substrate/dalvik/AndroidManifest.xml create mode 100644 modules/open/src/main/resources/META-INF/substrate/dalvik/res/xml/file_provider_paths.xml diff --git a/modules/file-chooser/build.gradle b/modules/file-chooser/build.gradle new file mode 100644 index 00000000..3b82c159 --- /dev/null +++ b/modules/file-chooser/build.gradle @@ -0,0 +1,6 @@ +dependencies { + implementation project(":util") +} + +ext.moduleName = 'com.gluonhq.attach.filechooser' +ext.description = 'Common API to access file features' \ No newline at end of file diff --git a/modules/file-chooser/src/main/java/com/gluonhq/attach/filechooser/FileChooserService.java b/modules/file-chooser/src/main/java/com/gluonhq/attach/filechooser/FileChooserService.java new file mode 100644 index 00000000..695f0f04 --- /dev/null +++ b/modules/file-chooser/src/main/java/com/gluonhq/attach/filechooser/FileChooserService.java @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2016, 2019 Gluon + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.gluonhq.attach.filechooser; + +import java.io.File; +import java.util.Optional; + +import com.gluonhq.attach.util.Services; + +/** + * The picture service allows the developer to load a picture from the device's local file + * system or from a picture taken directly using the device's camera. + * + *

Example

+ *
+ * {@code ImageView imageView = new ImageView();
+ *  PicturesService.create().ifPresent(service -> {
+ *      service.takePhoto(false).ifPresent(image -> imageView.setImage(image));
+ *  });}
+ * + *

It also allows the developer to retrieve the original file and work with it + * as needed, for instance sharing it with the ShareService.

+ * + *

Example

+ *
+ * {@code ImageView imageView = new ImageView();
+ *  PicturesService.create().ifPresent(service -> {
+ *      service.loadImageFromGallery().ifPresent(image -> imageView.setImage(image));
+ *      service.getImageFile().ifPresent(file ->
+ *          ShareService.create().ifPresent(share ->
+ *              share.share("image/jpeg", file)));
+ *  });}
+ * + *

Android Configuration

+ * + *

Create the file {@code /src/android/res/xml/file_provider_paths.xml} with + * the following content that allows access to the external storage:

+ *
+ * {@code
+ *    
+ *    
+ *        
+ *    
+ * }
+ * 
+ * + *

The permission android.permission.CAMERA needs to be added as well as the permissions + * android.permission.READ_EXTERNAL_STORAGE and android.permission.WRITE_EXTERNAL_STORAGE + * to be able to read and write images. Also a {@code provider} is required:

+ * + * Note: these modifications are handled automatically by Client plugin if it is used. + *
+ * {@code 
+ *    
+ *    
+ *    
+ *    
+ *       ...
+ *       
+ *       
+ *           
+ *       
+ *   
+ * }
+ * 
+ * + * + *

iOS Configuration

+ *

The following keys are required:

+ *
+ * {@code NSCameraUsageDescription
+ *  Reason to use Camera Service (iOS 10+)
+ *  NSPhotoLibraryUsageDescription
+ *  Reason to use Photo Library (iOS 10+)
+ *  NSPhotoLibraryAddUsageDescription
+ *  Reason to use Photo Library (iOS 10+)}
+ * + * @since 3.0.0 + */ +public interface FileChooserService { + + /** + * Returns an instance of {@link FileChooserService}. + * @return An instance of {@link FileChooserService}. + */ + static Optional create() { + return Services.get(FileChooserService.class); + } + + /** + * Retrieve an image from the device's gallery of images + * @return an Optional with the Image or empty if it failed or it was cancelled + */ + Optional loadFile(); + +} diff --git a/modules/file-chooser/src/main/java/com/gluonhq/attach/filechooser/impl/AndroidFileChooserService.java b/modules/file-chooser/src/main/java/com/gluonhq/attach/filechooser/impl/AndroidFileChooserService.java new file mode 100644 index 00000000..b6b89b21 --- /dev/null +++ b/modules/file-chooser/src/main/java/com/gluonhq/attach/filechooser/impl/AndroidFileChooserService.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2016, 2020, Gluon + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.gluonhq.attach.filechooser.impl; + +import com.gluonhq.attach.filechooser.FileChooserService; +import javafx.application.Platform; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; + +import java.io.File; +import java.util.Optional; +import java.util.logging.Logger; + +/** + *

Create the file {@code /src/android/res/xml/file_provider_paths.xml} with + * the following content that allows access to the external storage or to + * a temporal cache in case the file is not saved:

+ * + *
+ * {@code
+ *    
+ *    
+ *        
+ *        
+ *    
+ * }
+ * 
+ * + *

The permission android.permission.CAMERA needs to be added as well as the permissions + * android.permission.READ_EXTERNAL_STORAGE and android.permission.WRITE_EXTERNAL_STORAGE + * to be able to read and write images. Also a {@code provider} is required:

+ *
+ * {@code 
+ *    
+ *    
+ *    
+ *    
+ *       ...
+ *       
+ *       
+ *           
+ *       
+ *   
+ * }
+ * 
+ */ +public class AndroidFileChooserService implements FileChooserService { + + private static final Logger LOG = Logger.getLogger(AndroidFileChooserService.class.getName()); + + static { + System.loadLibrary("filechooser"); + } + + private static final ObjectProperty selectedFile = new SimpleObjectProperty<>(); + private static ObjectProperty result; + + + public AndroidFileChooserService() { + } + + @Override + public Optional loadFile() { + LOG.severe("Load image from gallery has been called."); + result = new SimpleObjectProperty<>(); + selectFile(); + try { + Platform.enterNestedEventLoop(result); + } catch (Exception e) { + LOG.severe("GalleryActivity: enterNestedEventLoop failed: " + e); + } + + return Optional.ofNullable(result.get()); + + } + + // native + public static native void selectFile(); + + // callback + public static void setResult(String filePath, int rotate) { + File file = new File(filePath); + selectedFile.set(file); + if (selectedFile.get() == null) { + result.set(selectedFile.get()); + } + final File finalImage = selectedFile.get(); + LOG.severe("Final File:"); + LOG.severe(finalImage.getAbsolutePath()); + Platform.runLater(() -> { + if (finalImage != null) { + result.set(selectedFile.get()); + } + try { + Platform.exitNestedEventLoop(result, null); + } catch (Exception e) { + LOG.severe("GalleryActivity: exitNestedEventLoop failed: " + e); + } + }); + } + +} diff --git a/modules/file-chooser/src/main/java/com/gluonhq/attach/filechooser/impl/DummyFileChooserService.java b/modules/file-chooser/src/main/java/com/gluonhq/attach/filechooser/impl/DummyFileChooserService.java new file mode 100644 index 00000000..75b677ad --- /dev/null +++ b/modules/file-chooser/src/main/java/com/gluonhq/attach/filechooser/impl/DummyFileChooserService.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2019, Gluon + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.gluonhq.attach.filechooser.impl; + +import com.gluonhq.attach.filechooser.FileChooserService; + +public abstract class DummyFileChooserService implements FileChooserService { +} diff --git a/modules/file-chooser/src/main/java/com/gluonhq/attach/filechooser/impl/IOSFileChooserService.java b/modules/file-chooser/src/main/java/com/gluonhq/attach/filechooser/impl/IOSFileChooserService.java new file mode 100644 index 00000000..9ac2c4d2 --- /dev/null +++ b/modules/file-chooser/src/main/java/com/gluonhq/attach/filechooser/impl/IOSFileChooserService.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2016, 2019, Gluon + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.gluonhq.attach.filechooser.impl; + +import com.gluonhq.attach.filechooser.FileChooserService; +import javafx.application.Platform; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.scene.image.Image; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.util.Base64; +import java.util.Optional; + +/** + * Note:Since iOS 10 requires {@code NSCameraUsageDescription}, + * {@code NSPhotoLibraryUsageDescription} and + * {@code NSPhotoLibraryAddUsageDescription} in pList. + */ +public class IOSFileChooserService implements FileChooserService { + + static { + System.loadLibrary("FileChooser"); + initFiles(); + } + + private static final ObjectProperty selectedFile = new SimpleObjectProperty<>(); + private static ObjectProperty result; + + @Override + public Optional loadFile() { + result = new SimpleObjectProperty<>(); + selectFile(); + try { + Platform.enterNestedEventLoop(result); + } catch (Exception e) { + System.out.println("GalleryActivity: enterNestedEventLoop failed: " + e); + } + return Optional.ofNullable(result.get()); + } + + // native + private static native void initFiles(); // init IDs for java callbacks from native + public static native void selectFile(); + + // callback + // Still needs some changes/adaptions to work for all files instead of just images. + public static void setResult(String v, String filePath) { + if (v != null && !v.isEmpty()) { + try { + byte[] fileBytes = Base64.getDecoder().decode(v.replaceAll("\\s+", "").getBytes()); + selectedFile.set(new File(filePath)); + Image temp = new Image(new ByteArrayInputStream(fileBytes)); + result.set(new File(temp.getUrl())); + } catch (Exception ex) { + System.err.println("Error setResult: " + ex); + } + } + Platform.runLater(() -> { + try { + Platform.exitNestedEventLoop(result, null); + } catch (Exception e) { + System.out.println("GalleryActivity: exitNestedEventLoop failed: " + e); + } + }); + } +} diff --git a/modules/file-chooser/src/main/java/com/gluonhq/attach/filechooser/package-info.java b/modules/file-chooser/src/main/java/com/gluonhq/attach/filechooser/package-info.java new file mode 100644 index 00000000..b3738459 --- /dev/null +++ b/modules/file-chooser/src/main/java/com/gluonhq/attach/filechooser/package-info.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2016, 2019 Gluon + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * Primary API package for Attach - Pictures plugin, + * contains the interface {@link com.gluonhq.attach.filechooser.FileChooserService} and related classes. + */ +package com.gluonhq.attach.filechooser; \ No newline at end of file diff --git a/modules/file-chooser/src/main/java/module-info.java b/modules/file-chooser/src/main/java/module-info.java new file mode 100644 index 00000000..d57388b4 --- /dev/null +++ b/modules/file-chooser/src/main/java/module-info.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2019, Gluon + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +module com.gluonhq.attach.filechooser { + + requires javafx.graphics; + requires com.gluonhq.attach.util; + + exports com.gluonhq.attach.filechooser; +} \ No newline at end of file diff --git a/modules/file-chooser/src/main/native/android/c/filechooser.c b/modules/file-chooser/src/main/native/android/c/filechooser.c new file mode 100644 index 00000000..12c7014b --- /dev/null +++ b/modules/file-chooser/src/main/native/android/c/filechooser.c @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2020, 2021, Gluon + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "util.h" + +static jclass jGraalFileChooserClass; +static jmethodID jGraalSendFileMethod; + +static jclass jFileChooserServiceClass; +static jobject jDalvikFileChooserService; +static jmethodID jFileChooserServiceSelectFileMethod; + +void initializeFileChooserGraalHandles(JNIEnv *graalEnv) { + jGraalFileChooserClass = (*graalEnv)->NewGlobalRef(graalEnv, (*graalEnv)->FindClass(graalEnv, "com/gluonhq/attach/filechooser/impl/AndroidFileChooserService")); + jGraalSendFileMethod = (*graalEnv)->GetStaticMethodID(graalEnv, jGraalFileChooserClass, "setResult", "(Ljava/lang/String;I)V"); +} + +void initializeFileChooserDalvikHandles() { + jFileChooserServiceClass = GET_REGISTER_DALVIK_CLASS(jFileChooserServiceClass, "com/gluonhq/helloandroid/DalvikFileChooserService"); + ATTACH_DALVIK(); + jmethodID jFileChooserServiceInitMethod = (*dalvikEnv)->GetMethodID(dalvikEnv, jFileChooserServiceClass, "", "(Landroid/app/Activity;)V"); + jFileChooserServiceSelectFileMethod = (*dalvikEnv)->GetMethodID(dalvikEnv, jFileChooserServiceClass, "selectFile", "()V"); + + jobject jActivity = substrateGetActivity(); + jobject jtmpobj = (*dalvikEnv)->NewObject(dalvikEnv, jFileChooserServiceClass, jFileChooserServiceInitMethod, jActivity); + jDalvikFileChooserService = (*dalvikEnv)->NewGlobalRef(dalvikEnv, jtmpobj); + DETACH_DALVIK(); + +} + +////////////////////////// +// From Graal to native // +////////////////////////// + + +JNIEXPORT jint JNICALL +JNI_OnLoad_filechooser(JavaVM *vm, void *reserved) +{ +ATTACH_LOG_INFO("JNI_OnLoad_filechooser called"); +#ifdef JNI_VERSION_1_8 + JNIEnv* graalEnv; + if ((*vm)->GetEnv(vm, (void **)&graalEnv, JNI_VERSION_1_8) != JNI_OK) { + ATTACH_LOG_WARNING("Error initializing native FileChooser from OnLoad"); + return JNI_FALSE; + } + ATTACH_LOG_FINE("[FileChooser] Initializing native FileChooser from OnLoad"); + initializeFileChooserGraalHandles(graalEnv); + initializeFileChooserDalvikHandles(); + ATTACH_LOG_FINE("Initializing native FileChooser done"); + return JNI_VERSION_1_8; +#else + #error Error: Java 8+ SDK is required to compile Attach +#endif +} + +// from Java to Android + +JNIEXPORT void JNICALL Java_com_gluonhq_attach_filechooser_impl_AndroidFileChooserService_selectFile +(JNIEnv *env, jclass jClass) +{ + ATTACH_DALVIK(); + (*dalvikEnv)->CallVoidMethod(dalvikEnv, jDalvikFileChooserService, jFileChooserServiceSelectFileMethod); + DETACH_DALVIK(); +} + +/////////////////////////// +// From Dalvik to native // +/////////////////////////// + +JNIEXPORT void JNICALL Java_com_gluonhq_helloandroid_DalvikFileChooserService_sendFile(JNIEnv *env, jobject service, jstring path, jint rotate) { + ATTACH_LOG_FINE("Send File\n"); + const char *pathChars = (*env)->GetStringUTFChars(env, path, NULL); + ATTACH_GRAAL(); + jstring jpath = (*graalEnv)->NewStringUTF(graalEnv, pathChars); + (*graalEnv)->CallStaticVoidMethod(graalEnv, jGraalFileChooserClass, jGraalSendFileMethod, jpath, rotate); + DETACH_GRAAL(); +} \ No newline at end of file diff --git a/modules/file-chooser/src/main/native/android/dalvik/DalvikFileChooserService.java b/modules/file-chooser/src/main/native/android/dalvik/DalvikFileChooserService.java new file mode 100644 index 00000000..c91ff24b --- /dev/null +++ b/modules/file-chooser/src/main/native/android/dalvik/DalvikFileChooserService.java @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2020, Gluon + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.gluonhq.helloandroid; + +import android.Manifest; +import android.app.Activity; +import android.content.Intent; +import android.database.Cursor; +import android.media.ExifInterface; +import android.media.MediaScannerConnection; +import android.net.Uri; +import android.os.Build; +import android.os.Environment; +import android.provider.MediaStore; +import android.provider.OpenableColumns; +import android.util.Log; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.text.SimpleDateFormat; +import java.util.Date; + +import static android.app.Activity.RESULT_OK; + +public class DalvikFileChooserService { + + private static final String TAG = Util.TAG; + private static final int RESULT_OK = -1; + private static final int SELECT_FILE = 1; + + private final Activity activity; + private final boolean debug; + private boolean verified; + + private final String authority; + private String filePath; + + public DalvikFileChooserService(Activity activity) { + this.activity = activity; + this.debug = Util.isDebug(); + authority = activity.getPackageName() + ".fileprovider"; + clearCache(); + } + + private boolean verifyPermissions() { + if (!verified) { + verified = Util.verifyPermissions(Manifest.permission.CAMERA, + Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE); + } + return verified; + } + + private void selectFile() { + Log.v(TAG, "Select File method called."); + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + intent.setType("*/*"); + + if (activity == null) { + Log.e(TAG, "Activity not found. This service is not allowed when " + + "running in background mode or from wearable"); + return; + } + + IntentHandler intentHandler = new IntentHandler() { + @Override + public void gotActivityResult(int requestCode, int resultCode, Intent intent) { + if (requestCode == SELECT_FILE && resultCode == RESULT_OK) { + Log.v(TAG, "File successfully selected"); + Uri selectedFileUri = intent.getData(); + if (selectedFileUri != null) { + if (debug) { + Log.v(TAG, "Copy file"); + } + File cachePhotoFile = copyFile(selectedFileUri); + if (debug) { + Log.v(TAG, "Setting file: " + cachePhotoFile.getAbsolutePath()); + } + if (cachePhotoFile.exists()) { + if (debug) { + Log.v(TAG, "File located at " + cachePhotoFile.getAbsolutePath()); + } + sendFile(cachePhotoFile.getAbsolutePath(), 0); + } + } + } else { + Log.v(TAG, "File not successfully selected"); + } + } + }; + + Util.setOnActivityResultHandler(intentHandler); + + // check for permissions + if (intent.resolveActivity(activity.getPackageManager()) != null) { + activity.startActivityForResult(Intent.createChooser(intent, "Select File"), SELECT_FILE); + } else { + Log.e(TAG, "GalleryActivity: resolveActivity failed"); + } + } + + private void clearCache() { // Adapted for all files + File[] files = activity.getCacheDir().listFiles(); + for (File file : files) { + file.delete(); + } + } + + private String getFileName(Uri uri) { + try (Cursor cursor = activity.getContentResolver().query(uri, null, null, null, null, null)) { + if (cursor != null && cursor.moveToFirst()) { + return cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)); + } + } + return "image.jpg"; + } + + private File copyFile(Uri uri) { + File selectedFile = new File(activity.getCacheDir(), getFileName(uri)); + try (InputStream is = activity.getContentResolver().openInputStream(uri); + OutputStream os = new FileOutputStream(selectedFile)) { + byte[] buffer = new byte[8 * 1024]; + int bytesRead; + while ((bytesRead = is.read(buffer)) != -1) { + os.write(buffer, 0, bytesRead); + } + } catch (FileNotFoundException ex) { + Log.e(TAG, "FILE NOT FOUND", ex); + } catch (IOException ex) { + Log.e(TAG, "IO ISSUES", ex); + } + return selectedFile; + } + + // native + private native void sendFile(String filePath, int rotate); + +} diff --git a/modules/file-chooser/src/main/native/ios/FileChooser.h b/modules/file-chooser/src/main/native/ios/FileChooser.h new file mode 100644 index 00000000..53b54ee7 --- /dev/null +++ b/modules/file-chooser/src/main/native/ios/FileChooser.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2016, 2019, Gluon + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import +#include "jni.h" +#include "AttachMacros.h" + +@interface Pictures : UIViewController {} +// - (void) takePicture; + - (void) selectFile; +@end + +void sendFileChooserResult(NSString *picResult, NSString *picPath); \ No newline at end of file diff --git a/modules/file-chooser/src/main/native/ios/FileChooser.m b/modules/file-chooser/src/main/native/ios/FileChooser.m new file mode 100644 index 00000000..e47aacbe --- /dev/null +++ b/modules/file-chooser/src/main/native/ios/FileChooser.m @@ -0,0 +1,283 @@ +/* + * Copyright (c) 2016, 2019, Gluon + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "FileChooser.h" + +JNIEnv *env; + +JNIEXPORT jint JNICALL +JNI_OnLoad_filechooser(JavaVM *vm, void *reserved) +{ +#ifdef JNI_VERSION_1_8 + //min. returned JNI_VERSION required by JDK8 for builtin libraries + if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_8) != JNI_OK) { + return JNI_VERSION_1_4; + } + return JNI_VERSION_1_8; +#else + return JNI_VERSION_1_4; +#endif +} + +static int filesInited = 0; + +// FileChooser +jclass mat_jFileChooserServiceClass; +jmethodID mat_jFileChooserService_setResult = 0; +FileChooser *_filechooser; +BOOL saveFile; + + +JNIEXPORT void JNICALL Java_com_gluonhq_attach_filechooser_impl_IOSFileChooserService_initFileChooser +(JNIEnv *env, jclass jClass) +{ + if (filesInited) + { + return; + } + filesInited = 1; + + mat_jFileChooserServiceClass = (*env)->NewGlobalRef(env, (*env)->FindClass(env, "com/gluonhq/attach/filechooser/impl/IOSFileChooserService")); + mat_jFileChooserService_setResult = (*env)->GetStaticMethodID(env, mat_jFileChooserServiceClass, "setResult", "(Ljava/lang/String;Ljava/lang/String;)V"); +} + +void sendFileChooserResult(NSString *fileResult, NSString *filePath) { + if (fileResult) + { + const char *fileChars = [fileResult UTF8String]; + jstring jfile = (*env)->NewStringUTF(env, fileChars); + const char *pathChars = [filePath UTF8String]; + jstring jpath = (*env)->NewStringUTF(env, pathChars); + (*env)->CallStaticVoidMethod(env, mat_jFileChooserServiceClass, mat_jFileChooserService_setResult, jfile, jpath); + (*env)->DeleteLocalRef(env, jfile); + (*env)->DeleteLocalRef(env, jpath); + AttachLog(@"Finished sending file"); + } else + { + (*env)->CallStaticVoidMethod(env, mat_jFileChooserServiceClass, mat_jFileChooserService_setResult, NULL, NULL); + } +} + +JNIEXPORT void JNICALL Java_com_gluonhq_attach_filechooser_impl_IOSFileChooserService_selectFile +(JNIEnv *env, jclass jClass) +{ + _files = [[FileChooser alloc] init]; + [_files selectFile]; + return; +} + +@implementation FileChooser + +- (void)selectFile { + if(![[UIApplication sharedApplication] keyWindow]) + { + AttachLog(@"key window was nil"); + return; + } + + NSArray *views = [[[UIApplication sharedApplication] keyWindow] subviews]; + if(![views count]) { + AttachLog(@"views size was 0"); + return; + } + + UIView *_currentView = views[0]; + + UIImagePickerController *picker = [[UIImagePickerController alloc] init]; + picker.delegate = self; + picker.allowsEditing = NO; + picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; + + [_currentView.window addSubview:picker.view]; + +} + +- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { + + AttachLog(@"Encoding and sending retrieved file"); + UIImage *originalImage = info[UIImagePickerControllerOriginalImage]; + + if (saveFile == YES) + { + AttachLog(@"Saving file..."); + UIImageWriteToSavedPhotosAlbum(originalImage, nil, nil, nil); + } + +// The original image could be too big (ie 3264x2448) and not properly rotated, +// what leads to: core.memory: GC Warning: Repeated allocation of very large block +// and even: malloc: *** mach_vm_map(size=67108864) failed (error code=3) -> NPE at +// com.sun.prism.impl.BaseGraphics.drawTexture(BaseGraphics.java) + +// Solution: limit max size to 1280x1280, and rotate properly: + + UIImage *image = [self scaleAndRotateImage:originalImage]; + + NSData *imageData = UIImagePNGRepresentation(image); + + NSString *base64StringOfImage = [imageData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength]; + + NSData *originalData = UIImagePNGRepresentation(originalImage); + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; + [formatter setDateFormat:@"yyyy-MM-dd_HH-mm-ss"]; + NSString *stringFromDate = [formatter stringFromDate:[NSDate date]]; + + NSString *filePath = [[paths objectAtIndex:0] stringByAppendingPathComponent:[NSString stringWithFormat:@"%@_%@.png",@"Image",stringFromDate]]; + [originalData writeToFile:filePath atomically:YES]; + + sendFileChooserResult(base64StringOfImage, filePath); + + [picker dismissViewControllerAnimated:YES completion:nil]; + [picker.view removeFromSuperview]; + [picker release]; + +} + +- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker { + + AttachLog(@"Camera cancelled"); + + NSString *result = nil; + sendFileChooserResult(result, result); + + [picker dismissViewControllerAnimated:YES completion:nil]; + [picker.view removeFromSuperview]; + [picker release]; + +} + +- (UIImage *)scaleAndRotateImage:(UIImage *)image +{ +// FIXME: hardcoded value, add it as a parameter + int kMaxResolution = 1280; + + CGImageRef imgRef = image.CGImage; + + CGFloat width = CGImageGetWidth(imgRef); + CGFloat height = CGImageGetHeight(imgRef); + + CGAffineTransform transform = CGAffineTransformIdentity; + CGRect bounds = CGRectMake(0, 0, width, height); + if (width > kMaxResolution || height > kMaxResolution) { + CGFloat ratio = width/height; + if (ratio > 1) { + bounds.size.width = kMaxResolution; + bounds.size.height = bounds.size.width / ratio; + } + else { + bounds.size.height = kMaxResolution; + bounds.size.width = bounds.size.height * ratio; + } + } + + CGFloat scaleRatio = bounds.size.width / width; + CGSize imageSize = CGSizeMake(CGImageGetWidth(imgRef), CGImageGetHeight(imgRef)); + CGFloat boundHeight; + UIImageOrientation orient = image.imageOrientation; + switch(orient) { + + case UIImageOrientationUp: //EXIF = 1 + transform = CGAffineTransformIdentity; + break; + + case UIImageOrientationUpMirrored: //EXIF = 2 + transform = CGAffineTransformMakeTranslation(imageSize.width, 0.0); + transform = CGAffineTransformScale(transform, -1.0, 1.0); + break; + + case UIImageOrientationDown: //EXIF = 3 + transform = CGAffineTransformMakeTranslation(imageSize.width, imageSize.height); + transform = CGAffineTransformRotate(transform, M_PI); + break; + + case UIImageOrientationDownMirrored: //EXIF = 4 + transform = CGAffineTransformMakeTranslation(0.0, imageSize.height); + transform = CGAffineTransformScale(transform, 1.0, -1.0); + break; + + case UIImageOrientationLeftMirrored: //EXIF = 5 + boundHeight = bounds.size.height; + bounds.size.height = bounds.size.width; + bounds.size.width = boundHeight; + transform = CGAffineTransformMakeTranslation(imageSize.height, imageSize.width); + transform = CGAffineTransformScale(transform, -1.0, 1.0); + transform = CGAffineTransformRotate(transform, 3.0 * M_PI / 2.0); + break; + + case UIImageOrientationLeft: //EXIF = 6 + boundHeight = bounds.size.height; + bounds.size.height = bounds.size.width; + bounds.size.width = boundHeight; + transform = CGAffineTransformMakeTranslation(0.0, imageSize.width); + transform = CGAffineTransformRotate(transform, 3.0 * M_PI / 2.0); + break; + + case UIImageOrientationRightMirrored: //EXIF = 7 + boundHeight = bounds.size.height; + bounds.size.height = bounds.size.width; + bounds.size.width = boundHeight; + transform = CGAffineTransformMakeScale(-1.0, 1.0); + transform = CGAffineTransformRotate(transform, M_PI / 2.0); + break; + + case UIImageOrientationRight: //EXIF = 8 + boundHeight = bounds.size.height; + bounds.size.height = bounds.size.width; + bounds.size.width = boundHeight; + transform = CGAffineTransformMakeTranslation(imageSize.height, 0.0); + transform = CGAffineTransformRotate(transform, M_PI / 2.0); + break; + + default: + [NSException raise:NSInternalInconsistencyException format:@"Invalid image orientation"]; + + } + + UIGraphicsBeginImageContext(bounds.size); + + CGContextRef context = UIGraphicsGetCurrentContext(); + + if (orient == UIImageOrientationRight || orient == UIImageOrientationLeft) { + CGContextScaleCTM(context, -scaleRatio, scaleRatio); + CGContextTranslateCTM(context, -height, 0); + } + else { + CGContextScaleCTM(context, scaleRatio, -scaleRatio); + CGContextTranslateCTM(context, 0, -height); + } + + CGContextConcatCTM(context, transform); + + CGContextDrawImage(UIGraphicsGetCurrentContext(), CGRectMake(0, 0, width, height), imgRef); + UIImage *imageCopy = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + + return imageCopy; +} + +@end diff --git a/modules/file-chooser/src/main/resources/META-INF/substrate/config/jniconfig-aarch64-android.json b/modules/file-chooser/src/main/resources/META-INF/substrate/config/jniconfig-aarch64-android.json new file mode 100644 index 00000000..9f9e0b90 --- /dev/null +++ b/modules/file-chooser/src/main/resources/META-INF/substrate/config/jniconfig-aarch64-android.json @@ -0,0 +1,6 @@ +[ + { + "name" : "com.gluonhq.attach.filechooser.impl.AndroidFileChooserService", + "methods":[{"name":"setResult","parameterTypes":["java.lang.String", "int"] }] + } +] \ No newline at end of file diff --git a/modules/file-chooser/src/main/resources/META-INF/substrate/config/jniconfig-arm64-ios.json b/modules/file-chooser/src/main/resources/META-INF/substrate/config/jniconfig-arm64-ios.json new file mode 100644 index 00000000..ce9cf50a --- /dev/null +++ b/modules/file-chooser/src/main/resources/META-INF/substrate/config/jniconfig-arm64-ios.json @@ -0,0 +1,6 @@ +[ + { + "name" : "com.gluonhq.attach.filechooser.impl.IOSFileChooserService", + "methods":[{"name":"setResult","parameterTypes":["java.lang.String","java.lang.String"] }] + } +] \ No newline at end of file diff --git a/modules/file-chooser/src/main/resources/META-INF/substrate/config/reflectionconfig-aarch64-android.json b/modules/file-chooser/src/main/resources/META-INF/substrate/config/reflectionconfig-aarch64-android.json new file mode 100644 index 00000000..9d8f32c8 --- /dev/null +++ b/modules/file-chooser/src/main/resources/META-INF/substrate/config/reflectionconfig-aarch64-android.json @@ -0,0 +1,6 @@ +[ + { + "name" : "com.gluonhq.attach.filechooser.impl.AndroidFileChooserService", + "methods":[{"name":"","parameterTypes":[] }] + } +] \ No newline at end of file diff --git a/modules/file-chooser/src/main/resources/META-INF/substrate/config/reflectionconfig-arm64-ios.json b/modules/file-chooser/src/main/resources/META-INF/substrate/config/reflectionconfig-arm64-ios.json new file mode 100644 index 00000000..f7764d87 --- /dev/null +++ b/modules/file-chooser/src/main/resources/META-INF/substrate/config/reflectionconfig-arm64-ios.json @@ -0,0 +1,6 @@ +[ + { + "name" : "com.gluonhq.attach.filechooser.impl.IOSFileChooserService", + "methods":[{"name":"","parameterTypes":[] }] + } +] \ No newline at end of file diff --git a/modules/file-chooser/src/main/resources/META-INF/substrate/dalvik/AndroidManifest.xml b/modules/file-chooser/src/main/resources/META-INF/substrate/dalvik/AndroidManifest.xml new file mode 100644 index 00000000..0af0a2d8 --- /dev/null +++ b/modules/file-chooser/src/main/resources/META-INF/substrate/dalvik/AndroidManifest.xml @@ -0,0 +1,15 @@ + + + + + + + + + + \ No newline at end of file diff --git a/modules/file-chooser/src/main/resources/META-INF/substrate/dalvik/res/xml/file_provider_paths.xml b/modules/file-chooser/src/main/resources/META-INF/substrate/dalvik/res/xml/file_provider_paths.xml new file mode 100644 index 00000000..37004306 --- /dev/null +++ b/modules/file-chooser/src/main/resources/META-INF/substrate/dalvik/res/xml/file_provider_paths.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/modules/open/build.gradle b/modules/open/build.gradle new file mode 100644 index 00000000..71864f98 --- /dev/null +++ b/modules/open/build.gradle @@ -0,0 +1,6 @@ +dependencies { + implementation project(':util') +} + +ext.moduleName = 'com.gluonhq.attach.open' +ext.description = 'Common API to access opening features' \ No newline at end of file diff --git a/modules/open/src/main/java/com/gluonhq/attach/open/OpenService.java b/modules/open/src/main/java/com/gluonhq/attach/open/OpenService.java new file mode 100644 index 00000000..ccfa77ac --- /dev/null +++ b/modules/open/src/main/java/com/gluonhq/attach/open/OpenService.java @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2017, 2019 Gluon + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.gluonhq.attach.open; + +import com.gluonhq.attach.util.Services; + +import java.io.File; +import java.util.Optional; + +/** + * + * The OpenService provides a way to open content (text and/or files) from the + * current app by using other suitable apps existing in the user device. + * + *

Example

+ *
+ * {@code OpenService.create().ifPresent(service -> {
+ *      service.open("This is the subject", "This is the content of the message");
+ *  });}
+ * + * When opening files, the Attach + * StorageService can be used to create/read the file. Note that on Android, + * the file has to be located in a public folder (see + * StorageService#getPublicStorage), or opening it won't be allowed. + * + *

Example

+ *
+ * {@code
+ * File root = StorageService.create()
+ *           .flatMap(s -> s.getPublicStorage("Documents"))
+ *           .orElseThrow(() -> new RuntimeException("Documents not available"));
+ *
+ * // select or create a file within Documents folder:
+ * File file = new File(root, "myFile.txt");
+ *
+ * // open the file
+ * OpenService.create().ifPresent(service -> {
+ *      service.open("text/plain", file);
+ * });
+ * }
+ * + *

Android Configuration

+ *

Create the file {@code /src/android/res/xml/file_provider_paths.xml} with + * the following content that allows access to the external storage:

+ * + * Note: these modifications are handled automatically by Client plugin if it is used. + *
+ * {@code
+ *    
+ *    
+ *        
+ *    
+ * }
+ * 
+ * + *

And add this {@code provider} to the manifest, within the Application tag:

+ *
+ * {@code 
+ *   
+ *       
+ *           
+ *       
+ *   
+ * }
+ * 
+ * + *

iOS Configuration: nothing is required, but if the app is opening + * images to the user's local gallery, the key + * {@code NSPhotoLibraryUsageDescription} is required in the app's plist file. + * + * Other similar keys could be required as well.

+ * + * @since 3.4.0 + */ +public interface OpenService { + + /** + * Returns an instance of {@link OpenService}. + * @return An instance of {@link OpenService}. + */ + static Optional create() { + return Services.get(OpenService.class); + } + + + /** + * Allows opening a file, selecting from the suitable apps available in the + * user device. + * + * Note: On Android, the file has to be located in a public folder (see + * StorageService#getPublicStorage), or opening it won't be allowed. + * + * @param type On Android only, the MIME type of the file. It can be + * '∗/∗', but not empty. On iOS it can be null. Usual types are: + *
  • application/xml
  • + *
  • application/zip
  • + *
  • application/pdf
  • + *
  • text/css
  • + *
  • text/html
  • + *
  • text/csv
  • + *
  • text/plain
  • + *
  • image/png
  • + *
  • image/jpeg
  • + *
  • image/gif
  • + *
  • image/*
+ * @param file A valid file to be opened. + */ + void open(String type, File file); + + void open(File file); + +} diff --git a/modules/open/src/main/java/com/gluonhq/attach/open/impl/AndroidOpenService.java b/modules/open/src/main/java/com/gluonhq/attach/open/impl/AndroidOpenService.java new file mode 100644 index 00000000..8fc3a1b8 --- /dev/null +++ b/modules/open/src/main/java/com/gluonhq/attach/open/impl/AndroidOpenService.java @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2017, 2020, Gluon + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.gluonhq.attach.open.impl; + + +import com.gluonhq.attach.open.OpenService; +import com.gluonhq.attach.util.Util; + +import java.io.File; +import java.util.logging.Level; +import java.util.logging.Logger; + + +/** + *

Create the file {@code /src/android/res/xml/file_provider_paths.xml} with + * the following content that allows access to the external storage:

+ * + *
+ * {@code
+ *    
+ *    
+ *        
+ *    
+ * }
+ * 
+ * + *

Add a {@code provider} to the AndroidManifest:

+ *
+ * {@code 
+ *    
+ *       ...
+ *       
+ *       
+ *           
+ *       
+ *   
+ * }
+ * 
+ */ + +public class AndroidOpenService implements OpenService { + + private static final Logger LOGGER = Logger.getLogger(AndroidOpenService.class.getName()); + + static { + System.loadLibrary("open"); + } + + public AndroidOpenService() { + } + + /** + * Forces the specified MIME Type. + * @param type On Android only, the MIME type of the file. It can be + * '∗/∗', but not empty. On iOS it can be null. Usual types are: + *
  • application/xml
  • + *
  • application/zip
  • + *
  • application/pdf
  • + *
  • text/css
  • + *
  • text/html
  • + *
  • text/csv
  • + *
  • text/plain
  • + *
  • image/png
  • + *
  • image/jpeg
  • + *
  • image/gif
  • + *
  • image/*
+ * @param file A valid file to be opened. + */ + @Override + public void open(String type, File file) { + if (file != null && file.exists()) { + if (Util.DEBUG) { + LOGGER.log(Level.INFO, "File to open: " + file); + } + } else { + LOGGER.log(Level.SEVERE, "Error: URL not valid for file: " + file); + return; + } + if (type == null || type.isEmpty()) { + LOGGER.log(Level.SEVERE, "Error: type not valid"); + return; + } + openFileWithType(type,file.getAbsolutePath()); + } + + @Override + public void open(File file) { + if (file != null && file.exists()) { + if (Util.DEBUG) { + LOGGER.log(Level.INFO, "File to open: " + file); + } + } else { + LOGGER.log(Level.SEVERE, "Error: URL not valid for file: " + file); + return; + } + openFile(file.getAbsolutePath()); + } + + + // native + private static native void openFile(String filePath); + private static native void openFileWithType(String type, String filePath); + +} \ No newline at end of file diff --git a/modules/open/src/main/java/com/gluonhq/attach/open/impl/DummyOpenService.java b/modules/open/src/main/java/com/gluonhq/attach/open/impl/DummyOpenService.java new file mode 100644 index 00000000..ea033807 --- /dev/null +++ b/modules/open/src/main/java/com/gluonhq/attach/open/impl/DummyOpenService.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2019, Gluon + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.gluonhq.attach.open.impl; + +import com.gluonhq.attach.open.OpenService; + +public abstract class DummyOpenService implements OpenService { + +} diff --git a/modules/open/src/main/java/com/gluonhq/attach/open/impl/IOSOpenService.java b/modules/open/src/main/java/com/gluonhq/attach/open/impl/IOSOpenService.java new file mode 100644 index 00000000..a4b4f349 --- /dev/null +++ b/modules/open/src/main/java/com/gluonhq/attach/open/impl/IOSOpenService.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2017, Gluon + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.gluonhq.attach.open.impl; + + +import com.gluonhq.attach.open.OpenService; + +import java.io.File; + + +public class IOSOpenService implements OpenService { + + static { + System.loadLibrary("Open"); + initOpen(); + } + + public IOSOpenService() { + } + + @Override + public void open(File file) { + open(null, null, null, file); + } + + @Override + public void open(String type, File file) { + open(null, null, type, file); + } + + private void open(String subject, String contentText, String type, File file) { + if (subject == null) { + subject = ""; + } + if (contentText == null) { + contentText = ""; + } + if (file != null && file.exists()) { + System.out.println("File to open: " + file); + } else { + System.out.println("Error: URL not valid"); + return; + } + nativeOpen(subject, contentText, file.getAbsolutePath()); + } + + // native + private static native void initOpen(); // init IDs for java callbacks from native + private static native void nativeOpen(String subject, String message, String filePath); + +} \ No newline at end of file diff --git a/modules/open/src/main/java/com/gluonhq/attach/open/package-info.java b/modules/open/src/main/java/com/gluonhq/attach/open/package-info.java new file mode 100644 index 00000000..656c5576 --- /dev/null +++ b/modules/open/src/main/java/com/gluonhq/attach/open/package-info.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2017, 2019 Gluon + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * Primary API package for Attach - Open plugin, + * contains the interface {@link com.gluonhq.attach.open.OpenService} and related classes. + */ +package com.gluonhq.attach.open; \ No newline at end of file diff --git a/modules/open/src/main/java/module-info.java b/modules/open/src/main/java/module-info.java new file mode 100644 index 00000000..7799144f --- /dev/null +++ b/modules/open/src/main/java/module-info.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2019, Gluon + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +module com.gluonhq.attach.open { + + requires com.gluonhq.attach.util; + + exports com.gluonhq.attach.open; + exports com.gluonhq.attach.open.impl to com.gluonhq.attach.util; +} \ No newline at end of file diff --git a/modules/open/src/main/native/android/c/open.c b/modules/open/src/main/native/android/c/open.c new file mode 100644 index 00000000..50252e8c --- /dev/null +++ b/modules/open/src/main/native/android/c/open.c @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2020, 2021, Gluon + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "util.h" + +static jclass jOpenServiceClass; +static jobject jDalvikOpenService; +static jmethodID jOpenServiceOpenFile; +static jmethodID jOpenServiceOpenFileWithType; + +static void initializeOpenDalvikHandles() { + jOpenServiceClass = GET_REGISTER_DALVIK_CLASS(jOpenServiceClass, "com/gluonhq/helloandroid/DalvikOpenService"); + ATTACH_DALVIK(); + jmethodID jOpenServiceInitMethod = (*dalvikEnv)->GetMethodID(dalvikEnv, jOpenServiceClass, "", "(Landroid/app/Activity;)V"); + jOpenServiceOpenFile = (*dalvikEnv)->GetMethodID(dalvikEnv, jOpenServiceClass, "openFile", "(Ljava/lang/String;)V"); + jOpenServiceOpenFileWithType = (*dalvikEnv)->GetMethodID(dalvikEnv, jOpenServiceClass, "openFileWithType", "(Ljava/lang/String;Ljava/lang/String;)V"); + jobject jActivity = substrateGetActivity(); + jobject jtmpobj = (*dalvikEnv)->NewObject(dalvikEnv, jOpenServiceClass, jOpenServiceInitMethod, jActivity); + jDalvikOpenService = (*dalvikEnv)->NewGlobalRef(dalvikEnv, jtmpobj); + DETACH_DALVIK(); +} + +////////////////////////// +// From Graal to native // +////////////////////////// + + +JNIEXPORT jint JNICALL +JNI_OnLoad_open(JavaVM *vm, void *reserved) +{ + JNIEnv* graalEnv; + ATTACH_LOG_INFO("JNI_OnLoad_open called"); +#ifdef JNI_VERSION_1_8 + if ((*vm)->GetEnv(vm, (void **)&graalEnv, JNI_VERSION_1_8) != JNI_OK) { + ATTACH_LOG_WARNING("Error initializing native Open from OnLoad"); + return JNI_FALSE; + } + ATTACH_LOG_FINE("[Open Service] Initializing native Open from OnLoad"); + initializeOpenDalvikHandles(); + return JNI_VERSION_1_8; +#else + #error Error: Java 8+ SDK is required to compile Attach +#endif +} + +// from Java to Android + +JNIEXPORT void JNICALL Java_com_gluonhq_attach_open_impl_AndroidOpenService_openFile +(JNIEnv *env, jclass jClass, jstring jfilename) +{ + const char *filenameChars = (*env)->GetStringUTFChars(env, jfilename, NULL); + ATTACH_DALVIK(); + jstring dfilename = (*dalvikEnv)->NewStringUTF(dalvikEnv, filenameChars); + if (isDebugAttach()) { + ATTACH_LOG_FINE("Open file, filename = %s\n", + filenameChars); + } + (*dalvikEnv)->CallVoidMethod(dalvikEnv, jDalvikOpenService, jOpenServiceOpenFile, dfilename); + DETACH_DALVIK(); +} + +JNIEXPORT void JNICALL Java_com_gluonhq_attach_open_impl_AndroidOpenService_openFileWithType +(JNIEnv *env, jclass jClass, jstring jtype, jstring jfilename) +{ + + const char *typeChars = (*env)->GetStringUTFChars(env, jtype, NULL); + const char *filenameChars = (*env)->GetStringUTFChars(env, jfilename, NULL); + ATTACH_DALVIK(); + jstring dtype = (*dalvikEnv)->NewStringUTF(dalvikEnv, typeChars); + jstring dfilename = (*dalvikEnv)->NewStringUTF(dalvikEnv, filenameChars); + if (isDebugAttach()) { + ATTACH_LOG_FINE("Open file, type = %s, filename = %s\n", + typeChars, filenameChars); + } + (*dalvikEnv)->CallVoidMethod(dalvikEnv, jDalvikOpenService, jOpenServiceOpenFileWithType, dtype, dfilename); + DETACH_DALVIK(); +} + diff --git a/modules/open/src/main/native/android/dalvik/DalvikOpenService.java b/modules/open/src/main/native/android/dalvik/DalvikOpenService.java new file mode 100644 index 00000000..22782447 --- /dev/null +++ b/modules/open/src/main/native/android/dalvik/DalvikOpenService.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2020, Gluon + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.gluonhq.helloandroid; + +import android.app.Activity; +import android.content.Intent; +import android.net.Uri; +import android.util.Log; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; + +import java.io.File; +import java.util.List; + +public class DalvikOpenService { + + private static final String TAG = Util.TAG; + + private final Activity activity; + private final String authority; + private final boolean debug; + + public DalvikOpenService(Activity activity) { + this.activity = activity; + authority = activity.getPackageName() + ".fileprovider"; + debug = Util.isDebug(); + } + + private void openFile(String fileName) { + Intent openingIntent = new Intent(Intent.ACTION_VIEW); + File file = new File(fileName); + + if (file != null && file.exists()) { + final Uri uriFile = FileProvider.getUriForFile(activity, authority, file); + openingIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + openingIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + openingIntent.setData(uriFile); + } else { + Log.e(TAG, "Error: A non empty file is required"); + return; + } + + Intent chooser = Intent.createChooser(openingIntent, "Open file"); + chooser.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + chooser.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + + activity.startActivity(chooser); + } + + private void openFileWithType(String type, String fileName) { + Intent openingIntent = new Intent(Intent.ACTION_VIEW); + + if (type != null && !type.isEmpty()) { + openingIntent.setType(type); + } else { + Log.e(TAG, "Error: A non empty type is required"); + return; + } + File file = new File(fileName); + if (file != null && file.exists()) { + if (debug) { + Log.v(TAG, String.format("File to open: %s", file)); + Log.v(TAG, "Application name provider: " + authority); + } + final Uri uriFile = FileProvider.getUriForFile(activity, authority, file); + if (debug) { + Log.v(TAG, String.format("Opened file URI: %s", file)); + } + openingIntent.putExtra(Intent.EXTRA_STREAM, uriFile); + openingIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + } else { + Log.e(TAG, "Error: A non empty file is required"); + return; + } + if (debug) { + Log.v(TAG, "Start file opening intent"); + } + + Intent chooser = Intent.createChooser(openingIntent, null); + chooser.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + chooser.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + activity.startActivity(chooser); + + } + + +} diff --git a/modules/open/src/main/native/ios/Open.h b/modules/open/src/main/native/ios/Open.h new file mode 100644 index 00000000..551f18c8 --- /dev/null +++ b/modules/open/src/main/native/ios/Open.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2017, 2019, Gluon + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#import +#include "jni.h" +#include "AttachMacros.h" + +@interface Open : UIViewController +{ +} + @property (nonatomic, strong) NSString *message; + @property (nonatomic, strong) NSString *subject; + @property (nonatomic, strong) NSString *filePath; + - (void) openText:(NSString *)subject message:(NSString *)message filePath:(NSString *)filePath; +@end diff --git a/modules/open/src/main/native/ios/Open.m b/modules/open/src/main/native/ios/Open.m new file mode 100644 index 00000000..994ad561 --- /dev/null +++ b/modules/open/src/main/native/ios/Open.m @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2017, 2019, Gluon + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "Open.h" + +JNIEnv *env; + +JNIEXPORT jint JNICALL +JNI_OnLoad_Open(JavaVM *vm, void *reserved) +{ +#ifdef JNI_VERSION_1_8 + //min. returned JNI_VERSION required by JDK8 for builtin libraries + if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_8) != JNI_OK) { + return JNI_VERSION_1_4; + } + return JNI_VERSION_1_8; +#else + return JNI_VERSION_1_4; +#endif +} + +static int OpenInited = 0; + +Open *_open; + +JNIEXPORT void JNICALL Java_com_gluonhq_attach_open_impl_IOSOpenService_initOpen +(JNIEnv *env, jclass jClass) +{ + if (OpenInited) + { + return; + } + OpenInited = 1; + + AttachLog(@"Init Open"); + _open = [[Open alloc] init]; +} + +JNIEXPORT void JNICALL Java_com_gluonhq_attach_open_impl_IOSOpenService_nativeOpen +(JNIEnv *env, jclass jClass, jstring jsubject, jstring jmessage, jstring jfilePath) { + + const jchar *charsSubject = (*env)->GetStringChars(env, jsubject, NULL); + NSString *subject = [NSString stringWithCharacters:(UniChar *)charsSubject length:(*env)->GetStringLength(env, jsubject)]; + (*env)->ReleaseStringChars(env, jsubject, charsSubject); + + const jchar *charsMessage = (*env)->GetStringChars(env, jmessage, NULL); + NSString *message = [NSString stringWithCharacters:(UniChar *)charsMessage length:(*env)->GetStringLength(env, jmessage)]; + (*env)->ReleaseStringChars(env, jmessage, charsMessage); + + const jchar *charsFilePath = (*env)->GetStringChars(env, jfilePath, NULL); + NSString *filePath = [NSString stringWithCharacters:(UniChar *)charsFilePath length:(*env)->GetStringLength(env, jfilePath)]; + (*env)->ReleaseStringChars(env, jfilePath, charsFilePath); + + [_open openText: subject message:message filePath:filePath]; + +} + + +@implementation Open + +- (void) openText: (NSString *)subject message:(NSString *)message filePath:(NSString *)filePath +{ + _subject = [[NSString alloc] initWithString:subject]; + _message = [[NSString alloc] initWithString:message]; + _filePath = [[NSString alloc] initWithString:filePath]; + + if(![[UIApplication openedApplication] keyWindow]) + { + AttachLog(@"key window was nil"); + return; + } + + NSArray *views = [[[UIApplication openedApplication] keyWindow] subviews]; + if(![views count]) { + AttachLog(@"views size was 0"); + return; + } + + UIViewController *rootViewController = [[[UIApplication openedApplication] keyWindow] rootViewController]; + if(!rootViewController) + { + AttachLog(@"rootViewController was nil"); + return; + } + + NSMutableArray *items = [[NSMutableArray alloc] init]; + [items addObject:self]; + + if ([_filePath length] > 0) { + [self logMessage:@"Open file: %@", _filePath]; + NSURL *fileUrl = [NSURL fileURLWithPath:_filePath]; + NSError *err; + if ([fileUrl checkResourceIsReachableAndReturnError:&err] == YES) + { + [self logMessage:@"Open fileUrl: %@", fileUrl]; + [items addObject:fileUrl]; + } else { + AttachLog(@"File resource not reachable: %@", err); + } + } + + NSArray *itemsToOpen = [NSArray arrayWithArray:items]; + + UIActivityViewController *activityViewController = [[UIActivityViewController alloc] initWithActivityItems:itemsToOpen applicationActivities:nil]; + if ([activityViewController respondsToSelector:@selector(popoverPresentationController)]) { + activityViewController.popoverPresentationController.sourceView = views[0]; + } + + activityViewController.completionWithItemsHandler = ^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError){ + [self logMessage:@"Activity Type selected: %@", activityType]; + if (completed) { + [self logMessage:@"Selected activity was performed."]; + } else { + if (activityType == NULL) { + [self logMessage:@"User dismissed the view controller without making a selection."]; + } else { + [self logMessage:@"Activity was not performed."]; + } + } + }; + + [rootViewController presentViewController:activityViewController animated:YES completion:nil]; + +} + +- (id)activityViewControllerPlaceholderItem:(UIActivityViewController *)activityViewController +{ + [self logMessage:@"Open activityViewControllerPlaceholderItem"]; + return @""; +} + +- (NSString *) activityViewController:(UIActivityViewController *)activityViewController subjectForActivityType:(NSString *)activityType +{ + [self logMessage:@"Open subjectForActivityType %@ - Subject: %@", activityType, _subject]; + return _subject; +} + +- (nullable id)activityViewController:(UIActivityViewController *)activityViewController itemForActivityType:(UIActivityType)activityType +{ + [self logMessage:@"Open itemForActivityType %@ - Message: %@", activityType, _message]; + return _message; +} + +- (void) logMessage:(NSString *)format, ...; +{ + if (debugAttach) + { + va_list args; + va_start(args, format); + NSLogv([@"[Debug] " stringByAppendingString:format], args); + va_end(args); + } +} +@end + diff --git a/modules/open/src/main/resources/META-INF/substrate/config/reflectionconfig-aarch64-android.json b/modules/open/src/main/resources/META-INF/substrate/config/reflectionconfig-aarch64-android.json new file mode 100644 index 00000000..36c98215 --- /dev/null +++ b/modules/open/src/main/resources/META-INF/substrate/config/reflectionconfig-aarch64-android.json @@ -0,0 +1,6 @@ +[ + { + "name" : "com.gluonhq.attach.open.impl.AndroidOpenService", + "methods":[{"name":"","parameterTypes":[] }] + } +] \ No newline at end of file diff --git a/modules/open/src/main/resources/META-INF/substrate/config/reflectionconfig-arm64-ios.json b/modules/open/src/main/resources/META-INF/substrate/config/reflectionconfig-arm64-ios.json new file mode 100644 index 00000000..efd00f5b --- /dev/null +++ b/modules/open/src/main/resources/META-INF/substrate/config/reflectionconfig-arm64-ios.json @@ -0,0 +1,6 @@ +[ + { + "name" : "com.gluonhq.attach.open.impl.IOSOpenService", + "methods":[{"name":"","parameterTypes":[] }] + } +] \ No newline at end of file diff --git a/modules/open/src/main/resources/META-INF/substrate/dalvik/AndroidManifest.xml b/modules/open/src/main/resources/META-INF/substrate/dalvik/AndroidManifest.xml new file mode 100644 index 00000000..d3cfa87b --- /dev/null +++ b/modules/open/src/main/resources/META-INF/substrate/dalvik/AndroidManifest.xml @@ -0,0 +1,14 @@ + + + + + + + + + \ No newline at end of file diff --git a/modules/open/src/main/resources/META-INF/substrate/dalvik/res/xml/file_provider_paths.xml b/modules/open/src/main/resources/META-INF/substrate/dalvik/res/xml/file_provider_paths.xml new file mode 100644 index 00000000..8248868e --- /dev/null +++ b/modules/open/src/main/resources/META-INF/substrate/dalvik/res/xml/file_provider_paths.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 4cf9880c..c9bb83ee 100644 --- a/settings.gradle +++ b/settings.gradle @@ -23,10 +23,13 @@ include 'connectivity' include 'device' include 'dialer' include 'display', 'display:desktop' +include 'display' +include 'file-chooser' include 'in-app-billing' include 'lifecycle', 'lifecycle:desktop' include 'local-notifications' include 'magnetometer' +include 'open' include 'orientation' include 'pictures' include 'position' @@ -56,11 +59,13 @@ project(':device').projectDir = file('modules/device') project(':dialer').projectDir = file('modules/dialer') project(':display').projectDir = file('modules/display') project(':display:desktop').projectDir = file('modules/display/desktop') +project(':file-chooser').projectDir = file('modules/file-chooser') project(':in-app-billing').projectDir = file('modules/in-app-billing') project(':lifecycle').projectDir = file('modules/lifecycle') project(':lifecycle:desktop').projectDir = file('modules/lifecycle/desktop') project(':local-notifications').projectDir = file('modules/local-notifications') project(':magnetometer').projectDir = file('modules/magnetometer') +project(':open').projectDir = file('modules/open') project(':orientation').projectDir = file('modules/orientation') project(':pictures').projectDir = file('modules/pictures') project(':position').projectDir = file('modules/position')