Merge "TTY: Telecomm" into lmp-dev
diff --git a/res/values/config.xml b/res/values/config.xml
index d3e253c..365e758 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -35,4 +35,7 @@
 
     <!-- Class name for the default main dialer activity [DO NOT TRANSLATE] -->
     <string name="dialer_default_class" translatable="false">com.android.dialer.DialtactsActivity</string>
+
+    <!-- Flag indicating if the tty is enabled -->
+    <bool name="tty_enabled">false</bool>
 </resources>
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 fee41ad..36f8cea 100644
--- a/src/com/android/telecomm/CallsManager.java
+++ b/src/com/android/telecomm/CallsManager.java
@@ -76,6 +76,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
@@ -95,10 +97,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));
@@ -239,6 +243,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 65c45df..70af0fd 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);
+            }
         }
     }
 }