Stop in call service when there are no calls.

Bug: 10610367
Change-Id: I1efd4cf0b69d5d457d7e07d16c47662e53d0bb05
diff --git a/src/com/android/phone/CallHandlerServiceProxy.java b/src/com/android/phone/CallHandlerServiceProxy.java
index 38373d7..a57ac5b 100644
--- a/src/com/android/phone/CallHandlerServiceProxy.java
+++ b/src/com/android/phone/CallHandlerServiceProxy.java
@@ -44,7 +44,7 @@
         implements CallModeler.Listener, AudioModeListener {
 
     private static final String TAG = CallHandlerServiceProxy.class.getSimpleName();
-    private static final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt(
+    private static final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 0) && (SystemProperties.getInt(
             "ro.debuggable", 0) == 1);
 
     public static final int RETRY_DELAY_MILLIS = 2000;
@@ -86,28 +86,36 @@
 
         mAudioRouter.addAudioModeListener(this);
         mCallModeler.addListener(this);
-
-        if (PhoneGlobals.sVoiceCapable) {
-            setupServiceConnection();
-        }
     }
 
     @Override
     public void onDisconnect(Call call) {
-        try {
-            synchronized (mServiceAndQueueLock) {
-                if (mCallHandlerServiceGuarded == null) {
-                    if (DBG) {
-                        Log.d(TAG, "CallHandlerService not connected.  Enqueue disconnect");
-                    }
-                    enqueueDisconnect(call);
-                    return;
+        synchronized (mServiceAndQueueLock) {
+            if (mCallHandlerServiceGuarded == null) {
+                if (DBG) {
+                    Log.d(TAG, "CallHandlerService not connected.  Enqueue disconnect");
                 }
+                enqueueDisconnect(call);
+                setupServiceConnection();
+                return;
             }
+        }
+        processDisconnect(call);
+    }
+
+    private void processDisconnect(Call call) {
+        try {
             if (DBG) {
                 Log.d(TAG, "onDisconnect: " + call);
             }
-            mCallHandlerServiceGuarded.onDisconnect(call);
+            synchronized (mServiceAndQueueLock) {
+                if (mCallHandlerServiceGuarded != null) {
+                    mCallHandlerServiceGuarded.onDisconnect(call);
+                }
+            }
+            if (!mCallModeler.hasLiveCall()) {
+                unbind();
+            }
         } catch (Exception e) {
             Log.e(TAG, "Remote exception handling onDisconnect ", e);
         }
@@ -115,23 +123,32 @@
 
     @Override
     public void onIncoming(Call call) {
-        try {
-            synchronized (mServiceAndQueueLock) {
-                if (mCallHandlerServiceGuarded == null) {
-                    if (DBG) {
-                        Log.d(TAG, "CallHandlerService not connected.  Enqueue incoming.");
-                    }
-                    enqueueIncoming(call);
-                    return;
+        synchronized (mServiceAndQueueLock) {
+            if (mCallHandlerServiceGuarded == null) {
+                if (DBG) {
+                    Log.d(TAG, "CallHandlerService not connected.  Enqueue incoming.");
                 }
+                enqueueIncoming(call);
+                setupServiceConnection();
+                return;
             }
-            if (DBG) {
-                Log.d(TAG, "onIncoming: " + call);
-            }
+        }
+        processIncoming(call);
+    }
+
+    private void processIncoming(Call call) {
+        if (DBG) {
+            Log.d(TAG, "onIncoming: " + call);
+        }
+        try {
             // TODO(klp): check RespondViaSmsManager.allowRespondViaSmsForCall()
             // must refactor call method to accept proper call object.
-            mCallHandlerServiceGuarded.onIncoming(call,
-                    RejectWithTextMessageManager.loadCannedResponses());
+            synchronized (mServiceAndQueueLock) {
+                if (mCallHandlerServiceGuarded != null) {
+                    mCallHandlerServiceGuarded.onIncoming(call,
+                            RejectWithTextMessageManager.loadCannedResponses());
+                }
+            }
         } catch (Exception e) {
             Log.e(TAG, "Remote exception handling onUpdate", e);
         }
@@ -139,21 +156,37 @@
 
     @Override
     public void onUpdate(List<Call> calls) {
+        synchronized (mServiceAndQueueLock) {
+            if (mCallHandlerServiceGuarded == null) {
+                if (DBG) {
+                    Log.d(TAG, "CallHandlerService not connected.  Enqueue update.");
+                }
+                enqueueUpdate(calls);
+                setupServiceConnection();
+                return;
+            }
+        }
+        processUpdate(calls);
+    }
+
+    private void processUpdate(List<Call> calls) {
+        if (DBG) {
+            Log.d(TAG, "onUpdate: " + calls.toString());
+        }
         try {
             synchronized (mServiceAndQueueLock) {
-                if (mCallHandlerServiceGuarded == null) {
-                    if (DBG) {
-                        Log.d(TAG, "CallHandlerService not connected.  Enqueue update.");
-                    }
-                    enqueueUpdate(calls);
-                    return;
+                if (mCallHandlerServiceGuarded != null) {
+                    mCallHandlerServiceGuarded.onUpdate(calls);
                 }
             }
-
-            if (DBG) {
-                Log.d(TAG, "onUpdate: " + calls.toString());
+            if (!mCallModeler.hasLiveCall()) {
+                // TODO: unbinding happens in both onUpdate and onDisconnect because the ordering
+                // is not deterministic.  Unbinding in both ensures that the service is unbound.
+                // But it also makes this in-efficient because we are unbinding twice, which leads
+                // to the CallHandlerService performing onCreate() and onDestroy() twice for each
+                // disconnect.
+                unbind();
             }
-            mCallHandlerServiceGuarded.onUpdate(calls);
         } catch (Exception e) {
             Log.e(TAG, "Remote exception handling onUpdate", e);
         }
@@ -163,7 +196,6 @@
     public void onAudioModeChange(int newMode, boolean muted) {
         try {
             synchronized (mServiceAndQueueLock) {
-                // TODO(klp): does this need to be enqueued?
                 if (mCallHandlerServiceGuarded == null) {
                     if (DBG) {
                         Log.d(TAG, "CallHandlerService not conneccted. Skipping "
@@ -191,7 +223,6 @@
     public void onSupportedAudioModeChange(int modeMask) {
         try {
             synchronized (mServiceAndQueueLock) {
-                // TODO(klp): does this need to be enqueued?
                 if (mCallHandlerServiceGuarded == null) {
                     if (DBG) {
                         Log.d(TAG, "CallHandlerService not conneccted. Skipping"
@@ -212,7 +243,9 @@
 
     }
 
-    private ServiceConnection mConnection = new ServiceConnection() {
+    private ServiceConnection mConnection = null;
+
+    private class InCallServiceConnection implements ServiceConnection {
         @Override public void onServiceConnected (ComponentName className, IBinder service){
             if (DBG) {
                 Log.d(TAG, "Service Connected");
@@ -224,17 +257,16 @@
         @Override public void onServiceDisconnected (ComponentName className){
             Log.i(TAG, "Disconnected from UI service.");
             synchronized (mServiceAndQueueLock) {
-                mCallHandlerServiceGuarded = null;
-
                 // Technically, unbindService is un-necessary since the framework will schedule and
                 // restart the crashed service.  But there is a exponential backoff for the restart.
                 // Unbind explicitly and setup again to avoid the backoff since it's important to
                 // always have an in call ui.
-                mContext.unbindService(mConnection);
-                setupServiceConnection();
+                unbind();
+
+                // TODO(klp): hang up all calls.
             }
         }
-    };
+    }
 
     public void bringToForeground() {
         // only support this call if the service is already connected.
@@ -250,48 +282,65 @@
         }
     }
 
+    private static Intent getInCallServiceIntent(Context context) {
+        final Intent serviceIntent = new Intent(ICallHandlerService.class.getName());
+        final ComponentName component = new ComponentName(context.getResources().getString(
+                R.string.incall_ui_default_package), context.getResources().getString(
+                R.string.incall_ui_default_class));
+        serviceIntent.setComponent(component);
+        return serviceIntent;
+    }
+
     /**
      * Sets up the connection with ICallHandlerService
      */
     private void setupServiceConnection() {
-        final Intent serviceIntent = new Intent(ICallHandlerService.class.getName());
-        final ComponentName component = new ComponentName(mContext.getResources().getString(
-                R.string.incall_ui_default_package), mContext.getResources().getString(
-                R.string.incall_ui_default_class));
-        serviceIntent.setComponent(component);
+        if (!PhoneGlobals.sVoiceCapable) {
+            return;
+        }
 
+        final Intent serviceIntent = getInCallServiceIntent(mContext);
         if (DBG) {
             Log.d(TAG, "binding to service " + serviceIntent);
         }
 
-        final PackageManager packageManger = mContext.getPackageManager();
-        final List<ResolveInfo> services = packageManger.queryIntentServices(serviceIntent, 0);
-        if (services.size() == 0) {
-            // Service not found, retry again after some delay
-            // This can happen if the service is being installed by the package manager.  Between
-            // deletes and installs, bindService could get a silent service not found error.
-            mBindRetryCount++;
-            if (mBindRetryCount < MAX_RETRY_COUNT) {
-                Log.w(TAG, "InCallUI service not found. " + serviceIntent + ". This happens if " +
-                                "the service is being installed and should be transient. Retrying" +
-                                RETRY_DELAY_MILLIS + " ms.");
-                sendMessageDelayed(Message.obtain(this, BIND_RETRY_MSG), RETRY_DELAY_MILLIS);
-            } else {
-                Log.e(TAG, "Tried to bind to in-call UI " + MAX_RETRY_COUNT + " times."
-                        + " Giving up.");
-            }
-            return;
-        }
-
         synchronized (mServiceAndQueueLock) {
-            if (mCallHandlerServiceGuarded == null) {
+            if (mConnection == null) {
+                mConnection = new InCallServiceConnection();
+
+                final PackageManager packageManger = mContext.getPackageManager();
+                final List<ResolveInfo> services = packageManger.queryIntentServices(serviceIntent,
+                        0);
+                if (services.size() == 0) {
+                    // Service not found, retry again after some delay
+                    // This can happen if the service is being installed by the package manager.
+                    // Between deletes and installs, bindService could get a silent service not
+                    // found error.
+                    mBindRetryCount++;
+                    if (mBindRetryCount < MAX_RETRY_COUNT) {
+                        Log.w(TAG, "InCallUI service not found. " + serviceIntent
+                                + ". This happens if the service is being installed and should be"
+                                + " transient. Retrying" + RETRY_DELAY_MILLIS + " ms.");
+                        sendMessageDelayed(Message.obtain(this, BIND_RETRY_MSG),
+                                RETRY_DELAY_MILLIS);
+                    } else {
+                        Log.e(TAG, "Tried to bind to in-call UI " + MAX_RETRY_COUNT + " times."
+                                + " Giving up.");
+                    }
+                    return;
+                }
+
+                if (DBG) {
+                    Log.d(TAG, "binding to service " + serviceIntent);
+                }
                 if (!mContext.bindService(serviceIntent, mConnection, Context.BIND_AUTO_CREATE)) {
-                    // This happens when the in-call package is in the middle of being installed.
+                    // This happens when the in-call package is in the middle of being
+                    // installed.
                     // Delay the retry.
                     mBindRetryCount++;
                     if (mBindRetryCount < MAX_RETRY_COUNT) {
-                        Log.e(TAG, "bindService failed on " + serviceIntent + ".  Retrying in " +
-                                RETRY_DELAY_MILLIS + " ms.");
+                        Log.e(TAG, "bindService failed on " + serviceIntent + ".  Retrying in "
+                                + RETRY_DELAY_MILLIS + " ms.");
                         sendMessageDelayed(Message.obtain(this, BIND_RETRY_MSG),
                                 RETRY_DELAY_MILLIS);
                     } else {
@@ -299,10 +348,24 @@
                                 + " Giving up.");
                     }
                 }
+
+            } else {
+                Log.d(TAG, "Service connection to in call service already started.");
             }
         }
     }
 
+    private void unbind() {
+        synchronized (mServiceAndQueueLock) {
+            if (mCallHandlerServiceGuarded != null) {
+                Log.d(TAG, "Unbinding service.");
+                mCallHandlerServiceGuarded = null;
+                mContext.unbindService(mConnection);
+            }
+            mConnection = null;
+        }
+    }
+
     /**
      * Called when the in-call UI service is connected.  Send command interface to in-call.
      */
@@ -356,25 +419,28 @@
     }
 
     private void processQueue() {
-        List<QueueParams> queue = getQueue();
-        for (QueueParams params : queue) {
-            switch (params.mMethod) {
-                case QueueParams.METHOD_INCOMING:
-                    onIncoming((Call) params.mArg);
-                    break;
-                case QueueParams.METHOD_UPDATE:
-                    onUpdate((List<Call>) params.mArg);
-                    break;
-                case QueueParams.METHOD_DISCONNECT:
-                    onDisconnect((Call) params.mArg);
-                    break;
-                default:
-                    throw new IllegalArgumentException("Method type " + params.mMethod +
-                            " not recognized.");
+        synchronized (mServiceAndQueueLock) {
+            if (mQueue != null) {
+                for (QueueParams params : mQueue) {
+                    switch (params.mMethod) {
+                        case QueueParams.METHOD_INCOMING:
+                            processIncoming((Call) params.mArg);
+                            break;
+                        case QueueParams.METHOD_UPDATE:
+                            processUpdate((List<Call>) params.mArg);
+                            break;
+                        case QueueParams.METHOD_DISCONNECT:
+                            processDisconnect((Call) params.mArg);
+                            break;
+                        default:
+                            throw new IllegalArgumentException("Method type " + params.mMethod +
+                                    " not recognized.");
+                    }
+                }
+                mQueue.clear();
+                mQueue = null;
             }
         }
-        mQueue.clear();
-        mQueue = null;
     }
 
     /**