Add "Turn speaker on"/"Turn speaker off" action button in notification.

Add action button when create in call notification and when audio state changes to ROUTE_SPEAKER or ROUTE_WIRED_OR_EARPIECE. Ignore when audio state is ROUTE_BLUETOOTH.

Screenshot:
Speaker on: https://screenshot.googleplex.com/hnsQL0YcFJj
Speaker off: https://screenshot.googleplex.com/oFrbvbs9gVG
Test: StatusBarNotifierTest
PiperOrigin-RevId: 169169372
Change-Id: I2f96f20170dd174b35dfd3f7578fe5b9450391ab
diff --git a/java/com/android/incallui/NotificationBroadcastReceiver.java b/java/com/android/incallui/NotificationBroadcastReceiver.java
index 0daa017..f83f84d 100644
--- a/java/com/android/incallui/NotificationBroadcastReceiver.java
+++ b/java/com/android/incallui/NotificationBroadcastReceiver.java
@@ -21,12 +21,14 @@
 import android.content.Intent;
 import android.os.Build.VERSION_CODES;
 import android.support.annotation.RequiresApi;
+import android.telecom.CallAudioState;
 import android.telecom.VideoProfile;
 import com.android.dialer.common.LogUtil;
 import com.android.dialer.logging.DialerImpression;
 import com.android.dialer.logging.Logger;
 import com.android.incallui.call.CallList;
 import com.android.incallui.call.DialerCall;
+import com.android.incallui.call.TelecomAdapter;
 
 /**
  * Accepts broadcast Intents which will be prepared by {@link StatusBarNotifier} and thus sent from
@@ -52,6 +54,9 @@
       "com.android.incallui.ACTION_ACCEPT_VIDEO_UPGRADE_REQUEST";
   public static final String ACTION_DECLINE_VIDEO_UPGRADE_REQUEST =
       "com.android.incallui.ACTION_DECLINE_VIDEO_UPGRADE_REQUEST";
+  public static final String ACTION_TURN_ON_SPEAKER = "com.android.incallui.ACTION_TURN_ON_SPEAKER";
+  public static final String ACTION_TURN_OFF_SPEAKER =
+      "com.android.incallui.ACTION_TURN_OFF_SPEAKER";
 
   @RequiresApi(VERSION_CODES.N_MR1)
   public static final String ACTION_PULL_EXTERNAL_CALL =
@@ -84,6 +89,10 @@
       context.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
       int notificationId = intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1);
       InCallPresenter.getInstance().getExternalCallNotifier().pullExternalCall(notificationId);
+    } else if (action.equals(ACTION_TURN_ON_SPEAKER)) {
+      TelecomAdapter.getInstance().setAudioRoute(CallAudioState.ROUTE_SPEAKER);
+    } else if (action.equals(ACTION_TURN_OFF_SPEAKER)) {
+      TelecomAdapter.getInstance().setAudioRoute(CallAudioState.ROUTE_WIRED_OR_EARPIECE);
     }
   }
 
diff --git a/java/com/android/incallui/StatusBarNotifier.java b/java/com/android/incallui/StatusBarNotifier.java
index 92ffae3..a2fa7e4 100644
--- a/java/com/android/incallui/StatusBarNotifier.java
+++ b/java/com/android/incallui/StatusBarNotifier.java
@@ -24,6 +24,8 @@
 import static com.android.incallui.NotificationBroadcastReceiver.ACTION_DECLINE_INCOMING_CALL;
 import static com.android.incallui.NotificationBroadcastReceiver.ACTION_DECLINE_VIDEO_UPGRADE_REQUEST;
 import static com.android.incallui.NotificationBroadcastReceiver.ACTION_HANG_UP_ONGOING_CALL;
+import static com.android.incallui.NotificationBroadcastReceiver.ACTION_TURN_OFF_SPEAKER;
+import static com.android.incallui.NotificationBroadcastReceiver.ACTION_TURN_ON_SPEAKER;
 
 import android.Manifest;
 import android.app.ActivityManager;
@@ -49,6 +51,7 @@
 import android.support.annotation.VisibleForTesting;
 import android.support.v4.os.BuildCompat;
 import android.telecom.Call.Details;
+import android.telecom.CallAudioState;
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
@@ -79,6 +82,7 @@
 import com.android.incallui.ContactInfoCache.ContactInfoCacheCallback;
 import com.android.incallui.InCallPresenter.InCallState;
 import com.android.incallui.async.PausableExecutorImpl;
+import com.android.incallui.audiomode.AudioModeProvider;
 import com.android.incallui.call.CallList;
 import com.android.incallui.call.DialerCall;
 import com.android.incallui.call.DialerCallListener;
@@ -92,7 +96,9 @@
 
 /** This class adds Notifications to the status bar for the in-call experience. */
 public class StatusBarNotifier
-    implements InCallPresenter.InCallStateListener, EnrichedCallManager.StateChangedListener {
+    implements InCallPresenter.InCallStateListener,
+        EnrichedCallManager.StateChangedListener,
+        AudioModeProvider.AudioModeListener {
 
   private static final String NOTIFICATION_TAG = "STATUS_BAR_NOTIFIER";
   private static final int NOTIFICATION_ID = 1;
@@ -120,6 +126,7 @@
   private String mSavedContent = null;
   private Bitmap mSavedLargeIcon;
   private String mSavedContentTitle;
+  private CallAudioState savedCallAudioState;
   private Uri mRingtone;
   private StatusBarCallListener mStatusBarCallListener;
 
@@ -132,6 +139,7 @@
             new InCallTonePlayer(new ToneGeneratorFactory(), new PausableExecutorImpl()),
             CallList.getInstance());
     mCurrentNotification = NOTIFICATION_NONE;
+    AudioModeProvider.getInstance().addListener(this);
   }
 
   /**
@@ -290,6 +298,7 @@
 
     Trace.beginSection("prepare work");
     final int callState = call.getState();
+    final CallAudioState callAudioState = AudioModeProvider.getInstance().getAudioState();
 
     // Check if data has changed; if nothing is different, don't issue another notification.
     final int iconResId = getIconToDisplay(call);
@@ -329,7 +338,8 @@
         contentTitle,
         callState,
         notificationType,
-        contactInfo.contactRingtoneUri)) {
+        contactInfo.contactRingtoneUri,
+        callAudioState)) {
       Trace.endSection();
       return;
     }
@@ -412,7 +422,7 @@
       addDismissUpgradeRequestAction(builder);
       addAcceptUpgradeRequestAction(builder);
     } else {
-      createIncomingCallNotification(call, callState, builder);
+      createIncomingCallNotification(call, callState, callAudioState, builder);
     }
 
     addPersonReference(builder, contactInfo, call);
@@ -479,7 +489,7 @@
   }
 
   private void createIncomingCallNotification(
-      DialerCall call, int state, Notification.Builder builder) {
+      DialerCall call, int state, CallAudioState callAudioState, Notification.Builder builder) {
     setNotificationWhen(call, state, builder);
 
     // Add hang up option for any active calls (active | onhold), outgoing calls (dialing).
@@ -487,6 +497,7 @@
         || state == DialerCall.State.ONHOLD
         || DialerCall.State.isDialing(state)) {
       addHangupAction(builder);
+      addSpeakerAction(builder, callAudioState);
     } else if (state == DialerCall.State.INCOMING || state == DialerCall.State.CALL_WAITING) {
       addDismissAction(builder);
       if (call.isVideoCall()) {
@@ -523,7 +534,8 @@
       String contentTitle,
       int state,
       int notificationType,
-      Uri ringtone) {
+      Uri ringtone,
+      CallAudioState callAudioState) {
 
     // The two are different:
     // if new title is not null, it should be different from saved version OR
@@ -542,7 +554,8 @@
             || (mCallState != state)
             || largeIconChanged
             || contentTitleChanged
-            || !Objects.equals(mRingtone, ringtone);
+            || !Objects.equals(mRingtone, ringtone)
+            || !Objects.equals(savedCallAudioState, callAudioState);
 
     // If we aren't showing a notification right now or the notification type is changing,
     // definitely do an update.
@@ -560,6 +573,7 @@
     mSavedLargeIcon = largeIcon;
     mSavedContentTitle = contentTitle;
     mRingtone = ringtone;
+    savedCallAudioState = callAudioState;
 
     if (retval) {
       LogUtil.d(
@@ -882,6 +896,47 @@
             .build());
   }
 
+  private void addSpeakerAction(Notification.Builder builder, CallAudioState callAudioState) {
+    if ((callAudioState.getSupportedRouteMask() & CallAudioState.ROUTE_BLUETOOTH)
+        == CallAudioState.ROUTE_BLUETOOTH) {
+      // Don't add speaker button if bluetooth is connected
+      return;
+    }
+    if (callAudioState.getRoute() == CallAudioState.ROUTE_SPEAKER) {
+      addSpeakerOffAction(builder);
+    } else if ((callAudioState.getRoute() & CallAudioState.ROUTE_WIRED_OR_EARPIECE) != 0) {
+      addSpeakerOnAction(builder);
+    }
+  }
+
+  private void addSpeakerOnAction(Notification.Builder builder) {
+    LogUtil.d(
+        "StatusBarNotifier.addSpeakerOnAction",
+        "will show \"Speaker on\" action in the ongoing active call Notification");
+    PendingIntent speakerOnPendingIntent =
+        createNotificationPendingIntent(mContext, ACTION_TURN_ON_SPEAKER);
+    builder.addAction(
+        new Notification.Action.Builder(
+                Icon.createWithResource(mContext, R.drawable.quantum_ic_volume_up_white_24),
+                mContext.getText(R.string.notification_action_speaker_on),
+                speakerOnPendingIntent)
+            .build());
+  }
+
+  private void addSpeakerOffAction(Notification.Builder builder) {
+    LogUtil.d(
+        "StatusBarNotifier.addSpeakerOffAction",
+        "will show \"Speaker off\" action in the ongoing active call Notification");
+    PendingIntent speakerOffPendingIntent =
+        createNotificationPendingIntent(mContext, ACTION_TURN_OFF_SPEAKER);
+    builder.addAction(
+        new Notification.Action.Builder(
+                Icon.createWithResource(mContext, R.drawable.quantum_ic_phone_in_talk_white_24),
+                mContext.getText(R.string.notification_action_speaker_off),
+                speakerOffPendingIntent)
+            .build());
+  }
+
   private void addVideoCallAction(Notification.Builder builder) {
     LogUtil.i(
         "StatusBarNotifier.addVideoCallAction",
@@ -977,6 +1032,16 @@
     mStatusBarCallListener = listener;
   }
 
+  @Override
+  public void onAudioStateChanged(CallAudioState audioState) {
+    if (CallList.getInstance().getActiveOrBackgroundCall() == null) {
+      // We only care about speaker mode when in call
+      return;
+    }
+
+    updateNotification(CallList.getInstance());
+  }
+
   private class StatusBarCallListener implements DialerCallListener {
 
     private DialerCall mDialerCall;
diff --git a/java/com/android/incallui/res/values/strings.xml b/java/com/android/incallui/res/values/strings.xml
index 177f386..7aa8ae3 100644
--- a/java/com/android/incallui/res/values/strings.xml
+++ b/java/com/android/incallui/res/values/strings.xml
@@ -122,6 +122,8 @@
        scenarios such as declining an incoming call or declining a video call request.
        [CHAR LIMIT=12] -->
   <string name="notification_action_dismiss">Decline</string>
+  <string name="notification_action_speaker_on">Turn speaker on</string>
+  <string name="notification_action_speaker_off">Turn speaker off</string>
 
   <!-- The "label" of the in-call Notification for an ongoing external call.
        External calls are a representation of a call which is in progress on the user's other