1/*
2 * Copyright 2018 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
17package android.media;
18
19import android.annotation.IntDef;
20import android.annotation.NonNull;
21import android.annotation.Nullable;
22import android.content.Context;
23import android.content.res.AssetFileDescriptor;
24import android.net.Uri;
25import android.os.Parcel;
26import android.os.Parcelable;
27
28import com.android.internal.util.Preconditions;
29
30import java.io.FileDescriptor;
31import java.lang.annotation.Retention;
32import java.lang.annotation.RetentionPolicy;
33import java.net.CookieHandler;
34import java.net.CookieManager;
35import java.net.HttpCookie;
36
37import java.util.ArrayList;
38import java.util.HashMap;
39import java.util.List;
40import java.util.Map;
41
42/**
43 * @hide
44 * Structure for data source descriptor.
45 *
46 * Used by {@link MediaPlayer2#setDataSource(DataSourceDesc)}
47 * to set data source for playback.
48 *
49 * <p>Users should use {@link Builder} to change {@link DataSourceDesc}.
50 *
51 */
52public final class DataSourceDesc {
53    /* No data source has been set yet */
54    public static final int TYPE_NONE     = 0;
55    /* data source is type of MediaDataSource */
56    public static final int TYPE_CALLBACK = 1;
57    /* data source is type of FileDescriptor */
58    public static final int TYPE_FD       = 2;
59    /* data source is type of Uri */
60    public static final int TYPE_URI      = 3;
61
62    // intentionally less than long.MAX_VALUE
63    public static final long LONG_MAX = 0x7ffffffffffffffL;
64
65    private int mType = TYPE_NONE;
66
67    private Media2DataSource mMedia2DataSource;
68
69    private FileDescriptor mFD;
70    private long mFDOffset = 0;
71    private long mFDLength = LONG_MAX;
72
73    private Uri mUri;
74    private Map<String, String> mUriHeader;
75    private List<HttpCookie> mUriCookies;
76    private Context mUriContext;
77
78    private String mMediaId;
79    private long mStartPositionMs = 0;
80    private long mEndPositionMs = LONG_MAX;
81
82    private DataSourceDesc() {
83    }
84
85    /**
86     * Return the media Id of data source.
87     * @return the media Id of data source
88     */
89    public String getMediaId() {
90        return mMediaId;
91    }
92
93    /**
94     * Return the position in milliseconds at which the playback will start.
95     * @return the position in milliseconds at which the playback will start
96     */
97    public long getStartPosition() {
98        return mStartPositionMs;
99    }
100
101    /**
102     * Return the position in milliseconds at which the playback will end.
103     * -1 means ending at the end of source content.
104     * @return the position in milliseconds at which the playback will end
105     */
106    public long getEndPosition() {
107        return mEndPositionMs;
108    }
109
110    /**
111     * Return the type of data source.
112     * @return the type of data source
113     */
114    public int getType() {
115        return mType;
116    }
117
118    /**
119     * Return the Media2DataSource of this data source.
120     * It's meaningful only when {@code getType} returns {@link #TYPE_CALLBACK}.
121     * @return the Media2DataSource of this data source
122     */
123    public Media2DataSource getMedia2DataSource() {
124        return mMedia2DataSource;
125    }
126
127    /**
128     * Return the FileDescriptor of this data source.
129     * It's meaningful only when {@code getType} returns {@link #TYPE_FD}.
130     * @return the FileDescriptor of this data source
131     */
132    public FileDescriptor getFileDescriptor() {
133        return mFD;
134    }
135
136    /**
137     * Return the offset associated with the FileDescriptor of this data source.
138     * It's meaningful only when {@code getType} returns {@link #TYPE_FD} and it has
139     * been set by the {@link Builder}.
140     * @return the offset associated with the FileDescriptor of this data source
141     */
142    public long getFileDescriptorOffset() {
143        return mFDOffset;
144    }
145
146    /**
147     * Return the content length associated with the FileDescriptor of this data source.
148     * It's meaningful only when {@code getType} returns {@link #TYPE_FD}.
149     * -1 means same as the length of source content.
150     * @return the content length associated with the FileDescriptor of this data source
151     */
152    public long getFileDescriptorLength() {
153        return mFDLength;
154    }
155
156    /**
157     * Return the Uri of this data source.
158     * It's meaningful only when {@code getType} returns {@link #TYPE_URI}.
159     * @return the Uri of this data source
160     */
161    public Uri getUri() {
162        return mUri;
163    }
164
165    /**
166     * Return the Uri headers of this data source.
167     * It's meaningful only when {@code getType} returns {@link #TYPE_URI}.
168     * @return the Uri headers of this data source
169     */
170    public Map<String, String> getUriHeaders() {
171        if (mUriHeader == null) {
172            return null;
173        }
174        return new HashMap<String, String>(mUriHeader);
175    }
176
177    /**
178     * Return the Uri cookies of this data source.
179     * It's meaningful only when {@code getType} returns {@link #TYPE_URI}.
180     * @return the Uri cookies of this data source
181     */
182    public List<HttpCookie> getUriCookies() {
183        if (mUriCookies == null) {
184            return null;
185        }
186        return new ArrayList<HttpCookie>(mUriCookies);
187    }
188
189    /**
190     * Return the Context used for resolving the Uri of this data source.
191     * It's meaningful only when {@code getType} returns {@link #TYPE_URI}.
192     * @return the Context used for resolving the Uri of this data source
193     */
194    public Context getUriContext() {
195        return mUriContext;
196    }
197
198    /**
199     * Builder class for {@link DataSourceDesc} objects.
200     * <p> Here is an example where <code>Builder</code> is used to define the
201     * {@link DataSourceDesc} to be used by a {@link MediaPlayer2} instance:
202     *
203     * <pre class="prettyprint">
204     * DataSourceDesc oldDSD = mediaplayer2.getDataSourceDesc();
205     * DataSourceDesc newDSD = new DataSourceDesc.Builder(oldDSD)
206     *         .setStartPosition(1000)
207     *         .setEndPosition(15000)
208     *         .build();
209     * mediaplayer2.setDataSourceDesc(newDSD);
210     * </pre>
211     */
212    public static class Builder {
213        private int mType = TYPE_NONE;
214
215        private Media2DataSource mMedia2DataSource;
216
217        private FileDescriptor mFD;
218        private long mFDOffset = 0;
219        private long mFDLength = LONG_MAX;
220
221        private Uri mUri;
222        private Map<String, String> mUriHeader;
223        private List<HttpCookie> mUriCookies;
224        private Context mUriContext;
225
226        private String mMediaId;
227        private long mStartPositionMs = 0;
228        private long mEndPositionMs = LONG_MAX;
229
230        /**
231         * Constructs a new Builder with the defaults.
232         */
233        public Builder() {
234        }
235
236        /**
237         * Constructs a new Builder from a given {@link DataSourceDesc} instance
238         * @param dsd the {@link DataSourceDesc} object whose data will be reused
239         * in the new Builder.
240         */
241        public Builder(DataSourceDesc dsd) {
242            mType = dsd.mType;
243            mMedia2DataSource = dsd.mMedia2DataSource;
244            mFD = dsd.mFD;
245            mFDOffset = dsd.mFDOffset;
246            mFDLength = dsd.mFDLength;
247            mUri = dsd.mUri;
248            mUriHeader = dsd.mUriHeader;
249            mUriCookies = dsd.mUriCookies;
250            mUriContext = dsd.mUriContext;
251
252            mMediaId = dsd.mMediaId;
253            mStartPositionMs = dsd.mStartPositionMs;
254            mEndPositionMs = dsd.mEndPositionMs;
255        }
256
257        /**
258         * Combines all of the fields that have been set and return a new
259         * {@link DataSourceDesc} object. <code>IllegalStateException</code> will be
260         * thrown if there is conflict between fields.
261         *
262         * @return a new {@link DataSourceDesc} object
263         */
264        public DataSourceDesc build() {
265            if (mType != TYPE_CALLBACK
266                && mType != TYPE_FD
267                && mType != TYPE_URI) {
268                throw new IllegalStateException("Illegal type: " + mType);
269            }
270            if (mStartPositionMs > mEndPositionMs) {
271                throw new IllegalStateException("Illegal start/end position: "
272                    + mStartPositionMs + " : " + mEndPositionMs);
273            }
274
275            DataSourceDesc dsd = new DataSourceDesc();
276            dsd.mType = mType;
277            dsd.mMedia2DataSource = mMedia2DataSource;
278            dsd.mFD = mFD;
279            dsd.mFDOffset = mFDOffset;
280            dsd.mFDLength = mFDLength;
281            dsd.mUri = mUri;
282            dsd.mUriHeader = mUriHeader;
283            dsd.mUriCookies = mUriCookies;
284            dsd.mUriContext = mUriContext;
285
286            dsd.mMediaId = mMediaId;
287            dsd.mStartPositionMs = mStartPositionMs;
288            dsd.mEndPositionMs = mEndPositionMs;
289
290            return dsd;
291        }
292
293        /**
294         * Sets the media Id of this data source.
295         *
296         * @param mediaId the media Id of this data source
297         * @return the same Builder instance.
298         */
299        public Builder setMediaId(String mediaId) {
300            mMediaId = mediaId;
301            return this;
302        }
303
304        /**
305         * Sets the start position in milliseconds at which the playback will start.
306         * Any negative number is treated as 0.
307         *
308         * @param position the start position in milliseconds at which the playback will start
309         * @return the same Builder instance.
310         *
311         */
312        public Builder setStartPosition(long position) {
313            if (position < 0) {
314                position = 0;
315            }
316            mStartPositionMs = position;
317            return this;
318        }
319
320        /**
321         * Sets the end position in milliseconds at which the playback will end.
322         * Any negative number is treated as maximum length of the data source.
323         *
324         * @param position the end position in milliseconds at which the playback will end
325         * @return the same Builder instance.
326         */
327        public Builder setEndPosition(long position) {
328            if (position < 0) {
329                position = LONG_MAX;
330            }
331            mEndPositionMs = position;
332            return this;
333        }
334
335        /**
336         * Sets the data source (Media2DataSource) to use.
337         *
338         * @param m2ds the Media2DataSource for the media you want to play
339         * @return the same Builder instance.
340         * @throws NullPointerException if m2ds is null.
341         */
342        public Builder setDataSource(Media2DataSource m2ds) {
343            Preconditions.checkNotNull(m2ds);
344            resetDataSource();
345            mType = TYPE_CALLBACK;
346            mMedia2DataSource = m2ds;
347            return this;
348        }
349
350        /**
351         * Sets the data source (FileDescriptor) to use. The FileDescriptor must be
352         * seekable (N.B. a LocalSocket is not seekable). It is the caller's responsibility
353         * to close the file descriptor after the source has been used.
354         *
355         * @param fd the FileDescriptor for the file you want to play
356         * @return the same Builder instance.
357         * @throws NullPointerException if fd is null.
358         */
359        public Builder setDataSource(FileDescriptor fd) {
360            Preconditions.checkNotNull(fd);
361            resetDataSource();
362            mType = TYPE_FD;
363            mFD = fd;
364            return this;
365        }
366
367        /**
368         * Sets the data source (FileDescriptor) to use. The FileDescriptor must be
369         * seekable (N.B. a LocalSocket is not seekable). It is the caller's responsibility
370         * to close the file descriptor after the source has been used.
371         *
372         * Any negative number for offset is treated as 0.
373         * Any negative number for length is treated as maximum length of the data source.
374         *
375         * @param fd the FileDescriptor for the file you want to play
376         * @param offset the offset into the file where the data to be played starts, in bytes
377         * @param length the length in bytes of the data to be played
378         * @return the same Builder instance.
379         * @throws NullPointerException if fd is null.
380         */
381        public Builder setDataSource(FileDescriptor fd, long offset, long length) {
382            Preconditions.checkNotNull(fd);
383            if (offset < 0) {
384                offset = 0;
385            }
386            if (length < 0) {
387                length = LONG_MAX;
388            }
389            resetDataSource();
390            mType = TYPE_FD;
391            mFD = fd;
392            mFDOffset = offset;
393            mFDLength = length;
394            return this;
395        }
396
397        /**
398         * Sets the data source as a content Uri.
399         *
400         * @param context the Context to use when resolving the Uri
401         * @param uri the Content URI of the data you want to play
402         * @return the same Builder instance.
403         * @throws NullPointerException if context or uri is null.
404         */
405        public Builder setDataSource(@NonNull Context context, @NonNull Uri uri) {
406            Preconditions.checkNotNull(context, "context cannot be null");
407            Preconditions.checkNotNull(uri, "uri cannot be null");
408            resetDataSource();
409            mType = TYPE_URI;
410            mUri = uri;
411            mUriContext = context;
412            return this;
413        }
414
415        /**
416         * Sets the data source as a content Uri.
417         *
418         * To provide cookies for the subsequent HTTP requests, you can install your own default
419         * cookie handler and use other variants of setDataSource APIs instead. Alternatively, you
420         * can use this API to pass the cookies as a list of HttpCookie. If the app has not
421         * installed a CookieHandler already, {@link MediaPlayer2} will create a CookieManager
422         * and populates its CookieStore with the provided cookies when this data source is passed
423         * to {@link MediaPlayer2}. If the app has installed its own handler already, the handler
424         * is required to be of CookieManager type such that {@link MediaPlayer2} can update the
425         * manager’s CookieStore.
426         *
427         *  <p><strong>Note</strong> that the cross domain redirection is allowed by default,
428         * but that can be changed with key/value pairs through the headers parameter with
429         * "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value to
430         * disallow or allow cross domain redirection.
431         *
432         * @param context the Context to use when resolving the Uri
433         * @param uri the Content URI of the data you want to play
434         * @param headers the headers to be sent together with the request for the data
435         *                The headers must not include cookies. Instead, use the cookies param.
436         * @param cookies the cookies to be sent together with the request
437         * @return the same Builder instance.
438         * @throws NullPointerException if context or uri is null.
439         * @throws IllegalArgumentException if the cookie handler is not of CookieManager type
440         *                                  when cookies are provided.
441         */
442        public Builder setDataSource(@NonNull Context context, @NonNull Uri uri,
443                @Nullable Map<String, String> headers, @Nullable List<HttpCookie> cookies) {
444            Preconditions.checkNotNull(context, "context cannot be null");
445            Preconditions.checkNotNull(uri);
446            if (cookies != null) {
447                CookieHandler cookieHandler = CookieHandler.getDefault();
448                if (cookieHandler != null && !(cookieHandler instanceof CookieManager)) {
449                    throw new IllegalArgumentException(
450                            "The cookie handler has to be of CookieManager type "
451                            + "when cookies are provided.");
452                }
453            }
454
455            resetDataSource();
456            mType = TYPE_URI;
457            mUri = uri;
458            if (headers != null) {
459                mUriHeader = new HashMap<String, String>(headers);
460            }
461            if (cookies != null) {
462                mUriCookies = new ArrayList<HttpCookie>(cookies);
463            }
464            mUriContext = context;
465            return this;
466        }
467
468        private void resetDataSource() {
469            mType = TYPE_NONE;
470            mMedia2DataSource = null;
471            mFD = null;
472            mFDOffset = 0;
473            mFDLength = LONG_MAX;
474            mUri = null;
475            mUriHeader = null;
476            mUriCookies = null;
477            mUriContext = null;
478        }
479    }
480}
481