Simplify deallocation/unbinding of services.

Call counts of call services and selectors are kept current during call
and during the outgoing call process. This allows us to unbind simply
when the call-count goes down to 0.

A second optimization that can be made is to remove associated-call
counts from ServiceBinder and use the callIdMapper to maintain counts of
the associated calls. This binds the call count to the mapper items,
however there are two small roadblocks:
1. It isn't as easy to deal with the replace() scenario, but doable
2. The caller-ID mapper implementations between CS and selectors are
   separate and it's nice to keep a single associated count implementation
   for all ServiceBinders...this is also addressable, just not that
   important at the moment.

Change-Id: Ibbf894ed5b7dd9ede1b088e530dd9cc2e0e649c2
diff --git a/src/com/android/telecomm/ServiceBinder.java b/src/com/android/telecomm/ServiceBinder.java
index b5c0050..3dc86be 100644
--- a/src/com/android/telecomm/ServiceBinder.java
+++ b/src/com/android/telecomm/ServiceBinder.java
@@ -25,6 +25,8 @@
 
 import com.google.common.base.Preconditions;
 import com.google.common.base.Strings;
+
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 
 import java.util.Set;
@@ -40,8 +42,16 @@
      * Callback to notify after a binding succeeds or fails.
      */
     interface BindCallback {
-        public void onSuccess();
-        public void onFailure();
+        void onSuccess();
+        void onFailure();
+    }
+
+    /**
+     * Listener for bind events on ServiceBinder.
+     */
+    interface Listener {
+        @SuppressWarnings("rawtypes")
+        void onUnbind(ServiceBinder serviceBinder);
     }
 
     /**
@@ -89,10 +99,12 @@
         @Override
         public void onServiceConnected(ComponentName componentName, IBinder binder) {
             ThreadUtil.checkOnMainThread();
+            Log.i(this, "Service bound %s", componentName);
 
             // Unbind request was queued so unbind immediately.
             if (mIsBindingAborted) {
                 clearAbort();
+                logServiceDisconnected("onServiceConnected");
                 mContext.unbindService(this);
                 handleFailedConnection();
                 return;
@@ -105,6 +117,8 @@
 
         @Override
         public void onServiceDisconnected(ComponentName componentName) {
+            logServiceDisconnected("onServiceDisconnected");
+
             mServiceConnection = null;
             clearAbort();
 
@@ -130,7 +144,6 @@
     /** The binder provided by {@link ServiceConnection#onServiceConnected} */
     private IBinder mBinder;
 
-    /** The number of calls currently associated with this service. */
     private int mAssociatedCallCount = 0;
 
     /**
@@ -140,6 +153,11 @@
     private boolean mIsBindingAborted;
 
     /**
+     * Set of currently registered listeners.
+     */
+    private Set<Listener> mListeners = Sets.newHashSet();
+
+    /**
      * Persists the specified parameters and initializes the new instance.
      *
      * @param serviceAction The intent-action used with {@link Context#bindService}.
@@ -156,11 +174,19 @@
 
     final void incrementAssociatedCallCount() {
         mAssociatedCallCount++;
+        Log.v(this, "Call count increment %d, %s", mAssociatedCallCount,
+                mComponentName.flattenToShortString());
     }
 
     final void decrementAssociatedCallCount() {
         if (mAssociatedCallCount > 0) {
             mAssociatedCallCount--;
+            Log.v(this, "Call count decrement %d, %s", mAssociatedCallCount,
+                    mComponentName.flattenToShortString());
+
+            if (mAssociatedCallCount == 0) {
+                unbind();
+            }
         } else {
             Log.wtf(this, "%s: ignoring a request to decrement mAssociatedCallCount below zero",
                     mComponentName.getClassName());
@@ -181,6 +207,7 @@
             // We're not yet bound, so queue up an abort request.
             mIsBindingAborted = true;
         } else {
+            logServiceDisconnected("unbind");
             mContext.unbindService(mServiceConnection);
             mServiceConnection = null;
             setBinder(null);
@@ -200,6 +227,26 @@
         return true;
     }
 
+    final void addListener(Listener listener) {
+        mListeners.add(listener);
+    }
+
+    final void removeListener(Listener listener) {
+        mListeners.remove(listener);
+    }
+
+    /**
+     * Logs a standard message upon service disconnection. This method exists because there is no
+     * single method called whenever the service unbinds and we want to log the same string in all
+     * instances where that occurs.  (Context.unbindService() does not cause onServiceDisconnected
+     * to execute).
+     *
+     * @param sourceTag Tag to disambiguate
+     */
+    private void logServiceDisconnected(String sourceTag) {
+        Log.i(this, "Service unbound %s, from %s.", mComponentName, sourceTag);
+    }
+
     /**
      * Notifies all the outstanding callbacks that the service is successfully bound. The list of
      * outstanding callbacks is cleared afterwards.
@@ -239,8 +286,19 @@
      * @param binder The new binder value.
      */
     private void setBinder(IBinder binder) {
-        mBinder = binder;
-        setServiceInterface(binder);
+        if (mBinder != binder) {
+            mBinder = binder;
+
+            setServiceInterface(binder);
+
+            if (binder == null) {
+                // Use a copy of the listener list to allow the listeners to unregister themselves
+                // as part of the unbind without causing issues.
+                for (Listener l : ImmutableSet.copyOf(mListeners)) {
+                    l.onUnbind(this);
+                }
+            }
+        }
     }
 
     /**