Sync for HeadsetMediaButton

Add synchronization and deferred execution to our interactions with
MediaSession, since MediaSession sometimes calls back via an RPC into
Telecom and can cause us to deadlock.

Bug: 21028885
Change-Id: I8fc0574269a81e817e1c139aa0fe56258c96bd64
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 845fe7f..24214cb 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -172,7 +172,7 @@
                 context, statusBarNotifier, mWiredHeadsetManager, mDockManager, this);
         InCallTonePlayer.Factory playerFactory = new InCallTonePlayer.Factory(mCallAudioManager, lock);
         mRinger = new Ringer(mCallAudioManager, this, playerFactory, context);
-        mHeadsetMediaButton = headsetMediaButtonFactory.create(context, this);
+        mHeadsetMediaButton = headsetMediaButtonFactory.create(context, this, mLock);
         mTtyManager = new TtyManager(context, mWiredHeadsetManager);
         mProximitySensorManager = proximitySensorManagerFactory.create(context, this);
         mPhoneStateBroadcaster = new PhoneStateBroadcaster(this);
diff --git a/src/com/android/server/telecom/HeadsetMediaButton.java b/src/com/android/server/telecom/HeadsetMediaButton.java
index 5d55edd..93dc6de 100644
--- a/src/com/android/server/telecom/HeadsetMediaButton.java
+++ b/src/com/android/server/telecom/HeadsetMediaButton.java
@@ -16,11 +16,13 @@
 
 package com.android.server.telecom;
 
-import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.Intent;
 import android.media.AudioAttributes;
 import android.media.session.MediaSession;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
 import android.view.KeyEvent;
 
 /**
@@ -36,35 +38,72 @@
             .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
             .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION).build();
 
+    private static final int MSG_MEDIA_SESSION_INITIALIZE = 0;
+    private static final int MSG_MEDIA_SESSION_SET_ACTIVE = 1;
+
     private final MediaSession.Callback mSessionCallback = new MediaSession.Callback() {
         @Override
         public boolean onMediaButtonEvent(Intent intent) {
             KeyEvent event = (KeyEvent) intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
             Log.v(this, "SessionCallback.onMediaButton()...  event = %s.", event);
             if ((event != null) && (event.getKeyCode() == KeyEvent.KEYCODE_HEADSETHOOK)) {
-                Log.v(this, "SessionCallback: HEADSETHOOK");
-                boolean consumed = handleHeadsetHook(event);
-                Log.v(this, "==> handleHeadsetHook(): consumed = %b.", consumed);
-                return consumed;
+                synchronized (mLock) {
+                    Log.v(this, "SessionCallback: HEADSETHOOK");
+                    boolean consumed = handleHeadsetHook(event);
+                    Log.v(this, "==> handleHeadsetHook(): consumed = %b.", consumed);
+                    return consumed;
+                }
             }
             return true;
         }
     };
 
+    private final Handler mMediaSessionHandler = new Handler(Looper.getMainLooper()) {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_MEDIA_SESSION_INITIALIZE: {
+                    MediaSession session = new MediaSession(
+                            mContext,
+                            HeadsetMediaButton.class.getSimpleName());
+                    session.setCallback(mSessionCallback);
+                    session.setFlags(MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY
+                            | MediaSession.FLAG_HANDLES_MEDIA_BUTTONS);
+                    session.setPlaybackToLocal(AUDIO_ATTRIBUTES);
+                    mSession = session;
+                    break;
+                }
+                case MSG_MEDIA_SESSION_SET_ACTIVE: {
+                    if (mSession != null) {
+                        boolean activate = msg.arg1 != 0;
+                        if (activate != mSession.isActive()) {
+                            mSession.setActive(activate);
+                        }
+                    }
+                    break;
+                }
+                default:
+                    break;
+            }
+        }
+    };
+
+    private final Context mContext;
     private final CallsManager mCallsManager;
+    private final TelecomSystem.SyncRoot mLock;
+    private MediaSession mSession;
 
-    private final MediaSession mSession;
-
-    public HeadsetMediaButton(Context context, CallsManager callsManager) {
+    public HeadsetMediaButton(
+            Context context,
+            CallsManager callsManager,
+            TelecomSystem.SyncRoot lock) {
+        mContext = context;
         mCallsManager = callsManager;
+        mLock = lock;
 
         // Create a MediaSession but don't enable it yet. This is a
         // replacement for MediaButtonReceiver
-        mSession = new MediaSession(context, HeadsetMediaButton.class.getSimpleName());
-        mSession.setCallback(mSessionCallback);
-        mSession.setFlags(MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY
-                | MediaSession.FLAG_HANDLES_MEDIA_BUTTONS);
-        mSession.setPlaybackToLocal(AUDIO_ATTRIBUTES);
+        mMediaSessionHandler.obtainMessage(MSG_MEDIA_SESSION_INITIALIZE).sendToTarget();
     }
 
     /**
@@ -87,18 +126,14 @@
     /** ${inheritDoc} */
     @Override
     public void onCallAdded(Call call) {
-        if (!mSession.isActive()) {
-            mSession.setActive(true);
-        }
+        mMediaSessionHandler.obtainMessage(MSG_MEDIA_SESSION_SET_ACTIVE, 1, 0).sendToTarget();
     }
 
     /** ${inheritDoc} */
     @Override
     public void onCallRemoved(Call call) {
         if (!mCallsManager.hasAnyCalls()) {
-            if (mSession.isActive()) {
-                mSession.setActive(false);
-            }
+            mMediaSessionHandler.obtainMessage(MSG_MEDIA_SESSION_SET_ACTIVE, 0, 0).sendToTarget();
         }
     }
 }
diff --git a/src/com/android/server/telecom/HeadsetMediaButtonFactory.java b/src/com/android/server/telecom/HeadsetMediaButtonFactory.java
index becabbf..13d5e4f 100644
--- a/src/com/android/server/telecom/HeadsetMediaButtonFactory.java
+++ b/src/com/android/server/telecom/HeadsetMediaButtonFactory.java
@@ -16,6 +16,8 @@
 
 package com.android.server.telecom;
 
+import com.android.server.telecom.components.TelecomService;
+
 import android.content.Context;
 
 /**
@@ -27,5 +29,8 @@
  */
 public interface HeadsetMediaButtonFactory {
 
-    HeadsetMediaButton create(Context context, CallsManager callsManager);
+    HeadsetMediaButton create(
+            Context context,
+            CallsManager callsManager,
+            TelecomSystem.SyncRoot lock);
 }
diff --git a/src/com/android/server/telecom/components/TelecomService.java b/src/com/android/server/telecom/components/TelecomService.java
index f7f4054..529645f 100644
--- a/src/com/android/server/telecom/components/TelecomService.java
+++ b/src/com/android/server/telecom/components/TelecomService.java
@@ -78,9 +78,11 @@
                             },
                             new HeadsetMediaButtonFactory() {
                                 @Override
-                                public HeadsetMediaButton create(Context context,
-                                        CallsManager callsManager) {
-                                    return new HeadsetMediaButton(context, callsManager);
+                                public HeadsetMediaButton create(
+                                        Context context,
+                                        CallsManager callsManager,
+                                        TelecomSystem.SyncRoot lock) {
+                                    return new HeadsetMediaButton(context, callsManager, lock);
                                 }
                             },
                             new ProximitySensorManagerFactory() {
diff --git a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
index 59ca981..b2ee6a2 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
@@ -170,7 +170,8 @@
 
         when(headsetMediaButtonFactory.create(
                 any(Context.class),
-                any(CallsManager.class)))
+                any(CallsManager.class),
+                any(TelecomSystem.SyncRoot.class)))
                 .thenReturn(mHeadsetMediaButton);
         when(proximitySensorManagerFactory.create(
                 any(Context.class),
@@ -191,7 +192,8 @@
 
         verify(headsetMediaButtonFactory).create(
                 eq(mComponentContextFixture.getTestDouble().getApplicationContext()),
-                any(CallsManager.class));
+                any(CallsManager.class),
+                any(TelecomSystem.SyncRoot.class));
         verify(proximitySensorManagerFactory).create(
                 eq(mComponentContextFixture.getTestDouble().getApplicationContext()),
                 any(CallsManager.class));