Add hold support to Telecomm service

Bug: 13169202

Change-Id: Ic2c1989de41c91d237841581e6fa531cb71d4eed
diff --git a/src/com/android/telecomm/Call.java b/src/com/android/telecomm/Call.java
index 0b60c4a..7eb4a6f 100644
--- a/src/com/android/telecomm/Call.java
+++ b/src/com/android/telecomm/Call.java
@@ -271,6 +271,28 @@
     }
 
     /**
+     * Puts the call on hold if it is currently active.
+     */
+    void hold() {
+        Preconditions.checkNotNull(mCallService);
+
+        if (mState == CallState.ACTIVE) {
+            mCallService.hold(mId);
+        }
+    }
+
+    /**
+     * Releases the call from hold if it is currently active.
+     */
+    void unhold() {
+        Preconditions.checkNotNull(mCallService);
+
+        if (mState == CallState.ON_HOLD) {
+            mCallService.unhold(mId);
+        }
+    }
+
+    /**
      * @return An object containing read-only information about this call.
      */
     CallInfo toCallInfo() {
diff --git a/src/com/android/telecomm/CallServiceAdapter.java b/src/com/android/telecomm/CallServiceAdapter.java
index 1e830d5..e30d338 100644
--- a/src/com/android/telecomm/CallServiceAdapter.java
+++ b/src/com/android/telecomm/CallServiceAdapter.java
@@ -172,6 +172,16 @@
         });
     }
 
+    /** {@inheritDoc} */
+    @Override public void setOnHold(final String callId) {
+        checkValidCallId(callId);
+        mHandler.post(new Runnable() {
+            @Override public void run() {
+                mCallsManager.markCallAsOnHold(callId);
+            }
+        });
+    }
+
     /**
      * Adds the specified call ID to the list of pending outgoing call IDs.
      * TODO(gilad): Consider passing the call processor (instead of the ID) both here and in the
diff --git a/src/com/android/telecomm/CallServiceWrapper.java b/src/com/android/telecomm/CallServiceWrapper.java
index 807f427..5585fc7 100644
--- a/src/com/android/telecomm/CallServiceWrapper.java
+++ b/src/com/android/telecomm/CallServiceWrapper.java
@@ -146,6 +146,28 @@
         }
     }
 
+    /** See {@link ICallService#hold}. */
+    public void hold(String callId) {
+        if (isServiceValid("hold")) {
+            try {
+                mServiceInterface.hold(callId);
+            } catch (RemoteException e) {
+                Log.e(this, e, "Failed to put on hold for call %s", callId);
+            }
+        }
+    }
+
+    /** See {@link ICallService#unhold}. */
+    public void unhold(String callId) {
+        if (isServiceValid("unhold")) {
+            try {
+                mServiceInterface.unhold(callId);
+            } catch (RemoteException e) {
+                Log.e(this, e, "Failed to remove from hold for call %s", callId);
+            }
+        }
+    }
+
     /**
      * Starts retrieval of details for an incoming call. Details are returned through the
      * call-service adapter using the specified call ID. Upon failure, the specified error callback
diff --git a/src/com/android/telecomm/CallsManager.java b/src/com/android/telecomm/CallsManager.java
index 4b4de71..2f1b764 100644
--- a/src/com/android/telecomm/CallsManager.java
+++ b/src/com/android/telecomm/CallsManager.java
@@ -258,6 +258,40 @@
         audioManager.abandonAudioFocusForCall();
     }
 
+    /**
+     * Instructs Telecomm to put the specified call on hold. Intended to be invoked by the
+     * in-call app through {@link InCallAdapter} for an ongoing call. This is usually triggered by
+     * the user hitting the hold button during an active call.
+     *
+     * @param callId The ID of the call.
+     */
+    void holdCall(String callId) {
+        Call call = mCalls.get(callId);
+        if (call == null) {
+            Log.w(this, "Unknown call (%s) asked to be put on hold", callId);
+        } else {
+            Log.d(this, "Putting call on hold: (%s)", callId);
+            call.hold();
+        }
+    }
+
+    /**
+     * Instructs Telecomm to release the specified call from hold. Intended to be invoked by
+     * the in-call app through {@link InCallAdapter} for an ongoing call. This is usually triggered
+     * by the user hitting the hold button during a held call.
+     *
+     * @param callId The ID of the call
+     */
+    void unholdCall(String callId) {
+        Call call = mCalls.get(callId);
+        if (call == null) {
+            Log.w(this, "Unknown call (%s) asked to be removed from hold", callId);
+        } else {
+            Log.d(this, "Removing call from hold: (%s)", callId);
+            call.unhold();
+        }
+    }
+
     void markCallAsRinging(String callId) {
         setCallState(callId, CallState.RINGING);
     }
@@ -282,6 +316,13 @@
         audioManager.setSpeakerphoneOn(false);
     }
 
+    void markCallAsOnHold(String callId) {
+        setCallState(callId, CallState.ON_HOLD);
+
+        // Notify the in-call UI
+        mInCallController.markCallAsOnHold(callId);
+    }
+
     /**
      * Marks the specified call as DISCONNECTED and notifies the in-call app. If this was the last
      * live call, then also disconnect from the in-call controller.
@@ -382,6 +423,7 @@
         switch (call.getState()) {
             case DIALING:
             case ACTIVE:
+            case ON_HOLD:
                 callState = TelephonyManager.EXTRA_STATE_OFFHOOK;
                 break;
 
diff --git a/src/com/android/telecomm/InCallAdapter.java b/src/com/android/telecomm/InCallAdapter.java
index 7232abf..37dc3b9 100644
--- a/src/com/android/telecomm/InCallAdapter.java
+++ b/src/com/android/telecomm/InCallAdapter.java
@@ -68,4 +68,24 @@
             }
         });
     }
+
+    /** {@inheritDoc} */
+    @Override
+    public void holdCall(final String callId) {
+        mHandler.post(new Runnable() {
+            @Override public void run() {
+                mCallsManager.holdCall(callId);
+            }
+        });
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void unholdCall(final String callId) {
+        mHandler.post(new Runnable() {
+            @Override public void run() {
+                mCallsManager.unholdCall(callId);
+            }
+        });
+    }
 }
diff --git a/src/com/android/telecomm/InCallController.java b/src/com/android/telecomm/InCallController.java
index 4da9190..4f863c5 100644
--- a/src/com/android/telecomm/InCallController.java
+++ b/src/com/android/telecomm/InCallController.java
@@ -141,6 +141,17 @@
         }
     }
 
+    void markCallAsOnHold(String callId) {
+        try {
+            if (mInCallService != null) {
+                Log.i(this, "Mark call as HOLD: %s", callId);
+                mInCallService.setOnHold(callId);
+            }
+        } catch (RemoteException e) {
+            Log.e(this, e, "Exception attempting to markCallAsHeld.");
+        }
+    }
+
     /**
      * Unbinds an existing bound connection to the in-call app.
      */