parent
4d27e707bb
commit
c74b79dbfc
@ -0,0 +1,3 @@ |
||||
# Vector Packages |
||||
|
||||
Some useful stuff from Vector that isn't in the Matrix SDK. |
@ -0,0 +1 @@ |
||||
/build |
@ -0,0 +1 @@ |
||||
Some core-utils classes from Vector |
@ -0,0 +1,41 @@ |
||||
plugins { |
||||
id("com.android.library") |
||||
id("org.jetbrains.kotlin.android") |
||||
} |
||||
|
||||
android { |
||||
namespace = "im.vector.lib.core.utils" |
||||
compileSdk = 33 |
||||
|
||||
defaultConfig { |
||||
minSdk = 24 |
||||
targetSdk = 33 |
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" |
||||
consumerProguardFiles("consumer-rules.pro") |
||||
} |
||||
|
||||
buildTypes { |
||||
release { |
||||
isMinifyEnabled = false |
||||
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") |
||||
} |
||||
} |
||||
compileOptions { |
||||
sourceCompatibility = JavaVersion.VERSION_1_8 |
||||
targetCompatibility = JavaVersion.VERSION_1_8 |
||||
} |
||||
kotlinOptions { |
||||
jvmTarget = "1.8" |
||||
} |
||||
} |
||||
|
||||
dependencies { |
||||
|
||||
implementation("androidx.core:core-ktx:1.7.0") |
||||
implementation("androidx.appcompat:appcompat:1.5.1") |
||||
implementation("com.google.android.material:material:1.7.0") |
||||
testImplementation("junit:junit:4.13.2") |
||||
androidTestImplementation("androidx.test.ext:junit:1.1.4") |
||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.0") |
||||
} |
@ -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 |
@ -0,0 +1,2 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<manifest /> |
@ -0,0 +1,70 @@ |
||||
/* |
||||
* Copyright (c) 2022 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.lib.core.utils.compat |
||||
|
||||
import android.content.Intent |
||||
import android.content.pm.PackageManager |
||||
import android.content.pm.ResolveInfo |
||||
import android.os.Build |
||||
import android.os.Bundle |
||||
import android.os.Parcelable |
||||
import java.io.Serializable |
||||
|
||||
inline fun <reified T> Intent.getParcelableExtraCompat(key: String): T? = when { |
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> getParcelableExtra(key, T::class.java) |
||||
else -> @Suppress("DEPRECATION") getParcelableExtra(key) as? T? |
||||
} |
||||
|
||||
inline fun <reified T : Parcelable> Intent.getParcelableArrayListExtraCompat(key: String): ArrayList<T>? = when { |
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> getParcelableArrayListExtra(key, T::class.java) |
||||
else -> @Suppress("DEPRECATION") getParcelableArrayListExtra<T>(key) |
||||
} |
||||
|
||||
inline fun <reified T> Bundle.getParcelableCompat(key: String): T? = when { |
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> getParcelable(key, T::class.java) |
||||
else -> @Suppress("DEPRECATION") getParcelable(key) as? T? |
||||
} |
||||
|
||||
inline fun <reified T : Serializable> Bundle.getSerializableCompat(key: String): T? = when { |
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> getSerializable(key, T::class.java) |
||||
else -> @Suppress("DEPRECATION") getSerializable(key) as? T? |
||||
} |
||||
|
||||
inline fun <reified T : Serializable> Intent.getSerializableExtraCompat(key: String): T? = when { |
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> getSerializableExtra(key, T::class.java) |
||||
else -> @Suppress("DEPRECATION") getSerializableExtra(key) as? T? |
||||
} |
||||
|
||||
fun PackageManager.queryIntentActivitiesCompat(data: Intent, flags: Int): List<ResolveInfo> { |
||||
return when { |
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> queryIntentActivities( |
||||
data, |
||||
PackageManager.ResolveInfoFlags.of(flags.toLong()) |
||||
) |
||||
else -> @Suppress("DEPRECATION") queryIntentActivities(data, flags) |
||||
} |
||||
} |
||||
|
||||
fun PackageManager.resolveActivityCompat(data: Intent, flags: Int): ResolveInfo? { |
||||
return when { |
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> resolveActivity( |
||||
data, |
||||
PackageManager.ResolveInfoFlags.of(flags.toLong()) |
||||
) |
||||
else -> @Suppress("DEPRECATION") resolveActivity(data, flags) |
||||
} |
||||
} |
@ -0,0 +1 @@ |
||||
/build |
@ -0,0 +1,3 @@ |
||||
# Multipicker |
||||
|
||||
Classes from Vector (Element) MultiPicker library. |
@ -0,0 +1,43 @@ |
||||
plugins { |
||||
id("com.android.library") |
||||
id("org.jetbrains.kotlin.android") |
||||
} |
||||
|
||||
android { |
||||
namespace = "im.vector.lib.multipicker" |
||||
compileSdk = 32 |
||||
|
||||
defaultConfig { |
||||
minSdk = 24 |
||||
targetSdk = 32 |
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" |
||||
consumerProguardFiles("consumer-rules.pro") |
||||
} |
||||
|
||||
buildTypes { |
||||
release { |
||||
isMinifyEnabled = false |
||||
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") |
||||
} |
||||
} |
||||
compileOptions { |
||||
sourceCompatibility = JavaVersion.VERSION_1_8 |
||||
targetCompatibility = JavaVersion.VERSION_1_8 |
||||
} |
||||
kotlinOptions { |
||||
jvmTarget = "1.8" |
||||
} |
||||
} |
||||
|
||||
dependencies { |
||||
|
||||
implementation("androidx.core:core-ktx:1.7.0") |
||||
implementation("androidx.appcompat:appcompat:1.5.1") |
||||
implementation("com.google.android.material:material:1.7.0") |
||||
implementation("androidx.exifinterface:exifinterface:1.3.5") |
||||
implementation(project(":vector:core-utils")) |
||||
testImplementation("junit:junit:4.13.2") |
||||
androidTestImplementation("androidx.test.ext:junit:1.1.4") |
||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.0") |
||||
} |
@ -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 |
@ -0,0 +1,16 @@ |
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" |
||||
package="im.vector.lib.multipicker"> |
||||
|
||||
<application> |
||||
<provider |
||||
android:name=".provider.MultiPickerFileProvider" |
||||
android:authorities="${applicationId}.multipicker.fileprovider" |
||||
android:exported="false" |
||||
android:grantUriPermissions="true"> |
||||
<meta-data |
||||
android:name="android.support.FILE_PROVIDER_PATHS" |
||||
android:resource="@xml/multipicker_provider_paths" /> |
||||
</provider> |
||||
</application> |
||||
|
||||
</manifest> |
@ -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.lib.multipicker |
||||
|
||||
import android.content.Context |
||||
import android.content.Intent |
||||
import im.vector.lib.multipicker.entity.MultiPickerAudioType |
||||
import im.vector.lib.multipicker.utils.toMultiPickerAudioType |
||||
|
||||
/** |
||||
* Audio file picker implementation. |
||||
*/ |
||||
class AudioPicker : Picker<MultiPickerAudioType>() { |
||||
|
||||
/** |
||||
* Call this function from onActivityResult(int, int, Intent). |
||||
* Returns selected audio files or empty list if user did not select any files. |
||||
*/ |
||||
override fun getSelectedFiles(context: Context, data: Intent?): List<MultiPickerAudioType> { |
||||
return getSelectedUriList(data).mapNotNull { selectedUri -> |
||||
selectedUri.toMultiPickerAudioType(context) |
||||
} |
||||
} |
||||
|
||||
override fun createIntent(): Intent { |
||||
return Intent(Intent.ACTION_GET_CONTENT).apply { |
||||
addCategory(Intent.CATEGORY_OPENABLE) |
||||
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, !single) |
||||
type = "audio/*" |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,69 @@ |
||||
/* |
||||
* 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.lib.multipicker |
||||
|
||||
import android.content.Context |
||||
import android.content.Intent |
||||
import android.net.Uri |
||||
import android.provider.MediaStore |
||||
import androidx.activity.result.ActivityResultLauncher |
||||
import androidx.core.content.FileProvider |
||||
import im.vector.lib.multipicker.entity.MultiPickerImageType |
||||
import im.vector.lib.multipicker.utils.MediaType |
||||
import im.vector.lib.multipicker.utils.createTemporaryMediaFile |
||||
import im.vector.lib.multipicker.utils.toMultiPickerImageType |
||||
|
||||
/** |
||||
* Implementation of taking a photo with Camera. |
||||
*/ |
||||
class CameraPicker { |
||||
|
||||
/** |
||||
* Start camera by using a ActivityResultLauncher. |
||||
* @return Uri of taken photo or null if the operation is cancelled. |
||||
*/ |
||||
fun startWithExpectingFile(context: Context, activityResultLauncher: ActivityResultLauncher<Intent>): Uri { |
||||
val photoUri = createPhotoUri(context) |
||||
val intent = createIntent().apply { |
||||
putExtra(MediaStore.EXTRA_OUTPUT, photoUri) |
||||
} |
||||
activityResultLauncher.launch(intent) |
||||
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, photoUri: Uri): MultiPickerImageType? { |
||||
return photoUri.toMultiPickerImageType(context) |
||||
} |
||||
|
||||
private fun createIntent(): Intent { |
||||
return Intent(MediaStore.ACTION_IMAGE_CAPTURE) |
||||
} |
||||
|
||||
companion object { |
||||
fun createPhotoUri(context: Context): Uri { |
||||
val file = createTemporaryMediaFile(context, MediaType.IMAGE) |
||||
val authority = context.packageName + ".multipicker.fileprovider" |
||||
return FileProvider.getUriForFile(context, authority, file) |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,69 @@ |
||||
/* |
||||
* Copyright (c) 2021 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.lib.multipicker |
||||
|
||||
import android.content.Context |
||||
import android.content.Intent |
||||
import android.net.Uri |
||||
import android.provider.MediaStore |
||||
import androidx.activity.result.ActivityResultLauncher |
||||
import androidx.core.content.FileProvider |
||||
import im.vector.lib.multipicker.entity.MultiPickerVideoType |
||||
import im.vector.lib.multipicker.utils.MediaType |
||||
import im.vector.lib.multipicker.utils.createTemporaryMediaFile |
||||
import im.vector.lib.multipicker.utils.toMultiPickerVideoType |
||||
|
||||
/** |
||||
* Implementation of taking a video with Camera. |
||||
*/ |
||||
class CameraVideoPicker { |
||||
|
||||
/** |
||||
* Start camera by using a ActivityResultLauncher. |
||||
* @return Uri of taken photo or null if the operation is cancelled. |
||||
*/ |
||||
fun startWithExpectingFile(context: Context, activityResultLauncher: ActivityResultLauncher<Intent>): Uri { |
||||
val videoUri = createVideoUri(context) |
||||
val intent = createIntent().apply { |
||||
putExtra(MediaStore.EXTRA_OUTPUT, videoUri) |
||||
} |
||||
activityResultLauncher.launch(intent) |
||||
return videoUri |
||||
} |
||||
|
||||
/** |
||||
* 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 getTakenVideo(context: Context, videoUri: Uri): MultiPickerVideoType? { |
||||
return videoUri.toMultiPickerVideoType(context) |
||||
} |
||||
|
||||
private fun createIntent(): Intent { |
||||
return Intent(MediaStore.ACTION_VIDEO_CAPTURE) |
||||
} |
||||
|
||||
companion object { |
||||
fun createVideoUri(context: Context): Uri { |
||||
val file = createTemporaryMediaFile(context, MediaType.VIDEO) |
||||
val authority = context.packageName + ".multipicker.fileprovider" |
||||
return FileProvider.getUriForFile(context, authority, file) |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,139 @@ |
||||
/* |
||||
* 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.lib.multipicker |
||||
|
||||
import android.annotation.SuppressLint |
||||
import android.content.ContentResolver |
||||
import android.content.Context |
||||
import android.content.Intent |
||||
import android.provider.ContactsContract |
||||
import androidx.core.database.getIntOrNull |
||||
import androidx.core.database.getStringOrNull |
||||
import im.vector.lib.multipicker.entity.MultiPickerContactType |
||||
import im.vector.lib.multipicker.utils.getColumnIndexOrNull |
||||
|
||||
/** |
||||
* Contact Picker implementation. |
||||
*/ |
||||
class ContactPicker : Picker<MultiPickerContactType>() { |
||||
|
||||
/** |
||||
* Call this function from onActivityResult(int, int, Intent). |
||||
* Returns selected contact or empty list if user did not select any contacts. |
||||
*/ |
||||
@SuppressLint("Recycle") |
||||
override fun getSelectedFiles(context: Context, data: Intent?): List<MultiPickerContactType> { |
||||
val contactList = mutableListOf<MultiPickerContactType>() |
||||
|
||||
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.getColumnIndexOrNull(ContactsContract.Contacts._ID) ?: return@use |
||||
val nameColumn = cursor.getColumnIndexOrNull(ContactsContract.Contacts.DISPLAY_NAME) ?: return@use |
||||
val photoUriColumn = cursor.getColumnIndexOrNull(ContactsContract.Contacts.PHOTO_URI) ?: return@use |
||||
|
||||
val contactId = cursor.getIntOrNull(idColumn) ?: return@use |
||||
var name = cursor.getStringOrNull(nameColumn) ?: return@use |
||||
val photoUri = cursor.getStringOrNull(photoUriColumn) |
||||
val phoneNumberList = mutableListOf<String>() |
||||
val emailList = mutableListOf<String>() |
||||
|
||||
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 inner@{ innerCursor -> |
||||
val mimeTypeColumnIndex = innerCursor.getColumnIndexOrNull(ContactsContract.Data.MIMETYPE) ?: return@inner |
||||
val data1ColumnIndex = innerCursor.getColumnIndexOrNull(ContactsContract.Data.DATA1) ?: return@inner |
||||
|
||||
while (innerCursor.moveToNext()) { |
||||
val mimeType = innerCursor.getStringOrNull(mimeTypeColumnIndex) |
||||
val contactData = innerCursor.getStringOrNull(data1ColumnIndex) ?: continue |
||||
|
||||
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.getColumnIndexOrNull(ContactsContract.RawContacts._ID) |
||||
?.let { cursor.getIntOrNull(it) } |
||||
} else null |
||||
} |
||||
} |
||||
|
||||
override fun createIntent(): Intent { |
||||
return Intent(Intent.ACTION_PICK).apply { |
||||
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, !single) |
||||
type = ContactsContract.Contacts.CONTENT_TYPE |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,83 @@ |
||||
/* |
||||
* 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.lib.multipicker |
||||
|
||||
import android.content.Context |
||||
import android.content.Intent |
||||
import android.provider.OpenableColumns |
||||
import androidx.core.database.getLongOrNull |
||||
import androidx.core.database.getStringOrNull |
||||
import im.vector.lib.multipicker.entity.MultiPickerBaseType |
||||
import im.vector.lib.multipicker.entity.MultiPickerFileType |
||||
import im.vector.lib.multipicker.utils.getColumnIndexOrNull |
||||
import im.vector.lib.multipicker.utils.isMimeTypeAudio |
||||
import im.vector.lib.multipicker.utils.isMimeTypeImage |
||||
import im.vector.lib.multipicker.utils.isMimeTypeVideo |
||||
import im.vector.lib.multipicker.utils.toMultiPickerAudioType |
||||
import im.vector.lib.multipicker.utils.toMultiPickerImageType |
||||
import im.vector.lib.multipicker.utils.toMultiPickerVideoType |
||||
|
||||
/** |
||||
* Implementation of selecting any type of files. |
||||
*/ |
||||
class FilePicker : Picker<MultiPickerBaseType>() { |
||||
|
||||
/** |
||||
* Call this function from onActivityResult(int, int, Intent). |
||||
* Returns selected files or empty list if user did not select any files. |
||||
*/ |
||||
override fun getSelectedFiles(context: Context, data: Intent?): List<MultiPickerBaseType> { |
||||
return getSelectedUriList(data).mapNotNull { selectedUri -> |
||||
val type = context.contentResolver.getType(selectedUri) |
||||
|
||||
when { |
||||
type.isMimeTypeVideo() -> selectedUri.toMultiPickerVideoType(context) |
||||
type.isMimeTypeImage() -> selectedUri.toMultiPickerImageType(context) |
||||
type.isMimeTypeAudio() -> selectedUri.toMultiPickerAudioType(context) |
||||
else -> { |
||||
// Other files |
||||
context.contentResolver.query(selectedUri, null, null, null, null) |
||||
?.use { cursor -> |
||||
val nameColumn = cursor.getColumnIndexOrNull(OpenableColumns.DISPLAY_NAME) ?: return@use null |
||||
val sizeColumn = cursor.getColumnIndexOrNull(OpenableColumns.SIZE) ?: return@use null |
||||
if (cursor.moveToFirst()) { |
||||
val name = cursor.getStringOrNull(nameColumn) |
||||
val size = cursor.getLongOrNull(sizeColumn) ?: 0 |
||||
|
||||
MultiPickerFileType( |
||||
name, |
||||
size, |
||||
context.contentResolver.getType(selectedUri), |
||||
selectedUri |
||||
) |
||||
} else { |
||||
null |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
override fun createIntent(): Intent { |
||||
return Intent(Intent.ACTION_GET_CONTENT).apply { |
||||
addCategory(Intent.CATEGORY_OPENABLE) |
||||
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, !single) |
||||
type = "*/*" |
||||
} |
||||
} |
||||
} |
@ -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.lib.multipicker |
||||
|
||||
import android.content.Context |
||||
import android.content.Intent |
||||
import im.vector.lib.multipicker.entity.MultiPickerImageType |
||||
import im.vector.lib.multipicker.utils.toMultiPickerImageType |
||||
|
||||
/** |
||||
* Image Picker implementation. |
||||
*/ |
||||
class ImagePicker : Picker<MultiPickerImageType>() { |
||||
|
||||
/** |
||||
* Call this function from onActivityResult(int, int, Intent). |
||||
* Returns selected image files or empty list if user did not select any files. |
||||
*/ |
||||
override fun getSelectedFiles(context: Context, data: Intent?): List<MultiPickerImageType> { |
||||
return getSelectedUriList(data).mapNotNull { selectedUri -> |
||||
selectedUri.toMultiPickerImageType(context) |
||||
} |
||||
} |
||||
|
||||
override fun createIntent(): Intent { |
||||
return Intent(Intent.ACTION_GET_CONTENT).apply { |
||||
addCategory(Intent.CATEGORY_OPENABLE) |
||||
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, !single) |
||||
type = "image/*" |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,57 @@ |
||||
/* |
||||
* 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.lib.multipicker |
||||
|
||||
import android.content.Context |
||||
import android.content.Intent |
||||
import im.vector.lib.multipicker.entity.MultiPickerBaseMediaType |
||||
import im.vector.lib.multipicker.utils.isMimeTypeVideo |
||||
import im.vector.lib.multipicker.utils.toMultiPickerImageType |
||||
import im.vector.lib.multipicker.utils.toMultiPickerVideoType |
||||
|
||||
/** |
||||
* Image/Video Picker implementation. |
||||
*/ |
||||
class MediaPicker : Picker<MultiPickerBaseMediaType>() { |
||||
|
||||
/** |
||||
* Call this function from onActivityResult(int, int, Intent). |
||||
* Returns selected image/video files or empty list if user did not select any files. |
||||
*/ |
||||
override fun getSelectedFiles(context: Context, data: Intent?): List<MultiPickerBaseMediaType> { |
||||
return getSelectedUriList(data).mapNotNull { selectedUri -> |
||||
val mimeType = context.contentResolver.getType(selectedUri) |
||||
|
||||
if (mimeType.isMimeTypeVideo()) { |
||||
selectedUri.toMultiPickerVideoType(context) |
||||
} else { |
||||
// Assume it's an image |
||||
selectedUri.toMultiPickerImageType(context) |
||||
} |
||||
} |
||||
} |
||||
|
||||
override fun createIntent(): Intent { |
||||
return Intent(Intent.ACTION_GET_CONTENT).apply { |
||||
addCategory(Intent.CATEGORY_OPENABLE) |
||||
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, !single) |
||||
type = "*/*" |
||||
val mimeTypes = arrayOf("image/*", "video/*") |
||||
putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes) |
||||
} |
||||
} |
||||
} |
@ -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.lib.multipicker |
||||
|
||||
class MultiPicker<T> private constructor() { |
||||
|
||||
companion object Type { |
||||
val IMAGE by lazy { MultiPicker<ImagePicker>() } |
||||
val MEDIA by lazy { MultiPicker<MediaPicker>() } |
||||
val FILE by lazy { MultiPicker<FilePicker>() } |
||||
val VIDEO by lazy { MultiPicker<VideoPicker>() } |
||||
val AUDIO by lazy { MultiPicker<AudioPicker>() } |
||||
val CONTACT by lazy { MultiPicker<ContactPicker>() } |
||||
val CAMERA by lazy { MultiPicker<CameraPicker>() } |
||||
val CAMERA_VIDEO by lazy { MultiPicker<CameraVideoPicker>() } |
||||
|
||||
@Suppress("UNCHECKED_CAST") |
||||
fun <T> get(type: MultiPicker<T>): T { |
||||
return when (type) { |
||||
IMAGE -> ImagePicker() as T |
||||
VIDEO -> VideoPicker() as T |
||||
MEDIA -> MediaPicker() as T |
||||
FILE -> FilePicker() as T |
||||
AUDIO -> AudioPicker() as T |
||||
CONTACT -> ContactPicker() as T |
||||
CAMERA -> CameraPicker() as T |
||||
CAMERA_VIDEO -> CameraVideoPicker() as T |
||||
else -> throw IllegalArgumentException("Unsupported type $type") |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,108 @@ |
||||
/* |
||||
* 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.lib.multipicker |
||||
|
||||
import android.content.Context |
||||
import android.content.Intent |
||||
import android.content.pm.PackageManager |
||||
import android.content.pm.ResolveInfo |
||||
import android.net.Uri |
||||
import androidx.activity.result.ActivityResultLauncher |
||||
import im.vector.lib.core.utils.compat.getParcelableArrayListExtraCompat |
||||
import im.vector.lib.core.utils.compat.getParcelableExtraCompat |
||||
import im.vector.lib.core.utils.compat.queryIntentActivitiesCompat |
||||
|
||||
/** |
||||
* Abstract class to provide all types of Pickers. |
||||
*/ |
||||
abstract class Picker<T> { |
||||
|
||||
protected var single = false |
||||
|
||||
/** |
||||
* Call this function from onActivityResult(int, int, Intent). |
||||
* @return selected files or empty list if user did not select any files. |
||||
*/ |
||||
abstract fun getSelectedFiles(context: Context, data: Intent?): List<T> |
||||
|
||||
/** |
||||
* 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<T> { |
||||
if (data == null) return emptyList() |
||||
|
||||
val uriList = mutableListOf<Uri>() |
||||
if (data.action == Intent.ACTION_SEND) { |
||||
data.getParcelableExtraCompat<Uri>(Intent.EXTRA_STREAM)?.let { uriList.add(it) } |
||||
} else if (data.action == Intent.ACTION_SEND_MULTIPLE) { |
||||
val extraUriList: List<Uri>? = data.getParcelableArrayListExtraCompat(Intent.EXTRA_STREAM) |
||||
extraUriList?.let { uriList.addAll(it) } |
||||
} |
||||
|
||||
val resInfoList: List<ResolveInfo> = context.packageManager.queryIntentActivitiesCompat(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, data) |
||||
} |
||||
|
||||
/** |
||||
* Call this function to disable multiple selection of files. |
||||
*/ |
||||
fun single(): Picker<T> { |
||||
single = true |
||||
return this |
||||
} |
||||
|
||||
abstract fun createIntent(): Intent |
||||
|
||||
/** |
||||
* Start Storage Access Framework UI by using a ActivityResultLauncher. |
||||
* @param activityResultLauncher to handle the result. |
||||
*/ |
||||
fun startWith(activityResultLauncher: ActivityResultLauncher<Intent>) { |
||||
activityResultLauncher.launch(createIntent().apply { addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) }) |
||||
} |
||||
|
||||
protected fun getSelectedUriList(data: Intent?): List<Uri> { |
||||
val selectedUriList = mutableListOf<Uri>() |
||||
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 { |
||||
(it as? List<*>)?.filterIsInstance<Uri>()?.let { uriList -> |
||||
selectedUriList.addAll(uriList) |
||||
} |
||||
if (it is Uri) { |
||||
selectedUriList.add(it) |
||||
} |
||||
} |
||||
} |
||||
return selectedUriList |
||||
} |
||||
} |
@ -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.lib.multipicker |
||||
|
||||
import android.content.Context |
||||
import android.content.Intent |
||||
import im.vector.lib.multipicker.entity.MultiPickerVideoType |
||||
import im.vector.lib.multipicker.utils.toMultiPickerVideoType |
||||
|
||||
/** |
||||
* Video Picker implementation. |
||||
*/ |
||||
class VideoPicker : Picker<MultiPickerVideoType>() { |
||||
|
||||
/** |
||||
* Call this function from onActivityResult(int, int, Intent). |
||||
* Returns selected video files or empty list if user did not select any files. |
||||
*/ |
||||
override fun getSelectedFiles(context: Context, data: Intent?): List<MultiPickerVideoType> { |
||||
return getSelectedUriList(data).mapNotNull { selectedUri -> |
||||
selectedUri.toMultiPickerVideoType(context) |
||||
} |
||||
} |
||||
|
||||
override fun createIntent(): Intent { |
||||
return Intent(Intent.ACTION_GET_CONTENT).apply { |
||||
addCategory(Intent.CATEGORY_OPENABLE) |
||||
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, !single) |
||||
type = "video/*" |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,28 @@ |
||||
/* |
||||
* 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.lib.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, |
||||
var waveform: List<Int>? = null |
||||
) : MultiPickerBaseType |
@ -0,0 +1,23 @@ |
||||
/* |
||||
* 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.lib.multipicker.entity |
||||
|
||||
interface MultiPickerBaseMediaType : MultiPickerBaseType { |
||||
val width: Int |
||||
val height: Int |
||||
val orientation: Int |
||||
} |
@ -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.lib.multipicker.entity |
||||
|
||||
|
||||
import android.net.Uri |
||||
|
||||
interface MultiPickerBaseType { |
||||
val displayName: String? |
||||
val size: Long |
||||
val mimeType: String? |
||||
val contentUri: Uri |
||||
} |
@ -0,0 +1,24 @@ |
||||
/* |
||||
* 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.lib.multipicker.entity |
||||
|
||||
data class MultiPickerContactType( |
||||
val displayName: String, |
||||
val photoUri: String?, |
||||
val phoneNumberList: List<String>, |
||||
val emailList: List<String> |
||||
) |
@ -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.lib.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 |
@ -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.lib.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, |
||||
override val width: Int, |
||||
override val height: Int, |
||||
override val orientation: Int |
||||
) : MultiPickerBaseMediaType |
@ -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.lib.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, |
||||
override val width: Int, |
||||
override val height: Int, |
||||
override val orientation: Int, |
||||
val duration: Long |
||||
) : MultiPickerBaseMediaType |
@ -0,0 +1,21 @@ |
||||
/* |
||||
* 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.lib.multipicker.provider |
||||
|
||||
import androidx.core.content.FileProvider |
||||
|
||||
class MultiPickerFileProvider : FileProvider() |
@ -0,0 +1,162 @@ |
||||
/* |
||||
* Copyright (c) 2021 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.lib.multipicker.utils |
||||
|
||||
import android.content.Context |
||||
import android.media.MediaMetadataRetriever |
||||
import android.net.Uri |
||||
import android.provider.MediaStore |
||||
import androidx.core.database.getLongOrNull |
||||
import androidx.core.database.getStringOrNull |
||||
import im.vector.lib.multipicker.entity.MultiPickerAudioType |
||||
import im.vector.lib.multipicker.entity.MultiPickerImageType |
||||
import im.vector.lib.multipicker.entity.MultiPickerVideoType |
||||
|
||||
internal fun Uri.toMultiPickerImageType(context: Context): MultiPickerImageType? { |
||||
val projection = arrayOf( |
||||
MediaStore.Images.Media.DISPLAY_NAME, |
||||
MediaStore.Images.Media.SIZE |
||||
) |
||||
|
||||
return context.contentResolver.query( |
||||
this, |
||||
projection, |
||||
null, |
||||
null, |
||||
null |
||||
)?.use { cursor -> |
||||
val nameColumn = cursor.getColumnIndexOrNull(MediaStore.Images.Media.DISPLAY_NAME) ?: return@use null |
||||
val sizeColumn = cursor.getColumnIndexOrNull(MediaStore.Images.Media.SIZE) ?: return@use null |
||||
|
||||
if (cursor.moveToNext()) { |
||||
val name = cursor.getStringOrNull(nameColumn) |
||||
val size = cursor.getLongOrNull(sizeColumn) ?: 0 |
||||
|
||||
val bitmap = ImageUtils.getBitmap(context, this) |
||||
val orientation = ImageUtils.getOrientation(context, this) |
||||
|
||||
MultiPickerImageType( |
||||
name, |
||||
size, |
||||
context.contentResolver.getType(this), |
||||
this, |
||||
bitmap?.width ?: 0, |
||||
bitmap?.height ?: 0, |
||||
orientation |
||||
) |
||||
} else { |
||||
null |
||||
} |
||||
} |
||||
} |
||||
|
||||
internal fun Uri.toMultiPickerVideoType(context: Context): MultiPickerVideoType? { |
||||
val projection = arrayOf( |
||||
MediaStore.Video.Media.DISPLAY_NAME, |
||||
MediaStore.Video.Media.SIZE |
||||
) |
||||
|
||||
return context.contentResolver.query( |
||||
this, |
||||
projection, |
||||
null, |
||||
null, |
||||
null |
||||
)?.use { cursor -> |
||||
val nameColumn = cursor.getColumnIndexOrNull(MediaStore.Video.Media.DISPLAY_NAME) ?: return@use null |
||||
val sizeColumn = cursor.getColumnIndexOrNull(MediaStore.Video.Media.SIZE) ?: return@use null |
||||
|
||||
if (cursor.moveToNext()) { |
||||
val name = cursor.getStringOrNull(nameColumn) |
||||
val size = cursor.getLongOrNull(sizeColumn) ?: 0 |
||||
var duration = 0L |
||||
var width = 0 |
||||
var height = 0 |
||||
var orientation = 0 |
||||
|
||||
context.contentResolver.openFileDescriptor(this, "r")?.use { pfd -> |
||||
val mediaMetadataRetriever = MediaMetadataRetriever() |
||||
mediaMetadataRetriever.setDataSource(pfd.fileDescriptor) |
||||
duration = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)?.toLong() ?: 0L |
||||
width = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)?.toInt() ?: 0 |
||||
height = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)?.toInt() ?: 0 |
||||
orientation = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION)?.toInt() ?: 0 |
||||
} |
||||
|
||||
MultiPickerVideoType( |
||||
name, |
||||
size, |
||||
context.contentResolver.getType(this), |
||||
this, |
||||
width, |
||||
height, |
||||
orientation, |
||||
duration |
||||
) |
||||
} else { |
||||
null |
||||
} |
||||
} |
||||
} |
||||
|
||||
fun Uri.toMultiPickerAudioType(context: Context): MultiPickerAudioType? { |
||||
val projection = arrayOf( |
||||
MediaStore.Audio.Media.DISPLAY_NAME, |
||||
MediaStore.Audio.Media.SIZE |
||||
) |
||||
|
||||
return context.contentResolver.query( |
||||
this, |
||||
projection, |
||||
null, |
||||
null, |
||||
null |
||||
)?.use { cursor -> |
||||
val nameColumn = cursor.getColumnIndexOrNull(MediaStore.Audio.Media.DISPLAY_NAME) ?: return@use null |
||||
val sizeColumn = cursor.getColumnIndexOrNull(MediaStore.Audio.Media.SIZE) ?: return@use null |
||||
|
||||
if (cursor.moveToNext()) { |
||||
val name = cursor.getStringOrNull(nameColumn) |
||||
val size = cursor.getLongOrNull(sizeColumn) ?: 0 |
||||
var duration = 0L |
||||
|
||||
context.contentResolver.openFileDescriptor(this, "r")?.use { pfd -> |
||||
val mediaMetadataRetriever = MediaMetadataRetriever() |
||||
mediaMetadataRetriever.setDataSource(pfd.fileDescriptor) |
||||
duration = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)?.toLong() ?: 0L |
||||
} |
||||
|
||||
MultiPickerAudioType( |
||||
name, |
||||
size, |
||||
sanitize(context.contentResolver.getType(this)), |
||||
this, |
||||
duration |
||||
) |
||||
} else { |
||||
null |
||||
} |
||||
} |
||||
} |
||||
|
||||
private fun sanitize(type: String?): String? { |
||||
if (type == "application/ogg") { |
||||
// Not supported on old system |
||||
return "audio/ogg" |
||||
} |
||||
return type |
||||
} |
@ -0,0 +1,29 @@ |
||||
/* |
||||
* Copyright (c) 2021 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.lib.multipicker.utils |
||||
|
||||
|
||||
import android.database.Cursor |
||||
import androidx.core.database.getStringOrNull |
||||
|
||||
fun Cursor.getColumnIndexOrNull(column: String): Int? { |
||||
return getColumnIndex(column).takeIf { it != -1 } |
||||
} |
||||
|
||||
fun Cursor.readStringColumnOrNull(column: String): String? { |
||||
return getColumnIndexOrNull(column)?.let { getStringOrNull(it) } |
||||
} |
@ -0,0 +1,60 @@ |
||||
/* |
||||
* 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.lib.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 android.util.Log |
||||
import androidx.exifinterface.media.ExifInterface |
||||
|
||||
object ImageUtils { |
||||
|
||||
fun getBitmap(context: Context, uri: Uri): Bitmap? { |
||||
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() |
||||
Log.e("Decoding Failed", "Cannot decode Bitmap: $uri") |
||||
null |
||||
} |
||||
} |
||||
|
||||
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() |
||||
Log.e("Decoding Failed", "Cannot read orientation: $uri") |
||||
} |
||||
} |
||||
return orientation |
||||
} |
||||
} |
@ -0,0 +1,43 @@ |
||||
|
||||
/* |
||||
* Copyright (c) 2022 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.lib.multipicker.utils |
||||
|
||||
import android.content.Context |
||||
import java.io.File |
||||
import java.text.SimpleDateFormat |
||||
import java.util.Date |
||||
import java.util.Locale |
||||
|
||||
internal fun createTemporaryMediaFile(context: Context, mediaType: MediaType): File { |
||||
val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date()) |
||||
val storageDir: File = context.filesDir.also { it.mkdirs() } |
||||
val fileSuffix = when (mediaType) { |
||||
MediaType.IMAGE -> ".jpg" |
||||
MediaType.VIDEO -> ".mp4" |
||||
} |
||||
|
||||
return File.createTempFile( |
||||
"${timeStamp}_", |
||||
fileSuffix, |
||||
storageDir |
||||
) |
||||
} |
||||
|
||||
internal enum class MediaType { |
||||
IMAGE, VIDEO |
||||
} |
@ -0,0 +1,21 @@ |
||||
/* |
||||
* Copyright (c) 2021 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.lib.multipicker.utils |
||||
|
||||
internal fun String?.isMimeTypeImage() = this?.startsWith("image/") == true |
||||
internal fun String?.isMimeTypeVideo() = this?.startsWith("video/") == true |
||||
internal fun String?.isMimeTypeAudio() = this?.startsWith("audio/") == true |
@ -0,0 +1,6 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<paths> |
||||
<files-path |
||||
name="external_files" |
||||
path="." /> |
||||
</paths> |
Loading…
Reference in new issue