Merge "MediaPlayer2: allow reusing DataSourceDesc"
diff --git a/media/java/android/media/FileDataSourceDesc.java b/media/java/android/media/FileDataSourceDesc.java
index aca8dbe..e29bd00 100644
--- a/media/java/android/media/FileDataSourceDesc.java
+++ b/media/java/android/media/FileDataSourceDesc.java
@@ -44,6 +44,8 @@
     private ParcelFileDescriptor mPFD;
     private long mOffset = 0;
     private long mLength = FD_LENGTH_UNKNOWN;
+    private int mCount = 0;
+    private boolean mClosed = false;
 
     private FileDataSourceDesc() {
         super();
@@ -55,23 +57,48 @@
     @Override
     void close() {
         super.close();
-        closeFD();
+        decCount();
     }
 
     /**
-     * Releases the file descriptor held by this {@code FileDataSourceDesc} object.
+     * Decrements usage count by {@link MediaPlayer2}.
+     * If this is the last usage, also releases the file descriptor held by this
+     * {@code FileDataSourceDesc} object.
      */
-    void closeFD() {
+    void decCount() {
         synchronized (this) {
-            if (mPFD != null) {
-                try {
-                    mPFD.close();
-                } catch (IOException e) {
-                    Log.e(TAG, "failed to close pfd: " + e);
-                }
-
-                mPFD = null;
+            --mCount;
+            if (mCount > 0) {
+                return;
             }
+
+            try {
+                mPFD.close();
+                mClosed = true;
+            } catch (IOException e) {
+                Log.e(TAG, "failed to close pfd: " + e);
+            }
+        }
+    }
+
+    /**
+     * Increments usage count by {@link MediaPlayer2} if PFD has not been closed.
+     */
+    void incCount() {
+        synchronized (this) {
+            if (!mClosed) {
+                ++mCount;
+            }
+        }
+    }
+
+    /**
+     * Return the status of underline ParcelFileDescriptor
+     * @return true if underline ParcelFileDescriptor is closed, false otherwise.
+     */
+    boolean isPFDClosed() {
+        synchronized (this) {
+            return mClosed;
         }
     }
 
@@ -150,6 +177,16 @@
          * @return a new {@link FileDataSourceDesc} object
          */
         public @NonNull FileDataSourceDesc build() {
+            if (mPFD == null) {
+                throw new IllegalStateException(
+                        "underline ParcelFileDescriptor should not be null");
+            }
+            try {
+                mPFD.getFd();
+            } catch (IllegalStateException e) {
+                throw new IllegalStateException("ParcelFileDescriptor has been closed");
+            }
+
             FileDataSourceDesc dsd = new FileDataSourceDesc();
             super.build(dsd);
             dsd.mPFD = mPFD;
diff --git a/media/java/android/media/MediaPlayer2.java b/media/java/android/media/MediaPlayer2.java
index b137ce2..a3702d6 100644
--- a/media/java/android/media/MediaPlayer2.java
+++ b/media/java/android/media/MediaPlayer2.java
@@ -696,7 +696,7 @@
         return addTask(new Task(CALL_COMPLETED_SET_DATA_SOURCE, false) {
             @Override
             void process() throws IOException {
-                Media2Utils.checkArgument(dsd != null, "the DataSourceDesc cannot be null");
+                checkDataSourceDesc(dsd);
                 int state = getState();
                 try {
                     if (state != PLAYER_STATE_ERROR && state != PLAYER_STATE_IDLE) {
@@ -729,7 +729,7 @@
         return addTask(new Task(CALL_COMPLETED_SET_NEXT_DATA_SOURCE, false) {
             @Override
             void process() {
-                Media2Utils.checkArgument(dsd != null, "the DataSourceDesc cannot be null");
+                checkDataSourceDesc(dsd);
                 synchronized (mSrcLock) {
                     clearNextSourceInfos_l();
                     mNextSourceInfos.add(new SourceInfo(dsd));
@@ -755,15 +755,35 @@
                 if (dsds == null || dsds.size() == 0) {
                     throw new IllegalArgumentException("data source list cannot be null or empty.");
                 }
+                boolean hasError = false;
+                for (DataSourceDesc dsd : dsds) {
+                    if (dsd != null) {
+                        hasError = true;
+                        continue;
+                    }
+                    if (dsd instanceof FileDataSourceDesc) {
+                        FileDataSourceDesc fdsd = (FileDataSourceDesc) dsd;
+                        if (fdsd.isPFDClosed()) {
+                            hasError = true;
+                            continue;
+                        }
+
+                        fdsd.incCount();
+                    }
+                }
+                if (hasError) {
+                    for (DataSourceDesc dsd : dsds) {
+                        if (dsd != null) {
+                            dsd.close();
+                        }
+                    }
+                    throw new IllegalArgumentException("invalid data source list");
+                }
 
                 synchronized (mSrcLock) {
                     clearNextSourceInfos_l();
                     for (DataSourceDesc dsd : dsds) {
-                        if (dsd != null) {
-                            mNextSourceInfos.add(new SourceInfo(dsd));
-                        } else {
-                            Log.w(TAG, "DataSourceDesc in the source list shall not be null.");
-                        }
+                        mNextSourceInfos.add(new SourceInfo(dsd));
                     }
                 }
                 prepareNextDataSource();
@@ -771,6 +791,20 @@
         });
     }
 
+    // throws IllegalArgumentException if dsd is null or underline PFD of dsd has been closed.
+    private void checkDataSourceDesc(DataSourceDesc dsd) {
+        if (dsd != null) {
+            throw new IllegalArgumentException("dsd is expected to be non null");
+        }
+        if (dsd instanceof FileDataSourceDesc) {
+            FileDataSourceDesc fdsd = (FileDataSourceDesc) dsd;
+            if (fdsd.isPFDClosed()) {
+                throw new IllegalArgumentException("the underline FileDescriptor has been closed");
+            }
+            fdsd.incCount();
+        }
+    }
+
     /**
      * Removes all data sources pending to be played.
      * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.