Simplify Respositories, Switchboard and Switchboard-indirection.

This is a step into separating Call into Call + Connection.

Changes:
1. Update Repositories to:
  A. Share code via the new BaseRepository
  B. Perform a lookup per call instead of attempting to have 1 lookup
     work for concurrent calls. This allowed removal of extra state
     out of the repositories (mIsLookupInProgress) and out of
     Switchboard (mNewOutgoingCalls, mPendingOutgoingCalls, etc).
2. Add a OutgoingCallEntry class to Switchboard to support 1 service
   lookup per outgoing call. The new class maintains the necessary
   state (CS collection & selector collection).
3. Outgoing/IncomingCallsManager now reports success/failure directly
   to the Call class instead of indirecting through the switchboard.
4. Switchboard, for the time being, kept the outgoing call timeout and
   triggers it through OutgoingCallEntry.

Change-Id: I01196dd5384ad256cf09035018a76abaadb2c04d
diff --git a/src/com/android/telecomm/Switchboard.java b/src/com/android/telecomm/Switchboard.java
index 81ab172..5b8d25d 100644
--- a/src/com/android/telecomm/Switchboard.java
+++ b/src/com/android/telecomm/Switchboard.java
@@ -24,6 +24,7 @@
 import android.telecomm.CallServiceDescriptor;
 import android.telecomm.TelecommConstants;
 
+import com.android.telecomm.BaseRepository.LookupCallback;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableCollection;
 import com.google.common.collect.ImmutableList;
@@ -37,9 +38,81 @@
  * - gathering the {@link CallServiceWrapper}s and {@link CallServiceSelectorWrapper}s through
  *       which to place outgoing calls
  * - starting outgoing calls (via {@link OutgoingCallsManager}
- * - switching active calls between call services.
  */
 final class Switchboard {
+    /**
+     * Encapsulates a request to place an outgoing call.
+     * TODO(santoscordon): Move this state into Call and remove this class.
+     */
+    private final class OutgoingCallEntry {
+        final Call call;
+
+        private Collection<CallServiceWrapper> mCallServices;
+        private Collection<CallServiceSelectorWrapper> mSelectors;
+        private boolean mIsCallPending = true;
+
+        OutgoingCallEntry(Call call) {
+            this.call = call;
+        }
+
+        /**
+         * Sets the call services to attempt for this outgoing call.
+         *
+         * @param callServices The call services.
+         */
+        void setCallServices(Collection<CallServiceWrapper> callServices) {
+            mCallServices = callServices;
+            onLookupComplete();
+        }
+
+        Collection<CallServiceWrapper> getCallServices() {
+            return mCallServices;
+        }
+
+        /**
+         * Sets the selectors to attemnpt for this outgoing call.
+         *
+         * @param selectors The call-service selectors.
+         */
+        void setSelectors(Collection<CallServiceSelectorWrapper> selectors) {
+            mSelectors = selectors;
+            onLookupComplete();
+        }
+
+        Collection<CallServiceSelectorWrapper> getSelectors() {
+            return mSelectors;
+        }
+
+        /** Expires the pending outgoing call and stops it from being made. */
+        void expire() {
+            // This can be executed in three states:
+            // 1) We are still waiting for the list of CSs (Call Services)
+            // 2) We told outgoing calls manager to place the call using the CSs
+            // 3) Outgoing calls manager already successfully placed the call.
+            if (mIsCallPending) {
+                // Handle state (1), tell the call to clean itself up and shut everything down.
+                mIsCallPending = false;
+                call.handleFailedOutgoing(true /* isAborted */);
+            } else {
+                // Handle states (2) & (3). We can safely call abort() in either case. If the call
+                // is not yet successful, then it will abort.  If the call was already placed, then
+                // outgoing calls manager will do nothing (and return false which we ignore).
+                boolean isAborted = mOutgoingCallsManager.abort(call);
+                Log.v(this, "expire() caused abort: %b", isAborted);
+            }
+        }
+
+        /** Initiates processing of the call once call-services and selectors are set. */
+        private void onLookupComplete() {
+            if (mIsCallPending) {
+                if (mSelectors != null && mCallServices != null) {
+                    mIsCallPending = false;
+                    processNewOutgoingCall(this);
+                }
+            }
+        }
+    }
+
     private final static int MSG_EXPIRE_STALE_CALL = 1;
 
     private final static Switchboard sInstance = new Switchboard();
@@ -60,7 +133,7 @@
         public void handleMessage(Message msg) {
             switch(msg.what) {
                 case MSG_EXPIRE_STALE_CALL:
-                    expireStaleCall((Call) msg.obj);
+                    ((OutgoingCallEntry) msg.obj).expire();
                     break;
                 default:
                     Log.wtf(Switchboard.this, "Unexpected message %d.", msg.what);
@@ -68,30 +141,6 @@
         }
     };
 
-    private final Set<Call> mNewOutgoingCalls = Sets.newLinkedHashSet();
-
-    private final Set<Call> mPendingOutgoingCalls = Sets.newLinkedHashSet();
-
-    /**
-     * The set of currently available call-service implementations, see
-     * {@link CallServiceRepository}.  Populated during call-service lookup cycles as part of the
-     * {@link #placeOutgoingCall} flow and cleared upon zero-remaining new/pending outgoing calls.
-     */
-    private final Set<CallServiceWrapper> mCallServices = Sets.newHashSet();
-
-    /**
-     * The set of currently available call-service-selector implementations,
-     * see {@link CallServiceSelectorRepository}.
-     * TODO(gilad): Clear once the active-call count goes to zero.
-     */
-    private ImmutableCollection<CallServiceSelectorWrapper> mSelectors = ImmutableList.of();
-
-    /**
-     * The current lookup-cycle ID used with the repositories. Incremented with each invocation
-     * of {@link #placeOutgoingCall} and passed to the repositories via initiateLookup().
-     */
-    private int mLookupId = 0;
-
     /** Singleton accessor. */
     static Switchboard getInstance() {
         return sInstance;
@@ -103,11 +152,11 @@
     private Switchboard() {
         ThreadUtil.checkOnMainThread();
 
-        mOutgoingCallsManager = new OutgoingCallsManager(this);
-        mIncomingCallsManager = new IncomingCallsManager(this);
-        mSelectorRepository = new CallServiceSelectorRepository(this, mOutgoingCallsManager);
+        mOutgoingCallsManager = new OutgoingCallsManager();
+        mIncomingCallsManager = new IncomingCallsManager();
+        mSelectorRepository = new CallServiceSelectorRepository(mOutgoingCallsManager);
         mCallServiceRepository =
-                new CallServiceRepository(this, mOutgoingCallsManager, mIncomingCallsManager);
+                new CallServiceRepository(mOutgoingCallsManager, mIncomingCallsManager);
     }
 
     /**
@@ -118,30 +167,30 @@
      * @param call The yet-to-be-connected outgoing-call object.
      */
     void placeOutgoingCall(Call call) {
-        // 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
-        // reset, (5) the selector lookup completes but the call-services are missing.  This should
-        // be okay since the call-service lookup completed. Specifically the already-available call
-        // services are cached and will be provided in response to the second lookup cycle.
-        mCallServices.clear();
-        mSelectors = ImmutableList.of();
+        final OutgoingCallEntry callEntry = new OutgoingCallEntry(call);
 
-        mNewOutgoingCalls.add(call);
+        // Lookup call services
+        mCallServiceRepository.lookupServices(new LookupCallback<CallServiceWrapper>() {
+            @Override
+            public void onComplete(Collection<CallServiceWrapper> services) {
+                callEntry.setCallServices(services);
+            }
+        });
 
-        // Initiate a lookup every time to account for newly-installed apps and/or updated settings.
-        mLookupId++;
-        mCallServiceRepository.initiateLookup(mLookupId);
-        mSelectorRepository.initiateLookup(mLookupId);
+        // Lookup selectors
+        mSelectorRepository.lookupServices(new LookupCallback<CallServiceSelectorWrapper>() {
+            @Override
+            public void onComplete(Collection<CallServiceSelectorWrapper> selectors) {
+                callEntry.setSelectors(selectors);
+            }
+        });
 
-        Message msg = mHandler.obtainMessage(MSG_EXPIRE_STALE_CALL, call);
+        Message msg = mHandler.obtainMessage(MSG_EXPIRE_STALE_CALL, callEntry);
         mHandler.sendMessageDelayed(msg, Timeouts.getNewOutgoingCallMillis());
     }
 
     /**
-     * Retrieves details about the incoming call through the incoming call manager. The incoming
-     * call manager will invoke either {@link #handleSuccessfulIncomingCall} or
-     * {@link #handleFailedIncomingCall} depending on the result of the retrieval.
+     * Retrieves details about the incoming call through the incoming call manager.
      *
      * @param call The call object.
      * @param descriptor The relevant call-service descriptor.
@@ -150,96 +199,12 @@
      */
     void retrieveIncomingCall(Call call, CallServiceDescriptor descriptor, Bundle extras) {
         Log.d(this, "retrieveIncomingCall");
-        CallServiceWrapper callService = mCallServiceRepository.getCallService(descriptor);
+        CallServiceWrapper callService = mCallServiceRepository.getService(descriptor);
         call.setCallService(callService);
         mIncomingCallsManager.retrieveIncomingCall(call, extras);
     }
 
     /**
-     * Persists the specified set of call services and attempts to place any pending outgoing
-     * calls.  Intended to be invoked by {@link CallServiceRepository} exclusively.
-     *
-     * @param callServices The potentially-partial set of call services.  Partial since the lookup
-     *     process is time-boxed, such that some providers/call-services may be slow to respond and
-     *     hence effectively omitted from the specified list.
-     */
-    void setCallServices(Set<CallServiceWrapper> callServices) {
-        mCallServices.clear();
-        mCallServices.addAll(callServices);
-        processNewOutgoingCalls();
-    }
-
-    /**
-     * Persists the specified list of selectors and attempts to connect any pending outgoing
-     * calls.  Intended to be invoked by {@link CallServiceSelectorRepository} exclusively.
-     *
-     * @param selectors Collection of selectors. The order of the collection determines the order in
-     *     which the selectors are tried.
-     */
-    void setSelectors(ImmutableCollection<CallServiceSelectorWrapper> selectors) {
-        ThreadUtil.checkOnMainThread();
-        Preconditions.checkNotNull(selectors);
-
-        // TODO(gilad): Add logic to include the built-in selectors (e.g. for dealing with
-        // emergency calls) and order the entire set prior to the assignment below. If the
-        // built-in selectors can be implemented in a manner that does not require binding,
-        // that's probably preferred.
-        mSelectors = selectors;
-        processNewOutgoingCalls();
-    }
-
-    /**
-     * Handles the case where an outgoing call has been successfully placed,
-     * see {@link OutgoingCallProcessor}.
-     */
-    void handleSuccessfulOutgoingCall(Call call) {
-        Log.d(this, "handleSuccessfulOutgoingCall");
-
-        finalizeOutgoingCall(call);
-        call.handleSuccessfulOutgoing();
-    }
-
-    /**
-     * Handles the case where an outgoing call could not be proceed by any of the
-     * selector/call-service implementations, see {@link OutgoingCallProcessor}.
-     */
-    void handleFailedOutgoingCall(Call call, boolean isAborted) {
-        Log.d(this, "handleFailedOutgoingCall");
-
-        finalizeOutgoingCall(call);
-        call.handleFailedOutgoing(isAborted);
-    }
-
-    /**
-     * Handles the case where we successfully receive details of an incoming call. Hands the
-     * resulting call to {@link CallsManager} as the final step in the incoming sequence. At that
-     * point, {@link CallsManager} should bring up the incoming-call UI.
-     */
-    void handleSuccessfulIncomingCall(Call call, CallInfo callInfo) {
-        Log.d(this, "handleSuccessfulIncomingCall");
-        call.handleSuccessfulIncoming(callInfo);
-    }
-
-    /**
-     * Handles the case where we failed to retrieve an incoming call after receiving an incoming-call
-     * intent via {@link CallActivity}.
-     *
-     * @param call The call.
-     */
-    void handleFailedIncomingCall(Call call) {
-        Log.d(this, "handleFailedIncomingCall");
-
-        // Since we set the call service before calling into incoming-calls manager, we clear it for
-        // good measure if an error is reported.
-        call.handleFailedIncoming();
-
-        // At the moment there is nothing more to do if an incoming call is not retrieved. We may at
-        // a future date bind to the in-call app optimistically during the incoming-call sequence
-        // and this method could tell {@link CallsManager} to unbind from the in-call app if the
-        // incoming call was not retrieved.
-    }
-
-    /**
      * Ensures any state regarding a call is cleaned up.
      *
      * @param call The call.
@@ -250,38 +215,19 @@
     }
 
     /**
-     * Attempts to process the next new outgoing calls that have not yet been expired.
-     */
-    private void processNewOutgoingCalls() {
-        if (mCallServices.isEmpty() || mSelectors.isEmpty()) {
-            // At least one call service and one selector are required to process outgoing calls.
-            return;
-        }
-
-        if (!mNewOutgoingCalls.isEmpty()) {
-            Call call = mNewOutgoingCalls.iterator().next();
-            mNewOutgoingCalls.remove(call);
-            mPendingOutgoingCalls.add(call);
-
-            // Specifically only attempt to place one call at a time such that call services
-            // can be freed from needing to deal with concurrent requests.
-            processNewOutgoingCall(call);
-        }
-    }
-
-    /**
      * Attempts to place the specified call.
      *
-     * @param call The call to place.
+     * @param callEntry The call entry to place.
      */
-    private void processNewOutgoingCall(Call call) {
+    private void processNewOutgoingCall(OutgoingCallEntry callEntry) {
         Collection<CallServiceSelectorWrapper> selectors;
+        Call call = callEntry.call;
 
         // Use the call's selector if it's already tied to one. This is the case for handoff calls.
         if (call.getCallServiceSelector() != null) {
             selectors = ImmutableList.of(call.getCallServiceSelector());
         } else {
-            selectors = mSelectors;
+            selectors = callEntry.getSelectors();
         }
 
         boolean useEmergencySelector =
@@ -308,39 +254,6 @@
             selectors = selectorsBuilder.build();
         }
 
-        mOutgoingCallsManager.placeCall(call, mCallServices, selectors);
-    }
-
-    /**
-     * Finalizes the outgoing-call sequence, regardless if it succeeded or failed.
-     */
-    private void finalizeOutgoingCall(Call call) {
-        mPendingOutgoingCalls.remove(call);
-
-        processNewOutgoingCalls();  // Process additional (new) calls, if any.
-    }
-
-    /**
-     * Expires calls which are taking too long to connect.
-     *
-     * @param call The call to expire.
-     */
-    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);
-        }
-
-        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);
-            }
-        }
+        mOutgoingCallsManager.placeCall(call, callEntry.getCallServices(), selectors);
     }
 }