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();