Skip to content
This repository has been archived by the owner on Feb 22, 2024. It is now read-only.

Introduce result type #24

Merged
merged 24 commits into from
Jun 30, 2021
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
bd3ffbc
Centralize exceptions in a file
yrezgui Jun 21, 2021
26d7497
Upgrade ModernStorage to 1.5.10
yrezgui Jun 21, 2021
1154b58
Change return types to Result type
yrezgui Jun 21, 2021
8121943
Merge branch 'main' into introduce-result-type
yrezgui Jun 21, 2021
8543f38
Change return types to Result type
yrezgui Jun 21, 2021
e55a68d
Upgrade fragment-ktx in the sample to 1.3.5
yrezgui Jun 21, 2021
d29661a
Remove duplicate plugins declaration
yrezgui Jun 21, 2021
96e4044
Generate metalava changes
yrezgui Jun 21, 2021
5a455a2
Modify coroutineScope to coroutineContext as optional parameter to al…
yrezgui Jun 21, 2021
2da9276
Update KDOC of MediaStoreRepository.kt
yrezgui Jun 22, 2021
49c46a5
Update sample/src/main/java/com/google/modernstorage/sample/mediastor…
yrezgui Jun 22, 2021
7899a14
Add README and mediastore guides
yrezgui Jun 23, 2021
51a8546
Add .editorconfig
yrezgui Jun 23, 2021
78a907a
Update MediaStoreRepository methods to only use optional context
yrezgui Jun 23, 2021
3cee0d5
Upgrade to Kotlin 1.5.20
yrezgui Jun 24, 2021
23a07bf
Add getFile method to FileResource
yrezgui Jun 24, 2021
96edf64
Improve mediastore sample architecture
yrezgui Jun 28, 2021
4a71c64
Revert Kotlin version to 1.4.32
yrezgui Jun 29, 2021
ac0e3e9
Use exception constructors instead of using utility functions
yrezgui Jun 30, 2021
0901d23
Add id property to FileResource
yrezgui Jun 30, 2021
ab05938
Add FileResource type property support
yrezgui Jun 30, 2021
5aef182
Use MediaColumns.DISPLAY_NAME instead of relying on each collection c…
yrezgui Jun 30, 2021
3c36614
Fix media resource uri check
yrezgui Jun 30, 2021
d9a398b
Add doc publishing CI task
yrezgui Jun 30, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
max_line_length = 100
insert_final_newline = true
trim_trailing_whitespace = true

# ktlint disabled rules
disabled_rules = no-wildcard-imports, import-ordering, final-newline
5 changes: 1 addition & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,14 @@ ModernStorage is available on `mavenCentral()`.

```kotlin
// For MediaStore interactions
implementation("com.google.modernstorage:modernstorage-mediastore:1.0.0-alpha01")
implementation("com.google.modernstorage:modernstorage-mediastore:1.0.0-alpha02")
```

## Quick Start

* For MediaStore interactions, check out the [sample app](/sample/src/main/java/com/google/modernstorage/sample/mediastore/)
* For non-media files on shared storage & SAF interactions, stay tuned for the upcoming **filesystem** package

## Is it ready?
Not yet! We've just started uploading our work but we will have proper documentation, tests and releases really soon!

## Contributions

We're still at an early stage sharing the vision of ModernStorage and would love to have more feature requests and ideas proposed as issues.
Expand Down
5 changes: 3 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ plugins {
id "org.jetbrains.dokka" version "1.4.32"
id "me.tylerbwong.gradle.metalava" version "0.1.7" apply false
id "com.vanniktech.maven.publish" version "0.15.1" apply false
id "org.jetbrains.kotlin.plugin.parcelize" version "1.4.32" apply false
}

allprojects {
Expand All @@ -59,7 +60,7 @@ subprojects {
target '**/*.gradle'
greclipse().configFile(rootProject.file('spotless/greclipse.properties'))
licenseHeaderFile rootProject.file('spotless/copyright.txt'),
'(buildscript|apply|import|plugins)'
'(buildscript|apply|import|plugins)'
}
}

Expand All @@ -71,4 +72,4 @@ subprojects {
reportLintsAsErrors = true
}
}
}
}
61 changes: 61 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
![ModernStorage](images/favicon.png)
# ModernStorage

ModernStorage is a group of libraries that provide an abstraction layer over storage on Android to
simplify its interactions by apps developers. ModernStorage is:

- **Easy to use**: ModernStorage focuses on API simplicity. Rather than calling four separate methods with the Android Framework API, you only need to call one.
- **Opinionated**: ModernStorage is written by the Android DevRel team in collaboration with the Android Storage team, taking in account all the feedback from the developer community to address common issues when dealing with storage on Android.

## Download

ModernStorage is available on `mavenCentral()`.

```kotlin
// For MediaStore interactions
implementation("com.google.modernstorage:modernstorage-mediastore:{{ lib_version }}")
```

## Quick start

* For MediaStore interactions, check out the [sample app][sample_app]
* For non-media files on shared storage & SAF interactions, stay tuned for the upcoming `filesystem`
package

## Is it ready?
It's experimental! Our current version is **{{ lib_version }}**. As it's an alpha release, we're
expecting API breaking changes between releases. While it always seems risky to rely on an
experimental library, we're enforcing storage best practises (including Scoped Storage), which
developers aren't always aware of.

We're looking for a stable release later this year (we don't have yet a precise date). We actively
listen to your feedback to make ModernStorage the default library for storage interactions on
Android.

## Contributions

We're still at an early stage sharing the vision of ModernStorage and would love to have more
feature requests and ideas proposed as issues. We would be glad to review pull requests, but keep in
mind that we want to minimize expanding the API surface until we get more feedback from developers.
Make sure to read the [Contributing][contributing] page first though.

## License

```
Copyright 2021 Google LLC

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

https://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.
```

[sample_app]: https://github.com/google/modernstorage/tree/main/sample/src/main/java/com/google/modernstorage/sample/mediastore/
[contributing]: https://github.com/google/modernstorage/blob/main/CONTRIBUTING.md
142 changes: 142 additions & 0 deletions docs/mediastore.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# MediaStore

On Android, every files on the shared storage (files visible when using a file manager) are indexed
by [MediaStore][mediastore_api], to allow apps to query them by type, date, size, etc.

On API 29 (Android 10), Scoped Storage has been introduced, enabling a sandboxed view on the shared
storage and the ability to add files without requesting permission. To read media files created by
other apps, you need to request `REQUEST_EXTERNAL_STORAGE` and editing/deleting them require to ask
[user's consent][manage_3rd_party_media_files].

`modernstorage-mediastore` is a library abstracting these [MediaStore][mediastore_api] API
interactions on API 21+ (Android Lollipop).

## Add dependency to project

`modernstorage-mediastore` is available on `mavenCentral()`.

```groovy
// build.gradle
implementation("com.google.modernstorage:modernstorage-mediastore:{{ lib_version }}")
```

## API reference
`modernstorage-mediastore` API reference is available [here][api_reference].

## Initialize repository
To interact with MediaStore, you need to initialize a [MediaStoreRepository][mediastore_repository]
instance:

```kotlin
val mediaStore by lazy { MediaStoreRepository(context) }
```

## Checking permissions
Before adding, editing or deleting files, you should check if you have the permissions to do so.

To avoid having a complex permission checking logic due to Scoped Storage changes, we have included
helper methods in [MediaStoreRepository][mediastore_repository]:

```kotlin
/**
* If you only need to read your own entries in MediaStore
*/
if(mediaStore.canReadOwnEntries()) {
println("Read my image")
}

/**
* If you only need to read and write your own entries in MediaStore
*/
if(mediaStore.canWriteOwnEntries()) {
println("Edit my image")
}

/**
* If you need to read your own entries as well as the ones created by other apps in MediaStore
*/
if(mediaStore.canReadSharedEntries()) {
println("Read an image created by another app")
}

/**
* If you need to read and write your own entries as well as the ones created by other apps in
* MediaStore
*/
if(mediaStore.canWriteSharedEntries()) {
println("Edit an image created by another app")
}
```

## Create media URI
If you're using intents like [ACTION_IMAGE_CAPTURE][intent_action_image_capture] and want to
personalize the MediaStore entry, use the `createMediaUri` method:

```kotlin
val photoUri = mediaStore.createMediaUri(
filename = "new-image.jpg",
type = FileType.IMAGE,
location = SharedPrimary
).getOrElse { reason ->
println("Creating Media URI failed: $reason")
}
```

## Add media file
You can add a media file by using the method `addMediaFromStream`. It will create a MediaStore URI,
save the `inputStream` content in it, and scans the file before returning its Uri:

```kotlin
/**
* If you already have an InputStream
*/
val photoUri = mediaStore.addMediaFromStream(
filename = "new-image.jpg",
type = FileType.IMAGE,
mimeType = "image/jpg",
inputStream = sample,
location = SharedPrimary
).getOrElse { reason ->
println("Creating Media URI failed: $reason")
}

/**
* Otherwise with a ByteArray
*/
val videoUri = mediaStore.addMediaFromStream(
filename = "new-video.mp4",
type = FileType.VIDEO,
mimeType = "video/mp4",
inputStream = ByteArrayInputStream(sampleByteArray),
location = SharedPrimary
).getOrElse { reason ->
println("Creating Media URI failed: $reason")
}
```

## Scan media URI
Modifying a file requires to scan it to make MediaStore aware of the file changes (size,
modification date, etc.). To request a scan for a media URI, use the `scanUri` method:

```kotlin
mediaStore.scanUri(updatedPhotoUri, "image/png").getOrElse { reason ->
println("Scanning failed ($mediaUri): $reason")
}
```

## Get media URI details
To get the details of a media URI (filename, size, file type, mime type), use the `getResourceByUri`
method:

```kotlin
val mediaDetails = mediaStore.getResourceByUri(mediaUri).getOrElse { reason ->
println("Fetching details failed ($mediaUri): $reason")
}
```


[mediastore_api]: https://developer.android.com/reference/kotlin/android/provider/MediaStore
[manage_3rd_party_media_files]: https://developer.android.com/training/data-storage/use-cases#modify-delete-media
[api_reference]: /api/mediastore
[mediastore_repository]: /api/mediastore/
[intent_action_image_capture]: https://developer.android.com/reference/kotlin/android/provider/MediaStore#action_image_capture
96 changes: 42 additions & 54 deletions mediastore/api/current.api
Original file line number Diff line number Diff line change
Expand Up @@ -15,76 +15,64 @@ package com.google.modernstorage.mediastore {
method public android.net.Uri? parseResult(int resultCode, android.content.Intent? intent);
}

public final class Internal extends com.google.modernstorage.mediastore.StorageLocation {
field public static final com.google.modernstorage.mediastore.Internal INSTANCE;
}

public final class MediaCollectionKt {
method public static android.net.Uri? getImageCollection(com.google.modernstorage.mediastore.StorageLocation location);
method public static android.net.Uri? getVideoCollection(com.google.modernstorage.mediastore.StorageLocation location);
}

public final class MediaQueryKt {
method public static suspend Object? getMediaResourceById(android.content.Context context, android.net.Uri mediaUri, kotlin.coroutines.Continuation<? super com.google.modernstorage.mediastore.MediaResource> p);
}

public final class MediaResource {
ctor public MediaResource(android.net.Uri uri, String filename, long size, com.google.modernstorage.mediastore.MediaType type, String mimeType);
method public android.net.Uri component1();
method public String component2();
method public long component3();
method public com.google.modernstorage.mediastore.MediaType component4();
method public String component5();
method public com.google.modernstorage.mediastore.MediaResource copy(android.net.Uri uri, String filename, long size, com.google.modernstorage.mediastore.MediaType type, String mimeType);
@kotlinx.parcelize.Parcelize public final class FileResource implements android.os.Parcelable {
ctor public FileResource(int id, android.net.Uri uri, String filename, long size, com.google.modernstorage.mediastore.FileType type, String mimeType, String? path);
method public int component1();
method public android.net.Uri component2();
method public String component3();
method public long component4();
method public com.google.modernstorage.mediastore.FileType component5();
method public String component6();
method public String? component7();
method @kotlinx.parcelize.Parcelize public com.google.modernstorage.mediastore.FileResource copy(int id, android.net.Uri uri, String filename, long size, com.google.modernstorage.mediastore.FileType type, String mimeType, String? path);
method public java.io.File? getFile();
method public String getFilename();
method public int getId();
method public String getMimeType();
method public String? getPath();
method public long getSize();
method public com.google.modernstorage.mediastore.MediaType getType();
method public com.google.modernstorage.mediastore.FileType getType();
method public android.net.Uri getUri();
property public final String filename;
property public final int id;
property public final String mimeType;
property public final String? path;
property public final long size;
property public final com.google.modernstorage.mediastore.MediaType type;
property public final com.google.modernstorage.mediastore.FileType type;
property public final android.net.Uri uri;
}

public final class MediaStoreClient {
ctor public MediaStoreClient(android.content.Context context);
method public suspend Object? addImageFromStream(String filename, java.io.InputStream inputStream, com.google.modernstorage.mediastore.StorageLocation location, kotlin.coroutines.Continuation<? super android.net.Uri> p);
method public suspend Object? addVideoFromStream(String filename, java.io.InputStream inputStream, com.google.modernstorage.mediastore.StorageLocation location, kotlin.coroutines.Continuation<? super android.net.Uri> p);
method public suspend Object? createImageUri(String filename, com.google.modernstorage.mediastore.StorageLocation location, kotlin.coroutines.Continuation<? super android.net.Uri> p);
method public suspend Object? createVideoUri(String filename, com.google.modernstorage.mediastore.StorageLocation location, kotlin.coroutines.Continuation<? super android.net.Uri> p);
method public suspend Object? getResourceByUri(android.net.Uri uri, kotlin.coroutines.Continuation<? super com.google.modernstorage.mediastore.MediaResource> p);
}

public final class MediaStoreClientKt {
method public static Exception getUnaccessibleCollectionException();
method public static Exception getUnopenableOutputStreamException();
method public static Exception getUriNotCreatedException();
}

public enum MediaType {
public enum FileType {
method public final int getValue();
property public final int value;
enum_constant public static final com.google.modernstorage.mediastore.MediaType AUDIO;
enum_constant public static final com.google.modernstorage.mediastore.MediaType DOCUMENT;
enum_constant public static final com.google.modernstorage.mediastore.MediaType IMAGE;
enum_constant public static final com.google.modernstorage.mediastore.MediaType NONE;
enum_constant public static final com.google.modernstorage.mediastore.MediaType PLAYLIST;
enum_constant public static final com.google.modernstorage.mediastore.MediaType SUBTITLE;
enum_constant public static final com.google.modernstorage.mediastore.MediaType VIDEO;
field public static final com.google.modernstorage.mediastore.MediaType.Companion Companion;
enum_constant public static final com.google.modernstorage.mediastore.FileType AUDIO;
enum_constant public static final com.google.modernstorage.mediastore.FileType DOCUMENT;
enum_constant public static final com.google.modernstorage.mediastore.FileType IMAGE;
enum_constant public static final com.google.modernstorage.mediastore.FileType NONE;
enum_constant public static final com.google.modernstorage.mediastore.FileType PLAYLIST;
enum_constant public static final com.google.modernstorage.mediastore.FileType SUBTITLE;
enum_constant public static final com.google.modernstorage.mediastore.FileType VIDEO;
field public static final com.google.modernstorage.mediastore.FileType.Companion Companion;
}

public static final class MediaType.Companion {
method public com.google.modernstorage.mediastore.MediaType getEnum(int value);
public static final class FileType.Companion {
method public com.google.modernstorage.mediastore.FileType getEnum(int value);
}

public final class Internal extends com.google.modernstorage.mediastore.StorageLocation {
field public static final com.google.modernstorage.mediastore.Internal INSTANCE;
}

public final class PermissionKt {
method public static boolean canReadOwnEntriesInMediaStore(android.content.Context context);
method public static boolean canReadSharedEntriesInMediaStore(android.content.Context context);
method public static boolean canWriteOwnEntriesInMediaStore(android.content.Context context);
method public static boolean canWriteSharedEntriesInMediaStore(android.content.Context context);
public final class MediaStoreRepository {
ctor public MediaStoreRepository(android.content.Context appContext);
method public suspend Object? addMediaFromStream-d1pmJ48(String filename, com.google.modernstorage.mediastore.FileType type, String mimeType, java.io.InputStream inputStream, com.google.modernstorage.mediastore.StorageLocation location, optional kotlin.coroutines.CoroutineContext context, optional kotlin.coroutines.Continuation<? super kotlin.Result<? extends android.net.Uri>> p);
method public boolean canReadOwnEntries();
method public boolean canReadSharedEntries();
method public boolean canWriteOwnEntries();
method public boolean canWriteSharedEntries();
method public suspend Object? createMediaUri-d1pmJ48(String filename, com.google.modernstorage.mediastore.FileType type, com.google.modernstorage.mediastore.StorageLocation location, optional kotlin.coroutines.CoroutineContext context, optional kotlin.coroutines.Continuation<? super kotlin.Result<? extends android.net.Uri>> p);
method public suspend Object? getResourceByUri-d1pmJ48(android.net.Uri uri, optional kotlin.coroutines.CoroutineContext context, optional kotlin.coroutines.Continuation<? super kotlin.Result<com.google.modernstorage.mediastore.FileResource>> p);
method public suspend Object? scanUri-d1pmJ48(android.net.Uri uri, String mimeType, optional kotlin.coroutines.CoroutineContext context, optional kotlin.coroutines.Continuation<? super kotlin.Result<java.lang.String>> p);
}

public final class SharedPrimary extends com.google.modernstorage.mediastore.StorageLocation {
Expand Down
Loading