Merge "Display primary call info in call card"
diff --git a/InCallUI/src/com/android/incallui/AnswerFragment.java b/InCallUI/src/com/android/incallui/AnswerFragment.java
index a20fdb9..196a66b 100644
--- a/InCallUI/src/com/android/incallui/AnswerFragment.java
+++ b/InCallUI/src/com/android/incallui/AnswerFragment.java
@@ -16,10 +16,19 @@
 
 package com.android.incallui;
 
+import com.google.common.base.Preconditions;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
 import android.os.Bundle;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+
+import java.util.ArrayList;
 
 /**
  *
@@ -27,6 +36,18 @@
 public class AnswerFragment extends BaseFragment<AnswerPresenter> implements
         GlowPadWrapper.AnswerListener, AnswerPresenter.AnswerUi {
 
+    /**
+     * The popup showing the list of canned responses.
+     *
+     * This is an AlertDialog containing a ListView showing the possible
+     * choices.  This may be null if the InCallScreen hasn't ever called
+     * showRespondViaSmsPopup() yet, or if the popup was visible once but
+     * then got dismissed.
+     */
+    private Dialog mCannedResponsePopup = null;
+
+    private ArrayAdapter<String> mTextResponsesAdapter = null;
+
     public AnswerFragment() {
     }
 
@@ -53,6 +74,55 @@
     }
 
     @Override
+    public void showTextButton(boolean show) {
+        // TODO(klp) Hide the text button when the call does not support reject by text.
+    }
+
+    @Override
+    public boolean isMessageDialogueShowing() {
+        return mCannedResponsePopup != null && mCannedResponsePopup.isShowing();
+    }
+
+    @Override
+    public void showMessageDialogue() {
+        final ListView lv = new ListView(getActivity());
+
+        Preconditions.checkNotNull(mTextResponsesAdapter);
+        lv.setAdapter(mTextResponsesAdapter);
+        lv.setOnItemClickListener(new RespondViaSmsItemClickListener());
+
+        final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
+                .setCancelable(true)
+                .setView(lv);
+        mCannedResponsePopup = builder.create();
+        mCannedResponsePopup.show();
+    }
+
+    /**
+     * Dismiss currently visible popups.
+     *
+     * This is safe to call even if the popup is already dismissed, and
+     * even if you never called showRespondViaSmsPopup() in the first
+     * place.
+     */
+    @Override
+    public void dismissPopup() {
+        if (mCannedResponsePopup != null) {
+            mCannedResponsePopup.dismiss();  // safe even if already dismissed
+            mCannedResponsePopup = null;
+        }
+    }
+
+    @Override
+    public void configureMessageDialogue(ArrayList<String> textResponses) {
+        textResponses.add(getResources().getString(R.string.respond_via_sms_custom_message));
+        mTextResponsesAdapter = new ArrayAdapter<String>(getActivity(),
+                                         android.R.layout.simple_list_item_1,
+                                         android.R.id.text1,
+                                         textResponses);
+    }
+
+    @Override
     public void onAnswer() {
         getPresenter().onAnswer();
     }
@@ -66,4 +136,31 @@
     public void onText() {
         getPresenter().onText();
     }
+
+    /**
+     * OnItemClickListener for the "Respond via SMS" popup.
+     */
+    public class RespondViaSmsItemClickListener implements AdapterView.OnItemClickListener {
+        /**
+         * Handles the user selecting an item from the popup.
+         */
+        @Override
+        public void onItemClick(AdapterView<?> parent,  // The ListView
+                                View view,  // The TextView that was clicked
+                                int position,
+                                long id) {
+            Logger.d(this, "RespondViaSmsItemClickListener.onItemClick(" + position + ")...");
+            final String message = (String) parent.getItemAtPosition(position);
+            Logger.v(this, "- message: '" + message + "'");
+
+            // The "Custom" choice is a special case.
+            // (For now, it's guaranteed to be the last item.)
+            if (position == (parent.getCount() - 1)) {
+                // Take the user to the standard SMS compose UI.
+                getPresenter().rejectCallWithMessage(null);
+            } else {
+                getPresenter().rejectCallWithMessage(message);
+            }
+        }
+    }
 }
diff --git a/InCallUI/src/com/android/incallui/AnswerPresenter.java b/InCallUI/src/com/android/incallui/AnswerPresenter.java
index 123fe9c..c3a7c13 100644
--- a/InCallUI/src/com/android/incallui/AnswerPresenter.java
+++ b/InCallUI/src/com/android/incallui/AnswerPresenter.java
@@ -22,6 +22,8 @@
 import com.android.incallui.InCallPresenter.InCallStateListener;
 import com.android.services.telephony.common.Call;
 
+import java.util.ArrayList;
+
 /**
  * Presenter for the Incoming call widget.
  */
@@ -29,6 +31,7 @@
         implements InCallStateListener {
 
     private Call mCall;
+    private ArrayList<String> mTextResponses;
 
     @Override
     public void onUiReady(AnswerUi ui) {
@@ -40,7 +43,13 @@
         if (state == InCallState.INCOMING) {
             getUi().showAnswerUi(true);
             mCall = callList.getIncomingCall();
-
+            mTextResponses = callList.getTextResponses(mCall);
+            if (mTextResponses != null) {
+                getUi().showTextButton(true);
+                getUi().configureMessageDialogue(mTextResponses);
+            } else {
+                getUi().showTextButton(false);
+            }
             Logger.d(this, "Showing incoming with: " + mCall);
         } else {
             getUi().showAnswerUi(false);
@@ -59,13 +68,25 @@
         Preconditions.checkNotNull(mCall);
         Logger.d(this, "onDecline " + mCall.getCallId());
 
-        CallCommandClient.getInstance().rejectCall(mCall.getCallId());
+        CallCommandClient.getInstance().rejectCall(mCall.getCallId(), false, null);
     }
 
     public void onText() {
+        getUi().showMessageDialogue();
+    }
+
+    public void rejectCallWithMessage(String message) {
+        Logger.d(this, "sendTextToDefaultActivity()...");
+        CallCommandClient.getInstance().rejectCall(mCall.getCallId(), true, message);
+        getUi().dismissPopup();
     }
 
     interface AnswerUi extends Ui {
         public void showAnswerUi(boolean show);
+        public void showTextButton(boolean show);
+        public boolean isMessageDialogueShowing();
+        public void showMessageDialogue();
+        public void dismissPopup();
+        public void configureMessageDialogue(ArrayList<String> textResponses);
     }
-}
+}
\ No newline at end of file
diff --git a/InCallUI/src/com/android/incallui/CallCommandClient.java b/InCallUI/src/com/android/incallui/CallCommandClient.java
index 381d8d3..80a9e9e 100644
--- a/InCallUI/src/com/android/incallui/CallCommandClient.java
+++ b/InCallUI/src/com/android/incallui/CallCommandClient.java
@@ -56,9 +56,9 @@
         }
     }
 
-    public void rejectCall(int callId) {
+    public void rejectCall(int callId, boolean rejectWithMessage, String message) {
         try {
-            mCommandService.rejectCall(callId);
+            mCommandService.rejectCall(callId, rejectWithMessage, message);
         } catch (RemoteException e) {
             Logger.e(this, "Error rejecting call.", e);
         }
diff --git a/InCallUI/src/com/android/incallui/CallHandlerService.java b/InCallUI/src/com/android/incallui/CallHandlerService.java
index dcba7d6..173f6a3 100644
--- a/InCallUI/src/com/android/incallui/CallHandlerService.java
+++ b/InCallUI/src/com/android/incallui/CallHandlerService.java
@@ -26,7 +26,10 @@
 import com.android.services.telephony.common.ICallCommandService;
 import com.android.services.telephony.common.ICallHandlerService;
 
+import java.util.AbstractMap;
+import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 
 /**
  * Service used to listen for call state changes.
@@ -35,6 +38,7 @@
 
     private static final int ON_UPDATE_CALL = 1;
     private static final int ON_UPDATE_MULTI_CALL = 2;
+    private static final int ON_UPDATE_CALL_WITH_TEXT_RESPONSES = 3;
 
     private CallList mCallList;
     private Handler mMainHandler;
@@ -72,10 +76,27 @@
         }
 
         @Override
+        public void onIncoming(Call call, List<String> textResponses) {
+            // TODO(klp): Add text responses to the call object.
+            Map.Entry<Call, List<String> > incomingCall = new AbstractMap.SimpleEntry<Call,
+                    List<String> >(call, textResponses);
+            mMainHandler.sendMessage(mMainHandler.obtainMessage(ON_UPDATE_CALL_WITH_TEXT_RESPONSES,
+                    0, 0, incomingCall));
+        }
+
+        @Override
         public void onUpdate(List<Call> calls, boolean fullUpdate) {
             // TODO(klp): Add use of fullUpdate to message
             mMainHandler.sendMessage(mMainHandler.obtainMessage(ON_UPDATE_MULTI_CALL, 0, 0, calls));
         }
+
+        @Override
+        public void onAudioModeChange(int mode) {
+        }
+
+        @Override
+        public void onAudioModeSupportChange(int modeMask) {
+        }
     };
 
     /**
@@ -101,6 +122,9 @@
             case ON_UPDATE_MULTI_CALL:
                 mCallList.onUpdate((List<Call>) msg.obj);
                 break;
+            case ON_UPDATE_CALL_WITH_TEXT_RESPONSES:
+                mCallList.onUpdate((AbstractMap.SimpleEntry<Call, List<String> >) msg.obj);
+                break;
             default:
                 break;
         }
diff --git a/InCallUI/src/com/android/incallui/CallList.java b/InCallUI/src/com/android/incallui/CallList.java
index c16c53d..3a90a7e 100644
--- a/InCallUI/src/com/android/incallui/CallList.java
+++ b/InCallUI/src/com/android/incallui/CallList.java
@@ -25,6 +25,8 @@
 
 import com.android.services.telephony.common.Call;
 
+import java.util.AbstractMap;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -49,6 +51,8 @@
     private static CallList sInstance;
 
     private final HashMap<Integer, Call> mCallMap = Maps.newHashMap();
+    private final HashMap<Integer, ArrayList<String>> mCallTextReponsesMap =
+            Maps.newHashMap();
     private final Set<Listener> mListeners = Sets.newArraySet();
 
     /**
@@ -74,6 +78,19 @@
         Logger.d(this, "onUpdate - " + safeCallString(call));
 
         updateCallInMap(call);
+
+        notifyListenersOfChange();
+    }
+
+    /**
+     * Called when a single call has changed.
+     */
+    public void onUpdate(AbstractMap.SimpleEntry<Call, List<String> > incomingCall) {
+        Logger.d(this, "onUpdate - " + safeCallString(incomingCall.getKey()));
+
+        updateCallInMap(incomingCall.getKey());
+        updateCallTextMap(incomingCall.getKey(), incomingCall.getValue());
+
         notifyListenersOfChange();
     }
 
@@ -88,6 +105,7 @@
             Logger.d(this, "\t" + safeCallString(call));
 
             updateCallInMap(call);
+            updateCallTextMap(call, null);
         }
 
         notifyListenersOfChange();
@@ -158,6 +176,10 @@
         return false;
     }
 
+    public ArrayList<String> getTextResponses(Call call) {
+        return mCallTextReponsesMap.get(call.getCallId());
+    }
+
     /**
      * Returns first call found in the call map with the specified state.
      */
@@ -210,6 +232,20 @@
         }
     }
 
+    private void updateCallTextMap(Call call, List<String> textResponses) {
+        Preconditions.checkNotNull(call);
+
+        final Integer id = new Integer(call.getCallId());
+
+        if (!isCallDead(call)) {
+            if (textResponses != null) {
+                mCallTextReponsesMap.put(id, (ArrayList<String>) textResponses);
+            }
+        } else if (mCallMap.containsKey(id)) {
+            mCallTextReponsesMap.remove(id);
+        }
+    }
+
     private boolean isCallDead(Call call) {
         final int state = call.getState();
         return Call.State.IDLE == state || Call.State.INVALID == state;