Move call-related SystemAPIs to TelecommManager.  (2/3)

Bug: 15672803
Change-Id: I46e448fe93a9c5b4ae013e8b2fd6f0ce89b94e69
diff --git a/src/com/android/telecomm/CallsManager.java b/src/com/android/telecomm/CallsManager.java
index 26f627f..8ccc0e3 100644
--- a/src/com/android/telecomm/CallsManager.java
+++ b/src/com/android/telecomm/CallsManager.java
@@ -30,6 +30,9 @@
 import com.google.common.collect.ImmutableCollection;
 import com.google.common.collect.ImmutableList;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.LinkedHashSet;
 import java.util.Set;
@@ -588,22 +591,11 @@
     }
 
     boolean hasActiveOrHoldingCall() {
-        for (Call call : mCalls) {
-            CallState state = call.getState();
-            if (state == CallState.ACTIVE || state == CallState.ON_HOLD) {
-                return true;
-            }
-        }
-        return false;
+        return getFirstCallWithState(CallState.ACTIVE, CallState.ON_HOLD) != null;
     }
 
     boolean hasRingingCall() {
-        for (Call call : mCalls) {
-            if (call.getState() == CallState.RINGING) {
-                return true;
-            }
-        }
-        return false;
+        return getFirstCallWithState(CallState.RINGING) != null;
     }
 
     boolean onMediaButton(int type) {
@@ -658,8 +650,13 @@
      * priority order so that any call with the first state will be returned before any call with
      * states listed later in the parameter list.
      */
-    private Call getFirstCallWithState(CallState... states) {
+    Call getFirstCallWithState(CallState... states) {
         for (CallState currentState : states) {
+            // check the foreground first
+            if (mForegroundCall != null && mForegroundCall.getState() == currentState) {
+                return mForegroundCall;
+            }
+
             for (Call call : mCalls) {
                 if (currentState == call.getState()) {
                     return call;
diff --git a/src/com/android/telecomm/TelecommServiceImpl.java b/src/com/android/telecomm/TelecommServiceImpl.java
index bf25432..24d6cc4 100644
--- a/src/com/android/telecomm/TelecommServiceImpl.java
+++ b/src/com/android/telecomm/TelecommServiceImpl.java
@@ -24,8 +24,10 @@
 import android.content.res.Resources;
 import android.net.Uri;
 import android.os.Handler;
+import android.os.Looper;
 import android.os.Message;
 import android.os.ServiceManager;
+import android.telecomm.CallState;
 import android.telecomm.Subscription;
 import android.text.TextUtils;
 
@@ -35,36 +37,76 @@
  * Implementation of the ITelecomm interface.
  */
 public class TelecommServiceImpl extends ITelecommService.Stub {
-    private static final String TAG = TelecommServiceImpl.class.getSimpleName();
-
-    private static final String SERVICE_NAME = "telecomm";
-
-    private static final int MSG_SILENCE_RINGER = 1;
-    private static final int MSG_SHOW_CALL_SCREEN = 2;
-
-    /** The singleton instance. */
-    private static TelecommServiceImpl sInstance;
+    /**
+     * A request object for use with {@link MainThreadHandler}. Requesters should wait() on the
+     * request after sending. The main thread will notify the request when it is complete.
+     */
+    private static final class MainThreadRequest {
+        /** The result of the request that is run on the main thread */
+        public Object result;
+    }
 
     /**
      * A handler that processes messages on the main thread in the phone process. Since many
      * of the Phone calls are not thread safe this is needed to shuttle the requests from the
      * inbound binder threads to the main thread in the phone process.
      */
-    private final Handler mHandler = new Handler() {
+    private final class MainThreadHandler extends Handler {
         @Override
         public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MSG_SILENCE_RINGER:
-                    silenceRingerInternal();
-                    break;
-                case MSG_SHOW_CALL_SCREEN:
-                    showCallScreenInternal(msg.arg1 == 1);
-                    break;
+            if (msg.obj instanceof MainThreadRequest) {
+                MainThreadRequest request = (MainThreadRequest) msg.obj;
+                Object result = null;
+                switch (msg.what) {
+                    case MSG_SILENCE_RINGER:
+                        mCallsManager.getRinger().silence();
+                        break;
+                    case MSG_SHOW_CALL_SCREEN:
+                        mCallsManager.getInCallController().bringToForeground(msg.arg1 == 1);
+                        break;
+                    case MSG_IS_IN_A_PHONE_CALL:
+                        result = mCallsManager.hasAnyCalls();
+                        break;
+                    case MSG_IS_RINGING:
+                        result = mCallsManager.hasRingingCall();
+                        break;
+                    case MSG_END_CALL:
+                        result = endCallInternal();
+                        break;
+                    case MSG_ACCEPT_RINGING_CALL:
+                        acceptRingingCallInternal();
+                        break;
+                }
+
+                if (result != null) {
+                    request.result = result;
+                    synchronized(request) {
+                        request.notifyAll();
+                    }
+                }
             }
         }
-    };
+    }
 
     /** Private constructor; @see init() */
+    private static final String TAG = TelecommServiceImpl.class.getSimpleName();
+
+    private static final String SERVICE_NAME = "telecomm";
+
+    private static final int MSG_SILENCE_RINGER = 1;
+    private static final int MSG_SHOW_CALL_SCREEN = 2;
+    private static final int MSG_IS_IN_A_PHONE_CALL = 3;
+    private static final int MSG_IS_RINGING = 4;
+    private static final int MSG_END_CALL = 5;
+    private static final int MSG_ACCEPT_RINGING_CALL = 6;
+
+    /** The singleton instance. */
+    private static TelecommServiceImpl sInstance;
+
+    private final CallsManager mCallsManager = CallsManager.getInstance();
+
+    private final MainThreadHandler mMainThreadHandler = new MainThreadHandler();
+
     private TelecommServiceImpl() {
         publish();
     }
@@ -85,7 +127,7 @@
     }
 
     //
-    // Implementation of the ITelephony interface.
+    // Implementation of the ITelecommService interface.
     //
 
     @Override
@@ -105,14 +147,19 @@
         // TODO
     }
 
+    /**
+     * @see TelecommManager#silenceringer
+     */
     @Override
     public void silenceRinger() {
         Log.d(this, "silenceRinger");
-        // TODO: find a more appropriate permission to check here.
         enforceModifyPermission();
-        mHandler.sendEmptyMessage(MSG_SILENCE_RINGER);
+        sendRequestAsync(MSG_SILENCE_RINGER, 0);
     }
 
+    /**
+     * @see TelecommManager#getDefaultPhoneApp
+     */
     @Override
     public ComponentName getDefaultPhoneApp() {
         Resources resources = TelecommApp.getInstance().getResources();
@@ -121,17 +168,81 @@
                 resources.getString(R.string.dialer_default_class));
     }
 
+    /**
+     * @see TelecommManager#isInAPhoneCall
+     */
+    @Override
+    public boolean isInAPhoneCall() {
+        enforceReadPermission();
+        return (boolean) sendRequest(MSG_IS_IN_A_PHONE_CALL);
+    }
+
+    /**
+     * @see TelecommManager#isRinging
+     */
+    @Override
+    public boolean isRinging() {
+        enforceReadPermission();
+        return (boolean) sendRequest(MSG_IS_RINGING);
+    }
+
+    /**
+     * @see TelecommManager#endCall
+     */
+    @Override
+    public boolean endCall() {
+        enforceModifyPermission();
+        return (boolean) sendRequest(MSG_END_CALL);
+    }
+
+    /**
+     * @see TelecommManager#acceptRingingCall
+     */
+    @Override
+    public void acceptRingingCall() {
+        enforceModifyPermission();
+        sendRequestAsync(MSG_ACCEPT_RINGING_CALL, 0);
+    }
+
+    @Override
+    public void showCallScreen(boolean showDialpad) {
+        mMainThreadHandler.obtainMessage(MSG_SHOW_CALL_SCREEN, showDialpad ? 1 : 0, 0)
+                .sendToTarget();
+    }
+
     //
     // Supporting methods for the ITelephony interface implementation.
     //
 
-    /**
-     * Internal implemenation of silenceRinger().
-     * This should only be called from the main thread of the Phone app.
-     * @see #silenceRinger
-     */
-    private void silenceRingerInternal() {
-        CallsManager.getInstance().getRinger().silence();
+    private void acceptRingingCallInternal() {
+        Call call = mCallsManager.getFirstCallWithState(CallState.RINGING);
+        if (call != null) {
+            call.answer();
+        }
+    }
+
+    private boolean endCallInternal() {
+        // Always operate on the foreground call if one exists, otherwise get the first call in
+        // priority order by call-state.
+        Call call = mCallsManager.getForegroundCall();
+        if (call == null) {
+            call = mCallsManager.getFirstCallWithState(
+                    CallState.ACTIVE,
+                    CallState.DIALING,
+                    CallState.RINGING,
+                    CallState.ON_HOLD);
+        }
+
+        if (call != null) {
+            if (call.getState() == CallState.RINGING) {
+                call.reject(false /* rejectWithMessage */, null);
+            } else {
+                call.disconnect();
+            }
+            return true;
+        }
+
+        return false;
     }
 
     /**
@@ -144,13 +255,9 @@
                 android.Manifest.permission.MODIFY_PHONE_STATE, null);
     }
 
-    @Override
-    public void showCallScreen(boolean showDialpad) {
-        mHandler.obtainMessage(MSG_SHOW_CALL_SCREEN, showDialpad ? 1 : 0, 0).sendToTarget();
-    }
-
-    private void showCallScreenInternal(boolean showDialpad) {
-        CallsManager.getInstance().getInCallController().bringToForeground(showDialpad);
+    private void enforceReadPermission() {
+        TelecommApp.getInstance().enforceCallingOrSelfPermission(
+                android.Manifest.permission.READ_PHONE_STATE, null);
     }
 
     // TODO (STOPSHIP): Static list of Subscriptions for testing and UX work only.
@@ -202,4 +309,34 @@
         Log.d(this, "publish: %s", this);
         ServiceManager.addService(SERVICE_NAME, this);
     }
+
+    private MainThreadRequest sendRequestAsync(int command, int arg1) {
+        MainThreadRequest request = new MainThreadRequest();
+        mMainThreadHandler.obtainMessage(command, arg1, 0, request).sendToTarget();
+        return request;
+    }
+
+    /**
+     * Posts the specified command to be executed on the main thread, waits for the request to
+     * complete, and returns the result.
+     */
+    private Object sendRequest(int command) {
+        if (Looper.myLooper() == mMainThreadHandler.getLooper()) {
+            throw new RuntimeException("This method will deadlock if called from the main thread.");
+        }
+
+        MainThreadRequest request = sendRequestAsync(command, 0);
+
+        // Wait for the request to complete
+        synchronized (request) {
+            while (request.result == null) {
+                try {
+                    request.wait();
+                } catch (InterruptedException e) {
+                    // Do nothing, go back and wait until the request is complete
+                }
+            }
+        }
+        return request.result;
+    }
 }