TTY: Telecomm

This CL makes the following changes:
  1. Added TtyManager. This class contains code that used
     to live in PhoneGlobals to listen to preferred TTY
     mode changes. Current TTY mode is based on the
     preferred mode, wired headset state, and device TTY
     support.

  2. Moved ownership of WiredHeadsetManager to CallsManager
     so that TtyManager can listen to headset plugged events.

  3. Added plumbing to TelecommServiceImpl to impement
     isTtySupported and getCurrentTtyMode.

  4. Added tty_enabled to config.xml. This is moved over
     from Telephony.

Change-Id: I652b095af30cc2732a06829dc23492e5355660da
diff --git a/src/com/android/telecomm/CallAudioManager.java b/src/com/android/telecomm/CallAudioManager.java
index 8156db0..e9008f8 100644
--- a/src/com/android/telecomm/CallAudioManager.java
+++ b/src/com/android/telecomm/CallAudioManager.java
@@ -26,13 +26,14 @@
 /**
  * This class manages audio modes, streams and other properties.
  */
-final class CallAudioManager extends CallsManagerListenerBase {
+final class CallAudioManager extends CallsManagerListenerBase
+        implements WiredHeadsetManager.Listener {
     private static final int STREAM_NONE = -1;
 
     private final StatusBarNotifier mStatusBarNotifier;
     private final AudioManager mAudioManager;
-    private final WiredHeadsetManager mWiredHeadsetManager;
     private final BluetoothManager mBluetoothManager;
+    private final WiredHeadsetManager mWiredHeadsetManager;
 
     private CallAudioState mAudioState;
     private int mAudioFocusStreamType;
@@ -40,11 +41,13 @@
     private boolean mIsTonePlaying;
     private boolean mWasSpeakerOn;
 
-    CallAudioManager(Context context, StatusBarNotifier statusBarNotifier) {
+    CallAudioManager(Context context, StatusBarNotifier statusBarNotifier,
+            WiredHeadsetManager wiredHeadsetManager) {
         mStatusBarNotifier = statusBarNotifier;
         mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
-        mWiredHeadsetManager = new WiredHeadsetManager(this);
         mBluetoothManager = new BluetoothManager(context, this);
+        mWiredHeadsetManager = wiredHeadsetManager;
+
         saveAudioState(getInitialAudioState(null));
         mAudioFocusStreamType = STREAM_NONE;
     }
@@ -107,6 +110,25 @@
         updateAudioStreamAndMode();
     }
 
+    /**
+      * Updates the audio route when the headset plugged in state changes. For example, if audio is
+      * being routed over speakerphone and a headset is plugged in then switch to wired headset.
+      */
+    @Override
+    public void onWiredHeadsetPluggedInChanged(boolean oldIsPluggedIn, boolean newIsPluggedIn) {
+        int newRoute = CallAudioState.ROUTE_EARPIECE;
+        if (newIsPluggedIn) {
+            newRoute = CallAudioState.ROUTE_WIRED_HEADSET;
+        } else if (mWasSpeakerOn) {
+            Call call = getForegroundCall();
+            if (call != null && call.isAlive()) {
+                // Restore the speaker state.
+                newRoute = CallAudioState.ROUTE_SPEAKER;
+            }
+        }
+        setSystemAudioState(mAudioState.isMuted, newRoute, calculateSupportedRoutes());
+    }
+
     void toggleMute() {
         mute(!mAudioState.isMuted);
     }
@@ -176,24 +198,6 @@
     }
 
     /**
-      * Updates the audio route when the headset plugged in state changes. For example, if audio is
-      * being routed over speakerphone and a headset is plugged in then switch to wired headset.
-      */
-    void onHeadsetPluggedInChanged(boolean oldIsPluggedIn, boolean newIsPluggedIn) {
-        int newRoute = CallAudioState.ROUTE_EARPIECE;
-        if (newIsPluggedIn) {
-            newRoute = CallAudioState.ROUTE_WIRED_HEADSET;
-        } else if (mWasSpeakerOn) {
-            Call call = getForegroundCall();
-            if (call != null && call.isAlive()) {
-                // Restore the speaker state.
-                newRoute = CallAudioState.ROUTE_SPEAKER;
-            }
-        }
-        setSystemAudioState(mAudioState.isMuted, newRoute, calculateSupportedRoutes());
-    }
-
-    /**
      * Updates the audio routing according to the bluetooth state.
      */
     void onBluetoothStateChange(BluetoothManager bluetoothManager) {
diff --git a/src/com/android/telecomm/CallsManager.java b/src/com/android/telecomm/CallsManager.java
index 5d07176..a78d750 100644
--- a/src/com/android/telecomm/CallsManager.java
+++ b/src/com/android/telecomm/CallsManager.java
@@ -75,6 +75,8 @@
     private final Ringer mRinger;
     private final Set<CallsManagerListener> mListeners = new HashSet<>();
     private final HeadsetMediaButton mHeadsetMediaButton;
+    private final WiredHeadsetManager mWiredHeadsetManager;
+    private final TtyManager mTtyManager;
 
     /**
      * The call the user is currently interacting with. This is the call that should have audio
@@ -94,10 +96,12 @@
         TelecommApp app = TelecommApp.getInstance();
 
         StatusBarNotifier statusBarNotifier = new StatusBarNotifier(app, this);
-        mCallAudioManager = new CallAudioManager(app, statusBarNotifier);
+        mWiredHeadsetManager = new WiredHeadsetManager(app);
+        mCallAudioManager = new CallAudioManager(app, statusBarNotifier, mWiredHeadsetManager);
         InCallTonePlayer.Factory playerFactory = new InCallTonePlayer.Factory(mCallAudioManager);
         mRinger = new Ringer(mCallAudioManager, this, playerFactory, app);
         mHeadsetMediaButton = new HeadsetMediaButton(app, this);
+        mTtyManager = new TtyManager(app, mWiredHeadsetManager);
 
         mListeners.add(statusBarNotifier);
         mListeners.add(new CallLogManager(app));
@@ -231,6 +235,14 @@
         return mCallAudioManager.getAudioState();
     }
 
+    boolean isTtySupported() {
+        return mTtyManager.isTtySupported();
+    }
+
+    int getCurrentTtyMode() {
+        return mTtyManager.getCurrentTtyMode();
+    }
+
     /**
      * Starts the process to attach the call to a connection service.
      *
diff --git a/src/com/android/telecomm/HeadsetMediaButton.java b/src/com/android/telecomm/HeadsetMediaButton.java
index a0d5858..6f533a4 100644
--- a/src/com/android/telecomm/HeadsetMediaButton.java
+++ b/src/com/android/telecomm/HeadsetMediaButton.java
@@ -16,7 +16,6 @@
 
 package com.android.telecomm;
 
-import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
diff --git a/src/com/android/telecomm/TelecommServiceImpl.java b/src/com/android/telecomm/TelecommServiceImpl.java
index 9bf4987..509307b 100644
--- a/src/com/android/telecomm/TelecommServiceImpl.java
+++ b/src/com/android/telecomm/TelecommServiceImpl.java
@@ -80,6 +80,12 @@
                     case MSG_CANCEL_MISSED_CALLS_NOTIFICATION:
                         mMissedCallNotifier.clearMissedCalls();
                         break;
+                    case MSG_IS_TTY_SUPPORTED:
+                        result = mCallsManager.isTtySupported();
+                        break;
+                    case MSG_GET_CURRENT_TTY_MODE:
+                        result = mCallsManager.getCurrentTtyMode();
+                        break;
                 }
 
                 if (result != null) {
@@ -102,6 +108,8 @@
     private static final int MSG_END_CALL = 3;
     private static final int MSG_ACCEPT_RINGING_CALL = 4;
     private static final int MSG_CANCEL_MISSED_CALLS_NOTIFICATION = 5;
+    private static final int MSG_IS_TTY_SUPPORTED = 6;
+    private static final int MSG_GET_CURRENT_TTY_MODE = 7;
 
     /** The singleton instance. */
     private static TelecommServiceImpl sInstance;
@@ -320,6 +328,24 @@
         return retval;
     }
 
+    /**
+     * @see TelecommManager#isTtySupported
+     */
+    @Override
+    public boolean isTtySupported() {
+        enforceReadPermission();
+        return (boolean) sendRequest(MSG_IS_TTY_SUPPORTED);
+    }
+
+    /**
+     * @see TelecommManager#getCurrentTtyMode
+     */
+    @Override
+    public int getCurrentTtyMode() {
+        enforceReadPermission();
+        return (int) sendRequest(MSG_GET_CURRENT_TTY_MODE);
+    }
+
     //
     // Supporting methods for the ITelecommService interface implementation.
     //
diff --git a/src/com/android/telecomm/TtyManager.java b/src/com/android/telecomm/TtyManager.java
new file mode 100644
index 0000000..c94cd30
--- /dev/null
+++ b/src/com/android/telecomm/TtyManager.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.telecomm;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.media.AudioManager;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.telecomm.TelecommConstants;
+
+final class TtyManager implements WiredHeadsetManager.Listener {
+    private final TtyBroadcastReceiver mReceiver = new TtyBroadcastReceiver();
+    private final Context mContext;
+    private final WiredHeadsetManager mWiredHeadsetManager;
+    private int mPreferredTtyMode = TelecommConstants.TTY_MODE_OFF;
+    private int mCurrentTtyMode = TelecommConstants.TTY_MODE_OFF;
+
+    TtyManager(Context context, WiredHeadsetManager wiredHeadsetManager) {
+        mContext = context;
+        mWiredHeadsetManager = wiredHeadsetManager;
+        mWiredHeadsetManager.addListener(this);
+
+        mPreferredTtyMode = Settings.Secure.getInt(
+                mContext.getContentResolver(),
+                Settings.Secure.PREFERRED_TTY_MODE,
+                TelecommConstants.TTY_MODE_OFF);
+
+        IntentFilter intentFilter = new IntentFilter(
+                TelecommConstants.ACTION_TTY_PREFERRED_MODE_CHANGED);
+        mContext.registerReceiver(mReceiver, intentFilter);
+
+        updateCurrentTtyMode();
+    }
+
+    boolean isTtySupported() {
+        boolean isEnabled = mContext.getResources().getBoolean(R.bool.tty_enabled);
+        Log.v(this, "isTtySupported: " + isEnabled);
+        return isEnabled;
+    }
+
+    int getCurrentTtyMode() {
+        return mCurrentTtyMode;
+    }
+
+    @Override
+    public void onWiredHeadsetPluggedInChanged(boolean oldIsPluggedIn, boolean newIsPluggedIn) {
+        Log.v(this, "onWiredHeadsetPluggedInChanged");
+        updateCurrentTtyMode();
+    }
+
+    private void updateCurrentTtyMode() {
+        int newTtyMode = TelecommConstants.TTY_MODE_OFF;
+        if (isTtySupported() && mWiredHeadsetManager.isPluggedIn()) {
+            newTtyMode = mPreferredTtyMode;
+        }
+        Log.v(this, "updateCurrentTtyMode, %d -> %d", mCurrentTtyMode, newTtyMode);
+
+        if (mCurrentTtyMode != newTtyMode) {
+            mCurrentTtyMode = newTtyMode;
+            Intent ttyModeChanged = new Intent(TelecommConstants.ACTION_CURRENT_TTY_MODE_CHANGED);
+            ttyModeChanged.putExtra(TelecommConstants.EXTRA_CURRENT_TTY_MODE, mCurrentTtyMode);
+            mContext.sendBroadcastAsUser(ttyModeChanged, UserHandle.ALL);
+
+            updateAudioTtyMode();
+        }
+    }
+
+    private void updateAudioTtyMode() {
+        String audioTtyMode;
+        switch (mCurrentTtyMode) {
+            case TelecommConstants.TTY_MODE_FULL:
+                audioTtyMode = "tty_full";
+                break;
+            case TelecommConstants.TTY_MODE_VCO:
+                audioTtyMode = "tty_vco";
+                break;
+            case TelecommConstants.TTY_MODE_HCO:
+                audioTtyMode = "tty_hco";
+                break;
+            case TelecommConstants.TTY_MODE_OFF:
+            default:
+                audioTtyMode = "tty_off";
+                break;
+        }
+        Log.v(this, "updateAudioTtyMode, %s", audioTtyMode);
+
+        AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+        audioManager.setParameters("tty_mode=" + audioTtyMode);
+    }
+
+    private final class TtyBroadcastReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            Log.v(TtyManager.this, "onReceive, action: %s", action);
+            if (action.equals(TelecommConstants.ACTION_TTY_PREFERRED_MODE_CHANGED)) {
+                int newPreferredTtyMode = intent.getIntExtra(
+                        TelecommConstants.EXTRA_TTY_PREFERRED_MODE, TelecommConstants.TTY_MODE_OFF);
+                if (mPreferredTtyMode != newPreferredTtyMode) {
+                    mPreferredTtyMode = newPreferredTtyMode;
+                    updateCurrentTtyMode();
+                }
+            }
+        }
+    }
+}
diff --git a/src/com/android/telecomm/WiredHeadsetManager.java b/src/com/android/telecomm/WiredHeadsetManager.java
index 329df71..e59f1a5 100644
--- a/src/com/android/telecomm/WiredHeadsetManager.java
+++ b/src/com/android/telecomm/WiredHeadsetManager.java
@@ -22,12 +22,14 @@
 import android.content.IntentFilter;
 import android.media.AudioManager;
 
-/**
- * Listens for and caches headset state.  Used By the CallAudioManger for maintaining
- * overall audio state for use in the UI layer. Also provides method for connecting the bluetooth
- * headset to the phone call.
- */
+import java.util.HashSet;
+
+/** Listens for and caches headset state. */
 class WiredHeadsetManager {
+    interface Listener {
+        void onWiredHeadsetPluggedInChanged(boolean oldIsPluggedIn, boolean newIsPluggedIn);
+    }
+
     /** Receiver for wired headset plugged and unplugged events. */
     private class WiredHeadsetBroadcastReceiver extends BroadcastReceiver {
         @Override
@@ -41,15 +43,13 @@
         }
     }
 
-    private final CallAudioManager mCallAudioManager;
     private final WiredHeadsetBroadcastReceiver mReceiver;
     private boolean mIsPluggedIn;
+    private final HashSet<Listener> mListeners = new HashSet<>();
 
-    WiredHeadsetManager(CallAudioManager callAudioManager) {
-        mCallAudioManager = callAudioManager;
+    WiredHeadsetManager(Context context) {
         mReceiver = new WiredHeadsetBroadcastReceiver();
 
-        Context context = TelecommApp.getInstance();
         AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
         mIsPluggedIn = audioManager.isWiredHeadsetOn();
 
@@ -58,6 +58,14 @@
         context.registerReceiver(mReceiver, intentFilter);
     }
 
+    void addListener(Listener listener) {
+        mListeners.add(listener);
+    }
+
+    void removeListener(Listener listener) {
+        mListeners.remove(listener);
+    }
+
     boolean isPluggedIn() {
         return mIsPluggedIn;
     }
@@ -68,7 +76,9 @@
                     isPluggedIn);
             boolean oldIsPluggedIn = mIsPluggedIn;
             mIsPluggedIn = isPluggedIn;
-            mCallAudioManager.onHeadsetPluggedInChanged(oldIsPluggedIn, mIsPluggedIn);
+            for (Listener listener : mListeners) {
+                listener.onWiredHeadsetPluggedInChanged(oldIsPluggedIn, mIsPluggedIn);
+            }
         }
     }
 }