1 /*
2  * Copyright 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.camera.video;
18 
19 import android.content.ContentResolver;
20 import android.content.ContentValues;
21 import android.content.Context;
22 import android.net.Uri;
23 import android.provider.MediaStore;
24 
25 import androidx.core.util.Preconditions;
26 
27 import com.google.auto.value.AutoValue;
28 
29 import org.jspecify.annotations.NonNull;
30 import org.jspecify.annotations.Nullable;
31 
32 /**
33  * A class providing options for storing output to MediaStore.
34  *
35  * <p>Example:
36  *
37  * <pre>{@code
38  *
39  * ContentValues contentValues = new ContentValues();
40  * contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, "NEW_VIDEO");
41  * contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "video/mp4");
42  *
43  * MediaStoreOutputOptions options =
44  *         new MediaStoreOutputOptions.Builder(
45  *             contentResolver, MediaStore.Video.Media.EXTERNAL_CONTENT_URI)
46  *         .setContentValues(contentValues)
47  *         .build();
48  *
49  * }</pre>
50  *
51  * <p>The output {@link Uri} can be obtained via {@link OutputResults#getOutputUri()} from
52  * {@link VideoRecordEvent.Finalize#getOutputResults()}.
53  *
54  * <p>For more information about setting collections {@link Uri} and {@link ContentValues}, read
55  * the <a href="https://developer.android.com/training/data-storage/shared/media">
56  *     Access media files from shared storage</a> and
57  * <a href="https://developer.android.com/reference/android/provider/MediaStore">MediaStore</a>
58  * developer guide.
59  */
60 public final class MediaStoreOutputOptions extends OutputOptions {
61 
62     /**
63      * An empty {@link ContentValues}.
64      */
65     public static final @NonNull ContentValues EMPTY_CONTENT_VALUES = new ContentValues();
66 
67     private final MediaStoreOutputOptionsInternal mMediaStoreOutputOptionsInternal;
68 
MediaStoreOutputOptions( @onNull MediaStoreOutputOptionsInternal mediaStoreOutputOptionsInternal)69     MediaStoreOutputOptions(
70             @NonNull MediaStoreOutputOptionsInternal mediaStoreOutputOptionsInternal) {
71         super(mediaStoreOutputOptionsInternal);
72         mMediaStoreOutputOptionsInternal = mediaStoreOutputOptionsInternal;
73     }
74 
75     /**
76      * Gets the ContentResolver instance.
77      *
78      * @see Builder#Builder(ContentResolver, Uri)
79      */
getContentResolver()80     public @NonNull ContentResolver getContentResolver() {
81         return mMediaStoreOutputOptionsInternal.getContentResolver();
82     }
83 
84     /**
85      * Gets the URI of the collection to insert into.
86      *
87      * @see Builder#Builder(ContentResolver, Uri)
88      */
getCollectionUri()89     public @NonNull Uri getCollectionUri() {
90         return mMediaStoreOutputOptionsInternal.getCollectionUri();
91     }
92 
93     /**
94      * Gets the content values to be included in the created video row.
95      *
96      * @see Builder#setContentValues(ContentValues)
97      */
getContentValues()98     public @NonNull ContentValues getContentValues() {
99         return mMediaStoreOutputOptionsInternal.getContentValues();
100     }
101 
102     @Override
toString()103     public @NonNull String toString() {
104         // Don't use Class.getSimpleName(), class name will be changed by proguard obfuscation.
105         return mMediaStoreOutputOptionsInternal.toString().replaceFirst(
106                 "MediaStoreOutputOptionsInternal", "MediaStoreOutputOptions");
107     }
108 
109     @Override
equals(@ullable Object o)110     public boolean equals(@Nullable Object o) {
111         if (this == o) {
112             return true;
113         }
114         if (!(o instanceof MediaStoreOutputOptions)) {
115             return false;
116         }
117         return mMediaStoreOutputOptionsInternal.equals(
118                 ((MediaStoreOutputOptions) o).mMediaStoreOutputOptionsInternal);
119     }
120 
121     @Override
hashCode()122     public int hashCode() {
123         return mMediaStoreOutputOptionsInternal.hashCode();
124     }
125 
126     /** The builder of the {@link MediaStoreOutputOptions} object. */
127     public static final class Builder extends
128             OutputOptions.Builder<MediaStoreOutputOptions, Builder> {
129 
130         private final MediaStoreOutputOptionsInternal.Builder mInternalBuilder;
131 
132         /**
133          * Creates a builder of the {@link MediaStoreOutputOptions} with media store options.
134          *
135          * <p>The ContentResolver can be obtained by app {@link Context#getContentResolver()
136          * context} and is used to access to MediaStore.
137          *
138          * <p>{@link MediaStore} class provides APIs to obtain the collection URI. A collection
139          * URI corresponds to a storage volume on the device shared storage. A common collection
140          * URI used to access the primary external storage is
141          * {@link MediaStore.Video.Media#EXTERNAL_CONTENT_URI}.
142          * {@link MediaStore.Video.Media#getContentUri} can also be used to query different
143          * storage volumes. For more information, read
144          * <a href="https://developer.android.com/training/data-storage/shared/media">
145          *     Access media files from shared storage</a> developer guide.
146          *
147          * <p>When recording a video, a corresponding video row will be created in the input
148          * collection, and the content values set by {@link #setContentValues} will also be
149          * written to this row.
150          *
151          * @param contentResolver the ContentResolver instance.
152          * @param collectionUri the URI of the collection to insert into.
153          */
Builder(@onNull ContentResolver contentResolver, @NonNull Uri collectionUri)154         public Builder(@NonNull ContentResolver contentResolver, @NonNull Uri collectionUri) {
155             super(new AutoValue_MediaStoreOutputOptions_MediaStoreOutputOptionsInternal.Builder());
156             Preconditions.checkNotNull(contentResolver, "Content resolver can't be null.");
157             Preconditions.checkNotNull(collectionUri, "Collection Uri can't be null.");
158             mInternalBuilder = (MediaStoreOutputOptionsInternal.Builder) mRootInternalBuilder;
159             mInternalBuilder.setContentResolver(contentResolver)
160                     .setCollectionUri(collectionUri)
161                     .setContentValues(EMPTY_CONTENT_VALUES);
162         }
163 
164         /**
165          * Sets the content values to be included in the created video row.
166          *
167          * <p>The content values is a set of key/value paris used to store the metadata of a
168          * video item. The keys are defined in {@link MediaStore.MediaColumns} and
169          * {@link MediaStore.Video.VideoColumns}.
170          * When recording a video, a corresponding video row will be created in the input
171          * collection, and this content values will also be written to this row. If a key is not
172          * defined in the MediaStore, the corresponding value will be ignored.
173          *
174          * <p>If not set, defaults to {@link #EMPTY_CONTENT_VALUES}.
175          *
176          * @param contentValues the content values to be inserted.
177          */
setContentValues(@onNull ContentValues contentValues)178         public @NonNull Builder setContentValues(@NonNull ContentValues contentValues) {
179             Preconditions.checkNotNull(contentValues, "Content values can't be null.");
180             mInternalBuilder.setContentValues(contentValues);
181             return this;
182         }
183 
184         /** Builds the {@link MediaStoreOutputOptions} instance. */
185         @Override
build()186         public @NonNull MediaStoreOutputOptions build() {
187             return new MediaStoreOutputOptions(mInternalBuilder.build());
188         }
189     }
190 
191     @AutoValue
192     abstract static class MediaStoreOutputOptionsInternal extends OutputOptionsInternal {
getContentResolver()193         abstract @NonNull ContentResolver getContentResolver();
getCollectionUri()194         abstract @NonNull Uri getCollectionUri();
getContentValues()195         abstract @NonNull ContentValues getContentValues();
196 
197         @SuppressWarnings("NullableProblems") // Nullable problem in AutoValue generated class
198         @AutoValue.Builder
199         abstract static class Builder extends OutputOptionsInternal.Builder<Builder> {
setContentResolver(@onNull ContentResolver contentResolver)200             abstract @NonNull Builder setContentResolver(@NonNull ContentResolver contentResolver);
setCollectionUri(@onNull Uri collectionUri)201             abstract @NonNull Builder setCollectionUri(@NonNull Uri collectionUri);
setContentValues(@onNull ContentValues contentValues)202             abstract @NonNull Builder setContentValues(@NonNull ContentValues contentValues);
203             @Override
build()204             abstract @NonNull MediaStoreOutputOptionsInternal build();
205         }
206     }
207 }
208