From 6db0de321c704ebc43220b4a6905ba2944239f3a Mon Sep 17 00:00:00 2001 From: onurays Date: Fri, 20 Mar 2020 12:12:59 +0300 Subject: [PATCH 01/15] Initial implementation of multipicker. --- .gitignore | 134 +++++++++++++++++ matrix-sdk-android/build.gradle | 2 +- multipicker/.gitignore | 1 + multipicker/build.gradle | 54 +++++++ multipicker/consumer-rules.pro | 0 multipicker/proguard-rules.pro | 21 +++ .../multipicker/ExampleInstrumentedTest.kt | 39 +++++ multipicker/src/main/AndroidManifest.xml | 2 + .../vector/riotx/multipicker/AudioPicker.kt | 106 ++++++++++++++ .../vector/riotx/multipicker/ContactPicker.kt | 135 ++++++++++++++++++ .../im/vector/riotx/multipicker/FilePicker.kt | 86 +++++++++++ .../vector/riotx/multipicker/ImagePicker.kt | 124 ++++++++++++++++ .../vector/riotx/multipicker/MultiPicker.kt | 46 ++++++ .../im/vector/riotx/multipicker/Picker.kt | 38 +++++ .../vector/riotx/multipicker/VideoPicker.kt | 115 +++++++++++++++ .../entity/MultiPickerAudioType.kt | 27 ++++ .../multipicker/entity/MultiPickerBaseType.kt | 26 ++++ .../entity/MultiPickerContactType.kt | 32 +++++ .../multipicker/entity/MultiPickerFileType.kt | 26 ++++ .../entity/MultiPickerImageType.kt | 29 ++++ .../entity/MultiPickerVideoType.kt | 30 ++++ .../riotx/multipicker/ExampleUnitTest.kt | 32 +++++ settings.gradle | 1 + vector/build.gradle | 1 + vector/src/main/AndroidManifest.xml | 1 + .../home/room/detail/RoomDetailFragment.kt | 33 ++++- 26 files changed, 1133 insertions(+), 8 deletions(-) create mode 100644 multipicker/.gitignore create mode 100644 multipicker/build.gradle create mode 100644 multipicker/consumer-rules.pro create mode 100644 multipicker/proguard-rules.pro create mode 100644 multipicker/src/androidTest/java/im/vector/riotx/multipicker/ExampleInstrumentedTest.kt create mode 100644 multipicker/src/main/AndroidManifest.xml create mode 100644 multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt create mode 100644 multipicker/src/main/java/im/vector/riotx/multipicker/ContactPicker.kt create mode 100644 multipicker/src/main/java/im/vector/riotx/multipicker/FilePicker.kt create mode 100644 multipicker/src/main/java/im/vector/riotx/multipicker/ImagePicker.kt create mode 100644 multipicker/src/main/java/im/vector/riotx/multipicker/MultiPicker.kt create mode 100644 multipicker/src/main/java/im/vector/riotx/multipicker/Picker.kt create mode 100644 multipicker/src/main/java/im/vector/riotx/multipicker/VideoPicker.kt create mode 100644 multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerAudioType.kt create mode 100644 multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerBaseType.kt create mode 100644 multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerContactType.kt create mode 100644 multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerFileType.kt create mode 100644 multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerImageType.kt create mode 100644 multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerVideoType.kt create mode 100644 multipicker/src/test/java/im/vector/riotx/multipicker/ExampleUnitTest.kt diff --git a/.gitignore b/.gitignore index 4a264a28d8..ecfdcb7510 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,137 @@ /tmp ktlint +multipicker/build/.transforms/3e0e90edbb388b6989e862c9faf75e87.bin +multipicker/build/.transforms/55ef863ef687bb455ca3376fbf042ba5.bin +multipicker/build/.transforms/3e0e90edbb388b6989e862c9faf75e87/classes/classes.dex +multipicker/build/.transforms/55ef863ef687bb455ca3376fbf042ba5/classes/classes.dex +multipicker/build/generated/source/buildConfig/debug/im/vector/riotx/multipicker/BuildConfig.java +multipicker/build/intermediates/aapt_friendly_merged_manifests/debug/aapt/AndroidManifest.xml +multipicker/build/intermediates/aapt_friendly_merged_manifests/debug/aapt/output.json +multipicker/build/intermediates/annotation_processor_list/debug/annotationProcessors.json +multipicker/build/intermediates/annotations_typedef_file/debug/extractDebugAnnotations/typedefs.txt +multipicker/build/intermediates/compile_library_classes/debug/classes.jar +multipicker/build/intermediates/compile_only_not_namespaced_r_class_jar/debug/R.jar +multipicker/build/intermediates/consumer_proguard_file/debug/proguard.txt +multipicker/build/intermediates/incremental/debug-mergeJavaRes/merge-state +multipicker/build/intermediates/incremental/debug-mergeNativeLibs/merge-state +multipicker/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml +multipicker/build/intermediates/incremental/mergeDebugShaders/merger.xml +multipicker/build/intermediates/incremental/packageDebugAssets/merger.xml +multipicker/build/intermediates/incremental/packageDebugResources/compile-file-map.properties +multipicker/build/intermediates/incremental/packageDebugResources/merger.xml +multipicker/build/intermediates/javac/debug/classes/im/vector/riotx/multipicker/BuildConfig.class +multipicker/build/intermediates/library_java_res/debug/res.jar +multipicker/build/intermediates/library_manifest/debug/AndroidManifest.xml +multipicker/build/intermediates/local_only_symbol_list/debug/parseDebugLibraryResources/R-def.txt +multipicker/build/intermediates/manifest_merge_blame_file/debug/manifest-merger-blame-debug-report.txt +multipicker/build/intermediates/merged_java_res/debug/out.jar +multipicker/build/intermediates/merged_manifests/debug/output.json +multipicker/build/intermediates/packaged-classes/debug/classes.jar +multipicker/build/intermediates/res/symbol-table-with-package/debug/package-aware-r.txt +multipicker/build/intermediates/runtime_library_classes/debug/classes.jar +multipicker/build/intermediates/symbols/debug/R.txt +multipicker/build/kotlin/compileDebugKotlin/build-history.bin +multipicker/build/kotlin/compileDebugKotlin/last-build.bin +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/inputs/source-to-output.tab +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/inputs/source-to-output.tab.keystream +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/inputs/source-to-output.tab.keystream.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/inputs/source-to-output.tab.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/inputs/source-to-output.tab.values.at +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/inputs/source-to-output.tab_i +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/inputs/source-to-output.tab_i.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.values.at +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/constants.tab +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/constants.tab.keystream +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/constants.tab.keystream.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/constants.tab.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/constants.tab.values.at +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/constants.tab_i +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/constants.tab_i.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab.values.at +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/proto.tab +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/proto.tab.keystream +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/proto.tab.keystream.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/proto.tab.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/proto.tab.values.at +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/proto.tab_i +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/proto.tab_i.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab.values.at +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab_i +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab_i.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/subtypes.tab +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/subtypes.tab.keystream +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/subtypes.tab.keystream.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/subtypes.tab.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/subtypes.tab.values.at +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/subtypes.tab_i +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/subtypes.tab_i.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/supertypes.tab +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/supertypes.tab.keystream +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/supertypes.tab.keystream.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/supertypes.tab.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/supertypes.tab.values.at +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/supertypes.tab_i +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/supertypes.tab_i.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/counters.tab +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/file-to-id.tab +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/file-to-id.tab.keystream +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/file-to-id.tab.keystream.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/file-to-id.tab.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/file-to-id.tab.values.at +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/file-to-id.tab_i +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/file-to-id.tab_i.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/id-to-file.tab +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/id-to-file.tab.keystream +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/id-to-file.tab.keystream.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/id-to-file.tab.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/id-to-file.tab.values.at +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/id-to-file.tab_i +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/id-to-file.tab_i.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/lookups.tab +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/lookups.tab.keystream +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/lookups.tab.keystream.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/lookups.tab.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/lookups.tab.values +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/lookups.tab.values.at +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/lookups.tab.values.s +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/lookups.tab_i +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/lookups.tab_i.len +multipicker/build/outputs/aar/multipicker-debug.aar +multipicker/build/outputs/logs/manifest-merger-debug-report.txt +multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/AudioPicker.class +multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/ContactPicker.class +multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/FilePicker.class +multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/ImagePicker.class +multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/MultiPicker$Type$AUDIO$2.class +multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/MultiPicker$Type$CONTACT$2.class +multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/MultiPicker$Type$FILE$2.class +multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/MultiPicker$Type$IMAGE$2.class +multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/MultiPicker$Type$VIDEO$2.class +multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/MultiPicker$Type.class +multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/MultiPicker.class +multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/Picker.class +multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/VideoPicker.class +multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/entity/MultiPickerAudioType.class +multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/entity/MultiPickerBaseType.class +multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/entity/MultiPickerContactType.class +multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/entity/MultiPickerFileType.class +multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/entity/MultiPickerImageType.class +multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/entity/MultiPickerVideoType.class +multipicker/build/tmp/kotlin-classes/debug/META-INF/multipicker_debug.kotlin_module diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index 5f614763d5..ca6c2ce6d9 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -119,7 +119,7 @@ dependencies { implementation "ru.noties.markwon:core:$markwon_version" // Image - implementation 'androidx.exifinterface:exifinterface:1.1.0' + implementation 'androidx.exifinterface:exifinterface:1.3.0-alpha01' implementation 'id.zelory:compressor:3.0.0' // Database diff --git a/multipicker/.gitignore b/multipicker/.gitignore new file mode 100644 index 0000000000..796b96d1c4 --- /dev/null +++ b/multipicker/.gitignore @@ -0,0 +1 @@ +/build diff --git a/multipicker/build.gradle b/multipicker/build.gradle new file mode 100644 index 0000000000..950e76020f --- /dev/null +++ b/multipicker/build.gradle @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + compileSdkVersion 29 + buildToolsVersion "29.0.3" + + defaultConfig { + minSdkVersion 19 + targetSdkVersion 29 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles 'consumer-rules.pro' + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation 'androidx.appcompat:appcompat:1.1.0' + implementation 'androidx.core:core-ktx:1.2.0' + testImplementation 'junit:junit:4.12' + androidTestImplementation 'androidx.test.ext:junit:1.1.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' + + implementation 'androidx.exifinterface:exifinterface:1.3.0-alpha01' +} diff --git a/multipicker/consumer-rules.pro b/multipicker/consumer-rules.pro new file mode 100644 index 0000000000..e69de29bb2 diff --git a/multipicker/proguard-rules.pro b/multipicker/proguard-rules.pro new file mode 100644 index 0000000000..f1b424510d --- /dev/null +++ b/multipicker/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/multipicker/src/androidTest/java/im/vector/riotx/multipicker/ExampleInstrumentedTest.kt b/multipicker/src/androidTest/java/im/vector/riotx/multipicker/ExampleInstrumentedTest.kt new file mode 100644 index 0000000000..25bf17559f --- /dev/null +++ b/multipicker/src/androidTest/java/im/vector/riotx/multipicker/ExampleInstrumentedTest.kt @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotx.multipicker + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 +import junit.framework.Assert.assertEquals + +import org.junit.Test +import org.junit.runner.RunWith + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("im.vector.riotx.multipicker.test", appContext.packageName) + } +} diff --git a/multipicker/src/main/AndroidManifest.xml b/multipicker/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..068a7c9a19 --- /dev/null +++ b/multipicker/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt new file mode 100644 index 0000000000..19aba7bf1a --- /dev/null +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotx.multipicker + +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.media.MediaMetadataRetriever +import android.net.Uri +import android.provider.MediaStore +import androidx.fragment.app.Fragment +import im.vector.riotx.multipicker.entity.MultiPickerAudioType + +class AudioPicker(override val requestCode: Int) : Picker(requestCode) { + + override fun startWith(activity: Activity) { + activity.startActivityForResult(createIntent(), requestCode) + } + + override fun startWith(fragment: Fragment) { + fragment.startActivityForResult(createIntent(), requestCode) + } + + override fun getSelectedFiles(context: Context, requestCode: Int, resultCode: Int, data: Intent?): List { + if (requestCode != this.requestCode && resultCode != Activity.RESULT_OK) { + return emptyList() + } + + val audioList = mutableListOf() + + val selectedUriList = mutableListOf() + val dataUri = data?.data + val clipData = data?.clipData + + if (clipData != null) { + for (i in 0 until clipData.itemCount) { + selectedUriList.add(clipData.getItemAt(i).uri) + } + } else if (dataUri != null) { + selectedUriList.add(dataUri) + } + + selectedUriList.forEach { selectedUri -> + val projection = arrayOf( + MediaStore.Audio.Media.DISPLAY_NAME, + MediaStore.Audio.Media.SIZE + ) + + context.contentResolver.query( + selectedUri, + projection, + null, + null, + null + )?.use { cursor -> + val nameColumn = cursor.getColumnIndex(MediaStore.Audio.Media.DISPLAY_NAME) + val sizeColumn = cursor.getColumnIndex(MediaStore.Audio.Media.SIZE) + + if (cursor.moveToNext()) { + val name = cursor.getString(nameColumn) + val size = cursor.getLong(sizeColumn) + var duration = 0L + + context.contentResolver.openFileDescriptor(selectedUri, "r")?.use { pfd -> + val mediaMetadataRetriever = MediaMetadataRetriever() + mediaMetadataRetriever.setDataSource(pfd.fileDescriptor) + duration = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION).toLong() + } + + audioList.add( + MultiPickerAudioType( + name, + size, + context.contentResolver.getType(selectedUri), + selectedUri, + duration + ) + ) + } + } + } + return audioList + } + + private fun createIntent(): Intent { + return Intent(Intent.ACTION_OPEN_DOCUMENT).apply { + addCategory(Intent.CATEGORY_OPENABLE) + putExtra(Intent.EXTRA_ALLOW_MULTIPLE, !single) + type = "audio/*" + } + } +} diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/ContactPicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/ContactPicker.kt new file mode 100644 index 0000000000..aebde6f439 --- /dev/null +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/ContactPicker.kt @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotx.multipicker + +import android.app.Activity +import android.content.ContentResolver +import android.content.Context +import android.content.Intent +import android.provider.ContactsContract +import androidx.fragment.app.Fragment +import im.vector.riotx.multipicker.entity.MultiPickerContactType + +class ContactPicker(override val requestCode: Int) : Picker(requestCode) { + + override fun startWith(activity: Activity) { + activity.startActivityForResult(createIntent(), requestCode) + } + + override fun startWith(fragment: Fragment) { + fragment.startActivityForResult(createIntent(), requestCode) + } + + override fun getSelectedFiles(context: Context, requestCode: Int, resultCode: Int, data: Intent?): List { + if (requestCode != this.requestCode && resultCode != Activity.RESULT_OK) { + return emptyList() + } + + val contactList = mutableListOf() + + data?.data?.let { selectedUri -> + val projection = arrayOf( + ContactsContract.Contacts.DISPLAY_NAME, + ContactsContract.Contacts.PHOTO_URI, + ContactsContract.Contacts._ID + ) + + context.contentResolver.query( + selectedUri, + projection, + null, + null, + null + )?.use { cursor -> + if (cursor.moveToFirst()) { + val idColumn = cursor.getColumnIndex(ContactsContract.Contacts._ID) + val nameColumn = cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME) + val photoUriColumn = cursor.getColumnIndex(ContactsContract.Contacts.PHOTO_URI) + + val contactId = cursor.getInt(idColumn) + var name = cursor.getString(nameColumn) + var photoUri = cursor.getString(photoUriColumn) + var phoneNumberList = mutableListOf() + var emailList = mutableListOf() + + getRawContactId(context.contentResolver, contactId)?.let { rawContactId -> + val selection = ContactsContract.Data.RAW_CONTACT_ID + " = ?" + val selectionArgs = arrayOf(rawContactId.toString()) + + context.contentResolver.query( + ContactsContract.Data.CONTENT_URI, + arrayOf( + ContactsContract.Data.MIMETYPE, + ContactsContract.Data.DATA1 + ), + selection, + selectionArgs, + null + )?.use { cursor -> + while (cursor.moveToNext()) { + val mimeType = cursor.getString(cursor.getColumnIndex(ContactsContract.Data.MIMETYPE)) + val contactData = cursor.getString(cursor.getColumnIndex(ContactsContract.Data.DATA1)) + + if (mimeType == ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) { + name = contactData + } + if (mimeType == ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE) { + phoneNumberList.add(contactData) + } + if (mimeType == ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE) { + emailList.add(contactData) + } + } + } + } + contactList.add( + MultiPickerContactType( + name, + photoUri, + phoneNumberList, + emailList + ) + ) + } + } + } + + return contactList + } + + private fun getRawContactId(contentResolver: ContentResolver, contactId: Int): Int? { + val projection = arrayOf(ContactsContract.RawContacts._ID) + val selection = ContactsContract.RawContacts.CONTACT_ID + " = ?" + val selectionArgs = arrayOf(contactId.toString() + "") + return contentResolver.query( + ContactsContract.RawContacts.CONTENT_URI, + projection, + selection, + selectionArgs, + null + )?.use { cursor -> + return if (cursor.moveToFirst()) cursor.getInt(cursor.getColumnIndex(ContactsContract.RawContacts._ID)) else null + } + } + + private fun createIntent(): Intent { + return Intent(Intent.ACTION_PICK).apply { + putExtra(Intent.EXTRA_ALLOW_MULTIPLE, !single) + type = ContactsContract.Contacts.CONTENT_TYPE + } + } +} diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/FilePicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/FilePicker.kt new file mode 100644 index 0000000000..cf6b74c62d --- /dev/null +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/FilePicker.kt @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotx.multipicker + +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.provider.OpenableColumns +import androidx.fragment.app.Fragment +import im.vector.riotx.multipicker.entity.MultiPickerFileType + +class FilePicker(override val requestCode: Int) : Picker(requestCode) { + + override fun startWith(activity: Activity) { + activity.startActivityForResult(createIntent(), requestCode) + } + + override fun startWith(fragment: Fragment) { + fragment.startActivityForResult(createIntent(), requestCode) + } + + override fun getSelectedFiles(context: Context, requestCode: Int, resultCode: Int, data: Intent?): List { + if (requestCode != this.requestCode && resultCode != Activity.RESULT_OK) { + return emptyList() + } + + val fileList = mutableListOf() + + val selectedUriList = mutableListOf() + val dataUri = data?.data + val clipData = data?.clipData + + if (clipData != null) { + for (i in 0 until clipData.itemCount) { + selectedUriList.add(clipData.getItemAt(i).uri) + } + } else if (dataUri != null) { + selectedUriList.add(dataUri) + } + + selectedUriList.forEach { selectedUri -> + context.contentResolver.query(selectedUri, null, null, null, null) + ?.use { cursor -> + val nameColumn = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME) + val sizeColumn = cursor.getColumnIndex(OpenableColumns.SIZE) + if (cursor.moveToFirst()) { + val name = cursor.getString(nameColumn) + val size = cursor.getLong(sizeColumn) + + fileList.add( + MultiPickerFileType( + name, + size, + context.contentResolver.getType(selectedUri), + selectedUri + ) + ) + } + } + } + return fileList + } + + private fun createIntent(): Intent { + return Intent(Intent.ACTION_OPEN_DOCUMENT).apply { + addCategory(Intent.CATEGORY_OPENABLE) + putExtra(Intent.EXTRA_ALLOW_MULTIPLE, !single) + type = "*/*" + } + } +} diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/ImagePicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/ImagePicker.kt new file mode 100644 index 0000000000..79832a2ad1 --- /dev/null +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/ImagePicker.kt @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotx.multipicker + +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.graphics.BitmapFactory +import android.graphics.ImageDecoder +import android.net.Uri +import android.os.Build +import android.provider.MediaStore +import androidx.exifinterface.media.ExifInterface +import androidx.fragment.app.Fragment +import im.vector.riotx.multipicker.entity.MultiPickerImageType + +class ImagePicker(override val requestCode: Int) : Picker(requestCode) { + + override fun startWith(activity: Activity) { + activity.startActivityForResult(createIntent(), requestCode) + } + + override fun startWith(fragment: Fragment) { + fragment.startActivityForResult(createIntent(), requestCode) + } + + override fun getSelectedFiles(context: Context, requestCode: Int, resultCode: Int, data: Intent?): List { + if (requestCode != this.requestCode && resultCode != Activity.RESULT_OK) { + return emptyList() + } + + val imageList = mutableListOf() + + val selectedUriList = mutableListOf() + val dataUri = data?.data + val clipData = data?.clipData + + if (clipData != null) { + for (i in 0 until clipData.itemCount) { + selectedUriList.add(clipData.getItemAt(i).uri) + } + } else if (dataUri != null) { + selectedUriList.add(dataUri) + } + + selectedUriList.forEach { selectedUri -> + val projection = arrayOf( + MediaStore.Images.Media.DISPLAY_NAME, + MediaStore.Images.Media.SIZE + ) + + context.contentResolver.query( + selectedUri, + projection, + null, + null, + null + )?.use { cursor -> + val nameColumn = cursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME) + val sizeColumn = cursor.getColumnIndex(MediaStore.Images.Media.SIZE) + + if (cursor.moveToNext()) { + val name = cursor.getString(nameColumn) + val size = cursor.getLong(sizeColumn) + + var orientation = 0 + + val bitmap = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + ImageDecoder.decodeBitmap(ImageDecoder.createSource(context.contentResolver, selectedUri)) + } else { + context.contentResolver.openInputStream(selectedUri)?.use { inputStream -> + BitmapFactory.decodeStream(inputStream) + } + } + + context.contentResolver.openInputStream(selectedUri)?.use { inputStream -> + try { + ExifInterface(inputStream).let { + orientation = it.rotationDegrees + } + } catch (e: Exception) { + e.printStackTrace() + } + } + + imageList.add( + MultiPickerImageType( + name, + size, + context.contentResolver.getType(selectedUri), + selectedUri, + bitmap?.width ?: 0, + bitmap?.height ?: 0, + orientation + ) + ) + } + } + } + return imageList + } + + private fun createIntent(): Intent { + return Intent(Intent.ACTION_OPEN_DOCUMENT).apply { + addCategory(Intent.CATEGORY_OPENABLE) + putExtra(Intent.EXTRA_ALLOW_MULTIPLE, !single) + type = "image/*" + } + } +} diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/MultiPicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/MultiPicker.kt new file mode 100644 index 0000000000..d10346c903 --- /dev/null +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/MultiPicker.kt @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotx.multipicker + +class MultiPicker { + + companion object Type { + val IMAGE by lazy { MultiPicker() } + val FILE by lazy { MultiPicker() } + val VIDEO by lazy { MultiPicker() } + val AUDIO by lazy { MultiPicker() } + val CONTACT by lazy { MultiPicker() } + + const val REQUEST_CODE_PICK_IMAGE = 5000 + const val REQUEST_CODE_PICK_VIDEO = 5001 + const val REQUEST_CODE_PICK_FILE = 5002 + const val REQUEST_CODE_PICK_AUDIO = 5003 + const val REQUEST_CODE_PICK_CONTACT = 5004 + + @Suppress("UNCHECKED_CAST") + fun get(type: MultiPicker): T { + return when (type) { + IMAGE -> ImagePicker(REQUEST_CODE_PICK_IMAGE) as T + VIDEO -> VideoPicker(REQUEST_CODE_PICK_VIDEO) as T + FILE -> FilePicker(REQUEST_CODE_PICK_FILE) as T + AUDIO -> AudioPicker(REQUEST_CODE_PICK_AUDIO) as T + CONTACT -> ContactPicker(REQUEST_CODE_PICK_CONTACT) as T + else -> throw IllegalArgumentException("Unsupported type $type") + } + } + } +} diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/Picker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/Picker.kt new file mode 100644 index 0000000000..62dff35062 --- /dev/null +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/Picker.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotx.multipicker + +import android.app.Activity +import android.content.Context +import android.content.Intent +import androidx.fragment.app.Fragment + +abstract class Picker(open val requestCode: Int) { + + protected var single = false + + abstract fun startWith(activity: Activity) + + abstract fun startWith(fragment: Fragment) + + abstract fun getSelectedFiles(context: Context, requestCode: Int, resultCode: Int, data: Intent?): List + + fun single(): Picker { + single = true + return this + } +} diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/VideoPicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/VideoPicker.kt new file mode 100644 index 0000000000..8066c0b43e --- /dev/null +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/VideoPicker.kt @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotx.multipicker + +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.media.MediaMetadataRetriever +import android.net.Uri +import android.provider.MediaStore +import androidx.fragment.app.Fragment +import im.vector.riotx.multipicker.entity.MultiPickerVideoType + +class VideoPicker(override val requestCode: Int) : Picker(requestCode) { + + override fun startWith(activity: Activity) { + activity.startActivityForResult(createIntent(), requestCode) + } + + override fun startWith(fragment: Fragment) { + fragment.startActivityForResult(createIntent(), requestCode) + } + + override fun getSelectedFiles(context: Context, requestCode: Int, resultCode: Int, data: Intent?): List { + if (requestCode != this.requestCode && resultCode != Activity.RESULT_OK) { + return emptyList() + } + + val videoList = mutableListOf() + + val selectedUriList = mutableListOf() + val dataUri = data?.data + val clipData = data?.clipData + + if (clipData != null) { + for (i in 0 until clipData.itemCount) { + selectedUriList.add(clipData.getItemAt(i).uri) + } + } else if (dataUri != null) { + selectedUriList.add(dataUri) + } + + selectedUriList.forEach { selectedUri -> + val projection = arrayOf( + MediaStore.Video.Media.DISPLAY_NAME, + MediaStore.Video.Media.SIZE + ) + + context.contentResolver.query( + selectedUri, + projection, + null, + null, + null + )?.use { cursor -> + val nameColumn = cursor.getColumnIndex(MediaStore.Video.Media.DISPLAY_NAME) + val sizeColumn = cursor.getColumnIndex(MediaStore.Video.Media.SIZE) + + if (cursor.moveToNext()) { + val name = cursor.getString(nameColumn) + val size = cursor.getLong(sizeColumn) + var duration = 0L + var width = 0 + var height = 0 + var orientation = 0 + + context.contentResolver.openFileDescriptor(selectedUri, "r")?.use { pfd -> + val mediaMetadataRetriever = MediaMetadataRetriever() + mediaMetadataRetriever.setDataSource(pfd.fileDescriptor) + duration = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION).toLong() + width = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH).toInt() + height = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT).toInt() + orientation = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION).toInt() + } + + videoList.add( + MultiPickerVideoType( + name, + size, + context.contentResolver.getType(selectedUri), + selectedUri, + width, + height, + orientation, + duration + ) + ) + } + } + } + return videoList + } + + private fun createIntent(): Intent { + return Intent(Intent.ACTION_OPEN_DOCUMENT).apply { + addCategory(Intent.CATEGORY_OPENABLE) + putExtra(Intent.EXTRA_ALLOW_MULTIPLE, !single) + type = "video/*" + } + } +} diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerAudioType.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerAudioType.kt new file mode 100644 index 0000000000..6afe022024 --- /dev/null +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerAudioType.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotx.multipicker.entity + +import android.net.Uri + +data class MultiPickerAudioType( + override val displayName: String?, + override val size: Long, + override val mimeType: String?, + override val contentUri: Uri, + val duration: Long +) : MultiPickerBaseType diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerBaseType.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerBaseType.kt new file mode 100644 index 0000000000..777e4d8441 --- /dev/null +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerBaseType.kt @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotx.multipicker.entity + +import android.net.Uri + +interface MultiPickerBaseType { + val displayName: String? + val size: Long + val mimeType: String? + val contentUri: Uri +} diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerContactType.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerContactType.kt new file mode 100644 index 0000000000..565304a546 --- /dev/null +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerContactType.kt @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotx.multipicker.entity + +data class MultiPickerContactType( + val displayName: String, + val photoUri: String?, + val phoneNumberList: List, + val emailList: List +) { + private val FORMAT_CONTACT = "Name: %s, Photo: %s, Phones: %s, Emails: %s" + + override fun toString(): String { + val phoneNumberString = phoneNumberList.joinToString(separator = ", ", prefix = "[", postfix = "]") + val emailString = emailList.joinToString(separator = ", ", prefix = "[", postfix = "]") + return String.format(FORMAT_CONTACT, displayName, photoUri, phoneNumberString, emailString) + } +} diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerFileType.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerFileType.kt new file mode 100644 index 0000000000..5417520d28 --- /dev/null +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerFileType.kt @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotx.multipicker.entity + +import android.net.Uri + +data class MultiPickerFileType( + override val displayName: String?, + override val size: Long, + override val mimeType: String?, + override val contentUri: Uri +) : MultiPickerBaseType diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerImageType.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerImageType.kt new file mode 100644 index 0000000000..b1aef171b4 --- /dev/null +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerImageType.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotx.multipicker.entity + +import android.net.Uri + +data class MultiPickerImageType( + override val displayName: String?, + override val size: Long, + override val mimeType: String?, + override val contentUri: Uri, + val width: Int, + val height: Int, + val orientation: Int +) : MultiPickerBaseType diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerVideoType.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerVideoType.kt new file mode 100644 index 0000000000..ba9a8d233e --- /dev/null +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerVideoType.kt @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotx.multipicker.entity + +import android.net.Uri + +data class MultiPickerVideoType( + override val displayName: String?, + override val size: Long, + override val mimeType: String?, + override val contentUri: Uri, + val width: Int, + val height: Int, + val orientation: Int, + val duration: Long +) : MultiPickerBaseType diff --git a/multipicker/src/test/java/im/vector/riotx/multipicker/ExampleUnitTest.kt b/multipicker/src/test/java/im/vector/riotx/multipicker/ExampleUnitTest.kt new file mode 100644 index 0000000000..07e464699f --- /dev/null +++ b/multipicker/src/test/java/im/vector/riotx/multipicker/ExampleUnitTest.kt @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotx.multipicker + +import junit.framework.Assert.assertEquals +import org.junit.Test + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} diff --git a/settings.gradle b/settings.gradle index d020abade4..04307e89d9 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,2 @@ include ':vector', ':matrix-sdk-android', ':matrix-sdk-android-rx', ':diff-match-patch' +include ':multipicker' diff --git a/vector/build.gradle b/vector/build.gradle index 66ec6808c8..447ad7a767 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -254,6 +254,7 @@ dependencies { implementation project(":matrix-sdk-android") implementation project(":matrix-sdk-android-rx") implementation project(":diff-match-patch") + implementation project(":multipicker") implementation 'com.android.support:multidex:1.0.3' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index 2e56e20ce7..092817a6cc 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -8,6 +8,7 @@ + diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt index e748478e6a..30f9551612 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt @@ -156,6 +156,7 @@ import im.vector.riotx.features.reactions.EmojiReactionPickerActivity import im.vector.riotx.features.settings.VectorPreferences import im.vector.riotx.features.share.SharedData import im.vector.riotx.features.themes.ThemeUtils +import im.vector.riotx.multipicker.MultiPicker import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.schedulers.Schedulers import kotlinx.android.parcel.Parcelize @@ -290,9 +291,9 @@ class RoomDetailFragment @Inject constructor( roomDetailViewModel.observeViewEvents { when (it) { - is RoomDetailViewEvents.Failure -> showErrorInSnackbar(it.throwable) - is RoomDetailViewEvents.OnNewTimelineEvents -> scrollOnNewMessageCallback.addNewTimelineEventIds(it.eventIds) - is RoomDetailViewEvents.ActionSuccess -> displayRoomDetailActionSuccess(it) + is RoomDetailViewEvents.Failure -> showErrorInSnackbar(it.throwable) + is RoomDetailViewEvents.OnNewTimelineEvents -> scrollOnNewMessageCallback.addNewTimelineEventIds(it.eventIds) + is RoomDetailViewEvents.ActionSuccess -> displayRoomDetailActionSuccess(it) is RoomDetailViewEvents.ActionFailure -> displayRoomDetailActionFailure(it) is RoomDetailViewEvents.ShowMessage -> showSnackWithMessage(it.message, Snackbar.LENGTH_LONG) is RoomDetailViewEvents.NavigateToEvent -> navigateToEvent(it) @@ -516,6 +517,24 @@ class RoomDetailFragment @Inject constructor( } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + when (requestCode) { + MultiPicker.REQUEST_CODE_PICK_IMAGE -> { + MultiPicker.get(MultiPicker.IMAGE).getSelectedFiles(requireContext(), requestCode, resultCode, data) + } + MultiPicker.REQUEST_CODE_PICK_VIDEO -> { + MultiPicker.get(MultiPicker.VIDEO).getSelectedFiles(requireContext(), requestCode, resultCode, data) + } + MultiPicker.REQUEST_CODE_PICK_FILE -> { + MultiPicker.get(MultiPicker.FILE).getSelectedFiles(requireContext(), requestCode, resultCode, data) + } + MultiPicker.REQUEST_CODE_PICK_AUDIO -> { + MultiPicker.get(MultiPicker.AUDIO).getSelectedFiles(requireContext(), requestCode, resultCode, data) + } + MultiPicker.REQUEST_CODE_PICK_CONTACT -> { + MultiPicker.get(MultiPicker.CONTACT).getSelectedFiles(requireContext(), requestCode, resultCode, data) + } + } + val hasBeenHandled = attachmentsHelper.onActivityResult(requestCode, resultCode, data) if (!hasBeenHandled && resultCode == RESULT_OK && data != null) { when (requestCode) { @@ -1351,10 +1370,10 @@ class RoomDetailFragment @Inject constructor( private fun launchAttachmentProcess(type: AttachmentTypeSelectorView.Type) { when (type) { AttachmentTypeSelectorView.Type.CAMERA -> attachmentsHelper.openCamera() - AttachmentTypeSelectorView.Type.FILE -> attachmentsHelper.selectFile() - AttachmentTypeSelectorView.Type.GALLERY -> attachmentsHelper.selectGallery() - AttachmentTypeSelectorView.Type.AUDIO -> attachmentsHelper.selectAudio() - AttachmentTypeSelectorView.Type.CONTACT -> attachmentsHelper.selectContact() + AttachmentTypeSelectorView.Type.FILE -> MultiPicker.get(MultiPicker.FILE).startWith(this) + AttachmentTypeSelectorView.Type.GALLERY -> MultiPicker.get(MultiPicker.IMAGE).startWith(this) + AttachmentTypeSelectorView.Type.AUDIO -> MultiPicker.get(MultiPicker.AUDIO).startWith(this) + AttachmentTypeSelectorView.Type.CONTACT -> MultiPicker.get(MultiPicker.CONTACT).startWith(this) AttachmentTypeSelectorView.Type.STICKER -> vectorBaseActivity.notImplemented("Adding stickers") }.exhaustive } From 5b875e057171e1fc3f3ee3dc3d24ff8307294272 Mon Sep 17 00:00:00 2001 From: onurays Date: Sun, 22 Mar 2020 18:27:59 +0300 Subject: [PATCH 02/15] CameraPicker & incoming share implementation. --- .gitignore | 135 +----------------- multipicker/src/main/AndroidManifest.xml | 16 ++- .../vector/riotx/multipicker/AudioPicker.kt | 7 + .../vector/riotx/multipicker/CameraPicker.kt | 132 +++++++++++++++++ .../im/vector/riotx/multipicker/FilePicker.kt | 7 + .../vector/riotx/multipicker/ImagePicker.kt | 7 + .../vector/riotx/multipicker/MultiPicker.kt | 3 + .../multipicker/MultiPickerFileProvider.kt | 22 +++ .../im/vector/riotx/multipicker/Picker.kt | 9 ++ .../vector/riotx/multipicker/VideoPicker.kt | 7 + .../entity/MultiPickerContactType.kt | 4 +- .../res/xml/multipicker_provider_paths.xml | 6 + .../home/room/detail/RoomDetailFragment.kt | 10 +- 13 files changed, 227 insertions(+), 138 deletions(-) create mode 100644 multipicker/src/main/java/im/vector/riotx/multipicker/CameraPicker.kt create mode 100644 multipicker/src/main/java/im/vector/riotx/multipicker/MultiPickerFileProvider.kt create mode 100644 multipicker/src/main/res/xml/multipicker_provider_paths.xml diff --git a/.gitignore b/.gitignore index ecfdcb7510..ab97ec340a 100644 --- a/.gitignore +++ b/.gitignore @@ -14,137 +14,4 @@ /tmp ktlint -multipicker/build/.transforms/3e0e90edbb388b6989e862c9faf75e87.bin -multipicker/build/.transforms/55ef863ef687bb455ca3376fbf042ba5.bin -multipicker/build/.transforms/3e0e90edbb388b6989e862c9faf75e87/classes/classes.dex -multipicker/build/.transforms/55ef863ef687bb455ca3376fbf042ba5/classes/classes.dex -multipicker/build/generated/source/buildConfig/debug/im/vector/riotx/multipicker/BuildConfig.java -multipicker/build/intermediates/aapt_friendly_merged_manifests/debug/aapt/AndroidManifest.xml -multipicker/build/intermediates/aapt_friendly_merged_manifests/debug/aapt/output.json -multipicker/build/intermediates/annotation_processor_list/debug/annotationProcessors.json -multipicker/build/intermediates/annotations_typedef_file/debug/extractDebugAnnotations/typedefs.txt -multipicker/build/intermediates/compile_library_classes/debug/classes.jar -multipicker/build/intermediates/compile_only_not_namespaced_r_class_jar/debug/R.jar -multipicker/build/intermediates/consumer_proguard_file/debug/proguard.txt -multipicker/build/intermediates/incremental/debug-mergeJavaRes/merge-state -multipicker/build/intermediates/incremental/debug-mergeNativeLibs/merge-state -multipicker/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml -multipicker/build/intermediates/incremental/mergeDebugShaders/merger.xml -multipicker/build/intermediates/incremental/packageDebugAssets/merger.xml -multipicker/build/intermediates/incremental/packageDebugResources/compile-file-map.properties -multipicker/build/intermediates/incremental/packageDebugResources/merger.xml -multipicker/build/intermediates/javac/debug/classes/im/vector/riotx/multipicker/BuildConfig.class -multipicker/build/intermediates/library_java_res/debug/res.jar -multipicker/build/intermediates/library_manifest/debug/AndroidManifest.xml -multipicker/build/intermediates/local_only_symbol_list/debug/parseDebugLibraryResources/R-def.txt -multipicker/build/intermediates/manifest_merge_blame_file/debug/manifest-merger-blame-debug-report.txt -multipicker/build/intermediates/merged_java_res/debug/out.jar -multipicker/build/intermediates/merged_manifests/debug/output.json -multipicker/build/intermediates/packaged-classes/debug/classes.jar -multipicker/build/intermediates/res/symbol-table-with-package/debug/package-aware-r.txt -multipicker/build/intermediates/runtime_library_classes/debug/classes.jar -multipicker/build/intermediates/symbols/debug/R.txt -multipicker/build/kotlin/compileDebugKotlin/build-history.bin -multipicker/build/kotlin/compileDebugKotlin/last-build.bin -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/inputs/source-to-output.tab -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/inputs/source-to-output.tab.keystream -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/inputs/source-to-output.tab.keystream.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/inputs/source-to-output.tab.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/inputs/source-to-output.tab.values.at -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/inputs/source-to-output.tab_i -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/inputs/source-to-output.tab_i.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.values.at -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/constants.tab -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/constants.tab.keystream -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/constants.tab.keystream.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/constants.tab.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/constants.tab.values.at -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/constants.tab_i -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/constants.tab_i.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab.values.at -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/proto.tab -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/proto.tab.keystream -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/proto.tab.keystream.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/proto.tab.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/proto.tab.values.at -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/proto.tab_i -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/proto.tab_i.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab.values.at -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab_i -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab_i.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/subtypes.tab -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/subtypes.tab.keystream -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/subtypes.tab.keystream.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/subtypes.tab.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/subtypes.tab.values.at -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/subtypes.tab_i -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/subtypes.tab_i.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/supertypes.tab -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/supertypes.tab.keystream -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/supertypes.tab.keystream.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/supertypes.tab.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/supertypes.tab.values.at -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/supertypes.tab_i -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/supertypes.tab_i.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/counters.tab -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/file-to-id.tab -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/file-to-id.tab.keystream -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/file-to-id.tab.keystream.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/file-to-id.tab.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/file-to-id.tab.values.at -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/file-to-id.tab_i -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/file-to-id.tab_i.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/id-to-file.tab -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/id-to-file.tab.keystream -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/id-to-file.tab.keystream.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/id-to-file.tab.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/id-to-file.tab.values.at -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/id-to-file.tab_i -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/id-to-file.tab_i.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/lookups.tab -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/lookups.tab.keystream -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/lookups.tab.keystream.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/lookups.tab.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/lookups.tab.values -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/lookups.tab.values.at -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/lookups.tab.values.s -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/lookups.tab_i -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/lookups.tab_i.len -multipicker/build/outputs/aar/multipicker-debug.aar -multipicker/build/outputs/logs/manifest-merger-debug-report.txt -multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/AudioPicker.class -multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/ContactPicker.class -multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/FilePicker.class -multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/ImagePicker.class -multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/MultiPicker$Type$AUDIO$2.class -multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/MultiPicker$Type$CONTACT$2.class -multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/MultiPicker$Type$FILE$2.class -multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/MultiPicker$Type$IMAGE$2.class -multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/MultiPicker$Type$VIDEO$2.class -multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/MultiPicker$Type.class -multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/MultiPicker.class -multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/Picker.class -multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/VideoPicker.class -multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/entity/MultiPickerAudioType.class -multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/entity/MultiPickerBaseType.class -multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/entity/MultiPickerContactType.class -multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/entity/MultiPickerFileType.class -multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/entity/MultiPickerImageType.class -multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/entity/MultiPickerVideoType.class -multipicker/build/tmp/kotlin-classes/debug/META-INF/multipicker_debug.kotlin_module + diff --git a/multipicker/src/main/AndroidManifest.xml b/multipicker/src/main/AndroidManifest.xml index 068a7c9a19..6f714ab388 100644 --- a/multipicker/src/main/AndroidManifest.xml +++ b/multipicker/src/main/AndroidManifest.xml @@ -1,2 +1,16 @@ + package="im.vector.riotx.multipicker"> + + + + + + + + diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt index 19aba7bf1a..8fb23ec49d 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt @@ -52,6 +52,13 @@ class AudioPicker(override val requestCode: Int) : Picker( } } else if (dataUri != null) { selectedUriList.add(dataUri) + } else { + data?.extras?.get(Intent.EXTRA_STREAM)?.let { + when (it) { + is List<*> -> selectedUriList.addAll(it as List) + else -> selectedUriList.add(it as Uri) + } + } } selectedUriList.forEach { selectedUri -> diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/CameraPicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/CameraPicker.kt new file mode 100644 index 0000000000..6962f2098a --- /dev/null +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/CameraPicker.kt @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotx.multipicker + +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.graphics.BitmapFactory +import android.graphics.ImageDecoder +import android.net.Uri +import android.os.Build +import android.os.Environment +import android.provider.MediaStore +import androidx.core.content.FileProvider +import androidx.exifinterface.media.ExifInterface +import androidx.fragment.app.Fragment +import im.vector.riotx.multipicker.entity.MultiPickerImageType +import java.io.File +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale + +class CameraPicker(val requestCode: Int) { + + fun startWithExpectingFile(activity: Activity): Uri? { + val photoUri = createPhotoUri(activity) + val intent = createIntent().apply { + putExtra(MediaStore.EXTRA_OUTPUT, photoUri) + } + activity.startActivityForResult(intent, requestCode) + return photoUri + } + + fun startWithExpectingFile(fragment: Fragment): Uri? { + val photoUri = createPhotoUri(fragment.requireContext()) + val intent = createIntent().apply { + putExtra(MediaStore.EXTRA_OUTPUT, photoUri) + } + fragment.startActivityForResult(intent, requestCode) + return photoUri + } + + fun getTakenPhoto(context: Context, requestCode: Int, resultCode: Int, photoUri: Uri): MultiPickerImageType? { + if (requestCode == this.requestCode && resultCode == Activity.RESULT_OK) { + val projection = arrayOf( + MediaStore.Images.Media.DISPLAY_NAME, + MediaStore.Images.Media.SIZE + ) + + context.contentResolver.query( + photoUri, + projection, + null, + null, + null + )?.use { cursor -> + val nameColumn = cursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME) + val sizeColumn = cursor.getColumnIndex(MediaStore.Images.Media.SIZE) + + if (cursor.moveToNext()) { + val name = cursor.getString(nameColumn) + val size = cursor.getLong(sizeColumn) + + var orientation = 0 + + val bitmap = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + ImageDecoder.decodeBitmap(ImageDecoder.createSource(context.contentResolver, photoUri)) + } else { + context.contentResolver.openInputStream(photoUri)?.use { inputStream -> + BitmapFactory.decodeStream(inputStream) + } + } + + context.contentResolver.openInputStream(photoUri)?.use { inputStream -> + try { + ExifInterface(inputStream).let { + orientation = it.rotationDegrees + } + } catch (e: Exception) { + e.printStackTrace() + } + } + + return MultiPickerImageType( + name, + size, + context.contentResolver.getType(photoUri), + photoUri, + bitmap?.width ?: 0, + bitmap?.height ?: 0, + orientation + ) + } + } + } + return null + } + + private fun createIntent(): Intent { + return Intent(MediaStore.ACTION_IMAGE_CAPTURE) + } + + private fun createPhotoUri(context: Context): Uri { + val file = createImageFile(context) + val authority = context.packageName + ".multipicker.fileprovider" + return FileProvider.getUriForFile(context, authority, file) + } + + private fun createImageFile(context: Context): File { + val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date()) + val storageDir: File = context.filesDir + return File.createTempFile( + "JPEG_${timeStamp}_", /* prefix */ + ".jpg", /* suffix */ + storageDir /* directory */ + ) + } +} diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/FilePicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/FilePicker.kt index cf6b74c62d..9d3292c115 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/FilePicker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/FilePicker.kt @@ -51,6 +51,13 @@ class FilePicker(override val requestCode: Int) : Picker(re } } else if (dataUri != null) { selectedUriList.add(dataUri) + } else { + data?.extras?.get(Intent.EXTRA_STREAM)?.let { + when (it) { + is List<*> -> selectedUriList.addAll(it as List) + else -> selectedUriList.add(it as Uri) + } + } } selectedUriList.forEach { selectedUri -> diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/ImagePicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/ImagePicker.kt index 79832a2ad1..f33ceb816c 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/ImagePicker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/ImagePicker.kt @@ -55,6 +55,13 @@ class ImagePicker(override val requestCode: Int) : Picker( } } else if (dataUri != null) { selectedUriList.add(dataUri) + } else { + data?.extras?.get(Intent.EXTRA_STREAM)?.let { + when (it) { + is List<*> -> selectedUriList.addAll(it as List) + else -> selectedUriList.add(it as Uri) + } + } } selectedUriList.forEach { selectedUri -> diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/MultiPicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/MultiPicker.kt index d10346c903..24769e11c3 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/MultiPicker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/MultiPicker.kt @@ -24,12 +24,14 @@ class MultiPicker { val VIDEO by lazy { MultiPicker() } val AUDIO by lazy { MultiPicker() } val CONTACT by lazy { MultiPicker() } + val CAMERA by lazy { MultiPicker() } const val REQUEST_CODE_PICK_IMAGE = 5000 const val REQUEST_CODE_PICK_VIDEO = 5001 const val REQUEST_CODE_PICK_FILE = 5002 const val REQUEST_CODE_PICK_AUDIO = 5003 const val REQUEST_CODE_PICK_CONTACT = 5004 + const val REQUEST_CODE_TAKE_PHOTO = 5005 @Suppress("UNCHECKED_CAST") fun get(type: MultiPicker): T { @@ -39,6 +41,7 @@ class MultiPicker { FILE -> FilePicker(REQUEST_CODE_PICK_FILE) as T AUDIO -> AudioPicker(REQUEST_CODE_PICK_AUDIO) as T CONTACT -> ContactPicker(REQUEST_CODE_PICK_CONTACT) as T + CAMERA -> CameraPicker(REQUEST_CODE_TAKE_PHOTO) as T else -> throw IllegalArgumentException("Unsupported type $type") } } diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/MultiPickerFileProvider.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/MultiPickerFileProvider.kt new file mode 100644 index 0000000000..832d721eef --- /dev/null +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/MultiPickerFileProvider.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotx.multipicker + +import androidx.core.content.FileProvider + +class MultiPickerFileProvider : FileProvider() { +} diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/Picker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/Picker.kt index 62dff35062..f162dd7608 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/Picker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/Picker.kt @@ -19,6 +19,7 @@ package im.vector.riotx.multipicker import android.app.Activity import android.content.Context import android.content.Intent +import android.net.Uri import androidx.fragment.app.Fragment abstract class Picker(open val requestCode: Int) { @@ -29,8 +30,16 @@ abstract class Picker(open val requestCode: Int) { abstract fun startWith(fragment: Fragment) + open fun startWithExpectingFile(activity: Activity): Uri? = null + + open fun startWithExpectingFile(fragment: Fragment): Uri? = null + abstract fun getSelectedFiles(context: Context, requestCode: Int, resultCode: Int, data: Intent?): List + fun getIncomingFiles(context: Context, data: Intent?): List { + return getSelectedFiles(context, requestCode, Activity.RESULT_OK, data) + } + fun single(): Picker { single = true return this diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/VideoPicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/VideoPicker.kt index 8066c0b43e..92d1e9c240 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/VideoPicker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/VideoPicker.kt @@ -52,6 +52,13 @@ class VideoPicker(override val requestCode: Int) : Picker( } } else if (dataUri != null) { selectedUriList.add(dataUri) + } else { + data?.extras?.get(Intent.EXTRA_STREAM)?.let { + when (it) { + is List<*> -> selectedUriList.addAll(it as List) + else -> selectedUriList.add(it as Uri) + } + } } selectedUriList.forEach { selectedUri -> diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerContactType.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerContactType.kt index 565304a546..5a9c064867 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerContactType.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerContactType.kt @@ -22,11 +22,11 @@ data class MultiPickerContactType( val phoneNumberList: List, val emailList: List ) { - private val FORMAT_CONTACT = "Name: %s, Photo: %s, Phones: %s, Emails: %s" + private val CONTACT_FORMAT = "Name: %s, Photo: %s, Phones: %s, Emails: %s" override fun toString(): String { val phoneNumberString = phoneNumberList.joinToString(separator = ", ", prefix = "[", postfix = "]") val emailString = emailList.joinToString(separator = ", ", prefix = "[", postfix = "]") - return String.format(FORMAT_CONTACT, displayName, photoUri, phoneNumberString, emailString) + return String.format(CONTACT_FORMAT, displayName, photoUri, phoneNumberString, emailString) } } diff --git a/multipicker/src/main/res/xml/multipicker_provider_paths.xml b/multipicker/src/main/res/xml/multipicker_provider_paths.xml new file mode 100644 index 0000000000..ff9b81ce98 --- /dev/null +++ b/multipicker/src/main/res/xml/multipicker_provider_paths.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt index 30f9551612..13de137e8f 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt @@ -533,6 +533,11 @@ class RoomDetailFragment @Inject constructor( MultiPicker.REQUEST_CODE_PICK_CONTACT -> { MultiPicker.get(MultiPicker.CONTACT).getSelectedFiles(requireContext(), requestCode, resultCode, data) } + MultiPicker.REQUEST_CODE_TAKE_PHOTO -> { + cameraPhotoUri?.let { + MultiPicker.get(MultiPicker.CAMERA).getTakenPhoto(requireContext(), requestCode, resultCode, it) + } + } } val hasBeenHandled = attachmentsHelper.onActivityResult(requestCode, resultCode, data) @@ -1367,9 +1372,12 @@ class RoomDetailFragment @Inject constructor( } } + private var cameraPhotoUri: Uri? = null private fun launchAttachmentProcess(type: AttachmentTypeSelectorView.Type) { when (type) { - AttachmentTypeSelectorView.Type.CAMERA -> attachmentsHelper.openCamera() + AttachmentTypeSelectorView.Type.CAMERA -> { + cameraPhotoUri = MultiPicker.get(MultiPicker.CAMERA).startWithExpectingFile(this) + } AttachmentTypeSelectorView.Type.FILE -> MultiPicker.get(MultiPicker.FILE).startWith(this) AttachmentTypeSelectorView.Type.GALLERY -> MultiPicker.get(MultiPicker.IMAGE).startWith(this) AttachmentTypeSelectorView.Type.AUDIO -> MultiPicker.get(MultiPicker.AUDIO).startWith(this) From f7fd23b153c3821b7e98efdf7047eca9a9405f23 Mon Sep 17 00:00:00 2001 From: onurays Date: Mon, 23 Mar 2020 16:31:32 +0300 Subject: [PATCH 03/15] App integration to the new multipicker library. --- .../session/content/ContentAttachmentData.kt | 4 +- .../internal/session/content/FileUploader.kt | 4 +- .../session/content/ThumbnailExtractor.kt | 68 ++++--- .../session/content/UploadContentWorker.kt | 188 ++++++++---------- .../room/send/LocalEchoEventFactory.kt | 16 +- .../vector/riotx/multipicker/AudioPicker.kt | 1 + .../vector/riotx/multipicker/CameraPicker.kt | 1 - .../im/vector/riotx/multipicker/FilePicker.kt | 1 + .../vector/riotx/multipicker/ImagePicker.kt | 1 + .../multipicker/MultiPickerFileProvider.kt | 3 +- .../vector/riotx/multipicker/VideoPicker.kt | 1 + vector/build.gradle | 3 - .../features/attachments/AttachmentsHelper.kt | 182 ++++++++--------- .../features/attachments/AttachmentsMapper.kt | 43 ++-- .../attachments/AttachmentsPickerCallback.kt | 96 --------- .../attachments/PickerManagerFactory.kt | 134 ------------- .../preview/AttachmentPreviewControllers.kt | 4 +- .../preview/AttachmentPreviewItems.kt | 3 +- .../preview/AttachmentsPreviewAction.kt | 3 +- .../preview/AttachmentsPreviewFragment.kt | 8 +- .../preview/AttachmentsPreviewViewModel.kt | 2 +- .../home/room/detail/RoomDetailFragment.kt | 40 +--- .../home/room/detail/RoomDetailViewModel.kt | 2 +- .../features/share/IncomingShareFragment.kt | 6 +- 24 files changed, 265 insertions(+), 549 deletions(-) delete mode 100644 vector/src/main/java/im/vector/riotx/features/attachments/AttachmentsPickerCallback.kt delete mode 100644 vector/src/main/java/im/vector/riotx/features/attachments/PickerManagerFactory.kt diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/content/ContentAttachmentData.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/content/ContentAttachmentData.kt index e32bb9f21f..b80a17b017 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/content/ContentAttachmentData.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/content/ContentAttachmentData.kt @@ -16,6 +16,7 @@ package im.vector.matrix.android.api.session.content +import android.net.Uri import android.os.Parcelable import androidx.exifinterface.media.ExifInterface import kotlinx.android.parcel.Parcelize @@ -29,8 +30,7 @@ data class ContentAttachmentData( val width: Long? = 0, val exifOrientation: Int = ExifInterface.ORIENTATION_UNDEFINED, val name: String? = null, - val queryUri: String, - val path: String, + val queryUri: Uri, private val mimeType: String?, val type: Type ) : Parcelable { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/FileUploader.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/FileUploader.kt index 4071c9224f..4fa0cb5013 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/FileUploader.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/FileUploader.kt @@ -53,9 +53,9 @@ internal class FileUploader @Inject constructor(@Authenticated suspend fun uploadByteArray(byteArray: ByteArray, filename: String?, - mimeType: String, + mimeType: String?, progressListener: ProgressRequestBody.Listener? = null): ContentUploadResponse { - val uploadBody = byteArray.toRequestBody(mimeType.toMediaTypeOrNull()) + val uploadBody = byteArray.toRequestBody(mimeType?.toMediaTypeOrNull()) return upload(uploadBody, filename, progressListener) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/ThumbnailExtractor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/ThumbnailExtractor.kt index 083cac0278..2ce249ab80 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/ThumbnailExtractor.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/ThumbnailExtractor.kt @@ -16,12 +16,14 @@ package im.vector.matrix.android.internal.session.content +import android.content.Context import android.graphics.Bitmap -import android.media.ThumbnailUtils -import android.provider.MediaStore +import android.media.MediaMetadataRetriever import im.vector.matrix.android.api.session.content.ContentAttachmentData +import timber.log.Timber import java.io.ByteArrayOutputStream -import java.io.File +import kotlin.math.max +import kotlin.math.roundToInt internal object ThumbnailExtractor { @@ -33,34 +35,48 @@ internal object ThumbnailExtractor { val mimeType: String ) - fun extractThumbnail(attachment: ContentAttachmentData): ThumbnailData? { - val file = File(attachment.path) - if (!file.exists() || !file.isFile) { - return null - } + fun extractThumbnail(context: Context, attachment: ContentAttachmentData): ThumbnailData? { return if (attachment.type == ContentAttachmentData.Type.VIDEO) { - extractVideoThumbnail(attachment) + extractVideoThumbnail(context, attachment) } else { null } } - private fun extractVideoThumbnail(attachment: ContentAttachmentData): ThumbnailData? { - val thumbnail = ThumbnailUtils.createVideoThumbnail(attachment.path, MediaStore.Video.Thumbnails.MINI_KIND) ?: return null - val outputStream = ByteArrayOutputStream() - thumbnail.compress(Bitmap.CompressFormat.JPEG, 100, outputStream) - val thumbnailWidth = thumbnail.width - val thumbnailHeight = thumbnail.height - val thumbnailSize = outputStream.size() - val thumbnailData = ThumbnailData( - width = thumbnailWidth, - height = thumbnailHeight, - size = thumbnailSize.toLong(), - bytes = outputStream.toByteArray(), - mimeType = "image/jpeg" - ) - thumbnail.recycle() - outputStream.reset() - return thumbnailData + private fun extractVideoThumbnail(context: Context, attachment: ContentAttachmentData): ThumbnailData? { + val mediaMetadataRetriever = MediaMetadataRetriever() + try { + mediaMetadataRetriever.setDataSource(context, attachment.queryUri) + var thumbnail = mediaMetadataRetriever.frameAtTime + // Scale down the bitmap if it's too large. + val width: Int = thumbnail.width + val height: Int = thumbnail.height + val max = max(width, height) + if (max > 512) { + val scale = 512f / max + val w = (scale * width).roundToInt() + val h = (scale * height).roundToInt() + thumbnail = Bitmap.createScaledBitmap(thumbnail, w, h, true) + } + + val outputStream = ByteArrayOutputStream() + thumbnail.compress(Bitmap.CompressFormat.JPEG, 100, outputStream) + val thumbnailWidth = thumbnail.width + val thumbnailHeight = thumbnail.height + val thumbnailSize = outputStream.size() + val thumbnailData = ThumbnailData( + width = thumbnailWidth, + height = thumbnailHeight, + size = thumbnailSize.toLong(), + bytes = outputStream.toByteArray(), + mimeType = "image/jpeg" + ) + thumbnail.recycle() + outputStream.reset() + return thumbnailData + } catch (e: Exception) { + Timber.e(e, "Cannot extract video thumbnail") + return null + } } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt index 1c88f87804..21ab649c23 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt @@ -17,12 +17,9 @@ package im.vector.matrix.android.internal.session.content import android.content.Context -import android.graphics.BitmapFactory import androidx.work.CoroutineWorker import androidx.work.WorkerParameters import com.squareup.moshi.JsonClass -import id.zelory.compressor.Compressor -import id.zelory.compressor.constraint.default import im.vector.matrix.android.api.session.content.ContentAttachmentData import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.toContent @@ -41,8 +38,6 @@ import im.vector.matrix.android.internal.worker.WorkerParamsFactory import im.vector.matrix.android.internal.worker.getSessionComponent import timber.log.Timber import java.io.ByteArrayInputStream -import java.io.File -import java.io.FileInputStream import javax.inject.Inject private data class NewImageAttributes( @@ -94,8 +89,84 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter var newImageAttributes: NewImageAttributes? = null - val attachmentFile = try { - File(attachment.path) + try { + val inputStream = context.contentResolver.openInputStream(attachment.queryUri) ?: return Result.success() + + inputStream.use { + var uploadedThumbnailUrl: String? = null + var uploadedThumbnailEncryptedFileInfo: EncryptedFileInfo? = null + + ThumbnailExtractor.extractThumbnail(context, params.attachment)?.let { thumbnailData -> + val thumbnailProgressListener = object : ProgressRequestBody.Listener { + override fun onProgress(current: Long, total: Long) { + notifyTracker(params) { contentUploadStateTracker.setProgressThumbnail(it, current, total) } + } + } + + try { + val contentUploadResponse = if (params.isEncrypted) { + Timber.v("Encrypt thumbnail") + notifyTracker(params) { contentUploadStateTracker.setEncryptingThumbnail(it) } + val encryptionResult = MXEncryptedAttachments.encryptAttachment(ByteArrayInputStream(thumbnailData.bytes), thumbnailData.mimeType) + uploadedThumbnailEncryptedFileInfo = encryptionResult.encryptedFileInfo + fileUploader.uploadByteArray(encryptionResult.encryptedByteArray, + "thumb_${attachment.name}", + "application/octet-stream", + thumbnailProgressListener) + } else { + fileUploader.uploadByteArray(thumbnailData.bytes, + "thumb_${attachment.name}", + thumbnailData.mimeType, + thumbnailProgressListener) + } + + uploadedThumbnailUrl = contentUploadResponse.contentUri + } catch (t: Throwable) { + Timber.e(t) + return handleFailure(params, t) + } + } + + val progressListener = object : ProgressRequestBody.Listener { + override fun onProgress(current: Long, total: Long) { + notifyTracker(params) { + if (isStopped) { + contentUploadStateTracker.setFailure(it, Throwable("Cancelled")) + } else { + contentUploadStateTracker.setProgress(it, current, total) + } + } + } + } + + var uploadedFileEncryptedFileInfo: EncryptedFileInfo? = null + + return try { + val contentUploadResponse = if (params.isEncrypted) { + Timber.v("Encrypt file") + notifyTracker(params) { contentUploadStateTracker.setEncrypting(it) } + + val encryptionResult = MXEncryptedAttachments.encryptAttachment(inputStream, attachment.getSafeMimeType()) + uploadedFileEncryptedFileInfo = encryptionResult.encryptedFileInfo + + fileUploader + .uploadByteArray(encryptionResult.encryptedByteArray, attachment.name, "application/octet-stream", progressListener) + } else { + fileUploader + .uploadByteArray(inputStream.readBytes(), attachment.name, attachment.getSafeMimeType(), progressListener) + } + + handleSuccess(params, + contentUploadResponse.contentUri, + uploadedFileEncryptedFileInfo, + uploadedThumbnailUrl, + uploadedThumbnailEncryptedFileInfo, + newImageAttributes) + } catch (t: Throwable) { + Timber.e(t) + handleFailure(params, t) + } + } } catch (e: Exception) { Timber.e(e) notifyTracker(params) { contentUploadStateTracker.setFailure(it, e) } @@ -106,109 +177,6 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter ) ) ) - } - .let { originalFile -> - if (attachment.type == ContentAttachmentData.Type.IMAGE) { - if (params.compressBeforeSending) { - Compressor.compress(context, originalFile) { - default( - width = MAX_IMAGE_SIZE, - height = MAX_IMAGE_SIZE - ) - }.also { compressedFile -> - // Update the params - val options = BitmapFactory.Options().apply { inJustDecodeBounds = true } - BitmapFactory.decodeFile(compressedFile.absolutePath, options) - val fileSize = compressedFile.length().toInt() - - newImageAttributes = NewImageAttributes( - options.outWidth, - options.outHeight, - fileSize - ) - } - } else { - // TODO Fix here the image rotation issue - originalFile - } - } else { - // Other type - originalFile - } - } - - var uploadedThumbnailUrl: String? = null - var uploadedThumbnailEncryptedFileInfo: EncryptedFileInfo? = null - - ThumbnailExtractor.extractThumbnail(params.attachment)?.let { thumbnailData -> - val thumbnailProgressListener = object : ProgressRequestBody.Listener { - override fun onProgress(current: Long, total: Long) { - notifyTracker(params) { contentUploadStateTracker.setProgressThumbnail(it, current, total) } - } - } - - try { - val contentUploadResponse = if (params.isEncrypted) { - Timber.v("Encrypt thumbnail") - notifyTracker(params) { contentUploadStateTracker.setEncryptingThumbnail(it) } - val encryptionResult = MXEncryptedAttachments.encryptAttachment(ByteArrayInputStream(thumbnailData.bytes), thumbnailData.mimeType) - uploadedThumbnailEncryptedFileInfo = encryptionResult.encryptedFileInfo - fileUploader.uploadByteArray(encryptionResult.encryptedByteArray, - "thumb_${attachment.name}", - "application/octet-stream", - thumbnailProgressListener) - } else { - fileUploader.uploadByteArray(thumbnailData.bytes, - "thumb_${attachment.name}", - thumbnailData.mimeType, - thumbnailProgressListener) - } - - uploadedThumbnailUrl = contentUploadResponse.contentUri - } catch (t: Throwable) { - Timber.e(t) - return handleFailure(params, t) - } - } - - val progressListener = object : ProgressRequestBody.Listener { - override fun onProgress(current: Long, total: Long) { - notifyTracker(params) { - if (isStopped) { - contentUploadStateTracker.setFailure(it, Throwable("Cancelled")) - } else { - contentUploadStateTracker.setProgress(it, current, total) - } - } - } - } - - var uploadedFileEncryptedFileInfo: EncryptedFileInfo? = null - - return try { - val contentUploadResponse = if (params.isEncrypted) { - Timber.v("Encrypt file") - notifyTracker(params) { contentUploadStateTracker.setEncrypting(it) } - - val encryptionResult = MXEncryptedAttachments.encryptAttachment(FileInputStream(attachmentFile), attachment.getSafeMimeType()) - uploadedFileEncryptedFileInfo = encryptionResult.encryptedFileInfo - - fileUploader - .uploadByteArray(encryptionResult.encryptedByteArray, attachment.name, "application/octet-stream", progressListener) - } else { - fileUploader - .uploadFile(attachmentFile, attachment.name, attachment.getSafeMimeType(), progressListener) - } - - handleSuccess(params, - contentUploadResponse.contentUri, - uploadedFileEncryptedFileInfo, - uploadedThumbnailUrl, - uploadedThumbnailEncryptedFileInfo, - newImageAttributes) - } catch (t: Throwable) { - Timber.e(t) - handleFailure(params, t) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt index f10c40ded5..a4a6eb6972 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt @@ -16,6 +16,7 @@ package im.vector.matrix.android.internal.session.room.send +import android.content.Context import android.graphics.Bitmap import android.media.MediaMetadataRetriever import androidx.exifinterface.media.ExifInterface @@ -74,6 +75,7 @@ import javax.inject.Inject * The transactionId is used as loc */ internal class LocalEchoEventFactory @Inject constructor( + private val context: Context, @UserId private val userId: String, private val stringProvider: StringProvider, private val textPillsUtils: TextPillsUtils, @@ -266,14 +268,14 @@ internal class LocalEchoEventFactory @Inject constructor( height = height?.toInt() ?: 0, size = attachment.size.toInt() ), - url = attachment.path + url = attachment.queryUri.toString() ) return createEvent(roomId, content) } private fun createVideoEvent(roomId: String, attachment: ContentAttachmentData): Event { val mediaDataRetriever = MediaMetadataRetriever() - mediaDataRetriever.setDataSource(attachment.path) + mediaDataRetriever.setDataSource(context, attachment.queryUri) // Use frame to calculate height and width as we are sure to get the right ones val firstFrame: Bitmap? = mediaDataRetriever.frameAtTime @@ -281,7 +283,7 @@ internal class LocalEchoEventFactory @Inject constructor( val width = firstFrame?.width ?: 0 mediaDataRetriever.release() - val thumbnailInfo = ThumbnailExtractor.extractThumbnail(attachment)?.let { + val thumbnailInfo = ThumbnailExtractor.extractThumbnail(context, attachment)?.let { ThumbnailInfo( width = it.width, height = it.height, @@ -299,10 +301,10 @@ internal class LocalEchoEventFactory @Inject constructor( size = attachment.size, duration = attachment.duration?.toInt() ?: 0, // Glide will be able to use the local path and extract a thumbnail. - thumbnailUrl = attachment.path, + thumbnailUrl = attachment.queryUri.toString(), thumbnailInfo = thumbnailInfo ), - url = attachment.path + url = attachment.queryUri.toString() ) return createEvent(roomId, content) } @@ -315,7 +317,7 @@ internal class LocalEchoEventFactory @Inject constructor( mimeType = attachment.getSafeMimeType()?.takeIf { it.isNotBlank() } ?: "audio/mpeg", size = attachment.size ), - url = attachment.path + url = attachment.queryUri.toString() ) return createEvent(roomId, content) } @@ -329,7 +331,7 @@ internal class LocalEchoEventFactory @Inject constructor( ?: "application/octet-stream", size = attachment.size ), - url = attachment.path + url = attachment.queryUri.toString() ) return createEvent(roomId, content) } diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt index 8fb23ec49d..23873aae1c 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt @@ -54,6 +54,7 @@ class AudioPicker(override val requestCode: Int) : Picker( selectedUriList.add(dataUri) } else { data?.extras?.get(Intent.EXTRA_STREAM)?.let { + @Suppress("UNCHECKED_CAST") when (it) { is List<*> -> selectedUriList.addAll(it as List) else -> selectedUriList.add(it as Uri) diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/CameraPicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/CameraPicker.kt index 6962f2098a..81e665a43f 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/CameraPicker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/CameraPicker.kt @@ -23,7 +23,6 @@ import android.graphics.BitmapFactory import android.graphics.ImageDecoder import android.net.Uri import android.os.Build -import android.os.Environment import android.provider.MediaStore import androidx.core.content.FileProvider import androidx.exifinterface.media.ExifInterface diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/FilePicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/FilePicker.kt index 9d3292c115..0e1169755e 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/FilePicker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/FilePicker.kt @@ -53,6 +53,7 @@ class FilePicker(override val requestCode: Int) : Picker(re selectedUriList.add(dataUri) } else { data?.extras?.get(Intent.EXTRA_STREAM)?.let { + @Suppress("UNCHECKED_CAST") when (it) { is List<*> -> selectedUriList.addAll(it as List) else -> selectedUriList.add(it as Uri) diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/ImagePicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/ImagePicker.kt index f33ceb816c..8bf589800d 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/ImagePicker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/ImagePicker.kt @@ -57,6 +57,7 @@ class ImagePicker(override val requestCode: Int) : Picker( selectedUriList.add(dataUri) } else { data?.extras?.get(Intent.EXTRA_STREAM)?.let { + @Suppress("UNCHECKED_CAST") when (it) { is List<*> -> selectedUriList.addAll(it as List) else -> selectedUriList.add(it as Uri) diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/MultiPickerFileProvider.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/MultiPickerFileProvider.kt index 832d721eef..1549b43fd7 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/MultiPickerFileProvider.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/MultiPickerFileProvider.kt @@ -18,5 +18,4 @@ package im.vector.riotx.multipicker import androidx.core.content.FileProvider -class MultiPickerFileProvider : FileProvider() { -} +class MultiPickerFileProvider : FileProvider() diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/VideoPicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/VideoPicker.kt index 92d1e9c240..d4b8d6a985 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/VideoPicker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/VideoPicker.kt @@ -54,6 +54,7 @@ class VideoPicker(override val requestCode: Int) : Picker( selectedUriList.add(dataUri) } else { data?.extras?.get(Intent.EXTRA_STREAM)?.let { + @Suppress("UNCHECKED_CAST") when (it) { is List<*> -> selectedUriList.addAll(it as List) else -> selectedUriList.add(it as Uri) diff --git a/vector/build.gradle b/vector/build.gradle index 447ad7a767..2887ebba46 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -348,9 +348,6 @@ dependencies { // Badge for compatibility implementation 'me.leolin:ShortcutBadger:1.1.22@aar' - // File picker - implementation 'com.kbeanie:multipicker:1.6@aar' - // DI implementation "com.google.dagger:dagger:$daggerVersion" kapt "com.google.dagger:dagger-compiler:$daggerVersion" diff --git a/vector/src/main/java/im/vector/riotx/features/attachments/AttachmentsHelper.kt b/vector/src/main/java/im/vector/riotx/features/attachments/AttachmentsHelper.kt index c576ebe1b9..daea538e12 100644 --- a/vector/src/main/java/im/vector/riotx/features/attachments/AttachmentsHelper.kt +++ b/vector/src/main/java/im/vector/riotx/features/attachments/AttachmentsHelper.kt @@ -18,20 +18,13 @@ package im.vector.riotx.features.attachments import android.app.Activity import android.content.Context import android.content.Intent +import android.net.Uri import android.os.Bundle import androidx.fragment.app.Fragment -import com.kbeanie.multipicker.api.Picker.PICK_AUDIO -import com.kbeanie.multipicker.api.Picker.PICK_CONTACT -import com.kbeanie.multipicker.api.Picker.PICK_FILE -import com.kbeanie.multipicker.api.Picker.PICK_IMAGE_CAMERA -import com.kbeanie.multipicker.api.Picker.PICK_IMAGE_DEVICE -import com.kbeanie.multipicker.core.ImagePickerImpl -import com.kbeanie.multipicker.core.PickerManager -import com.kbeanie.multipicker.utils.IntentUtils import im.vector.matrix.android.BuildConfig import im.vector.matrix.android.api.session.content.ContentAttachmentData import im.vector.riotx.core.platform.Restorable -import im.vector.riotx.features.attachments.AttachmentsHelper.Callback +import im.vector.riotx.multipicker.MultiPicker import timber.log.Timber private const val CAPTURE_PATH_KEY = "CAPTURE_PATH_KEY" @@ -39,20 +32,8 @@ private const val PENDING_TYPE_KEY = "PENDING_TYPE_KEY" /** * This class helps to handle attachments by providing simple methods. - * The process is asynchronous and you must implement [Callback] methods to get the data or a failure. */ -class AttachmentsHelper private constructor(private val context: Context, - private val pickerManagerFactory: PickerManagerFactory) : Restorable { - - companion object { - fun create(fragment: Fragment, callback: Callback): AttachmentsHelper { - return AttachmentsHelper(fragment.requireContext(), FragmentPickerManagerFactory(fragment, callback)) - } - - fun create(activity: Activity, callback: Callback): AttachmentsHelper { - return AttachmentsHelper(activity, ActivityPickerManagerFactory(activity, callback)) - } - } +class AttachmentsHelper(val context: Context, val callback: Callback) : Restorable { interface Callback { fun onContactAttachmentReady(contactAttachment: ContactAttachment) { @@ -66,39 +47,15 @@ class AttachmentsHelper private constructor(private val context: Context, } // Capture path allows to handle camera image picking. It must be restored if the activity gets killed. - private var capturePath: String? = null + private var captureUri: Uri? = null // The pending type is set if we have to handle permission request. It must be restored if the activity gets killed. var pendingType: AttachmentTypeSelectorView.Type? = null - private val imagePicker by lazy { - pickerManagerFactory.createImagePicker() - } - - private val videoPicker by lazy { - pickerManagerFactory.createVideoPicker() - } - - private val cameraImagePicker by lazy { - pickerManagerFactory.createCameraImagePicker() - } - - private val filePicker by lazy { - pickerManagerFactory.createFilePicker() - } - - private val audioPicker by lazy { - pickerManagerFactory.createAudioPicker() - } - - private val contactPicker by lazy { - pickerManagerFactory.createContactPicker() - } - // Restorable override fun onSaveInstanceState(outState: Bundle) { - capturePath?.also { - outState.putString(CAPTURE_PATH_KEY, it) + captureUri?.also { + outState.putParcelable(CAPTURE_PATH_KEY, it) } pendingType?.also { outState.putSerializable(PENDING_TYPE_KEY, it) @@ -106,10 +63,7 @@ class AttachmentsHelper private constructor(private val context: Context, } override fun onRestoreInstanceState(savedInstanceState: Bundle?) { - capturePath = savedInstanceState?.getString(CAPTURE_PATH_KEY) - if (capturePath != null) { - cameraImagePicker.reinitialize(capturePath) - } + captureUri = savedInstanceState?.getParcelable(CAPTURE_PATH_KEY) as? Uri pendingType = savedInstanceState?.getSerializable(PENDING_TYPE_KEY) as? AttachmentTypeSelectorView.Type } @@ -118,36 +72,36 @@ class AttachmentsHelper private constructor(private val context: Context, /** * Starts the process for handling file picking */ - fun selectFile() { - filePicker.pickFile() + fun selectFile(fragment: Fragment) { + MultiPicker.get(MultiPicker.FILE).startWith(fragment) } /** * Starts the process for handling image picking */ - fun selectGallery() { - imagePicker.pickImage() + fun selectGallery(fragment: Fragment) { + MultiPicker.get(MultiPicker.IMAGE).startWith(fragment) } /** * Starts the process for handling audio picking */ - fun selectAudio() { - audioPicker.pickAudio() + fun selectAudio(fragment: Fragment) { + MultiPicker.get(MultiPicker.AUDIO).startWith(fragment) } /** * Starts the process for handling capture image picking */ - fun openCamera() { - capturePath = cameraImagePicker.pickImage() + fun openCamera(fragment: Fragment) { + captureUri = MultiPicker.get(MultiPicker.CAMERA).startWithExpectingFile(fragment) } /** * Starts the process for handling contact picking */ - fun selectContact() { - contactPicker.pickContact() + fun selectContact(fragment: Fragment) { + MultiPicker.get(MultiPicker.CONTACT).startWith(fragment) } /** @@ -157,14 +111,58 @@ class AttachmentsHelper private constructor(private val context: Context, */ fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean { if (resultCode == Activity.RESULT_OK) { - val pickerManager = getPickerManagerForRequestCode(requestCode) - if (pickerManager != null) { - if (pickerManager is ImagePickerImpl) { - pickerManager.reinitialize(capturePath) + when (requestCode) { + MultiPicker.REQUEST_CODE_PICK_FILE -> { + callback.onContentAttachmentsReady( + MultiPicker.get(MultiPicker.FILE) + .getSelectedFiles(context, requestCode, resultCode, data) + .map { it.toContentAttachmentData() } + ) } - pickerManager.submit(data) - return true + MultiPicker.REQUEST_CODE_PICK_AUDIO -> { + callback.onContentAttachmentsReady( + MultiPicker.get(MultiPicker.AUDIO) + .getSelectedFiles(context, requestCode, resultCode, data) + .map { it.toContentAttachmentData() } + ) + } + MultiPicker.REQUEST_CODE_PICK_CONTACT -> { + MultiPicker.get(MultiPicker.CONTACT) + .getSelectedFiles(context, requestCode, resultCode, data) + .firstOrNull() + ?.toContactAttachment() + ?.let { + callback.onContactAttachmentReady(it) + } + } + MultiPicker.REQUEST_CODE_PICK_IMAGE -> { + callback.onContentAttachmentsReady( + MultiPicker.get(MultiPicker.IMAGE) + .getSelectedFiles(context, requestCode, resultCode, data) + .map { it.toContentAttachmentData() } + ) + } + MultiPicker.REQUEST_CODE_TAKE_PHOTO -> { + captureUri?.let { captureUri -> + MultiPicker.get(MultiPicker.CAMERA) + .getTakenPhoto(context, requestCode, resultCode, captureUri) + ?.let { + callback.onContentAttachmentsReady( + listOf(it).map { it.toContentAttachmentData() } + ) + } + } + } + MultiPicker.REQUEST_CODE_PICK_VIDEO -> { + callback.onContentAttachmentsReady( + MultiPicker.get(MultiPicker.VIDEO) + .getSelectedFiles(context, requestCode, resultCode, data) + .map { it.toContentAttachmentData() } + ) + } + else -> return false } + return true } return false } @@ -174,39 +172,35 @@ class AttachmentsHelper private constructor(private val context: Context, * * @return true if it can handle the intent data, false otherwise */ - fun handleShareIntent(intent: Intent): Boolean { + fun handleShareIntent(context: Context, intent: Intent): Boolean { val type = intent.resolveType(context) ?: return false if (type.startsWith("image")) { - imagePicker.submit(safeShareIntent(intent)) + callback.onContentAttachmentsReady( + MultiPicker.get(MultiPicker.IMAGE).getIncomingFiles(context, intent).map { + it.toContentAttachmentData() + } + ) } else if (type.startsWith("video")) { - videoPicker.submit(safeShareIntent(intent)) + callback.onContentAttachmentsReady( + MultiPicker.get(MultiPicker.VIDEO).getIncomingFiles(context, intent).map { + it.toContentAttachmentData() + } + ) } else if (type.startsWith("audio")) { - videoPicker.submit(safeShareIntent(intent)) + callback.onContentAttachmentsReady( + MultiPicker.get(MultiPicker.AUDIO).getIncomingFiles(context, intent).map { + it.toContentAttachmentData() + } + ) } else if (type.startsWith("application") || type.startsWith("file") || type.startsWith("*")) { - filePicker.submit(safeShareIntent(intent)) + callback.onContentAttachmentsReady( + MultiPicker.get(MultiPicker.FILE).getIncomingFiles(context, intent).map { + it.toContentAttachmentData() + } + ) } else { return false } return true } - - private fun safeShareIntent(intent: Intent): Intent { - // Work around for getPickerIntentForSharing doing NPE in android 10 - return try { - IntentUtils.getPickerIntentForSharing(intent) - } catch (failure: Throwable) { - intent - } - } - - private fun getPickerManagerForRequestCode(requestCode: Int): PickerManager? { - return when (requestCode) { - PICK_IMAGE_DEVICE -> imagePicker - PICK_IMAGE_CAMERA -> cameraImagePicker - PICK_FILE -> filePicker - PICK_CONTACT -> contactPicker - PICK_AUDIO -> audioPicker - else -> null - } - } } diff --git a/vector/src/main/java/im/vector/riotx/features/attachments/AttachmentsMapper.kt b/vector/src/main/java/im/vector/riotx/features/attachments/AttachmentsMapper.kt index a3de5084de..02b712b8a7 100644 --- a/vector/src/main/java/im/vector/riotx/features/attachments/AttachmentsMapper.kt +++ b/vector/src/main/java/im/vector/riotx/features/attachments/AttachmentsMapper.kt @@ -16,51 +16,48 @@ package im.vector.riotx.features.attachments -import com.kbeanie.multipicker.api.entity.ChosenAudio -import com.kbeanie.multipicker.api.entity.ChosenContact -import com.kbeanie.multipicker.api.entity.ChosenFile -import com.kbeanie.multipicker.api.entity.ChosenImage -import com.kbeanie.multipicker.api.entity.ChosenVideo import im.vector.matrix.android.api.session.content.ContentAttachmentData +import im.vector.riotx.multipicker.entity.MultiPickerAudioType +import im.vector.riotx.multipicker.entity.MultiPickerBaseType +import im.vector.riotx.multipicker.entity.MultiPickerContactType +import im.vector.riotx.multipicker.entity.MultiPickerFileType +import im.vector.riotx.multipicker.entity.MultiPickerImageType +import im.vector.riotx.multipicker.entity.MultiPickerVideoType import timber.log.Timber -fun ChosenContact.toContactAttachment(): ContactAttachment { +fun MultiPickerContactType.toContactAttachment(): ContactAttachment { return ContactAttachment( displayName = displayName, photoUri = photoUri, - emails = emails.toList(), - phones = phones.toList() + emails = emailList.toList(), + phones = phoneNumberList.toList() ) } -fun ChosenFile.toContentAttachmentData(): ContentAttachmentData { +fun MultiPickerFileType.toContentAttachmentData(): ContentAttachmentData { if (mimeType == null) Timber.w("No mimeType") return ContentAttachmentData( - path = originalPath, mimeType = mimeType, type = mapType(), size = size, - date = createdAt?.time ?: System.currentTimeMillis(), name = displayName, - queryUri = queryUri + queryUri = contentUri ) } -fun ChosenAudio.toContentAttachmentData(): ContentAttachmentData { +fun MultiPickerAudioType.toContentAttachmentData(): ContentAttachmentData { if (mimeType == null) Timber.w("No mimeType") return ContentAttachmentData( - path = originalPath, mimeType = mimeType, type = mapType(), size = size, - date = createdAt?.time ?: System.currentTimeMillis(), name = displayName, duration = duration, - queryUri = queryUri + queryUri = contentUri ) } -private fun ChosenFile.mapType(): ContentAttachmentData.Type { +private fun MultiPickerBaseType.mapType(): ContentAttachmentData.Type { return when { mimeType?.startsWith("image/") == true -> ContentAttachmentData.Type.IMAGE mimeType?.startsWith("video/") == true -> ContentAttachmentData.Type.VIDEO @@ -69,10 +66,9 @@ private fun ChosenFile.mapType(): ContentAttachmentData.Type { } } -fun ChosenImage.toContentAttachmentData(): ContentAttachmentData { +fun MultiPickerImageType.toContentAttachmentData(): ContentAttachmentData { if (mimeType == null) Timber.w("No mimeType") return ContentAttachmentData( - path = originalPath, mimeType = mimeType, type = mapType(), name = displayName, @@ -80,23 +76,20 @@ fun ChosenImage.toContentAttachmentData(): ContentAttachmentData { height = height.toLong(), width = width.toLong(), exifOrientation = orientation, - date = createdAt?.time ?: System.currentTimeMillis(), - queryUri = queryUri + queryUri = contentUri ) } -fun ChosenVideo.toContentAttachmentData(): ContentAttachmentData { +fun MultiPickerVideoType.toContentAttachmentData(): ContentAttachmentData { if (mimeType == null) Timber.w("No mimeType") return ContentAttachmentData( - path = originalPath, mimeType = mimeType, type = ContentAttachmentData.Type.VIDEO, size = size, - date = createdAt?.time ?: System.currentTimeMillis(), height = height.toLong(), width = width.toLong(), duration = duration, name = displayName, - queryUri = queryUri + queryUri = contentUri ) } diff --git a/vector/src/main/java/im/vector/riotx/features/attachments/AttachmentsPickerCallback.kt b/vector/src/main/java/im/vector/riotx/features/attachments/AttachmentsPickerCallback.kt deleted file mode 100644 index 62956e08c8..0000000000 --- a/vector/src/main/java/im/vector/riotx/features/attachments/AttachmentsPickerCallback.kt +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright 2019 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.riotx.features.attachments - -import com.kbeanie.multipicker.api.callbacks.AudioPickerCallback -import com.kbeanie.multipicker.api.callbacks.ContactPickerCallback -import com.kbeanie.multipicker.api.callbacks.FilePickerCallback -import com.kbeanie.multipicker.api.callbacks.ImagePickerCallback -import com.kbeanie.multipicker.api.callbacks.VideoPickerCallback -import com.kbeanie.multipicker.api.entity.ChosenAudio -import com.kbeanie.multipicker.api.entity.ChosenContact -import com.kbeanie.multipicker.api.entity.ChosenFile -import com.kbeanie.multipicker.api.entity.ChosenImage -import com.kbeanie.multipicker.api.entity.ChosenVideo - -/** - * This class delegates the PickerManager callbacks to an [AttachmentsHelper.Callback] - */ -class AttachmentsPickerCallback(private val callback: AttachmentsHelper.Callback) - : ImagePickerCallback, - FilePickerCallback, - VideoPickerCallback, - AudioPickerCallback, - ContactPickerCallback { - - override fun onContactChosen(contact: ChosenContact?) { - if (contact == null) { - callback.onAttachmentsProcessFailed() - } else { - val contactAttachment = contact.toContactAttachment() - callback.onContactAttachmentReady(contactAttachment) - } - } - - override fun onAudiosChosen(audios: MutableList?) { - if (audios.isNullOrEmpty()) { - callback.onAttachmentsProcessFailed() - } else { - val attachments = audios.map { - it.toContentAttachmentData() - } - callback.onContentAttachmentsReady(attachments) - } - } - - override fun onFilesChosen(files: MutableList?) { - if (files.isNullOrEmpty()) { - callback.onAttachmentsProcessFailed() - } else { - val attachments = files.map { - it.toContentAttachmentData() - } - callback.onContentAttachmentsReady(attachments) - } - } - - override fun onImagesChosen(images: MutableList?) { - if (images.isNullOrEmpty()) { - callback.onAttachmentsProcessFailed() - } else { - val attachments = images.map { - it.toContentAttachmentData() - } - callback.onContentAttachmentsReady(attachments) - } - } - - override fun onVideosChosen(videos: MutableList?) { - if (videos.isNullOrEmpty()) { - callback.onAttachmentsProcessFailed() - } else { - val attachments = videos.map { - it.toContentAttachmentData() - } - callback.onContentAttachmentsReady(attachments) - } - } - - override fun onError(error: String?) { - callback.onAttachmentsProcessFailed() - } -} diff --git a/vector/src/main/java/im/vector/riotx/features/attachments/PickerManagerFactory.kt b/vector/src/main/java/im/vector/riotx/features/attachments/PickerManagerFactory.kt deleted file mode 100644 index 6c03f21ab3..0000000000 --- a/vector/src/main/java/im/vector/riotx/features/attachments/PickerManagerFactory.kt +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright 2019 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.riotx.features.attachments - -import android.app.Activity -import androidx.fragment.app.Fragment -import com.kbeanie.multipicker.api.AudioPicker -import com.kbeanie.multipicker.api.CameraImagePicker -import com.kbeanie.multipicker.api.ContactPicker -import com.kbeanie.multipicker.api.FilePicker -import com.kbeanie.multipicker.api.ImagePicker -import com.kbeanie.multipicker.api.VideoPicker - -/** - * Factory for creating different pickers. It allows to use with fragment or activity builders. - */ -interface PickerManagerFactory { - - fun createImagePicker(): ImagePicker - - fun createCameraImagePicker(): CameraImagePicker - - fun createVideoPicker(): VideoPicker - - fun createFilePicker(): FilePicker - - fun createAudioPicker(): AudioPicker - - fun createContactPicker(): ContactPicker -} - -class ActivityPickerManagerFactory(private val activity: Activity, callback: AttachmentsHelper.Callback) : PickerManagerFactory { - - private val attachmentsPickerCallback = AttachmentsPickerCallback(callback) - - override fun createImagePicker(): ImagePicker { - return ImagePicker(activity).also { - it.setImagePickerCallback(attachmentsPickerCallback) - it.allowMultiple() - } - } - - override fun createCameraImagePicker(): CameraImagePicker { - return CameraImagePicker(activity).also { - it.setImagePickerCallback(attachmentsPickerCallback) - } - } - - override fun createVideoPicker(): VideoPicker { - return VideoPicker(activity).also { - it.setVideoPickerCallback(attachmentsPickerCallback) - it.allowMultiple() - } - } - - override fun createFilePicker(): FilePicker { - return FilePicker(activity).also { - it.allowMultiple() - it.setFilePickerCallback(attachmentsPickerCallback) - } - } - - override fun createAudioPicker(): AudioPicker { - return AudioPicker(activity).also { - it.allowMultiple() - it.setAudioPickerCallback(attachmentsPickerCallback) - } - } - - override fun createContactPicker(): ContactPicker { - return ContactPicker(activity).also { - it.setContactPickerCallback(attachmentsPickerCallback) - } - } -} - -class FragmentPickerManagerFactory(private val fragment: Fragment, callback: AttachmentsHelper.Callback) : PickerManagerFactory { - - private val attachmentsPickerCallback = AttachmentsPickerCallback(callback) - - override fun createImagePicker(): ImagePicker { - return ImagePicker(fragment).also { - it.setImagePickerCallback(attachmentsPickerCallback) - it.allowMultiple() - } - } - - override fun createCameraImagePicker(): CameraImagePicker { - return CameraImagePicker(fragment).also { - it.setImagePickerCallback(attachmentsPickerCallback) - } - } - - override fun createVideoPicker(): VideoPicker { - return VideoPicker(fragment).also { - it.setVideoPickerCallback(attachmentsPickerCallback) - it.allowMultiple() - } - } - - override fun createFilePicker(): FilePicker { - return FilePicker(fragment).also { - it.allowMultiple() - it.setFilePickerCallback(attachmentsPickerCallback) - } - } - - override fun createAudioPicker(): AudioPicker { - return AudioPicker(fragment).also { - it.allowMultiple() - it.setAudioPickerCallback(attachmentsPickerCallback) - } - } - - override fun createContactPicker(): ContactPicker { - return ContactPicker(fragment).also { - it.setContactPickerCallback(attachmentsPickerCallback) - } - } -} diff --git a/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentPreviewControllers.kt b/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentPreviewControllers.kt index 34f018aaf9..60ee722116 100644 --- a/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentPreviewControllers.kt +++ b/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentPreviewControllers.kt @@ -25,7 +25,7 @@ class AttachmentBigPreviewController @Inject constructor() : TypedEpoxyControlle override fun buildModels(data: AttachmentsPreviewViewState) { data.attachments.forEach { attachmentBigPreviewItem { - id(it.path) + id(it.queryUri.toString()) attachment(it) } } @@ -43,7 +43,7 @@ class AttachmentMiniaturePreviewController @Inject constructor() : TypedEpoxyCon override fun buildModels(data: AttachmentsPreviewViewState) { data.attachments.forEachIndexed { index, contentAttachmentData -> attachmentMiniaturePreviewItem { - id(contentAttachmentData.path) + id(contentAttachmentData.queryUri.toString()) attachment(contentAttachmentData) checked(data.currentAttachmentIndex == index) clickListener { _ -> diff --git a/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentPreviewItems.kt b/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentPreviewItems.kt index 3b43fa6e20..373298bf31 100644 --- a/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentPreviewItems.kt +++ b/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentPreviewItems.kt @@ -33,11 +33,10 @@ abstract class AttachmentPreviewItem : VectorE abstract val attachment: ContentAttachmentData override fun bind(holder: H) { - val path = attachment.path if (attachment.type == ContentAttachmentData.Type.VIDEO || attachment.type == ContentAttachmentData.Type.IMAGE) { Glide.with(holder.view.context) .asBitmap() - .load(path) + .load(attachment.queryUri) .apply(RequestOptions().frame(0)) .into(holder.imageView) } else { diff --git a/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewAction.kt b/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewAction.kt index 5acc59b035..aef724331f 100644 --- a/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewAction.kt @@ -17,10 +17,11 @@ package im.vector.riotx.features.attachments.preview +import android.net.Uri import im.vector.riotx.core.platform.VectorViewModelAction sealed class AttachmentsPreviewAction : VectorViewModelAction { object RemoveCurrentAttachment : AttachmentsPreviewAction() data class SetCurrentAttachment(val index: Int): AttachmentsPreviewAction() - data class UpdatePathOfCurrentAttachment(val newPath: String): AttachmentsPreviewAction() + data class UpdatePathOfCurrentAttachment(val newUri: Uri): AttachmentsPreviewAction() } diff --git a/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewFragment.kt b/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewFragment.kt index e52b497df4..f059da7d85 100644 --- a/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewFragment.kt @@ -172,9 +172,9 @@ class AttachmentsPreviewFragment @Inject constructor( } private fun handleCropResult(result: Intent) { - val resultPath = UCrop.getOutput(result)?.path - if (resultPath != null) { - viewModel.handle(AttachmentsPreviewAction.UpdatePathOfCurrentAttachment(resultPath)) + val resultUri = UCrop.getOutput(result) + if (resultUri != null) { + viewModel.handle(AttachmentsPreviewAction.UpdatePathOfCurrentAttachment(resultUri)) } else { Toast.makeText(requireContext(), "Cannot retrieve cropped value", Toast.LENGTH_SHORT).show() } @@ -203,7 +203,7 @@ class AttachmentsPreviewFragment @Inject constructor( val currentAttachment = it.attachments.getOrNull(it.currentAttachmentIndex) ?: return@withState val destinationFile = File(requireContext().cacheDir, "${currentAttachment.name}_edited_image_${System.currentTimeMillis()}") // Note: using currentAttachment.queryUri.toUri() make the app crash when sharing from Google Photos - val uri = File(currentAttachment.path).toUri() + val uri = currentAttachment.queryUri UCrop.of(uri, destinationFile.toUri()) .withOptions( UCrop.Options() diff --git a/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewViewModel.kt b/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewViewModel.kt index 1f6c8c2f8b..d1e44fa963 100644 --- a/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewViewModel.kt @@ -62,7 +62,7 @@ class AttachmentsPreviewViewModel @AssistedInject constructor(@Assisted initialS private fun handleUpdatePathOfCurrentAttachment(action: AttachmentsPreviewAction.UpdatePathOfCurrentAttachment) = withState { val attachments = it.attachments.mapIndexed { index, contentAttachmentData -> if (index == it.currentAttachmentIndex) { - contentAttachmentData.copy(path = action.newPath) + contentAttachmentData.copy(queryUri = action.newUri) } else { contentAttachmentData } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt index 13de137e8f..779b7fb089 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt @@ -251,7 +251,7 @@ class RoomDetailFragment @Inject constructor( override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) sharedActionViewModel = activityViewModelProvider.get(MessageSharedActionViewModel::class.java) - attachmentsHelper = AttachmentsHelper.create(this, this).register() + attachmentsHelper = AttachmentsHelper(requireContext(), this).register() keyboardStateUtils = KeyboardStateUtils(requireActivity()) setupToolbar(roomToolbar) setupRecyclerView() @@ -517,29 +517,6 @@ class RoomDetailFragment @Inject constructor( } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - when (requestCode) { - MultiPicker.REQUEST_CODE_PICK_IMAGE -> { - MultiPicker.get(MultiPicker.IMAGE).getSelectedFiles(requireContext(), requestCode, resultCode, data) - } - MultiPicker.REQUEST_CODE_PICK_VIDEO -> { - MultiPicker.get(MultiPicker.VIDEO).getSelectedFiles(requireContext(), requestCode, resultCode, data) - } - MultiPicker.REQUEST_CODE_PICK_FILE -> { - MultiPicker.get(MultiPicker.FILE).getSelectedFiles(requireContext(), requestCode, resultCode, data) - } - MultiPicker.REQUEST_CODE_PICK_AUDIO -> { - MultiPicker.get(MultiPicker.AUDIO).getSelectedFiles(requireContext(), requestCode, resultCode, data) - } - MultiPicker.REQUEST_CODE_PICK_CONTACT -> { - MultiPicker.get(MultiPicker.CONTACT).getSelectedFiles(requireContext(), requestCode, resultCode, data) - } - MultiPicker.REQUEST_CODE_TAKE_PHOTO -> { - cameraPhotoUri?.let { - MultiPicker.get(MultiPicker.CAMERA).getTakenPhoto(requireContext(), requestCode, resultCode, it) - } - } - } - val hasBeenHandled = attachmentsHelper.onActivityResult(requestCode, resultCode, data) if (!hasBeenHandled && resultCode == RESULT_OK && data != null) { when (requestCode) { @@ -689,7 +666,7 @@ class RoomDetailFragment @Inject constructor( private fun sendUri(uri: Uri): Boolean { roomDetailViewModel.preventAttachmentPreview = true val shareIntent = Intent(Intent.ACTION_SEND, uri) - val isHandled = attachmentsHelper.handleShareIntent(shareIntent) + val isHandled = attachmentsHelper.handleShareIntent(requireContext(), shareIntent) if (!isHandled) { roomDetailViewModel.preventAttachmentPreview = false Toast.makeText(requireContext(), R.string.error_handling_incoming_share, Toast.LENGTH_SHORT).show() @@ -1372,16 +1349,13 @@ class RoomDetailFragment @Inject constructor( } } - private var cameraPhotoUri: Uri? = null private fun launchAttachmentProcess(type: AttachmentTypeSelectorView.Type) { when (type) { - AttachmentTypeSelectorView.Type.CAMERA -> { - cameraPhotoUri = MultiPicker.get(MultiPicker.CAMERA).startWithExpectingFile(this) - } - AttachmentTypeSelectorView.Type.FILE -> MultiPicker.get(MultiPicker.FILE).startWith(this) - AttachmentTypeSelectorView.Type.GALLERY -> MultiPicker.get(MultiPicker.IMAGE).startWith(this) - AttachmentTypeSelectorView.Type.AUDIO -> MultiPicker.get(MultiPicker.AUDIO).startWith(this) - AttachmentTypeSelectorView.Type.CONTACT -> MultiPicker.get(MultiPicker.CONTACT).startWith(this) + AttachmentTypeSelectorView.Type.CAMERA -> attachmentsHelper.openCamera(this) + AttachmentTypeSelectorView.Type.FILE -> attachmentsHelper.selectFile(this) + AttachmentTypeSelectorView.Type.GALLERY -> attachmentsHelper.selectGallery(this) + AttachmentTypeSelectorView.Type.AUDIO -> attachmentsHelper.selectAudio(this) + AttachmentTypeSelectorView.Type.CONTACT -> attachmentsHelper.selectContact(this) AttachmentTypeSelectorView.Type.STICKER -> vectorBaseActivity.notImplemented("Adding stickers") }.exhaustive } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt index 2ad90f073a..cef172da73 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt @@ -610,7 +610,7 @@ class RoomDetailViewModel @AssistedInject constructor( when (val tooBigFile = attachments.find { it.size > maxUploadFileSize }) { null -> room.sendMedias(attachments, action.compressBeforeSending, emptySet()) else -> _viewEvents.post(RoomDetailViewEvents.FileTooBigError( - tooBigFile.name ?: tooBigFile.path, + tooBigFile.name ?: tooBigFile.queryUri.toString(), tooBigFile.size, maxUploadFileSize )) diff --git a/vector/src/main/java/im/vector/riotx/features/share/IncomingShareFragment.kt b/vector/src/main/java/im/vector/riotx/features/share/IncomingShareFragment.kt index 74821ab2fe..aa665b5653 100644 --- a/vector/src/main/java/im/vector/riotx/features/share/IncomingShareFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/share/IncomingShareFragment.kt @@ -72,18 +72,18 @@ class IncomingShareFragment @Inject constructor( super.onViewCreated(view, savedInstanceState) setupRecyclerView() setupToolbar(incomingShareToolbar) - attachmentsHelper = AttachmentsHelper.create(this, this).register() + attachmentsHelper = AttachmentsHelper(requireContext(), this).register() val intent = vectorBaseActivity.intent val isShareManaged = when (intent?.action) { Intent.ACTION_SEND -> { - var isShareManaged = attachmentsHelper.handleShareIntent(intent) + var isShareManaged = attachmentsHelper.handleShareIntent(requireContext(), intent) if (!isShareManaged) { isShareManaged = handleTextShare(intent) } isShareManaged } - Intent.ACTION_SEND_MULTIPLE -> attachmentsHelper.handleShareIntent(intent) + Intent.ACTION_SEND_MULTIPLE -> attachmentsHelper.handleShareIntent(requireContext(), intent) else -> false } From 2651f82337a8110d6f241a0eee0f377299ff456b Mon Sep 17 00:00:00 2001 From: onurays Date: Tue, 24 Mar 2020 11:03:41 +0300 Subject: [PATCH 04/15] Refactor duplicated code. --- .gitignore | 1 - .../session/content/ThumbnailExtractor.kt | 8 ++-- .../multipicker/ExampleInstrumentedTest.kt | 39 ------------------- .../vector/riotx/multipicker/AudioPicker.kt | 22 +---------- .../im/vector/riotx/multipicker/FilePicker.kt | 22 +---------- .../vector/riotx/multipicker/ImagePicker.kt | 22 +---------- .../im/vector/riotx/multipicker/Picker.kt | 23 +++++++++++ .../vector/riotx/multipicker/VideoPicker.kt | 22 +---------- .../riotx/multipicker/ExampleUnitTest.kt | 32 --------------- .../preview/AttachmentsPreviewFragment.kt | 1 - 10 files changed, 32 insertions(+), 160 deletions(-) delete mode 100644 multipicker/src/androidTest/java/im/vector/riotx/multipicker/ExampleInstrumentedTest.kt delete mode 100644 multipicker/src/test/java/im/vector/riotx/multipicker/ExampleUnitTest.kt diff --git a/.gitignore b/.gitignore index ab97ec340a..4a264a28d8 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,3 @@ /tmp ktlint - diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/ThumbnailExtractor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/ThumbnailExtractor.kt index 2ce249ab80..ad23ff8d78 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/ThumbnailExtractor.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/ThumbnailExtractor.kt @@ -44,6 +44,7 @@ internal object ThumbnailExtractor { } private fun extractVideoThumbnail(context: Context, attachment: ContentAttachmentData): ThumbnailData? { + var thumbnailData: ThumbnailData? = null val mediaMetadataRetriever = MediaMetadataRetriever() try { mediaMetadataRetriever.setDataSource(context, attachment.queryUri) @@ -64,7 +65,7 @@ internal object ThumbnailExtractor { val thumbnailWidth = thumbnail.width val thumbnailHeight = thumbnail.height val thumbnailSize = outputStream.size() - val thumbnailData = ThumbnailData( + thumbnailData = ThumbnailData( width = thumbnailWidth, height = thumbnailHeight, size = thumbnailSize.toLong(), @@ -73,10 +74,11 @@ internal object ThumbnailExtractor { ) thumbnail.recycle() outputStream.reset() - return thumbnailData } catch (e: Exception) { Timber.e(e, "Cannot extract video thumbnail") - return null + } finally { + mediaMetadataRetriever.release() } + return thumbnailData } } diff --git a/multipicker/src/androidTest/java/im/vector/riotx/multipicker/ExampleInstrumentedTest.kt b/multipicker/src/androidTest/java/im/vector/riotx/multipicker/ExampleInstrumentedTest.kt deleted file mode 100644 index 25bf17559f..0000000000 --- a/multipicker/src/androidTest/java/im/vector/riotx/multipicker/ExampleInstrumentedTest.kt +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2020 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.riotx.multipicker - -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.ext.junit.runners.AndroidJUnit4 -import junit.framework.Assert.assertEquals - -import org.junit.Test -import org.junit.runner.RunWith - -/** - * Instrumented test, which will execute on an Android device. - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -@RunWith(AndroidJUnit4::class) -class ExampleInstrumentedTest { - @Test - fun useAppContext() { - // Context of the app under test. - val appContext = InstrumentationRegistry.getInstrumentation().targetContext - assertEquals("im.vector.riotx.multipicker.test", appContext.packageName) - } -} diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt index 23873aae1c..0f45f90d2b 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt @@ -42,27 +42,7 @@ class AudioPicker(override val requestCode: Int) : Picker( val audioList = mutableListOf() - val selectedUriList = mutableListOf() - val dataUri = data?.data - val clipData = data?.clipData - - if (clipData != null) { - for (i in 0 until clipData.itemCount) { - selectedUriList.add(clipData.getItemAt(i).uri) - } - } else if (dataUri != null) { - selectedUriList.add(dataUri) - } else { - data?.extras?.get(Intent.EXTRA_STREAM)?.let { - @Suppress("UNCHECKED_CAST") - when (it) { - is List<*> -> selectedUriList.addAll(it as List) - else -> selectedUriList.add(it as Uri) - } - } - } - - selectedUriList.forEach { selectedUri -> + getSelectedUriList(data).forEach { selectedUri -> val projection = arrayOf( MediaStore.Audio.Media.DISPLAY_NAME, MediaStore.Audio.Media.SIZE diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/FilePicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/FilePicker.kt index 0e1169755e..41b20341d0 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/FilePicker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/FilePicker.kt @@ -41,27 +41,7 @@ class FilePicker(override val requestCode: Int) : Picker(re val fileList = mutableListOf() - val selectedUriList = mutableListOf() - val dataUri = data?.data - val clipData = data?.clipData - - if (clipData != null) { - for (i in 0 until clipData.itemCount) { - selectedUriList.add(clipData.getItemAt(i).uri) - } - } else if (dataUri != null) { - selectedUriList.add(dataUri) - } else { - data?.extras?.get(Intent.EXTRA_STREAM)?.let { - @Suppress("UNCHECKED_CAST") - when (it) { - is List<*> -> selectedUriList.addAll(it as List) - else -> selectedUriList.add(it as Uri) - } - } - } - - selectedUriList.forEach { selectedUri -> + getSelectedUriList(data).forEach { selectedUri -> context.contentResolver.query(selectedUri, null, null, null, null) ?.use { cursor -> val nameColumn = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME) diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/ImagePicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/ImagePicker.kt index 8bf589800d..bd27244ed5 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/ImagePicker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/ImagePicker.kt @@ -45,27 +45,7 @@ class ImagePicker(override val requestCode: Int) : Picker( val imageList = mutableListOf() - val selectedUriList = mutableListOf() - val dataUri = data?.data - val clipData = data?.clipData - - if (clipData != null) { - for (i in 0 until clipData.itemCount) { - selectedUriList.add(clipData.getItemAt(i).uri) - } - } else if (dataUri != null) { - selectedUriList.add(dataUri) - } else { - data?.extras?.get(Intent.EXTRA_STREAM)?.let { - @Suppress("UNCHECKED_CAST") - when (it) { - is List<*> -> selectedUriList.addAll(it as List) - else -> selectedUriList.add(it as Uri) - } - } - } - - selectedUriList.forEach { selectedUri -> + getSelectedUriList(data).forEach { selectedUri -> val projection = arrayOf( MediaStore.Images.Media.DISPLAY_NAME, MediaStore.Images.Media.SIZE diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/Picker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/Picker.kt index f162dd7608..58754cd74e 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/Picker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/Picker.kt @@ -44,4 +44,27 @@ abstract class Picker(open val requestCode: Int) { single = true return this } + + protected fun getSelectedUriList(data: Intent?): List { + val selectedUriList = mutableListOf() + val dataUri = data?.data + val clipData = data?.clipData + + if (clipData != null) { + for (i in 0 until clipData.itemCount) { + selectedUriList.add(clipData.getItemAt(i).uri) + } + } else if (dataUri != null) { + selectedUriList.add(dataUri) + } else { + data?.extras?.get(Intent.EXTRA_STREAM)?.let { + @Suppress("UNCHECKED_CAST") + when (it) { + is List<*> -> selectedUriList.addAll(it as List) + else -> selectedUriList.add(it as Uri) + } + } + } + return selectedUriList + } } diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/VideoPicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/VideoPicker.kt index d4b8d6a985..739c24c6c7 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/VideoPicker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/VideoPicker.kt @@ -42,27 +42,7 @@ class VideoPicker(override val requestCode: Int) : Picker( val videoList = mutableListOf() - val selectedUriList = mutableListOf() - val dataUri = data?.data - val clipData = data?.clipData - - if (clipData != null) { - for (i in 0 until clipData.itemCount) { - selectedUriList.add(clipData.getItemAt(i).uri) - } - } else if (dataUri != null) { - selectedUriList.add(dataUri) - } else { - data?.extras?.get(Intent.EXTRA_STREAM)?.let { - @Suppress("UNCHECKED_CAST") - when (it) { - is List<*> -> selectedUriList.addAll(it as List) - else -> selectedUriList.add(it as Uri) - } - } - } - - selectedUriList.forEach { selectedUri -> + getSelectedUriList(data).forEach { selectedUri -> val projection = arrayOf( MediaStore.Video.Media.DISPLAY_NAME, MediaStore.Video.Media.SIZE diff --git a/multipicker/src/test/java/im/vector/riotx/multipicker/ExampleUnitTest.kt b/multipicker/src/test/java/im/vector/riotx/multipicker/ExampleUnitTest.kt deleted file mode 100644 index 07e464699f..0000000000 --- a/multipicker/src/test/java/im/vector/riotx/multipicker/ExampleUnitTest.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) 2020 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.riotx.multipicker - -import junit.framework.Assert.assertEquals -import org.junit.Test - -/** - * Example local unit test, which will execute on the development machine (host). - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -class ExampleUnitTest { - @Test - fun addition_isCorrect() { - assertEquals(4, 2 + 2) - } -} diff --git a/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewFragment.kt b/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewFragment.kt index f059da7d85..3b1972ffbc 100644 --- a/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewFragment.kt @@ -202,7 +202,6 @@ class AttachmentsPreviewFragment @Inject constructor( private fun doHandleEditAction() = withState(viewModel) { val currentAttachment = it.attachments.getOrNull(it.currentAttachmentIndex) ?: return@withState val destinationFile = File(requireContext().cacheDir, "${currentAttachment.name}_edited_image_${System.currentTimeMillis()}") - // Note: using currentAttachment.queryUri.toUri() make the app crash when sharing from Google Photos val uri = currentAttachment.queryUri UCrop.of(uri, destinationFile.toUri()) .withOptions( From 727d86236bf966f4f67a0b5398bd5188041c0a7b Mon Sep 17 00:00:00 2001 From: onurays Date: Tue, 24 Mar 2020 11:31:27 +0300 Subject: [PATCH 05/15] ImageUtils created with helper functions. --- multipicker/src/main/AndroidManifest.xml | 2 +- .../vector/riotx/multipicker/AudioPicker.kt | 10 +--- .../vector/riotx/multipicker/CameraPicker.kt | 22 ++------ .../vector/riotx/multipicker/ContactPicker.kt | 10 +--- .../im/vector/riotx/multipicker/FilePicker.kt | 12 +---- .../vector/riotx/multipicker/ImagePicker.kt | 32 ++---------- .../im/vector/riotx/multipicker/Picker.kt | 14 +++-- .../vector/riotx/multipicker/VideoPicker.kt | 10 +--- .../{ => provider}/MultiPickerFileProvider.kt | 2 +- .../riotx/multipicker/utils/ImageUtils.kt | 52 +++++++++++++++++++ 10 files changed, 75 insertions(+), 91 deletions(-) rename multipicker/src/main/java/im/vector/riotx/multipicker/{ => provider}/MultiPickerFileProvider.kt (93%) create mode 100644 multipicker/src/main/java/im/vector/riotx/multipicker/utils/ImageUtils.kt diff --git a/multipicker/src/main/AndroidManifest.xml b/multipicker/src/main/AndroidManifest.xml index 6f714ab388..e1f12697e0 100644 --- a/multipicker/src/main/AndroidManifest.xml +++ b/multipicker/src/main/AndroidManifest.xml @@ -3,7 +3,7 @@ diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt index 0f45f90d2b..752cd84e1e 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt @@ -27,14 +27,6 @@ import im.vector.riotx.multipicker.entity.MultiPickerAudioType class AudioPicker(override val requestCode: Int) : Picker(requestCode) { - override fun startWith(activity: Activity) { - activity.startActivityForResult(createIntent(), requestCode) - } - - override fun startWith(fragment: Fragment) { - fragment.startActivityForResult(createIntent(), requestCode) - } - override fun getSelectedFiles(context: Context, requestCode: Int, resultCode: Int, data: Intent?): List { if (requestCode != this.requestCode && resultCode != Activity.RESULT_OK) { return emptyList() @@ -84,7 +76,7 @@ class AudioPicker(override val requestCode: Int) : Picker( return audioList } - private fun createIntent(): Intent { + override fun createIntent(): Intent { return Intent(Intent.ACTION_OPEN_DOCUMENT).apply { addCategory(Intent.CATEGORY_OPENABLE) putExtra(Intent.EXTRA_ALLOW_MULTIPLE, !single) diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/CameraPicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/CameraPicker.kt index 81e665a43f..d8c16279cc 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/CameraPicker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/CameraPicker.kt @@ -28,6 +28,7 @@ import androidx.core.content.FileProvider import androidx.exifinterface.media.ExifInterface import androidx.fragment.app.Fragment import im.vector.riotx.multipicker.entity.MultiPickerImageType +import im.vector.riotx.multipicker.utils.ImageUtils import java.io.File import java.text.SimpleDateFormat import java.util.Date @@ -74,25 +75,8 @@ class CameraPicker(val requestCode: Int) { val name = cursor.getString(nameColumn) val size = cursor.getLong(sizeColumn) - var orientation = 0 - - val bitmap = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - ImageDecoder.decodeBitmap(ImageDecoder.createSource(context.contentResolver, photoUri)) - } else { - context.contentResolver.openInputStream(photoUri)?.use { inputStream -> - BitmapFactory.decodeStream(inputStream) - } - } - - context.contentResolver.openInputStream(photoUri)?.use { inputStream -> - try { - ExifInterface(inputStream).let { - orientation = it.rotationDegrees - } - } catch (e: Exception) { - e.printStackTrace() - } - } + val bitmap = ImageUtils.getBitmap(context, photoUri) + val orientation = ImageUtils.getOrientation(context, photoUri) return MultiPickerImageType( name, diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/ContactPicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/ContactPicker.kt index aebde6f439..968c5623e5 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/ContactPicker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/ContactPicker.kt @@ -26,14 +26,6 @@ import im.vector.riotx.multipicker.entity.MultiPickerContactType class ContactPicker(override val requestCode: Int) : Picker(requestCode) { - override fun startWith(activity: Activity) { - activity.startActivityForResult(createIntent(), requestCode) - } - - override fun startWith(fragment: Fragment) { - fragment.startActivityForResult(createIntent(), requestCode) - } - override fun getSelectedFiles(context: Context, requestCode: Int, resultCode: Int, data: Intent?): List { if (requestCode != this.requestCode && resultCode != Activity.RESULT_OK) { return emptyList() @@ -126,7 +118,7 @@ class ContactPicker(override val requestCode: Int) : Picker(requestCode) { - override fun startWith(activity: Activity) { - activity.startActivityForResult(createIntent(), requestCode) - } - - override fun startWith(fragment: Fragment) { - fragment.startActivityForResult(createIntent(), requestCode) - } - override fun getSelectedFiles(context: Context, requestCode: Int, resultCode: Int, data: Intent?): List { if (requestCode != this.requestCode && resultCode != Activity.RESULT_OK) { return emptyList() @@ -64,7 +54,7 @@ class FilePicker(override val requestCode: Int) : Picker(re return fileList } - private fun createIntent(): Intent { + override fun createIntent(): Intent { return Intent(Intent.ACTION_OPEN_DOCUMENT).apply { addCategory(Intent.CATEGORY_OPENABLE) putExtra(Intent.EXTRA_ALLOW_MULTIPLE, !single) diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/ImagePicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/ImagePicker.kt index bd27244ed5..452724f4c7 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/ImagePicker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/ImagePicker.kt @@ -27,17 +27,10 @@ import android.provider.MediaStore import androidx.exifinterface.media.ExifInterface import androidx.fragment.app.Fragment import im.vector.riotx.multipicker.entity.MultiPickerImageType +import im.vector.riotx.multipicker.utils.ImageUtils class ImagePicker(override val requestCode: Int) : Picker(requestCode) { - override fun startWith(activity: Activity) { - activity.startActivityForResult(createIntent(), requestCode) - } - - override fun startWith(fragment: Fragment) { - fragment.startActivityForResult(createIntent(), requestCode) - } - override fun getSelectedFiles(context: Context, requestCode: Int, resultCode: Int, data: Intent?): List { if (requestCode != this.requestCode && resultCode != Activity.RESULT_OK) { return emptyList() @@ -65,25 +58,8 @@ class ImagePicker(override val requestCode: Int) : Picker( val name = cursor.getString(nameColumn) val size = cursor.getLong(sizeColumn) - var orientation = 0 - - val bitmap = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - ImageDecoder.decodeBitmap(ImageDecoder.createSource(context.contentResolver, selectedUri)) - } else { - context.contentResolver.openInputStream(selectedUri)?.use { inputStream -> - BitmapFactory.decodeStream(inputStream) - } - } - - context.contentResolver.openInputStream(selectedUri)?.use { inputStream -> - try { - ExifInterface(inputStream).let { - orientation = it.rotationDegrees - } - } catch (e: Exception) { - e.printStackTrace() - } - } + val bitmap = ImageUtils.getBitmap(context, selectedUri) + val orientation = ImageUtils.getOrientation(context, selectedUri) imageList.add( MultiPickerImageType( @@ -102,7 +78,7 @@ class ImagePicker(override val requestCode: Int) : Picker( return imageList } - private fun createIntent(): Intent { + override fun createIntent(): Intent { return Intent(Intent.ACTION_OPEN_DOCUMENT).apply { addCategory(Intent.CATEGORY_OPENABLE) putExtra(Intent.EXTRA_ALLOW_MULTIPLE, !single) diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/Picker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/Picker.kt index 58754cd74e..c1784ee054 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/Picker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/Picker.kt @@ -26,10 +26,6 @@ abstract class Picker(open val requestCode: Int) { protected var single = false - abstract fun startWith(activity: Activity) - - abstract fun startWith(fragment: Fragment) - open fun startWithExpectingFile(activity: Activity): Uri? = null open fun startWithExpectingFile(fragment: Fragment): Uri? = null @@ -45,6 +41,16 @@ abstract class Picker(open val requestCode: Int) { return this } + abstract fun createIntent(): Intent + + fun startWith(activity: Activity) { + activity.startActivityForResult(createIntent(), requestCode) + } + + fun startWith(fragment: Fragment) { + fragment.startActivityForResult(createIntent(), requestCode) + } + protected fun getSelectedUriList(data: Intent?): List { val selectedUriList = mutableListOf() val dataUri = data?.data diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/VideoPicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/VideoPicker.kt index 739c24c6c7..c2a441c15a 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/VideoPicker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/VideoPicker.kt @@ -27,14 +27,6 @@ import im.vector.riotx.multipicker.entity.MultiPickerVideoType class VideoPicker(override val requestCode: Int) : Picker(requestCode) { - override fun startWith(activity: Activity) { - activity.startActivityForResult(createIntent(), requestCode) - } - - override fun startWith(fragment: Fragment) { - fragment.startActivityForResult(createIntent(), requestCode) - } - override fun getSelectedFiles(context: Context, requestCode: Int, resultCode: Int, data: Intent?): List { if (requestCode != this.requestCode && resultCode != Activity.RESULT_OK) { return emptyList() @@ -93,7 +85,7 @@ class VideoPicker(override val requestCode: Int) : Picker( return videoList } - private fun createIntent(): Intent { + override fun createIntent(): Intent { return Intent(Intent.ACTION_OPEN_DOCUMENT).apply { addCategory(Intent.CATEGORY_OPENABLE) putExtra(Intent.EXTRA_ALLOW_MULTIPLE, !single) diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/MultiPickerFileProvider.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/provider/MultiPickerFileProvider.kt similarity index 93% rename from multipicker/src/main/java/im/vector/riotx/multipicker/MultiPickerFileProvider.kt rename to multipicker/src/main/java/im/vector/riotx/multipicker/provider/MultiPickerFileProvider.kt index 1549b43fd7..048b2ca199 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/MultiPickerFileProvider.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/provider/MultiPickerFileProvider.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.riotx.multipicker +package im.vector.riotx.multipicker.provider import androidx.core.content.FileProvider diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/utils/ImageUtils.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/utils/ImageUtils.kt new file mode 100644 index 0000000000..76f4b677a0 --- /dev/null +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/utils/ImageUtils.kt @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotx.multipicker.utils + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.graphics.ImageDecoder +import android.net.Uri +import android.os.Build +import androidx.exifinterface.media.ExifInterface + +object ImageUtils { + + fun getBitmap(context: Context, uri: Uri): Bitmap? { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + ImageDecoder.decodeBitmap(ImageDecoder.createSource(context.contentResolver, uri)) + } else { + context.contentResolver.openInputStream(uri)?.use { inputStream -> + BitmapFactory.decodeStream(inputStream) + } + } + } + + fun getOrientation(context: Context, uri: Uri): Int { + var orientation = 0 + context.contentResolver.openInputStream(uri)?.use { inputStream -> + try { + ExifInterface(inputStream).let { + orientation = it.rotationDegrees + } + } catch (e: Exception) { + e.printStackTrace() + } + } + return orientation + } +} From d20b1cb64ab25edc4b4d28d15e88eef6d3bece8d Mon Sep 17 00:00:00 2001 From: onurays Date: Tue, 24 Mar 2020 12:15:14 +0300 Subject: [PATCH 06/15] Add documentation. --- .../vector/riotx/multipicker/AudioPicker.kt | 2 -- .../vector/riotx/multipicker/CameraPicker.kt | 23 ++++++++++++--- .../vector/riotx/multipicker/ContactPicker.kt | 10 ++++++- .../im/vector/riotx/multipicker/FilePicker.kt | 9 ++++++ .../vector/riotx/multipicker/ImagePicker.kt | 15 ++++++---- .../im/vector/riotx/multipicker/Picker.kt | 28 ++++++++++++++++--- .../vector/riotx/multipicker/VideoPicker.kt | 11 ++++++-- .../home/room/detail/RoomDetailFragment.kt | 1 - 8 files changed, 79 insertions(+), 20 deletions(-) diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt index 752cd84e1e..a9c89a9ec0 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt @@ -20,9 +20,7 @@ import android.app.Activity import android.content.Context import android.content.Intent import android.media.MediaMetadataRetriever -import android.net.Uri import android.provider.MediaStore -import androidx.fragment.app.Fragment import im.vector.riotx.multipicker.entity.MultiPickerAudioType class AudioPicker(override val requestCode: Int) : Picker(requestCode) { diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/CameraPicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/CameraPicker.kt index d8c16279cc..d7a4d55d22 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/CameraPicker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/CameraPicker.kt @@ -19,13 +19,9 @@ package im.vector.riotx.multipicker import android.app.Activity import android.content.Context import android.content.Intent -import android.graphics.BitmapFactory -import android.graphics.ImageDecoder import android.net.Uri -import android.os.Build import android.provider.MediaStore import androidx.core.content.FileProvider -import androidx.exifinterface.media.ExifInterface import androidx.fragment.app.Fragment import im.vector.riotx.multipicker.entity.MultiPickerImageType import im.vector.riotx.multipicker.utils.ImageUtils @@ -34,8 +30,16 @@ import java.text.SimpleDateFormat import java.util.Date import java.util.Locale +/** + * Implementation of taking a photo with Camera + */ class CameraPicker(val requestCode: Int) { + /** + * Start camera by using an Activity + * @param activity Activity to handle onActivityResult(). + * @return Uri of taken photo or null if the operation is cancelled. + */ fun startWithExpectingFile(activity: Activity): Uri? { val photoUri = createPhotoUri(activity) val intent = createIntent().apply { @@ -45,6 +49,11 @@ class CameraPicker(val requestCode: Int) { return photoUri } + /** + * Start camera by using a Fragment + * @param fragment Fragment to handle onActivityResult(). + * @return Uri of taken photo or null if the operation is cancelled. + */ fun startWithExpectingFile(fragment: Fragment): Uri? { val photoUri = createPhotoUri(fragment.requireContext()) val intent = createIntent().apply { @@ -54,6 +63,12 @@ class CameraPicker(val requestCode: Int) { return photoUri } + /** + * Call this function from onActivityResult(int, int, Intent). + * @return Taken photo or null if request code is wrong + * or result code is not Activity.RESULT_OK + * or user cancelled the operation. + */ fun getTakenPhoto(context: Context, requestCode: Int, resultCode: Int, photoUri: Uri): MultiPickerImageType? { if (requestCode == this.requestCode && resultCode == Activity.RESULT_OK) { val projection = arrayOf( diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/ContactPicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/ContactPicker.kt index 968c5623e5..b0ae0e4cda 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/ContactPicker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/ContactPicker.kt @@ -21,11 +21,19 @@ import android.content.ContentResolver import android.content.Context import android.content.Intent import android.provider.ContactsContract -import androidx.fragment.app.Fragment import im.vector.riotx.multipicker.entity.MultiPickerContactType +/** + * Contact Picker implementation + */ class ContactPicker(override val requestCode: Int) : Picker(requestCode) { + /** + * Call this function from onActivityResult(int, int, Intent). + * Returns selected contact or empty list if request code is wrong + * or result code is not Activity.RESULT_OK + * or user did not select any files. + */ override fun getSelectedFiles(context: Context, requestCode: Int, resultCode: Int, data: Intent?): List { if (requestCode != this.requestCode && resultCode != Activity.RESULT_OK) { return emptyList() diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/FilePicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/FilePicker.kt index 20f62faf24..e8c74fad19 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/FilePicker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/FilePicker.kt @@ -22,8 +22,17 @@ import android.content.Intent import android.provider.OpenableColumns import im.vector.riotx.multipicker.entity.MultiPickerFileType +/** + * Implementation of selecting any type of files + */ class FilePicker(override val requestCode: Int) : Picker(requestCode) { + /** + * Call this function from onActivityResult(int, int, Intent). + * Returns selected files or empty list if request code is wrong + * or result code is not Activity.RESULT_OK + * or user did not select any files. + */ override fun getSelectedFiles(context: Context, requestCode: Int, resultCode: Int, data: Intent?): List { if (requestCode != this.requestCode && resultCode != Activity.RESULT_OK) { return emptyList() diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/ImagePicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/ImagePicker.kt index 452724f4c7..d7bf383f03 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/ImagePicker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/ImagePicker.kt @@ -19,18 +19,21 @@ package im.vector.riotx.multipicker import android.app.Activity import android.content.Context import android.content.Intent -import android.graphics.BitmapFactory -import android.graphics.ImageDecoder -import android.net.Uri -import android.os.Build import android.provider.MediaStore -import androidx.exifinterface.media.ExifInterface -import androidx.fragment.app.Fragment import im.vector.riotx.multipicker.entity.MultiPickerImageType import im.vector.riotx.multipicker.utils.ImageUtils +/** + * Image Picker implementation + */ class ImagePicker(override val requestCode: Int) : Picker(requestCode) { + /** + * Call this function from onActivityResult(int, int, Intent). + * Returns selected image files or empty list if request code is wrong + * or result code is not Activity.RESULT_OK + * or user did not select any files. + */ override fun getSelectedFiles(context: Context, requestCode: Int, resultCode: Int, data: Intent?): List { if (requestCode != this.requestCode && resultCode != Activity.RESULT_OK) { return emptyList() diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/Picker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/Picker.kt index c1784ee054..ff20c1303b 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/Picker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/Picker.kt @@ -22,20 +22,32 @@ import android.content.Intent import android.net.Uri import androidx.fragment.app.Fragment +/** + * Abstract class to provide all types of Pickers + */ abstract class Picker(open val requestCode: Int) { protected var single = false - open fun startWithExpectingFile(activity: Activity): Uri? = null - - open fun startWithExpectingFile(fragment: Fragment): Uri? = null - + /** + * Call this function from onActivityResult(int, int, Intent). + * @return selected files or empty list if request code is wrong + * or result code is not Activity.RESULT_OK + * or user did not select any files. + */ abstract fun getSelectedFiles(context: Context, requestCode: Int, resultCode: Int, data: Intent?): List + /** + * Use this function to retrieve files which are shared from another application or internally + * by using android.intent.action.SEND or android.intent.action.SEND_MULTIPLE actions. + */ fun getIncomingFiles(context: Context, data: Intent?): List { return getSelectedFiles(context, requestCode, Activity.RESULT_OK, data) } + /** + * Call this function to disable multiple selection of files. + */ fun single(): Picker { single = true return this @@ -43,10 +55,18 @@ abstract class Picker(open val requestCode: Int) { abstract fun createIntent(): Intent + /** + * Start Storage Access Framework UI by using an Activity. + * @param activity Activity to handle onActivityResult(). + */ fun startWith(activity: Activity) { activity.startActivityForResult(createIntent(), requestCode) } + /** + * Start Storage Access Framework UI by using a Fragment. + * @param fragment Fragment to handle onActivityResult(). + */ fun startWith(fragment: Fragment) { fragment.startActivityForResult(createIntent(), requestCode) } diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/VideoPicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/VideoPicker.kt index c2a441c15a..b85ffacd48 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/VideoPicker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/VideoPicker.kt @@ -20,13 +20,20 @@ import android.app.Activity import android.content.Context import android.content.Intent import android.media.MediaMetadataRetriever -import android.net.Uri import android.provider.MediaStore -import androidx.fragment.app.Fragment import im.vector.riotx.multipicker.entity.MultiPickerVideoType +/** + * Video Picker implementation + */ class VideoPicker(override val requestCode: Int) : Picker(requestCode) { + /** + * Call this function from onActivityResult(int, int, Intent). + * Returns selected video files or empty list if request code is wrong + * or result code is not Activity.RESULT_OK + * or user did not select any files. + */ override fun getSelectedFiles(context: Context, requestCode: Int, resultCode: Int, data: Intent?): List { if (requestCode != this.requestCode && resultCode != Activity.RESULT_OK) { return emptyList() diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt index 779b7fb089..f58d7be718 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt @@ -156,7 +156,6 @@ import im.vector.riotx.features.reactions.EmojiReactionPickerActivity import im.vector.riotx.features.settings.VectorPreferences import im.vector.riotx.features.share.SharedData import im.vector.riotx.features.themes.ThemeUtils -import im.vector.riotx.multipicker.MultiPicker import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.schedulers.Schedulers import kotlinx.android.parcel.Parcelize From e583c037516403169931287cc08b732933883c16 Mon Sep 17 00:00:00 2001 From: onurays Date: Tue, 24 Mar 2020 12:32:37 +0300 Subject: [PATCH 07/15] Add documentation. --- .../main/java/im/vector/riotx/multipicker/AudioPicker.kt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt index a9c89a9ec0..05e4c337b6 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt @@ -23,8 +23,17 @@ import android.media.MediaMetadataRetriever import android.provider.MediaStore import im.vector.riotx.multipicker.entity.MultiPickerAudioType +/** + * Audio file picker implementation + */ class AudioPicker(override val requestCode: Int) : Picker(requestCode) { + /** + * Call this function from onActivityResult(int, int, Intent). + * Returns selected audio files or empty list if request code is wrong + * or result code is not Activity.RESULT_OK + * or user did not select any files. + */ override fun getSelectedFiles(context: Context, requestCode: Int, resultCode: Int, data: Intent?): List { if (requestCode != this.requestCode && resultCode != Activity.RESULT_OK) { return emptyList() From 6bf89aeac91f9cb5ce45144a5233f2b5e063054b Mon Sep 17 00:00:00 2001 From: onurays Date: Tue, 24 Mar 2020 12:37:37 +0300 Subject: [PATCH 08/15] Remove JPEG_ prefix from file name. --- .../src/main/java/im/vector/riotx/multipicker/CameraPicker.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/CameraPicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/CameraPicker.kt index d7a4d55d22..240d809373 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/CameraPicker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/CameraPicker.kt @@ -122,7 +122,7 @@ class CameraPicker(val requestCode: Int) { val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date()) val storageDir: File = context.filesDir return File.createTempFile( - "JPEG_${timeStamp}_", /* prefix */ + "${timeStamp}_", /* prefix */ ".jpg", /* suffix */ storageDir /* directory */ ) From f9aed2873225d1220e31dd3a41661d12c619eb2d Mon Sep 17 00:00:00 2001 From: onurays Date: Wed, 25 Mar 2020 15:51:15 +0300 Subject: [PATCH 09/15] grantUriPermission to handle incoming sharing. --- .../im/vector/riotx/multipicker/Picker.kt | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/Picker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/Picker.kt index ff20c1303b..e05dd50d40 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/Picker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/Picker.kt @@ -19,6 +19,8 @@ package im.vector.riotx.multipicker import android.app.Activity import android.content.Context import android.content.Intent +import android.content.pm.PackageManager +import android.content.pm.ResolveInfo import android.net.Uri import androidx.fragment.app.Fragment @@ -42,6 +44,23 @@ abstract class Picker(open val requestCode: Int) { * by using android.intent.action.SEND or android.intent.action.SEND_MULTIPLE actions. */ fun getIncomingFiles(context: Context, data: Intent?): List { + if (data == null) return emptyList() + + val uriList = mutableListOf() + if (data.action == Intent.ACTION_SEND) { + (data.getParcelableExtra(Intent.EXTRA_STREAM) as? Uri)?.let { uriList.add(it) } + } else if (data.action == Intent.ACTION_SEND_MULTIPLE) { + val extraUriList: List? = data.getParcelableArrayListExtra(Intent.EXTRA_STREAM) + extraUriList?.let { uriList.addAll(it) } + } + + val resInfoList: List = context.packageManager.queryIntentActivities(data, PackageManager.MATCH_DEFAULT_ONLY) + uriList.forEach { + for (resolveInfo in resInfoList) { + val packageName: String = resolveInfo.activityInfo.packageName + context.grantUriPermission(packageName, it, Intent.FLAG_GRANT_READ_URI_PERMISSION) + } + } return getSelectedFiles(context, requestCode, Activity.RESULT_OK, data) } @@ -60,7 +79,7 @@ abstract class Picker(open val requestCode: Int) { * @param activity Activity to handle onActivityResult(). */ fun startWith(activity: Activity) { - activity.startActivityForResult(createIntent(), requestCode) + activity.startActivityForResult(createIntent().apply { addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) }, requestCode) } /** @@ -68,7 +87,7 @@ abstract class Picker(open val requestCode: Int) { * @param fragment Fragment to handle onActivityResult(). */ fun startWith(fragment: Fragment) { - fragment.startActivityForResult(createIntent(), requestCode) + fragment.startActivityForResult(createIntent().apply { addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) }, requestCode) } protected fun getSelectedUriList(data: Intent?): List { From f68e98b2c71af9fbde01a28ba6c23640607158a8 Mon Sep 17 00:00:00 2001 From: onurays Date: Wed, 25 Mar 2020 17:35:18 +0300 Subject: [PATCH 10/15] Do not resize video thumbnail. --- .../internal/session/content/ThumbnailExtractor.kt | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/ThumbnailExtractor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/ThumbnailExtractor.kt index ad23ff8d78..eae2bf8f6d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/ThumbnailExtractor.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/ThumbnailExtractor.kt @@ -22,8 +22,6 @@ import android.media.MediaMetadataRetriever import im.vector.matrix.android.api.session.content.ContentAttachmentData import timber.log.Timber import java.io.ByteArrayOutputStream -import kotlin.math.max -import kotlin.math.roundToInt internal object ThumbnailExtractor { @@ -48,17 +46,7 @@ internal object ThumbnailExtractor { val mediaMetadataRetriever = MediaMetadataRetriever() try { mediaMetadataRetriever.setDataSource(context, attachment.queryUri) - var thumbnail = mediaMetadataRetriever.frameAtTime - // Scale down the bitmap if it's too large. - val width: Int = thumbnail.width - val height: Int = thumbnail.height - val max = max(width, height) - if (max > 512) { - val scale = 512f / max - val w = (scale * width).roundToInt() - val h = (scale * height).roundToInt() - thumbnail = Bitmap.createScaledBitmap(thumbnail, w, h, true) - } + val thumbnail = mediaMetadataRetriever.frameAtTime val outputStream = ByteArrayOutputStream() thumbnail.compress(Bitmap.CompressFormat.JPEG, 100, outputStream) From 5cb47dae3539598af72bde53579343b22027d31e Mon Sep 17 00:00:00 2001 From: onurays Date: Wed, 25 Mar 2020 18:03:20 +0300 Subject: [PATCH 11/15] Return a failure message if the file cannot be opened. --- .../internal/session/content/UploadContentWorker.kt | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt index 21ab649c23..1b736d349f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt @@ -90,7 +90,14 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter var newImageAttributes: NewImageAttributes? = null try { - val inputStream = context.contentResolver.openInputStream(attachment.queryUri) ?: return Result.success() + val inputStream = context.contentResolver.openInputStream(attachment.queryUri) + ?: return Result.success( + WorkerParamsFactory.toData( + params.copy( + lastFailureMessage = "Cannot openInputStream for file: " + attachment.queryUri.toString() + ) + ) + ) inputStream.use { var uploadedThumbnailUrl: String? = null @@ -122,8 +129,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter uploadedThumbnailUrl = contentUploadResponse.contentUri } catch (t: Throwable) { - Timber.e(t) - return handleFailure(params, t) + Timber.e(t, "Thumbnail update failed") } } From 3c1e1090e7fd1e687ab296eba746b46dc3264093 Mon Sep 17 00:00:00 2001 From: onurays Date: Wed, 25 Mar 2020 18:20:34 +0300 Subject: [PATCH 12/15] Avoid UNCHECKED_CAST. --- multipicker/build.gradle | 1 - .../src/main/java/im/vector/riotx/multipicker/Picker.kt | 9 +++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/multipicker/build.gradle b/multipicker/build.gradle index 950e76020f..d8cfeee3c9 100644 --- a/multipicker/build.gradle +++ b/multipicker/build.gradle @@ -20,7 +20,6 @@ apply plugin: 'kotlin-android-extensions' android { compileSdkVersion 29 - buildToolsVersion "29.0.3" defaultConfig { minSdkVersion 19 diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/Picker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/Picker.kt index e05dd50d40..43ac5d5fdd 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/Picker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/Picker.kt @@ -103,10 +103,11 @@ abstract class Picker(open val requestCode: Int) { selectedUriList.add(dataUri) } else { data?.extras?.get(Intent.EXTRA_STREAM)?.let { - @Suppress("UNCHECKED_CAST") - when (it) { - is List<*> -> selectedUriList.addAll(it as List) - else -> selectedUriList.add(it as Uri) + (it as? List<*>)?.filterIsInstance()?.let { uriList -> + selectedUriList.addAll(uriList) + } + if (it is Uri) { + selectedUriList.add(it) } } } From 6130a0a6543b1cdf99f2ce26872040c28a5f8d24 Mon Sep 17 00:00:00 2001 From: onurays Date: Wed, 25 Mar 2020 18:34:04 +0300 Subject: [PATCH 13/15] Remove unused toString(). --- .../riotx/multipicker/entity/MultiPickerContactType.kt | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerContactType.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerContactType.kt index 5a9c064867..a9135443a2 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerContactType.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerContactType.kt @@ -21,12 +21,4 @@ data class MultiPickerContactType( val photoUri: String?, val phoneNumberList: List, val emailList: List -) { - private val CONTACT_FORMAT = "Name: %s, Photo: %s, Phones: %s, Emails: %s" - - override fun toString(): String { - val phoneNumberString = phoneNumberList.joinToString(separator = ", ", prefix = "[", postfix = "]") - val emailString = emailList.joinToString(separator = ", ", prefix = "[", postfix = "]") - return String.format(CONTACT_FORMAT, displayName, photoUri, phoneNumberString, emailString) - } -} +) From 5db1010e47a6b6b2dcf81f57a5ec13eec3b0d068 Mon Sep 17 00:00:00 2001 From: onurays Date: Wed, 25 Mar 2020 18:39:35 +0300 Subject: [PATCH 14/15] Catch exceptions if the file cannot be decoded. --- .../vector/riotx/multipicker/utils/ImageUtils.kt | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/utils/ImageUtils.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/utils/ImageUtils.kt index 76f4b677a0..009418fd5c 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/utils/ImageUtils.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/utils/ImageUtils.kt @@ -27,12 +27,17 @@ import androidx.exifinterface.media.ExifInterface object ImageUtils { fun getBitmap(context: Context, uri: Uri): Bitmap? { - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - ImageDecoder.decodeBitmap(ImageDecoder.createSource(context.contentResolver, uri)) - } else { - context.contentResolver.openInputStream(uri)?.use { inputStream -> - BitmapFactory.decodeStream(inputStream) + return try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + ImageDecoder.decodeBitmap(ImageDecoder.createSource(context.contentResolver, uri)) + } else { + context.contentResolver.openInputStream(uri)?.use { inputStream -> + BitmapFactory.decodeStream(inputStream) + } } + } catch (e: Exception) { + e.printStackTrace() + null } } From a01482dca441cc00eabc78aea6de9bf4942037e6 Mon Sep 17 00:00:00 2001 From: onurays Date: Wed, 25 Mar 2020 18:51:55 +0300 Subject: [PATCH 15/15] Use Timber log instead of printStackTrace. --- multipicker/build.gradle | 3 +++ .../java/im/vector/riotx/multipicker/utils/ImageUtils.kt | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/multipicker/build.gradle b/multipicker/build.gradle index d8cfeee3c9..8b08a9d3ef 100644 --- a/multipicker/build.gradle +++ b/multipicker/build.gradle @@ -50,4 +50,7 @@ dependencies { androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' implementation 'androidx.exifinterface:exifinterface:1.3.0-alpha01' + + // Log + implementation 'com.jakewharton.timber:timber:4.7.1' } diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/utils/ImageUtils.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/utils/ImageUtils.kt index 009418fd5c..c5171e7d84 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/utils/ImageUtils.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/utils/ImageUtils.kt @@ -23,6 +23,7 @@ import android.graphics.ImageDecoder import android.net.Uri import android.os.Build import androidx.exifinterface.media.ExifInterface +import timber.log.Timber object ImageUtils { @@ -36,7 +37,7 @@ object ImageUtils { } } } catch (e: Exception) { - e.printStackTrace() + Timber.e(e, "Cannot decode Bitmap: %s", uri.toString()) null } } @@ -49,7 +50,7 @@ object ImageUtils { orientation = it.rotationDegrees } } catch (e: Exception) { - e.printStackTrace() + Timber.e(e, "Cannot read orientation: %s", uri.toString()) } } return orientation