Ringtone: add support for volume control and looping

Bug 22182606

Change-Id: Ied910b9fe02a5da9c4822a107ee884677c8b4991
diff --git a/media/java/android/media/IRingtonePlayer.aidl b/media/java/android/media/IRingtonePlayer.aidl
index 7c011e6..aa5fde3 100644
--- a/media/java/android/media/IRingtonePlayer.aidl
+++ b/media/java/android/media/IRingtonePlayer.aidl
@@ -25,9 +25,10 @@
  */
 interface IRingtonePlayer {
     /** Used for Ringtone.java playback */
-    void play(IBinder token, in Uri uri, in AudioAttributes aa);
+    void play(IBinder token, in Uri uri, in AudioAttributes aa, float volume, boolean looping);
     void stop(IBinder token);
     boolean isPlaying(IBinder token);
+    void setPlaybackProperties(IBinder token, float volume, boolean looping);
 
     /** Used for Notification sound playback. */
     void playAsync(in Uri uri, in UserHandle user, boolean looping, in AudioAttributes aa);
diff --git a/media/java/android/media/Ringtone.java b/media/java/android/media/Ringtone.java
index 166ff38..faeebe6 100644
--- a/media/java/android/media/Ringtone.java
+++ b/media/java/android/media/Ringtone.java
@@ -76,6 +76,10 @@
             .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
             .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
             .build();
+    // playback properties, use synchronized with mPlaybackSettingsLock
+    private boolean mIsLooping = false;
+    private float mVolume = 1.0f;
+    private final Object mPlaybackSettingsLock = new Object();
 
     /** {@hide} */
     public Ringtone(Context context, boolean allowRemote) {
@@ -136,6 +140,52 @@
     }
 
     /**
+     * @hide
+     * Sets the player to be looping or non-looping.
+     * @param looping whether to loop or not
+     */
+    public void setLooping(boolean looping) {
+        synchronized (mPlaybackSettingsLock) {
+            mIsLooping = looping;
+            applyPlaybackProperties_sync();
+        }
+    }
+
+    /**
+     * @hide
+     * Sets the volume on this player.
+     * @param volume a raw scalar in range 0.0 to 1.0, where 0.0 mutes this player, and 1.0
+     *   corresponds to no attenuation being applied.
+     */
+    public void setVolume(float volume) {
+        synchronized (mPlaybackSettingsLock) {
+            if (volume < 0.0f) { volume = 0.0f; }
+            if (volume > 1.0f) { volume = 1.0f; }
+            mVolume = volume;
+            applyPlaybackProperties_sync();
+        }
+    }
+
+    /**
+     * Must be called synchronized on mPlaybackSettingsLock
+     */
+    private void applyPlaybackProperties_sync() {
+        if (mLocalPlayer != null) {
+            mLocalPlayer.setVolume(mVolume);
+            mLocalPlayer.setLooping(mIsLooping);
+        } else if (mAllowRemote && (mRemotePlayer != null)) {
+            try {
+                mRemotePlayer.setPlaybackProperties(mRemoteToken, mVolume, mIsLooping);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Problem setting playback properties: ", e);
+            }
+        } else {
+            Log.w(TAG,
+                    "Neither local nor remote player available when applying playback properties");
+        }
+    }
+
+    /**
      * Returns a human-presentable title for ringtone. Looks in media
      * content provider. If not in either, uses the filename
      * 
@@ -221,6 +271,9 @@
         try {
             mLocalPlayer.setDataSource(mContext, mUri);
             mLocalPlayer.setAudioAttributes(mAudioAttributes);
+            synchronized (mPlaybackSettingsLock) {
+                applyPlaybackProperties_sync();
+            }
             mLocalPlayer.prepare();
 
         } catch (SecurityException | IOException e) {
@@ -257,8 +310,14 @@
             }
         } else if (mAllowRemote && (mRemotePlayer != null)) {
             final Uri canonicalUri = mUri.getCanonicalUri();
+            final boolean looping;
+            final float volume;
+            synchronized (mPlaybackSettingsLock) {
+                looping = mIsLooping;
+                volume = mVolume;
+            }
             try {
-                mRemotePlayer.play(mRemoteToken, canonicalUri, mAudioAttributes);
+                mRemotePlayer.play(mRemoteToken, canonicalUri, mAudioAttributes, volume, looping);
             } catch (RemoteException e) {
                 if (!playFallbackRingtone()) {
                     Log.w(TAG, "Problem playing ringtone: " + e);
@@ -349,6 +408,9 @@
                                     afd.getDeclaredLength());
                         }
                         mLocalPlayer.setAudioAttributes(mAudioAttributes);
+                        synchronized (mPlaybackSettingsLock) {
+                            applyPlaybackProperties_sync();
+                        }
                         mLocalPlayer.prepare();
                         startLocalPlayer();
                         afd.close();
diff --git a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
index 7eed7f2..e9a256c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
@@ -92,7 +92,8 @@
 
     private IRingtonePlayer mCallback = new IRingtonePlayer.Stub() {
         @Override
-        public void play(IBinder token, Uri uri, AudioAttributes aa) throws RemoteException {
+        public void play(IBinder token, Uri uri, AudioAttributes aa, float volume, boolean looping)
+                throws RemoteException {
             if (LOGD) {
                 Log.d(TAG, "play(token=" + token + ", uri=" + uri + ", uid="
                         + Binder.getCallingUid() + ")");
@@ -107,6 +108,8 @@
                     mClients.put(token, client);
                 }
             }
+            client.mRingtone.setLooping(looping);
+            client.mRingtone.setVolume(volume);
             client.mRingtone.play();
         }
 
@@ -138,6 +141,19 @@
         }
 
         @Override
+        public void setPlaybackProperties(IBinder token, float volume, boolean looping) {
+            Client client;
+            synchronized (mClients) {
+                client = mClients.get(token);
+            }
+            if (client != null) {
+                client.mRingtone.setVolume(volume);
+                client.mRingtone.setLooping(looping);
+            }
+            // else no client for token when setting playback properties but will be set at play()
+        }
+
+        @Override
         public void playAsync(Uri uri, UserHandle user, boolean looping, AudioAttributes aa) {
             if (LOGD) Log.d(TAG, "playAsync(uri=" + uri + ", user=" + user + ")");
             if (Binder.getCallingUid() != Process.SYSTEM_UID) {