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/BinderDeallocator.java b/src/com/android/telecomm/BinderDeallocator.java
deleted file mode 100644
index 22f0758..0000000
--- a/src/com/android/telecomm/BinderDeallocator.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * 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 com.google.common.collect.Sets;
-
-import java.util.Iterator;
-import java.util.Set;
-
-/**
- * Keeps track of all outstanding binder connections and provides a facility to clean up unnecessary
- * binders (after initializing outgoing or incoming calls). This in turn allows the classes involved
- * in incoming and outgoing initialization to not have to unbind at all possible failure conditions,
- * which are numerous.
- *
- * To provide (theoretical) context, the two candidate approaches seem to be (1) provide a notion of
- * guaranteed outcome to every attempted action only deallocating resources upon no pending actions,
- * and (2) while other outcomes may be provided, only guarantee the potential outcome of success and
- * then rely on timeouts to treat all expired actions as failures. Since Telecomm pretty much has to
- * deal with timeouts (e.g. due to relying on third-party code), it seems to make little or no sense
- * to guarantee an outcome to every attempted action particularly via using timeouts. Instead, since
- * relying on timeouts is required in both cases, using a centralized timeout solution appears to be
- * beneficial. This gives rise to the following implementation. Any time the switchboard enters what
- * can be thought of as a critical section, it is likely to cause certain resources to be created or
- * bounded. Additional resources may be created and/or bounded throughout that section just as well.
- * To account for that, the switchboard is expected to acquire a use permit and then release it once
- * the critical section is exited.  Since two or more critical sections may overlap (by design), it
- * is imperative that no resources are deallocated until the last critical section is exited.  This
- * ensures that resources that have been obtained but not yet used aren't identified as unused which
- * would otherwise lead to their removal.  This also allows switchboard to maintain a single timeout
- * loop, freeing for example the incoming/outgoing call managers from needing to implement the same.
- * Once the switchboard expires certain actions (e.g. pending outgoing calls) it should also release
- * the corresponding permit, just as it would upon explicit success/failure outcomes.  Subsequently,
- * once all pending permits are released, a resource-deallocation cycle can safely follow.  In terms
- * of implementation, there is no real need to associate specific resources with the action/s during
- * which these resources turned up to be necessary such that counting permits seems sufficient.
- */
-final class BinderDeallocator {
-
-    /**
-     * The number of actions currently permitted to use previously-allocated resources and/or create
-     * new ones.
-     */
-    private int mPermitCount = 0;
-
-    /**
-     * The set of all known binders, either in use or potentially about to be used.
-     */
-    @SuppressWarnings("rawtypes")
-    private final Set<ServiceBinder> mBinders = Sets.newHashSet();
-
-    /**
-     * Accounts for the action entering a critical section (i.e. potentially needing access to
-     * resources).
-     */
-    void acquireUsePermit() {
-        ThreadUtil.checkOnMainThread();
-
-        mPermitCount++;
-    }
-
-    /**
-     * Updates the set of binders.
-     *
-     * @param binders The binders to potentially add to the all-inclusive set of known binders,
-     *     see {@link #mBinders}.
-     */
-    @SuppressWarnings("rawtypes")
-    void updateBinders(Set<? extends ServiceBinder> binders) {
-        ThreadUtil.checkOnMainThread();
-
-        if (binders != null) {
-            mBinders.addAll(binders);
-        }
-    }
-
-    /**
-     * Accounts for the action exiting a critical section (i.e. no longer needing access to
-     * resources).
-     */
-    void releaseUsePermit() {
-        ThreadUtil.checkOnMainThread();
-
-        if (mPermitCount < 1) {
-            Log.wtf(this, "releaseUsePermit should only be invoked upon mPermitCount > 0");
-        } else if (--mPermitCount == 0) {
-            deallocateUnusedResources();
-        }
-    }
-
-    /**
-     * Starts a resource-deallocation cycle.
-     */
-    @SuppressWarnings("rawtypes")
-    private void deallocateUnusedResources() {
-        Iterator<ServiceBinder> iterator = mBinders.iterator();
-        while (iterator.hasNext()) {
-            ServiceBinder binder = iterator.next();
-            if (binder.getAssociatedCallCount() < 1) {
-                binder.unbind();
-                mBinders.remove(binder);
-            }
-        }
-    }
-}
diff --git a/src/com/android/telecomm/Call.java b/src/com/android/telecomm/Call.java
index 024d7d4..e21f3de 100644
--- a/src/com/android/telecomm/Call.java
+++ b/src/com/android/telecomm/Call.java
@@ -278,9 +278,17 @@
      */
     void clearCallService() {
         if (mCallService != null) {
-            decrementAssociatedCallCount(mCallService);
-            mCallService.removeCall(this);
+            CallServiceWrapper callServiceTemp = mCallService;
             mCallService = null;
+            callServiceTemp.removeCall(this);
+
+            // Decrementing the count can cause the service to unbind, which itself can trigger the
+            // service-death code.  Since the service death code tries to clean up any associated
+            // calls, we need to make sure to remove that information (e.g., removeCall()) before
+            // we decrement. Technically, invoking removeCall() prior to decrementing is all that is
+            // necessary, but cleaning up mCallService prior to triggering an unbind is good to do.
+            // If you change this, make sure to update {@link clearCallServiceSelector} as well.
+            decrementAssociatedCallCount(callServiceTemp);
         }
     }
 
@@ -293,16 +301,19 @@
 
         clearCallServiceSelector();
 
+        selector.incrementAssociatedCallCount();
         mCallServiceSelector = selector;
         mCallServiceSelector.addCall(this);
     }
 
     void clearCallServiceSelector() {
         if (mCallServiceSelector != null) {
-            // TODO(sail): Stop leaking selectors.
-            // decrementAssociatedCallCount(mCallServiceSelector);
-            mCallServiceSelector.removeCall(this);
+            CallServiceSelectorWrapper selectorTemp = mCallServiceSelector;
             mCallServiceSelector = null;
+            selectorTemp.removeCall(this);
+
+            // See comment on {@link #clearCallService}.
+            decrementAssociatedCallCount(selectorTemp);
         }
     }
 
diff --git a/src/com/android/telecomm/CallServiceRepository.java b/src/com/android/telecomm/CallServiceRepository.java
index 50ef977..c58a332 100644
--- a/src/com/android/telecomm/CallServiceRepository.java
+++ b/src/com/android/telecomm/CallServiceRepository.java
@@ -29,6 +29,8 @@
 import com.android.internal.telecomm.ICallServiceLookupResponse;
 import com.android.internal.telecomm.ICallServiceProvider;
 import com.google.common.base.Preconditions;
+
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
@@ -90,6 +92,11 @@
     private final Map<ComponentName, CallServiceWrapper> mCallServices = Maps.newHashMap();
 
     /**
+     * The set of call-service components found during a single lookup.
+     */
+    private final Set<ComponentName> mFoundCallServices = Sets.newHashSet();
+
+    /**
      * Persists the specified parameters.
      *
      * @param switchboard The switchboard.
@@ -120,6 +127,7 @@
             return;
         }
 
+        mFoundCallServices.clear();
         List<ComponentName> providerNames = getProviderNames();
         if (providerNames.isEmpty()) {
             Log.i(this, "No ICallServiceProvider implementations found, bailing out.");
@@ -156,28 +164,6 @@
     }
 
     /**
-     * Iterates through the map of active services and removes the ones that are not associated
-     * with active calls.
-     * TODO(gilad): Invoke this from Switchboard upon resource deallocation cycles.
-     */
-    void purgeInactiveCallServices() {
-        Iterator<ComponentName> iterator = mCallServices.keySet().iterator();
-        while (iterator.hasNext()) {
-            ComponentName callServiceName = iterator.next();
-            CallServiceWrapper callService = mCallServices.get(callServiceName);
-
-            // TODO(gilad): Either add ICallService.getActiveCallCount() or have this tracked by the
-            // Switchboard if we rather not rely on 3rd-party code to do the bookkeeping for us. If
-            // we prefer the latter, we can also have purgeInactiveCallService(descriptor).
-            // Otherwise this might look something like:
-            //
-            // if (callService.getActiveCallCount() < 1) {
-            //     mCallServices.remove(callServiceName);
-            // }
-        }
-    }
-
-    /**
      * Returns the all-inclusive list of call-service-provider names.
      *
      * @return The list containing the (component) names of all known ICallServiceProvider
@@ -258,6 +244,7 @@
         if (mOutstandingProviders.contains(providerName)) {
             // Add all the call services from this provider to the call-service cache.
             for (CallServiceDescriptor descriptor : callServiceDescriptors) {
+                mFoundCallServices.add(descriptor.getServiceComponent());
                 registerCallService(descriptor);
             }
 
@@ -310,6 +297,22 @@
         mHandler.removeCallbacks(mTimeoutLookupTerminator);
         mOutstandingProviders.clear();
 
+        // Clean out any old call services. Since the set of call services can change (app installs,
+        // uninstalls, etc.) between lookups, we need to make sure that we purge our registry
+        // of any old call services (call services which were not found in this lookup). However,
+        // we dont purge any call services that might still have calls associated with them.
+        for (ComponentName component : ImmutableSet.copyOf(mCallServices.keySet().iterator())) {
+            if (!mFoundCallServices.contains(component)) {
+                // This component is registered, but was not found in the latest lookup...so try
+                // to remove it.
+                CallServiceWrapper callService = mCallServices.get(component);
+                if (callService.getAssociatedCallCount() < 1) {
+                    mCallServices.remove(component);
+                }
+            }
+        }
+        mFoundCallServices.clear();
+
         updateSwitchboard();
         mIsLookupInProgress = false;
     }
diff --git a/src/com/android/telecomm/CallServiceSelectorRepository.java b/src/com/android/telecomm/CallServiceSelectorRepository.java
index 709d440..90e2d55 100644
--- a/src/com/android/telecomm/CallServiceSelectorRepository.java
+++ b/src/com/android/telecomm/CallServiceSelectorRepository.java
@@ -17,7 +17,6 @@
 package com.android.telecomm;
 
 import android.content.ComponentName;
-import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
@@ -26,6 +25,7 @@
 
 import com.android.internal.telecomm.ICallServiceSelector;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 
@@ -36,7 +36,7 @@
  * Helper class to retrieve {@link ICallServiceSelector} implementations on the device and
  * asynchronously bind to them.
  */
-final class CallServiceSelectorRepository {
+final class CallServiceSelectorRepository implements ServiceBinder.Listener {
 
     private final Switchboard mSwitchboard;
     private final OutgoingCallsManager mOutgoingCallsManager;
@@ -68,15 +68,57 @@
         ThreadUtil.checkOnMainThread();
 
         List<ComponentName> selectorNames = getSelectorNames();
+        List<CallServiceSelectorWrapper> foundSelectors = Lists.newLinkedList();
+
+        // Register any new selectors.
         for (ComponentName name : selectorNames) {
-            if (!mCallServiceSelectors.containsKey(name)) {
-                mCallServiceSelectors.put(name, new CallServiceSelectorWrapper(name,
-                        CallsManager.getInstance(), mOutgoingCallsManager));
+            CallServiceSelectorWrapper selector = mCallServiceSelectors.get(name);
+            if (selector == null) {
+                selector = createWrapper(name);
+                mCallServiceSelectors.put(name, selector);
+            }
+
+            if (TelephonyUtil.isTelephonySelector(selector)) {
+                // Add telephony selectors to the end to serve as a fallback.
+                foundSelectors.add(selector);
+            } else {
+                // TODO(sail): Need a way to order selectors.
+                foundSelectors.add(0, selector);
             }
         }
 
         Log.i(this, "Found %d implementations of ICallServiceSelector", selectorNames.size());
-        updateSwitchboard();
+        updateSwitchboard(foundSelectors);
+    }
+
+    /**
+     * Removes the specified selector (as a ServiceBinder) from the map of registered selectors.
+     *
+     * {@inheritDoc}
+     */
+    @SuppressWarnings("rawtypes")
+    @Override
+    public void onUnbind(ServiceBinder serviceBinder) {
+        if (serviceBinder instanceof CallServiceSelectorWrapper) {
+            CallServiceSelectorWrapper selector = (CallServiceSelectorWrapper) serviceBinder;
+            mCallServiceSelectors.remove(selector.getComponentName());
+        } else {
+            Log.wtf(this, "Received unbind notice from non-selector: %s.",
+                    serviceBinder.getComponentName().flattenToShortString());
+        }
+    }
+
+    /**
+     * Creates a wrapper for the specified component name and starts listening to it's unbind event.
+     *
+     * @param componentName The component name of the call-service selector.
+     * @return The wrapper for the selector.
+     */
+    private CallServiceSelectorWrapper createWrapper(ComponentName componentName) {
+        CallServiceSelectorWrapper selector = new CallServiceSelectorWrapper(
+                componentName, CallsManager.getInstance(), mOutgoingCallsManager);
+        selector.addListener(this);
+        return selector;
     }
 
     /**
@@ -104,21 +146,10 @@
 
     /**
      * Updates the switchboard passing the relevant call services selectors.
+     *
+     * @param selectors The selectors found during lookup.
      */
-    private void updateSwitchboard() {
-        ThreadUtil.checkOnMainThread();
-
-        List<CallServiceSelectorWrapper> selectors = Lists.newLinkedList();
-        for (CallServiceSelectorWrapper selector : mCallServiceSelectors.values()) {
-            if (TelephonyUtil.isTelephonySelector(selector)) {
-                // Add telephony selectors to the end to serve as a fallback.
-                selectors.add(selector);
-            } else {
-                // TODO(sail): Need a way to order selectors.
-                selectors.add(0, selector);
-            }
-        }
-
-        mSwitchboard.setSelectors(ImmutableList.copyOf((selectors)));
+    private void updateSwitchboard(List<CallServiceSelectorWrapper> selectors) {
+        mSwitchboard.setSelectors(ImmutableList.copyOf(selectors));
     }
 }
diff --git a/src/com/android/telecomm/CallsManager.java b/src/com/android/telecomm/CallsManager.java
index 81243a9..04dd9ad 100644
--- a/src/com/android/telecomm/CallsManager.java
+++ b/src/com/android/telecomm/CallsManager.java
@@ -550,6 +550,7 @@
     private void removeCall(Call call) {
         // If a handoff is pending then the original call shouldn't be removed.
         Preconditions.checkState(call.getHandoffCallServiceDescriptor() == null);
+        Log.v(this, "removeCall(%s)", call);
 
         call.clearCallService();
         call.clearCallServiceSelector();
diff --git a/src/com/android/telecomm/OutgoingCallsManager.java b/src/com/android/telecomm/OutgoingCallsManager.java
index def5211..7bbfd04 100644
--- a/src/com/android/telecomm/OutgoingCallsManager.java
+++ b/src/com/android/telecomm/OutgoingCallsManager.java
@@ -162,12 +162,17 @@
      * Aborts any ongoing attempts to connect the specified (outgoing) call.
      *
      * @param call The call to be aborted.
+     * @return False if the call was not found; True otherwise, indicating that the abort was
+     *         successful.
      */
-    void abort(Call call) {
+    boolean abort(Call call) {
         Log.v(this, "abort, call: %s", call);
         OutgoingCallProcessor processor = mOutgoingCallProcessors.remove(call);
         if (processor != null) {
             processor.abort();
+            return true;
         }
+
+        return false;
     }
 }
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);
+                }
+            }
+        }
     }
 
     /**
diff --git a/src/com/android/telecomm/Switchboard.java b/src/com/android/telecomm/Switchboard.java
index fdc271e..82f94fa 100644
--- a/src/com/android/telecomm/Switchboard.java
+++ b/src/com/android/telecomm/Switchboard.java
@@ -20,6 +20,8 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
+
+import android.os.Message;
 import android.telecomm.CallInfo;
 import android.telecomm.CallServiceDescriptor;
 import android.telecomm.TelecommConstants;
@@ -41,6 +43,7 @@
  * - switching active calls between call services.
  */
 final class Switchboard {
+    private final static int MSG_EXPIRE_STALE_CALL = 1;
 
     private final CallsManager mCallsManager;
 
@@ -54,22 +57,16 @@
 
     private final CallServiceSelectorRepository mSelectorRepository;
 
-    private final BinderDeallocator mBinderDeallocator;
-
     /** Used to schedule tasks on the main (UI) thread. */
-    private final Handler mHandler = new Handler(Looper.getMainLooper());
-
-    /**
-     * Executes a single tick task and potentially schedules the next such that polling continues
-     * as long as necessary.
-     * NOTE(gilad): by design no two tick invocations should ever overlap.
-     */
-    private final Runnable mTicker = new Runnable() {
+    private final Handler mHandler = new Handler() {
         @Override
-        public void run() {
-            tick();
-            if (isTicking()) {
-                scheduleNextTick();
+        public void handleMessage(Message msg) {
+            switch(msg.what) {
+                case MSG_EXPIRE_STALE_CALL:
+                    expireStaleCall((Call) msg.obj);
+                    break;
+                default:
+                    Log.wtf(Switchboard.this, "Unexpected message %d.", msg.what);
             }
         }
     };
@@ -102,14 +99,14 @@
      * Persists the specified parameters and initializes Switchboard.
      */
     Switchboard(CallsManager callsManager) {
+        ThreadUtil.checkOnMainThread();
+
         mCallsManager = callsManager;
         mOutgoingCallsManager = new OutgoingCallsManager(this);
         mIncomingCallsManager = new IncomingCallsManager(this);
         mSelectorRepository = new CallServiceSelectorRepository(this, mOutgoingCallsManager);
         mCallServiceRepository =
                 new CallServiceRepository(this, mOutgoingCallsManager, mIncomingCallsManager);
-
-        mBinderDeallocator = new BinderDeallocator();
     }
 
     /**
@@ -120,8 +117,6 @@
      * @param call The yet-to-be-connected outgoing-call object.
      */
     void placeOutgoingCall(Call call) {
-        mBinderDeallocator.acquireUsePermit();
-
         // Reset prior to initiating the next lookup. One case to consider is (1) placeOutgoingCall
         // is invoked with call A, (2) the call-service lookup completes, but the one for selectors
         // does not, (3) placeOutgoingCall is invoked again with call B, (4) mCallServices below is
@@ -137,6 +132,9 @@
         mLookupId++;
         mCallServiceRepository.initiateLookup(mLookupId);
         mSelectorRepository.initiateLookup(mLookupId);
+
+        Message msg = mHandler.obtainMessage(MSG_EXPIRE_STALE_CALL, call);
+        mHandler.sendMessageDelayed(msg, Timeouts.getNewOutgoingCallMillis());
     }
 
     /**
@@ -151,8 +149,6 @@
      */
     void retrieveIncomingCall(Call call, CallServiceDescriptor descriptor, Bundle extras) {
         Log.d(this, "retrieveIncomingCall");
-        mBinderDeallocator.acquireUsePermit();
-
         CallServiceWrapper callService = mCallServiceRepository.getCallService(descriptor);
         call.setCallService(callService);
         mIncomingCallsManager.retrieveIncomingCall(call, extras);
@@ -167,8 +163,6 @@
      *     hence effectively omitted from the specified list.
      */
     void setCallServices(Set<CallServiceWrapper> callServices) {
-        mBinderDeallocator.updateBinders(mCallServices);
-
         mCallServices.clear();
         mCallServices.addAll(callServices);
         processNewOutgoingCalls();
@@ -182,7 +176,6 @@
      *     which the selectors are tried.
      */
     void setSelectors(ImmutableCollection<CallServiceSelectorWrapper> selectors) {
-        // TODO(santoscordon):: Need to invoke updateBinders(selectors).
         ThreadUtil.checkOnMainThread();
         Preconditions.checkNotNull(selectors);
 
@@ -225,7 +218,6 @@
         Log.d(this, "handleSuccessfulIncomingCall");
 
         mCallsManager.handleSuccessfulIncomingCall(call, callInfo);
-        mBinderDeallocator.releaseUsePermit();
     }
 
     /**
@@ -240,9 +232,6 @@
         // Since we set the call service before calling into incoming-calls manager, we clear it for
         // good measure if an error is reported.
         call.clearCallService();
-
-        mBinderDeallocator.releaseUsePermit();
-
         mCallsManager.handleUnsuccessfulIncomingCall(call);
 
         // At the moment there is nothing more to do if an incoming call is not retrieved. We may at
@@ -262,22 +251,6 @@
     }
 
     /**
-     * @return True if ticking should continue (or be resumed) and false otherwise.
-     */
-    private boolean isTicking() {
-        // TODO(gilad): return true every time at least one outgoing call is pending (i.e. waiting
-        // to be connected by a call service).
-        return false;
-    }
-
-    /**
-     * Schedules the next tick invocation.
-     */
-    private void scheduleNextTick() {
-         mHandler.postDelayed(mTicker, Timeouts.getTickMillis());
-    }
-
-    /**
      * Attempts to process the next new outgoing calls that have not yet been expired.
      */
     private void processNewOutgoingCalls() {
@@ -345,48 +318,29 @@
     private void finalizeOutgoingCall(Call call) {
         mPendingOutgoingCalls.remove(call);
 
-        mBinderDeallocator.releaseUsePermit();
         processNewOutgoingCalls();  // Process additional (new) calls, if any.
     }
 
     /**
-     * Performs the set of tasks that needs to be executed on polling basis.
-     */
-    private void tick() {
-        // TODO(gilad): Add the necessary logic to support switching.
-
-        expireStaleOutgoingCalls(mNewOutgoingCalls);
-        expireStaleOutgoingCalls(mPendingOutgoingCalls);
-    }
-
-    /**
-     * Identifies stale calls and takes the necessary steps to mark these as expired.
+     * Expires calls which are taking too long to connect.
      *
-     * @param calls The set of calls to iterate through.
+     * @param call The call to expire.
      */
-    private void expireStaleOutgoingCalls(Set<Call> calls) {
-        if (calls.isEmpty()) {
-            return;
+    private void expireStaleCall(Call call) {
+        final long newCallTimeoutMillis = Timeouts.getNewOutgoingCallMillis();
+
+        if (call.getAgeMillis() < newCallTimeoutMillis) {
+            Log.wtf(this, "Expiring a call early. Age: %d, Time since attempt: %d",
+                    call.getAgeMillis(), newCallTimeoutMillis);
         }
 
-        final long newCallTimeoutMillis = Timeouts.getNewOutgoingCallMillis();
-        Iterator<Call> iterator = calls.iterator();
-        while (iterator.hasNext()) {
-            Call call = iterator.next();
-            if (call.getAgeMillis() >= newCallTimeoutMillis) {
-                Log.d(this, "Call %s timed out.", call);
-                mOutgoingCallsManager.abort(call);
-                calls.remove(call);
-
-                // TODO(gilad): We may also have expired calls that are not yet associated with an
-                // OutgoingCallProcessor (e.g. when newer calls are "blocked" on older-yet-expired
-                // ones), in which case call.abort may need to be invoked directly.  Alternatively
-                // we can also create an OutgoingCallsManager instance for every new call at intent-
-                // processing time.
-
-                // TODO(gilad): Notify the user in the relevant cases (at least outgoing).
-
-                mBinderDeallocator.releaseUsePermit();
+        if (mNewOutgoingCalls.remove(call)) {
+            // The call had not yet been processed so all we have to do is report the
+            // failure.
+            handleFailedOutgoingCall(call, true /* isAborted */);
+        } else if (mPendingOutgoingCalls.remove(call)) {
+            if (!mOutgoingCallsManager.abort(call)) {
+                Log.wtf(this, "Pending call failed to abort, call: %s.", call);
             }
         }
     }
diff --git a/src/com/android/telecomm/Timeouts.java b/src/com/android/telecomm/Timeouts.java
index 4f8dc1c..0221ccd 100644
--- a/src/com/android/telecomm/Timeouts.java
+++ b/src/com/android/telecomm/Timeouts.java
@@ -54,20 +54,10 @@
     }
 
     /**
-     * @return How frequently, in milliseconds, to run {@link Switchboard}'s clean-up "tick" cycle.
-     */
-    public static long getTickMillis() {
-        return get("tick_ms", 250);
-    }
-
-    /**
      * Returns the longest period, in milliseconds, each new outgoing call is allowed to wait before
      * being established. If the call does not connect before this time, abort the call.
-     *
-     * @return The longest period, in milliseconds, each new call is allowed to wait before being
-     *     established.
      */
     public static long getNewOutgoingCallMillis() {
-        return get("new_outgoing_call_ms", 5000);
+        return get("new_outgoing_call_ms", 60 * 1000L);
     }
 }
diff --git a/tests/src/com/android/telecomm/testcallservice/DummyCallServiceSelector.java b/tests/src/com/android/telecomm/testcallservice/DummyCallServiceSelector.java
index 86319a4..28085ea 100644
--- a/tests/src/com/android/telecomm/testcallservice/DummyCallServiceSelector.java
+++ b/tests/src/com/android/telecomm/testcallservice/DummyCallServiceSelector.java
@@ -33,14 +33,12 @@
 public class DummyCallServiceSelector extends CallServiceSelector {
     private static DummyCallServiceSelector sInstance;
     private static final String SCHEME_TEL = "tel";
-    private static final String TELEPHONY_PACKAGE_NAME =
-            "com.android.phone";
+    private static final String TELEPHONY_PACKAGE_NAME = "com.android.phone";
     private static final String CUSTOM_HANDOFF_KEY = "custom_handoff_key";
     private static final String CUSTOM_HANDOFF_VALUE = "custom_handoff_value";
 
     public DummyCallServiceSelector() {
         log("constructor");
-        Preconditions.checkState(sInstance == null);
         sInstance = this;
     }