Adding simple number data to callcard

This CL allows the number to be displayed in the CallCard.

Previous interaction:
CallHandlerService->CallList     CallCardFragment (isolated, no data)

New interaction:
CallHandlerService->MainHandler->CallList->CallCardPresenter->CallCardFragment

1) CallHandlerService.MainHandler: executes remote method calls on the
main thread.
2) CallList: Added support for listeners.
3) CallCardPresenter: Presenter layer for fragment. Listens to CallList
4) CallCardFragment: Gets real data from presenter, displays phone #.

Change-Id: I6de3a9da15b69d44826c3226701eed6a1b86239c
diff --git a/src/com/android/incallui/CallCardFragment.java b/src/com/android/incallui/CallCardFragment.java
index fb60c0b..2fb3172 100644
--- a/src/com/android/incallui/CallCardFragment.java
+++ b/src/com/android/incallui/CallCardFragment.java
@@ -16,20 +16,47 @@
 
 package com.android.incallui;
 
-import android.app.Fragment;
 import android.os.Bundle;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.TextView;
 
 /**
  * Fragment for call card.
  */
-public class CallCardFragment extends Fragment {
+public class CallCardFragment extends BaseFragment<CallCardPresenter>
+        implements CallCardPresenter.CallCardUi {
+
+    private TextView mPhoneNumber;
+
+    @Override
+    CallCardPresenter createPresenter() {
+        return new CallCardPresenter();
+    }
 
     @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container,
             Bundle savedInstanceState) {
         return inflater.inflate(R.layout.call_card_fragment, container, false);
     }
+
+    @Override
+    public void onViewCreated(View view, Bundle savedInstanceState) {
+        mPhoneNumber = (TextView) view.findViewById(R.id.phoneNumber);
+
+        // This method call will begin the callbacks on CallCardUi. We need to ensure
+        // everything needed for the callbacks is set up before this is called.
+        getPresenter().onUiReady(this);
+    }
+
+    @Override
+    public void setNumber(String number)  {
+        mPhoneNumber.setText(number);
+    }
+
+    @Override
+    public void setName(String name) {
+    }
+
 }
diff --git a/src/com/android/incallui/CallCardPresenter.java b/src/com/android/incallui/CallCardPresenter.java
new file mode 100644
index 0000000..85f0534
--- /dev/null
+++ b/src/com/android/incallui/CallCardPresenter.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2013 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.incallui;
+
+import com.android.services.telephony.common.Call;
+
+/**
+ * Presenter for the Call Card Fragment.
+ * This class listens for changes to CallList and passes it along to the fragment.
+ */
+public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi>
+        implements CallList.Listener {
+
+    @Override
+    public void onUiReady(CallCardUi ui) {
+        super.onUiReady(ui);
+
+        CallList.getInstance().addListener(this);
+
+        // When UI is ready, manually trigger a change
+        onCallListChange(CallList.getInstance());
+    }
+
+    @Override
+    public void onCallListChange(CallList callList) {
+        Call call = callList.getIncomingOrActive();
+
+        if (call != null) {
+            getUi().setNumber(call.getNumber());
+        } else {
+            // When there is no longer an incoming/active call, we need to reset everything
+            // so that no data survives for the next call.
+            getUi().setNumber("");
+        }
+    }
+
+    public interface CallCardUi extends Ui {
+        public void setNumber(String number);
+        public void setName(String name);
+    }
+}
diff --git a/src/com/android/incallui/CallHandlerService.java b/src/com/android/incallui/CallHandlerService.java
index 127e575..4a14dab 100644
--- a/src/com/android/incallui/CallHandlerService.java
+++ b/src/com/android/incallui/CallHandlerService.java
@@ -18,7 +18,9 @@
 
 import android.app.Service;
 import android.content.Intent;
+import android.os.Handler;
 import android.os.IBinder;
+import android.os.Message;
 import android.util.Log;
 
 import com.android.services.telephony.common.Call;
@@ -35,13 +37,18 @@
     private static final String TAG = CallHandlerService.class.getSimpleName();
     private static final boolean DBG = false; // TODO: Have a shared location for this.
 
+    private static final int ON_UPDATE_CALL = 1;
+    private static final int ON_UPDATE_MULTI_CALL = 2;
+
     private CallList mCallList;
+    private Handler mMainHandler;
 
     @Override
     public void onCreate() {
         super.onCreate();
 
-        mCallList = new CallList();
+        mCallList = CallList.getInstance();
+        mMainHandler = new MainHandler();
     }
 
     @Override
@@ -63,21 +70,21 @@
 
         @Override
         public void onIncomingCall(Call call) {
-            mCallList.onUpdate(call);
-
             final Intent intent = new Intent(getApplication(), InCallActivity.class);
             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
             startActivity(intent);
+
+            mMainHandler.sendMessage(mMainHandler.obtainMessage(ON_UPDATE_CALL, 0, 0, call));
         }
 
         @Override
         public void onDisconnect(Call call) {
-            mCallList.onUpdate(call);
+            mMainHandler.sendMessage(mMainHandler.obtainMessage(ON_UPDATE_CALL, 0, 0, call));
         }
 
         @Override
         public void onUpdate(List<Call> calls) {
-            mCallList.onUpdate(calls);
+            mMainHandler.sendMessage(mMainHandler.obtainMessage(ON_UPDATE_MULTI_CALL, 0, 0, calls));
         }
     };
 
@@ -86,4 +93,32 @@
             Log.d(TAG, message);
         }
     }
+
+    /**
+     * Handles messages from the service so that they get executed on the main thread, where they
+     * can interact with UI.
+     */
+    private class MainHandler extends Handler {
+        MainHandler() {
+            super(getApplicationContext().getMainLooper(), null, true);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            executeMessage(msg);
+        }
+    }
+
+    private void executeMessage(Message msg) {
+        switch (msg.what) {
+            case ON_UPDATE_CALL:
+                mCallList.onUpdate((Call) msg.obj);
+                break;
+            case ON_UPDATE_MULTI_CALL:
+                mCallList.onUpdate((List<Call>) msg.obj);
+                break;
+            default:
+                break;
+        }
+    }
 }
diff --git a/src/com/android/incallui/CallList.java b/src/com/android/incallui/CallList.java
index 5fe2619..ac3e3bf 100644
--- a/src/com/android/incallui/CallList.java
+++ b/src/com/android/incallui/CallList.java
@@ -16,14 +16,19 @@
 
 package com.android.incallui;
 
+import com.google.android.collect.Lists;
 import com.google.android.collect.Maps;
+import com.google.android.collect.Sets;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
 
+import android.util.Log;
+
 import com.android.services.telephony.common.Call;
 
 import java.util.HashMap;
 import java.util.List;
+import java.util.Set;
 
 /**
  * Maintains the list of active calls received from CallHandlerService.
@@ -31,18 +36,89 @@
  * changes to calls.
  */
 public class CallList {
+    private static String TAG = CallList.class.getSimpleName();
 
-    final HashMap<Integer, Call> callMap = Maps.newHashMap();
+    private static CallList sInstance;
 
-    public void onUpdate(Call call) {
-        updateCallInMap(call);
+    private final HashMap<Integer, Call> mCallMap = Maps.newHashMap();
+    private final Set<Listener> mListeners = Sets.newArraySet();
+
+    /**
+     * Static singleton accessor method.
+     */
+    public static synchronized CallList getInstance() {
+        if (sInstance == null) {
+            sInstance = new CallList();
+        }
+        return sInstance;
     }
 
+    /**
+     * Private constructor.  Instance should only be acquired through getInstance().
+     */
+    private CallList() {
+    }
+
+    /**
+     * Called when a single call has changed.
+     */
+    public void onUpdate(Call call) {
+        updateCallInMap(call);
+
+        notifyListenersOfChange();
+    }
+
+    /**
+     * Called when multiple calls have changed.
+     */
     public void onUpdate(List<Call> callsToUpdate) {
         Preconditions.checkNotNull(callsToUpdate);
         for (Call call : callsToUpdate) {
             updateCallInMap(call);
         }
+
+        notifyListenersOfChange();
+    }
+
+    public void addListener(Listener listener) {
+        Preconditions.checkNotNull(listener);
+        mListeners.add(listener);
+    }
+
+    public void removeListener(Listener listener) {
+        Preconditions.checkNotNull(listener);
+        mListeners.remove(listener);
+    }
+
+    /**
+     * TODO(klp): Change so that this function is not needed. Instead of assuming there is an active
+     * call, the code should rely on the status of a specific Call and allow the presenters to
+     * update the Call object when the active call changes.
+     */
+    public Call getIncomingOrActive() {
+        Call retval = null;
+
+        for (Call call : mCallMap.values()) {
+            if (call.getState() == Call.State.INCOMING) {
+                retval = call;
+                // incoming call takes precedence, cut out early.
+                break;
+            } else if (retval == null && call.getState() == Call.State.ACTIVE) {
+                retval = call;
+            }
+        }
+
+        return retval;
+    }
+
+    /**
+     * Sends a generic notification to all listeners that something has changed.
+     * It is up to the listeners to call back to determine what changed.
+     */
+    private void notifyListenersOfChange() {
+        for (Listener listener : mListeners) {
+            listener.onCallListChange(this);
+        }
     }
 
     private void updateCallInMap(Call call) {
@@ -50,15 +126,23 @@
 
         final Integer id = new Integer(call.getCallId());
 
-        if (isCallActive(call)) {
-            callMap.put(id, call);
-        } else if (callMap.containsKey(id)) {
-            callMap.remove(id);
+        if (!isCallDead(call)) {
+            mCallMap.put(id, call);
+        } else if (mCallMap.containsKey(id)) {
+            mCallMap.remove(id);
         }
     }
 
-    private boolean isCallActive(Call call) {
+    private boolean isCallDead(Call call) {
         final int state = call.getState();
-        return Call.State.IDLE != state && Call.State.INVALID != state;
+        return Call.State.IDLE == state || Call.State.INVALID == state;
+    }
+
+    /**
+     * Listener interface for any class that wants to be notified of changes
+     * to the call list.
+     */
+    public interface Listener {
+        public void onCallListChange(CallList callList);
     }
 }
diff --git a/src/com/android/incallui/InCallActivity.java b/src/com/android/incallui/InCallActivity.java
index 71e981b..04f1f09 100644
--- a/src/com/android/incallui/InCallActivity.java
+++ b/src/com/android/incallui/InCallActivity.java
@@ -38,6 +38,7 @@
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
 
     private CallButtonPresenter mCallButtonPresenter;
+    private CallCardPresenter mCallCardPresenter;
 
     @Override
     protected void onCreate(Bundle icicle) {
@@ -57,6 +58,8 @@
         // Inflate everything in incall_screen.xml and add it to the screen.
         setContentView(R.layout.incall_screen);
 
+        initializeInCall();
+
         logD("onCreate(): exit");
     }
 
@@ -65,11 +68,6 @@
     protected void onResume() {
         logD("onResume()...");
 
-        final CallButtonFragment callButtonFragment = (CallButtonFragment) getFragmentManager()
-                .findFragmentById(R.id.callButtonFragment);
-        mCallButtonPresenter = callButtonFragment.getPresenter();
-        mCallButtonPresenter.setEndCallListener(this);
-
         // TODO(klp): create once and reset when needed.
         final AnswerFragment answerFragment = new AnswerFragment();
         final AnswerPresenter presenter = answerFragment.getPresenter();
@@ -207,8 +205,20 @@
         return super.onKeyDown(keyCode, event);
     }
 
+    private void initializeInCall() {
+
+        final CallButtonFragment callButtonFragment = (CallButtonFragment) getFragmentManager()
+                .findFragmentById(R.id.callButtonFragment);
+        mCallButtonPresenter = callButtonFragment.getPresenter();
+        mCallButtonPresenter.setEndCallListener(this);
+
+        final CallCardFragment callCardFragment = (CallCardFragment) getFragmentManager()
+                .findFragmentById(R.id.callCardFragment);
+    }
+
     private void toast(String text) {
         final Toast toast = Toast.makeText(this, text, Toast.LENGTH_SHORT);
+
         toast.show();
     }