Merge "Don't call onStartRtt unless RTT property changes"
diff --git a/res/raw/endcall.ogg b/res/raw/endcall.ogg
new file mode 100644
index 0000000..1af440b
--- /dev/null
+++ b/res/raw/endcall.ogg
Binary files differ
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 20ac66d..528c7af 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -26,6 +26,7 @@
 import android.media.AudioManager;
 import android.media.AudioSystem;
 import android.media.ToneGenerator;
+import android.media.MediaPlayer;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
@@ -408,8 +409,12 @@
                         wiredHeadsetManager,
                         mDockManager);
 
+        AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+        InCallTonePlayer.MediaPlayerFactory mediaPlayerFactory =
+                (resourceId, attributes) -> MediaPlayer.create(mContext, resourceId, attributes,
+                        audioManager.generateAudioSessionId());
         InCallTonePlayer.Factory playerFactory = new InCallTonePlayer.Factory(
-                callAudioRoutePeripheralAdapter, lock, toneGeneratorFactory);
+                callAudioRoutePeripheralAdapter, lock, toneGeneratorFactory, mediaPlayerFactory);
 
         SystemSettingsUtil systemSettingsUtil = new SystemSettingsUtil();
         RingtoneFactory ringtoneFactory = new RingtoneFactory(this, context);
@@ -419,8 +424,7 @@
                 emergencyCallHelper);
         mRinger = new Ringer(playerFactory, context, systemSettingsUtil, asyncRingtonePlayer,
                 ringtoneFactory, systemVibrator, mInCallController);
-        mCallRecordingTonePlayer = new CallRecordingTonePlayer(mContext,
-                (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE), mLock);
+        mCallRecordingTonePlayer = new CallRecordingTonePlayer(mContext, audioManager, mLock);
         mCallAudioManager = new CallAudioManager(callAudioRouteStateMachine,
                 this, callAudioModeStateMachineFactory.create((AudioManager)
                         mContext.getSystemService(Context.AUDIO_SERVICE)),
diff --git a/src/com/android/server/telecom/InCallTonePlayer.java b/src/com/android/server/telecom/InCallTonePlayer.java
index a258aee..8314dd9 100644
--- a/src/com/android/server/telecom/InCallTonePlayer.java
+++ b/src/com/android/server/telecom/InCallTonePlayer.java
@@ -16,7 +16,9 @@
 
 package com.android.server.telecom;
 
+import android.media.AudioAttributes;
 import android.media.AudioManager;
+import android.media.MediaPlayer;
 import android.media.ToneGenerator;
 import android.os.Handler;
 import android.os.Looper;
@@ -26,10 +28,15 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
 /**
- * Play a call-related tone (ringback, busy signal, etc.) through ToneGenerator. To use, create an
- * instance using InCallTonePlayer.Factory (passing in the TONE_* constant for the tone you want)
- * and start() it. Implemented on top of {@link Thread} so that the tone plays in its own thread.
+ * Play a call-related tone (ringback, busy signal, etc.) either through ToneGenerator, or using a
+ * media resource file.
+ * To use, create an instance using InCallTonePlayer.Factory (passing in the TONE_* constant for
+ * the tone you want) and start() it. Implemented on top of {@link Thread} so that the tone plays in
+ * its own thread.
  */
 public class InCallTonePlayer extends Thread {
 
@@ -41,12 +48,15 @@
         private final CallAudioRoutePeripheralAdapter mCallAudioRoutePeripheralAdapter;
         private final TelecomSystem.SyncRoot mLock;
         private final ToneGeneratorFactory mToneGeneratorFactory;
+        private final MediaPlayerFactory mMediaPlayerFactory;
 
         Factory(CallAudioRoutePeripheralAdapter callAudioRoutePeripheralAdapter,
-                TelecomSystem.SyncRoot lock, ToneGeneratorFactory toneGeneratorFactory) {
+                TelecomSystem.SyncRoot lock, ToneGeneratorFactory toneGeneratorFactory,
+                MediaPlayerFactory mediaPlayerFactory) {
             mCallAudioRoutePeripheralAdapter = callAudioRoutePeripheralAdapter;
             mLock = lock;
             mToneGeneratorFactory = toneGeneratorFactory;
+            mMediaPlayerFactory = mediaPlayerFactory;
         }
 
         public void setCallAudioManager(CallAudioManager callAudioManager) {
@@ -55,7 +65,8 @@
 
         public InCallTonePlayer createPlayer(int tone) {
             return new InCallTonePlayer(tone, mCallAudioManager,
-                    mCallAudioRoutePeripheralAdapter, mLock, mToneGeneratorFactory);
+                    mCallAudioRoutePeripheralAdapter, mLock, mToneGeneratorFactory,
+                    mMediaPlayerFactory);
         }
     }
 
@@ -63,6 +74,10 @@
         ToneGenerator get (int streamType, int volume);
     }
 
+    public interface MediaPlayerFactory {
+        MediaPlayer get (int resourceId, AudioAttributes attributes);
+    }
+
     // The possible tones that we can play.
     public static final int TONE_INVALID = 0;
     public static final int TONE_BUSY = 1;
@@ -80,9 +95,12 @@
     public static final int TONE_VOICE_PRIVACY = 13;
     public static final int TONE_VIDEO_UPGRADE = 14;
 
+    private static final int TONE_RESOURCE_ID_UNDEFINED = -1;
+
     private static final int RELATIVE_VOLUME_EMERGENCY = 100;
     private static final int RELATIVE_VOLUME_HIPRI = 80;
     private static final int RELATIVE_VOLUME_LOPRI = 50;
+    private static final int RELATIVE_VOLUME_UNDEFINED = -1;
 
     // Buffer time (in msec) to add on to the tone timeout value. Needed mainly when the timeout
     // value for a tone is exact duration of the tone itself.
@@ -111,6 +129,9 @@
     /** Current state of the tone player. */
     private int mState;
 
+    /** For tones which are not generated using ToneGenerator. */
+    private MediaPlayer mToneMediaPlayer = null;
+
     /** Telecom lock object. */
     private final TelecomSystem.SyncRoot mLock;
 
@@ -118,6 +139,7 @@
     private final Object mSessionLock = new Object();
 
     private final ToneGeneratorFactory mToneGenerator;
+    private final MediaPlayerFactory mMediaPlayerFactory;
 
     /**
      * Initializes the tone player. Private; use the {@link Factory} to create tone players.
@@ -129,19 +151,20 @@
             CallAudioManager callAudioManager,
             CallAudioRoutePeripheralAdapter callAudioRoutePeripheralAdapter,
             TelecomSystem.SyncRoot lock,
-            ToneGeneratorFactory toneGeneratorFactory) {
+            ToneGeneratorFactory toneGeneratorFactory,
+            MediaPlayerFactory mediaPlayerFactor) {
         mState = STATE_OFF;
         mToneId = toneId;
         mCallAudioManager = callAudioManager;
         mCallAudioRoutePeripheralAdapter = callAudioRoutePeripheralAdapter;
         mLock = lock;
         mToneGenerator = toneGeneratorFactory;
+        mMediaPlayerFactory = mediaPlayerFactor;
     }
 
     /** {@inheritDoc} */
     @Override
     public void run() {
-        ToneGenerator toneGenerator = null;
         try {
             synchronized (mSessionLock) {
                 if (mSession != null) {
@@ -154,6 +177,8 @@
             final int toneType;  // Passed to ToneGenerator.startTone.
             final int toneVolume;  // Passed to the ToneGenerator constructor.
             final int toneLengthMillis;
+            final int mediaResourceId; // The resourceId of the tone to play.  Used for media-based
+                                      // tones.
 
             switch (mToneId) {
                 case TONE_BUSY:
@@ -161,11 +186,16 @@
                     toneType = ToneGenerator.TONE_SUP_BUSY;
                     toneVolume = RELATIVE_VOLUME_HIPRI;
                     toneLengthMillis = 4000;
+                    mediaResourceId = TONE_RESOURCE_ID_UNDEFINED;
                     break;
                 case TONE_CALL_ENDED:
-                    toneType = ToneGenerator.TONE_PROP_PROMPT;
-                    toneVolume = RELATIVE_VOLUME_HIPRI;
-                    toneLengthMillis = 200;
+                    // Don't use tone generator
+                    toneType = ToneGenerator.TONE_UNKNOWN;
+                    toneVolume = RELATIVE_VOLUME_UNDEFINED;
+                    toneLengthMillis = 0;
+
+                    // Use a tone resource file for a more rich, full-bodied tone experience.
+                    mediaResourceId = R.raw.endcall;
                     break;
                 case TONE_OTA_CALL_ENDED:
                     // TODO: fill in
@@ -174,46 +204,55 @@
                     toneType = ToneGenerator.TONE_SUP_CALL_WAITING;
                     toneVolume = RELATIVE_VOLUME_HIPRI;
                     toneLengthMillis = Integer.MAX_VALUE - TIMEOUT_BUFFER_MILLIS;
+                    mediaResourceId = TONE_RESOURCE_ID_UNDEFINED;
                     break;
                 case TONE_CDMA_DROP:
                     toneType = ToneGenerator.TONE_CDMA_CALLDROP_LITE;
                     toneVolume = RELATIVE_VOLUME_LOPRI;
                     toneLengthMillis = 375;
+                    mediaResourceId = TONE_RESOURCE_ID_UNDEFINED;
                     break;
                 case TONE_CONGESTION:
                     toneType = ToneGenerator.TONE_SUP_CONGESTION;
                     toneVolume = RELATIVE_VOLUME_HIPRI;
                     toneLengthMillis = 4000;
+                    mediaResourceId = TONE_RESOURCE_ID_UNDEFINED;
                     break;
                 case TONE_INTERCEPT:
                     toneType = ToneGenerator.TONE_CDMA_ABBR_INTERCEPT;
                     toneVolume = RELATIVE_VOLUME_LOPRI;
                     toneLengthMillis = 500;
+                    mediaResourceId = TONE_RESOURCE_ID_UNDEFINED;
                     break;
                 case TONE_OUT_OF_SERVICE:
                     toneType = ToneGenerator.TONE_CDMA_CALLDROP_LITE;
                     toneVolume = RELATIVE_VOLUME_LOPRI;
                     toneLengthMillis = 375;
+                    mediaResourceId = TONE_RESOURCE_ID_UNDEFINED;
                     break;
                 case TONE_REDIAL:
                     toneType = ToneGenerator.TONE_CDMA_ALERT_AUTOREDIAL_LITE;
                     toneVolume = RELATIVE_VOLUME_LOPRI;
                     toneLengthMillis = 5000;
+                    mediaResourceId = TONE_RESOURCE_ID_UNDEFINED;
                     break;
                 case TONE_REORDER:
                     toneType = ToneGenerator.TONE_CDMA_REORDER;
                     toneVolume = RELATIVE_VOLUME_HIPRI;
                     toneLengthMillis = 4000;
+                    mediaResourceId = TONE_RESOURCE_ID_UNDEFINED;
                     break;
                 case TONE_RING_BACK:
                     toneType = ToneGenerator.TONE_SUP_RINGTONE;
                     toneVolume = RELATIVE_VOLUME_HIPRI;
                     toneLengthMillis = Integer.MAX_VALUE - TIMEOUT_BUFFER_MILLIS;
+                    mediaResourceId = TONE_RESOURCE_ID_UNDEFINED;
                     break;
                 case TONE_UNOBTAINABLE_NUMBER:
                     toneType = ToneGenerator.TONE_SUP_ERROR;
                     toneVolume = RELATIVE_VOLUME_HIPRI;
                     toneLengthMillis = 4000;
+                    mediaResourceId = TONE_RESOURCE_ID_UNDEFINED;
                     break;
                 case TONE_VOICE_PRIVACY:
                     // TODO: fill in.
@@ -223,6 +262,7 @@
                     toneType = ToneGenerator.TONE_SUP_CALL_WAITING;
                     toneVolume = RELATIVE_VOLUME_HIPRI;
                     toneLengthMillis = 4000;
+                    mediaResourceId = TONE_RESOURCE_ID_UNDEFINED;
                     break;
                 default:
                     throw new IllegalStateException("Bad toneId: " + mToneId);
@@ -233,16 +273,38 @@
                 stream = AudioManager.STREAM_BLUETOOTH_SCO;
             }
 
+            if (toneType != ToneGenerator.TONE_UNKNOWN) {
+                playToneGeneratorTone(stream, toneVolume, toneType, toneLengthMillis);
+            } else if (mediaResourceId != TONE_RESOURCE_ID_UNDEFINED) {
+                playMediaTone(stream, mediaResourceId);
+            }
+        } finally {
+            cleanUpTonePlayer();
+            Log.endSession();
+        }
+    }
+
+    /**
+     * Play a tone generated by the {@link ToneGenerator}.
+     * @param stream The stream on which the tone will be played.
+     * @param toneVolume The volume of the tone.
+     * @param toneType The type of tone to play.
+     * @param toneLengthMillis How long to play the tone.
+     */
+    private void playToneGeneratorTone(int stream, int toneVolume, int toneType,
+            int toneLengthMillis) {
+        ToneGenerator toneGenerator = null;
+        try {
             // If the ToneGenerator creation fails, just continue without it. It is a local audio
             // signal, and is not as important.
             try {
-                Log.v(this, "Creating generator");
                 toneGenerator = mToneGenerator.get(stream, toneVolume);
             } catch (RuntimeException e) {
                 Log.w(this, "Failed to create ToneGenerator.", e);
                 return;
             }
 
+            Log.i(this, "playToneGeneratorTone: toneType=%d", toneType);
             // TODO: Certain CDMA tones need to check the ringer-volume state before
             // playing. See CallNotifier.InCallTonePlayer.
 
@@ -267,11 +329,53 @@
             if (toneGenerator != null) {
                 toneGenerator.release();
             }
-            cleanUpTonePlayer();
-            Log.endSession();
         }
     }
-    
+
+    /**
+     * Plays an audio-file based media tone.
+     * @param stream The audio stream on which to play the tone.
+     * @param toneResourceId The resource ID of the tone to play.
+     */
+    private void playMediaTone(int stream, int toneResourceId) {
+        synchronized (this) {
+            if (mState != STATE_STOPPED) {
+                mState = STATE_ON;
+            }
+            Log.i(this, "playMediaTone: toneResourceId=%d", toneResourceId);
+            AudioAttributes attributes = new AudioAttributes.Builder()
+                    .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
+                    .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
+                    .setLegacyStreamType(stream)
+                    .build();
+            mToneMediaPlayer = mMediaPlayerFactory.get(toneResourceId, attributes);
+            mToneMediaPlayer.setLooping(false);
+            int durationMillis = mToneMediaPlayer.getDuration();
+            final CountDownLatch toneLatch = new CountDownLatch(1);
+            mToneMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
+                @Override
+                public void onCompletion(MediaPlayer mp) {
+                    Log.i(this, "playMediaTone: toneResourceId=%d completed.", toneResourceId);
+                    synchronized (this) {
+                        mState = STATE_OFF;
+                    }
+                    mToneMediaPlayer.release();
+                    mToneMediaPlayer = null;
+                    toneLatch.countDown();
+                }
+            });
+            mToneMediaPlayer.start();
+            try {
+                // Wait for the tone to stop playing; timeout at 2x the length of the file just to
+                // be on the safe side.
+                toneLatch.await(durationMillis * 2, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException ie) {
+                Log.e(this, ie, "playMediaTone: tone playback interrupted.");
+            }
+        }
+
+    }
+
     @VisibleForTesting
     public void startTone() {
         sTonesPlaying++;
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java b/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
index be115b3..5992c49 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
@@ -30,7 +30,9 @@
 import com.android.server.telecom.BluetoothHeadsetProxy;
 import com.android.server.telecom.TelecomSystem;
 
+import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.LinkedList;
 import java.util.List;
@@ -103,11 +105,16 @@
     }
 
     public int getNumConnectedDevices() {
-        return mConnectedDevicesByAddress.size();
+        synchronized (mLock) {
+            return mConnectedDevicesByAddress.size();
+        }
     }
 
     public Collection<BluetoothDevice> getConnectedDevices() {
-        return mConnectedDevicesByAddress.values();
+        synchronized (mLock) {
+            return Collections.unmodifiableCollection(
+                    new ArrayList<>(mConnectedDevicesByAddress.values()));
+        }
     }
 
     public String getMostRecentlyConnectedDevice(String excludeAddress) {
@@ -131,7 +138,9 @@
     }
 
     public BluetoothDevice getDeviceFromAddress(String address) {
-        return mConnectedDevicesByAddress.get(address);
+        synchronized (mLock) {
+            return mConnectedDevicesByAddress.get(address);
+        }
     }
 
     void onDeviceConnected(BluetoothDevice device) {
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java b/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
index f2ed9e0..c8e936f 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
@@ -33,9 +33,7 @@
 import com.android.server.telecom.TelecomSystem;
 import com.android.server.telecom.Timeouts;
 
-import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedHashSet;
@@ -572,8 +570,7 @@
     }
 
     public Collection<BluetoothDevice> getConnectedDevices() {
-        return Collections.unmodifiableCollection(
-                new ArrayList<>(mDeviceManager.getConnectedDevices()));
+        return mDeviceManager.getConnectedDevices();
     }
 
     private String connectHfpAudio(String address) {
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java
index a7b1350..93a2c7f 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java
@@ -17,6 +17,10 @@
 package com.android.server.telecom.testapps;
 
 import android.app.Activity;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.media.AudioAttributes;
+import android.media.RingtoneManager;
 import android.net.Uri;
 import android.os.Bundle;
 import android.telecom.ConnectionRequest;
@@ -100,6 +104,7 @@
                         | WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
 
         getWindow().addFlags(flags);
+        configureNotificationChannel();
         setContentView(R.layout.self_managed_sample_main);
         mCheckIfPermittedBeforeCalling = (CheckBox) findViewById(
                 R.id.checkIfPermittedBeforeCalling);
@@ -196,4 +201,20 @@
         }
         tm.addNewIncomingCall(getSelectedPhoneAccountHandle(), extras);
     }
+
+    private void configureNotificationChannel() {
+        NotificationChannel channel = new NotificationChannel(
+                SelfManagedConnection.INCOMING_CALL_CHANNEL_ID, "Incoming Calls",
+                NotificationManager.IMPORTANCE_MAX);
+        channel.setShowBadge(false);
+        Uri ringtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE);
+        channel.setSound(ringtoneUri, new AudioAttributes.Builder()
+                .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
+                .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+                .build());
+        channel.enableLights(true);
+
+        NotificationManager mgr = getSystemService(NotificationManager.class);
+        mgr.createNotificationChannel(channel);
+    }
 }
\ No newline at end of file
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedConnection.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedConnection.java
index 8d0af04..ebd7423 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedConnection.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedConnection.java
@@ -22,7 +22,10 @@
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.drawable.Icon;
+import android.media.AudioAttributes;
 import android.media.MediaPlayer;
+import android.media.RingtoneManager;
+import android.net.Uri;
 import android.os.Bundle;
 import android.telecom.Call;
 import android.telecom.CallAudioState;
@@ -43,7 +46,7 @@
         public void onConnectionStateChanged(SelfManagedConnection connection) {}
         public void onConnectionRemoved(SelfManagedConnection connection) {}
     }
-
+    public static final String INCOMING_CALL_CHANNEL_ID = "INCOMING_CALL_CHANNEL_ID";
     public static final String EXTRA_PHONE_ACCOUNT_HANDLE =
             "com.android.server.telecom.testapps.extra.PHONE_ACCOUNT_HANDLE";
     public static final String CALL_NOTIFICATION = "com.android.server.telecom.testapps.CALL";
@@ -58,6 +61,7 @@
     private boolean mIsIncomingCallUiShowing;
     private Listener mListener;
     private boolean mIsHandover;
+    private Notification.Builder mNotificationBuilder;
 
     SelfManagedConnection(SelfManagedCallList callList, Context context, boolean isIncoming) {
         mCallList = callList;
@@ -93,7 +97,8 @@
         PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 1, intent, 0);
 
         // Build the notification as an ongoing high priority item.
-        final Notification.Builder builder = new Notification.Builder(mContext);
+        final Notification.Builder builder = new Notification.Builder(mContext,
+                INCOMING_CALL_CHANNEL_ID);
         builder.setOngoing(true);
         builder.setPriority(Notification.PRIORITY_HIGH);
 
@@ -131,9 +136,12 @@
                                 PendingIntent.FLAG_UPDATE_CURRENT))
                         .build());
 
+        Notification notification = builder.build();
+        notification.flags |= Notification.FLAG_INSISTENT;
         NotificationManager notificationManager = mContext.getSystemService(
                 NotificationManager.class);
-        notificationManager.notify(CALL_NOTIFICATION, mCallId, builder.build());
+        mNotificationBuilder = builder;
+        notificationManager.notify(CALL_NOTIFICATION, mCallId, notification);
     }
 
     @Override
@@ -172,6 +180,15 @@
         setConnectionDisconnected(DisconnectCause.LOCAL);
     }
 
+    @Override
+    public void onSilence() {
+        // Re-post our notification without a ringtone.
+        mNotificationBuilder.setOnlyAlertOnce(true);
+        NotificationManager notificationManager = mContext.getSystemService(
+                NotificationManager.class);
+        notificationManager.notify(CALL_NOTIFICATION, mCallId, mNotificationBuilder.build());
+    }
+
     public void setConnectionActive() {
         mMediaPlayer.start();
         setActive();