DO NOT MERGE. Port "respond via SMS message" feature to new Telecomm. (2/4)

Bug: 15275904
Bug: 15196474
Change-Id: I3e2ee62b3e32ad5715457fee1b0e714f88ecea8e
diff --git a/src/com/android/telecomm/Call.java b/src/com/android/telecomm/Call.java
index 607aeb1..104ff4e 100644
--- a/src/com/android/telecomm/Call.java
+++ b/src/com/android/telecomm/Call.java
@@ -27,6 +27,7 @@
 import android.telecomm.CallServiceDescriptor;
 import android.telecomm.CallState;
 import android.telecomm.GatewayInfo;
+import android.telecomm.Response;
 import android.telecomm.TelecommConstants;
 import android.telephony.DisconnectCause;
 import android.telephony.PhoneNumberUtils;
@@ -36,11 +37,12 @@
 import com.android.internal.telephony.CallerInfoAsyncQuery;
 import com.android.internal.telephony.CallerInfoAsyncQuery.OnQueryCompleteListener;
 
+import com.android.internal.telephony.SmsApplication;
 import com.android.telecomm.ContactsAsyncHelper.OnImageLoadCompleteListener;
-import com.google.android.collect.Sets;
 import com.google.common.base.Preconditions;
+import com.google.common.collect.Sets;
 
-import java.util.HashSet;
+import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Locale;
@@ -68,6 +70,7 @@
         void onConfirmedConferenceCall(Call call);
         void onParentChanged(Call call);
         void onChildrenChanged(Call call);
+        void onCannedSmsResponsesLoaded(Call call);
     }
 
     private static final OnQueryCompleteListener sCallerInfoQueryListener =
@@ -196,6 +199,12 @@
 
     private List<Call> mChildCalls = new LinkedList<>();
 
+    /** Set of text message responses allowed for this call, if applicable. */
+    private List<String> mCannedSmsResponses = Collections.EMPTY_LIST;
+
+    /** Whether an attempt has been made to load the text message responses. */
+    private boolean mCannedSmsResponsesLoadingStarted = false;
+
     /**
      * Creates an empty call object.
      *
@@ -218,6 +227,7 @@
         mGatewayInfo = gatewayInfo;
         mIsIncoming = isIncoming;
         mIsConference = isConference;
+        maybeLoadCannedSmsResponses();
     }
 
     void addListener(Listener listener) {
@@ -261,6 +271,7 @@
         if (mState != newState) {
             Log.v(this, "setState %s -> %s", mState, newState);
             mState = newState;
+            maybeLoadCannedSmsResponses();
         }
     }
 
@@ -506,7 +517,7 @@
                 // TODO(santoscordon): Once we move State handling from CallsManager to Call, we
                 // will not need to set RINGING state prior to calling reject.
                 setState(CallState.RINGING);
-                reject();
+                reject(false, null);
             } else {
                 // TODO(santoscordon): Make this class (not CallsManager) responsible for changing
                 // the call state to RINGING.
@@ -670,8 +681,11 @@
 
     /**
      * Rejects the call if it is ringing.
+     *
+     * @param rejectWithMessage Whether to send a text message as part of the call rejection.
+     * @param textMessage An optional text message to send as part of the rejection.
      */
-    void reject() {
+    void reject(boolean rejectWithMessage, String textMessage) {
         Preconditions.checkNotNull(mCallService);
 
         // Check to verify that the call is still in the ringing state. A call can change states
@@ -854,6 +868,66 @@
     }
 
     /**
+     * Return whether the user can respond to this {@code Call} via an SMS message.
+     *
+     * @return true if the "Respond via SMS" feature should be enabled
+     * for this incoming call.
+     *
+     * The general rule is that we *do* allow "Respond via SMS" except for
+     * the few (relatively rare) cases where we know for sure it won't
+     * work, namely:
+     *   - a bogus or blank incoming number
+     *   - a call from a SIP address
+     *   - a "call presentation" that doesn't allow the number to be revealed
+     *
+     * In all other cases, we allow the user to respond via SMS.
+     *
+     * Note that this behavior isn't perfect; for example we have no way
+     * to detect whether the incoming call is from a landline (with most
+     * networks at least), so we still enable this feature even though
+     * SMSes to that number will silently fail.
+     */
+    boolean isRespondViaSmsCapable() {
+        if (mState != CallState.RINGING) {
+            return false;
+        }
+
+        if (getHandle() == null) {
+            // No incoming number known or call presentation is "PRESENTATION_RESTRICTED", in
+            // other words, the user should not be able to see the incoming phone number.
+            return false;
+        }
+
+        if (PhoneNumberUtils.isUriNumber(getHandle().toString())) {
+            // The incoming number is actually a URI (i.e. a SIP address),
+            // not a regular PSTN phone number, and we can't send SMSes to
+            // SIP addresses.
+            // (TODO: That might still be possible eventually, though. Is
+            // there some SIP-specific equivalent to sending a text message?)
+            return false;
+        }
+
+        // Is there a valid SMS application on the phone?
+        if (SmsApplication.getDefaultRespondViaMessageApplication(TelecommApp.getInstance(),
+                true /*updateIfNeeded*/) == null) {
+            return false;
+        }
+
+        // TODO: with some carriers (in certain countries) you *can* actually
+        // tell whether a given number is a mobile phone or not. So in that
+        // case we could potentially return false here if the incoming call is
+        // from a land line.
+
+        // If none of the above special cases apply, it's OK to enable the
+        // "Respond via SMS" feature.
+        return true;
+    }
+
+    List<String> getCannedSmsResponses() {
+        return mCannedSmsResponses;
+    }
+
+    /**
      * @return True if the call is ringing, else logs the action name.
      */
     private boolean isRinging(String actionName) {
@@ -934,4 +1008,33 @@
             mCallerInfo.cachedPhotoIcon = photoIcon;
         }
     }
+
+    private void maybeLoadCannedSmsResponses() {
+        if (mIsIncoming && isRespondViaSmsCapable() && !mCannedSmsResponsesLoadingStarted) {
+            Log.d(this, "maybeLoadCannedSmsResponses: starting task to load messages");
+            mCannedSmsResponsesLoadingStarted = true;
+            RespondViaSmsManager.getInstance().loadCannedTextMessages(
+                    new Response<Void, List<String>>() {
+                        @Override
+                        public void onResult(Void request, List<String>... result) {
+                            if (result.length > 0) {
+                                Log.d(this, "maybeLoadCannedSmsResponses: got %s", result[0]);
+                                mCannedSmsResponses = result[0];
+                                for (Listener l : mListeners) {
+                                    l.onCannedSmsResponsesLoaded(Call.this);
+                                }
+                            }
+                        }
+
+                        @Override
+                        public void onError(Void request, int code, String msg) {
+                            Log.w(Call.this, "Error obtaining canned SMS responses: %d %s", code,
+                                    msg);
+                        }
+                    }
+            );
+        } else {
+            Log.d(this, "maybeLoadCannedSmsResponses: doing nothing");
+        }
+    }
 }