DO NOT MERGE. Port "respond via SMS message" feature to new Telecomm. (2/4)
Bug: 15275904
Bug: 15196474
Change-Id: I3e2ee62b3e32ad5715457fee1b0e714f88ecea8e
diff --git a/Android.mk b/Android.mk
index 48eece0..852d0a4 100644
--- a/Android.mk
+++ b/Android.mk
@@ -3,6 +3,7 @@
# Build the Telecomm service.
include $(CLEAR_VARS)
+LOCAL_JAVA_LIBRARIES := telephony-common
LOCAL_STATIC_JAVA_LIBRARIES := \
guava \
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 2a90f86..c49dca9 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -177,5 +177,12 @@
</intent-filter>
</receiver>
+ <activity android:name=".RespondViaSmsSettings$Settings"
+ android:label="@string/respond_via_sms_setting_title"
+ android:configChanges="orientation|screenSize|keyboardHidden">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ </intent-filter>
+ </activity>
</application>
</manifest>
diff --git a/res/menu/respond_via_message_settings_menu.xml b/res/menu/respond_via_message_settings_menu.xml
new file mode 100644
index 0000000..6f5e246
--- /dev/null
+++ b/res/menu/respond_via_message_settings_menu.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 Google Inc.
+
+ 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.
+-->
+
+<!-- Menu for Respond-via-Message settings screen. -->
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:id="@+id/respond_via_message_reset"
+ android:title="@string/respond_via_sms_menu_reset_default_activity" />
+</menu>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 0c46053..747eca7 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -49,4 +49,30 @@
<!-- Content description of the speakerphone enabled notification icon for
accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_speakerphone_enabled">Speakerphone enabled.</string>
+
+ <!-- Canned response for the "Respond via SMS" feature for incoming calls. [CHAR LIMIT=35] -->
+ <string name="respond_via_sms_canned_response_1">Can\'t talk now. What\'s up?</string>
+ <!-- Canned response for the "Respond via SMS" feature for incoming calls. [CHAR LIMIT=35] -->
+ <string name="respond_via_sms_canned_response_2">I\'ll call you right back.</string>
+ <!-- Canned response for the "Respond via SMS" feature for incoming calls. [CHAR LIMIT=35] -->
+ <string name="respond_via_sms_canned_response_3">I\'ll call you later.</string>
+ <!-- Canned response for the "Respond via SMS" feature for incoming calls. [CHAR LIMIT=35] -->
+ <string name="respond_via_sms_canned_response_4">Can\'t talk now. Call me later?</string>
+
+ <!-- Title of settings screen for managing the "Respond via SMS" feature. [CHAR LIMIT=30] -->
+ <string name="respond_via_sms_setting_title">Quick responses</string>
+ <!-- Slightly more verbose title of settings screen for managing the
+ "Respond via SMS" feature. [CHAR LIMIT=30] -->
+ <string name="respond_via_sms_setting_title_2">Edit quick responses</string>
+ <!-- Settings summary string for the "Respond via SMS" feature. [CHAR LIMIT=40] -->
+ <string name="respond_via_sms_setting_summary"></string>
+ <!-- Dialog title when changing a string for the "Respond via SMS" feature. [CHAR LIMIT=30] -->
+ <string name="respond_via_sms_edittext_dialog_title">Quick response</string>
+ <!-- Menu option in "Respond via SMS" that allows user to reset the default
+ activity used to handle "Respond via SMS" [CHAR LIMIT=30] -->
+ <string name="respond_via_sms_menu_reset_default_activity">Reset default app</string>
+
+ <!-- "Respond via SMS": Confirmation message shown after sending
+ a text response. [CHAR LIMIT=40] -->
+ <string name="respond_via_sms_confirmation_format">Message sent to <xliff:g id="phone_number">%s</xliff:g>.</string>
</resources>
diff --git a/res/xml/respond_via_sms_settings.xml b/res/xml/respond_via_sms_settings.xml
new file mode 100644
index 0000000..a8eb46c
--- /dev/null
+++ b/res/xml/respond_via_sms_settings.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+
+<!-- Settings screen that lets the user manage the canned responses
+ for the "Respond via SMS" feature; see RespondViaSmsManager.java -->
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+ android:title="@string/respond_via_sms_setting_title_2">
+
+ <!-- Ultra-simple implementation for now: just provide 4 fixed slots
+ with customizable strings. -->
+
+ <!-- TODO: Potential UI improvements:
+ (1) Allow editing the strings in place, rather than having to tap
+ each one and edit it via the popup dialog.
+ (2) Allow reordering the strings by drag-and-drop.
+ (3) Provide an "Add new string..." option? -->
+
+ <!-- The defaultValues here must agree with the values used with
+ prefs.getString() in RespondViaSmsManager.loadCannedResponses(). -->
+
+ <!-- Use MultiLineTitleEditTextPreference instead of the standard
+ EditTextPreference here, to allow the preference "title" to wrap
+ onto multiple lines if the customized messages are long enough. -->
+
+ <com.android.telecomm.MultiLineTitleEditTextPreference
+ android:key="canned_response_pref_1"
+ android:defaultValue="@string/respond_via_sms_canned_response_1"
+ android:dialogTitle="@string/respond_via_sms_edittext_dialog_title" />
+
+ <com.android.telecomm.MultiLineTitleEditTextPreference
+ android:key="canned_response_pref_2"
+ android:defaultValue="@string/respond_via_sms_canned_response_2"
+ android:dialogTitle="@string/respond_via_sms_edittext_dialog_title" />
+
+ <com.android.telecomm.MultiLineTitleEditTextPreference
+ android:key="canned_response_pref_3"
+ android:defaultValue="@string/respond_via_sms_canned_response_3"
+ android:dialogTitle="@string/respond_via_sms_edittext_dialog_title" />
+
+ <com.android.telecomm.MultiLineTitleEditTextPreference
+ android:key="canned_response_pref_4"
+ android:defaultValue="@string/respond_via_sms_canned_response_4"
+ android:dialogTitle="@string/respond_via_sms_edittext_dialog_title" />
+
+</PreferenceScreen>
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");
+ }
+ }
}
diff --git a/src/com/android/telecomm/CallsManager.java b/src/com/android/telecomm/CallsManager.java
index 6057350..5e5f710 100644
--- a/src/com/android/telecomm/CallsManager.java
+++ b/src/com/android/telecomm/CallsManager.java
@@ -57,12 +57,13 @@
CallServiceDescriptor oldDescriptor,
CallServiceDescriptor newDescriptor);
void onIncomingCallAnswered(Call call);
- void onIncomingCallRejected(Call call);
+ void onIncomingCallRejected(Call call, boolean rejectWithMessage, String textMessage);
void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall);
void onAudioStateChanged(CallAudioState oldAudioState, CallAudioState newAudioState);
void onRequestingRingback(Call call, boolean ringback);
void onIsConferenceCapableChanged(Call call, boolean isConferenceCapable);
void onIsConferencedChanged(Call call);
+ void onCannedSmsResponsesLoaded(Call call);
}
private static final CallsManager INSTANCE = new CallsManager();
@@ -121,6 +122,7 @@
mListeners.add(app.getMissedCallNotifier());
mListeners.add(mDtmfLocalTonePlayer);
mListeners.add(mHeadsetMediaButton);
+ mListeners.add(RespondViaSmsManager.getInstance());
}
@Override
@@ -213,6 +215,13 @@
}
}
+ @Override
+ public void onCannedSmsResponsesLoaded(Call call) {
+ for (CallsManagerListener listener : mListeners) {
+ listener.onCannedSmsResponsesLoaded(call);
+ }
+ }
+
ImmutableCollection<Call> getCalls() {
return ImmutableList.copyOf(mCalls);
}
@@ -338,14 +347,14 @@
* app through {@link InCallAdapter} after Telecomm notifies it of an incoming call followed by
* the user opting to reject said call.
*/
- void rejectCall(Call call) {
+ void rejectCall(Call call, boolean rejectWithMessage, String textMessage) {
if (!mCalls.contains(call)) {
Log.i(this, "Request to reject a non-existent call %s", call);
} else {
for (CallsManagerListener listener : mListeners) {
- listener.onIncomingCallRejected(call);
+ listener.onIncomingCallRejected(call, rejectWithMessage, textMessage);
}
- call.reject();
+ call.reject(rejectWithMessage, textMessage);
}
}
diff --git a/src/com/android/telecomm/CallsManagerListenerBase.java b/src/com/android/telecomm/CallsManagerListenerBase.java
index 60fdbf6..55fa0a1 100644
--- a/src/com/android/telecomm/CallsManagerListenerBase.java
+++ b/src/com/android/telecomm/CallsManagerListenerBase.java
@@ -60,7 +60,7 @@
}
@Override
- public void onIncomingCallRejected(Call call) {
+ public void onIncomingCallRejected(Call call, boolean rejectWithMessage, String textMessage) {
}
@Override
@@ -82,4 +82,8 @@
@Override
public void onIsConferencedChanged(Call call) {
}
+
+ @Override
+ public void onCannedSmsResponsesLoaded(Call call) {
+ }
}
diff --git a/src/com/android/telecomm/InCallAdapter.java b/src/com/android/telecomm/InCallAdapter.java
index 7ae12ab..7937e95 100644
--- a/src/com/android/telecomm/InCallAdapter.java
+++ b/src/com/android/telecomm/InCallAdapter.java
@@ -45,54 +45,111 @@
private final class InCallAdapterHandler extends Handler {
@Override
public void handleMessage(Message msg) {
- Call call = null;
- if (msg.obj != null) {
- call = mCallIdMapper.getCall(msg.obj);
- if (call == null) {
- Log.w(this, "Unknown call id: %s, msg: %d", msg.obj, msg.what);
- return;
- }
- }
-
+ Call call;
switch (msg.what) {
case MSG_ANSWER_CALL:
- mCallsManager.answerCall(call);
+ call = mCallIdMapper.getCall(msg.obj);
+ if (call != null) {
+ mCallsManager.answerCall(call);
+ } else {
+ Log.w(this, "answerCall, unknown call id: %s", msg.obj);
+ }
break;
case MSG_REJECT_CALL:
- mCallsManager.rejectCall(call);
+ SomeArgs args = (SomeArgs) msg.obj;
+ try {
+ call = mCallIdMapper.getCall(args.arg1);
+ boolean rejectWithMessage = args.argi1 == 1;
+ String textMessage = (String) args.arg2;
+ if (call != null) {
+ mCallsManager.rejectCall(call, rejectWithMessage, textMessage);
+ } else {
+ Log.w(this, "setRingback, unknown call id: %s", args.arg1);
+ }
+ } finally {
+ args.recycle();
+ }
break;
case MSG_PLAY_DTMF_TONE:
- mCallsManager.playDtmfTone(call, (char) msg.arg1);
+ call = mCallIdMapper.getCall(msg.obj);
+ if (call != null) {
+ mCallsManager.playDtmfTone(call, (char) msg.arg1);
+ } else {
+ Log.w(this, "playDtmfTone, unknown call id: %s", msg.obj);
+ }
break;
case MSG_STOP_DTMF_TONE:
- mCallsManager.stopDtmfTone(call);
+ call = mCallIdMapper.getCall(msg.obj);
+ if (call != null) {
+ mCallsManager.stopDtmfTone(call);
+ } else {
+ Log.w(this, "stopDtmfTone, unknown call id: %s", msg.obj);
+ }
break;
case MSG_POST_DIAL_CONTINUE:
+ call = mCallIdMapper.getCall(msg.obj);
mCallsManager.postDialContinue(call, msg.arg1 == 1);
+ call = mCallIdMapper.getCall(msg.obj);
+ if (call != null) {
+ mCallsManager.postDialContinue(call, msg.arg1 == 1);
+ } else {
+ Log.w(this, "postDialContinue, unknown call id: %s", msg.obj);
+ }
break;
case MSG_DISCONNECT_CALL:
- mCallsManager.disconnectCall(call);
+ call = mCallIdMapper.getCall(msg.obj);
+ if (call != null) {
+ mCallsManager.disconnectCall(call);
+ } else {
+ Log.w(this, "disconnectCall, unknown call id: %s", msg.obj);
+ }
break;
case MSG_HOLD_CALL:
- mCallsManager.holdCall(call);
+ call = mCallIdMapper.getCall(msg.obj);
+ if (call != null) {
+ mCallsManager.holdCall(call);
+ } else {
+ Log.w(this, "holdCall, unknown call id: %s", msg.obj);
+ }
break;
case MSG_UNHOLD_CALL:
- mCallsManager.unholdCall(call);
+ call = mCallIdMapper.getCall(msg.obj);
+ if (call != null) {
+ mCallsManager.unholdCall(call);
+ } else {
+ Log.w(this, "unholdCall, unknown call id: %s", msg.obj);
+ }
break;
case MSG_HANDOFF_CALL:
- mCallsManager.startHandoffForCall(call);
+ call = mCallIdMapper.getCall(msg.obj);
+ if (call != null) {
+ mCallsManager.startHandoffForCall(call);
+ } else {
+ Log.w(this, "startHandoffForCall, unknown call id: %s", msg.obj);
+ }
break;
case MSG_MUTE:
- mCallsManager.mute(msg.arg1 == 1 ? true : false);
+ mCallsManager.mute(msg.arg1 == 1);
break;
case MSG_SET_AUDIO_ROUTE:
mCallsManager.setAudioRoute(msg.arg1);
break;
case MSG_CONFERENCE:
- mCallsManager.conference(call);
+ call = mCallIdMapper.getCall(msg.obj);
+ if (call != null) {
+ mCallsManager.conference(call);
+ } else {
+ Log.w(this, "conference, unknown call id: %s", msg.obj);
+ }
+
break;
case MSG_SPLIT_FROM_CONFERENCE:
- call.splitFromConference();
+ call = mCallIdMapper.getCall(msg.obj);
+ if (call != null) {
+ call.splitFromConference();
+ } else {
+ Log.w(this, "splitFromConference, unknown call id: %s", msg.obj);
+ }
break;
}
}
@@ -119,10 +176,14 @@
/** {@inheritDoc} */
@Override
- public void rejectCall(String callId) {
- Log.d(this, "rejectCall(%s)", callId);
+ public void rejectCall(String callId, boolean rejectWithMessage, String textMessage) {
+ Log.d(this, "rejectCall(%s,%b,%s)", callId, rejectWithMessage, textMessage);
mCallIdMapper.checkValidCallId(callId);
- mHandler.obtainMessage(MSG_REJECT_CALL, callId).sendToTarget();
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = callId;
+ args.argi1 = rejectWithMessage ? 1 : 0;
+ args.arg2 = textMessage;
+ mHandler.obtainMessage(MSG_REJECT_CALL, args).sendToTarget();
}
/** {@inheritDoc} */
diff --git a/src/com/android/telecomm/InCallController.java b/src/com/android/telecomm/InCallController.java
index 434c709..3dd0539 100644
--- a/src/com/android/telecomm/InCallController.java
+++ b/src/com/android/telecomm/InCallController.java
@@ -29,14 +29,13 @@
import android.telecomm.CallServiceDescriptor;
import android.telecomm.CallState;
import android.telecomm.InCallCall;
-import android.telecomm.CallState;
import com.android.internal.telecomm.IInCallService;
import com.google.common.collect.ImmutableCollection;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
-import java.util.Set;
/**
* Binds to {@link IInCallService} and provides the service to {@link CallsManager} through which it
@@ -92,10 +91,12 @@
bind();
} else {
Log.i(this, "Adding call: %s", call);
- mCallIdMapper.addCall(call);
- try {
- mInCallService.addCall(toInCallCall(call));
- } catch (RemoteException ignored) {
+ if (mCallIdMapper.getCallId(call) == null) {
+ mCallIdMapper.addCall(call);
+ try {
+ mInCallService.addCall(toInCallCall(call));
+ } catch (RemoteException ignored) {
+ }
}
}
}
@@ -168,6 +169,11 @@
updateCall(call);
}
+ @Override
+ public void onCannedSmsResponsesLoaded(Call call) {
+ updateCall(call);
+ }
+
void bringToForeground(boolean showDialpad) {
if (mInCallService != null) {
try {
@@ -308,9 +314,14 @@
}
}
+ if (call.isRespondViaSmsCapable()) {
+ capabilities |= CallCapabilities.RESPOND_VIA_TEXT;
+ }
+
return new InCallCall(callId, state, call.getDisconnectCause(), call.getDisconnectMessage(),
- capabilities, connectTimeMillis, call.getHandle(), call.getGatewayInfo(),
- descriptor, call.getHandoffCallServiceDescriptor(), parentCallId, childCallIds);
+ call.getCannedSmsResponses(), capabilities, connectTimeMillis, call.getHandle(),
+ call.getGatewayInfo(), descriptor, call.getHandoffCallServiceDescriptor(),
+ parentCallId, childCallIds);
}
}
diff --git a/src/com/android/telecomm/MultiLineTitleEditTextPreference.java b/src/com/android/telecomm/MultiLineTitleEditTextPreference.java
new file mode 100644
index 0000000..d40cb21
--- /dev/null
+++ b/src/com/android/telecomm/MultiLineTitleEditTextPreference.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2011 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 android.content.Context;
+import android.preference.EditTextPreference;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.TextView;
+
+/**
+ * Ultra-simple subclass of EditTextPreference that allows the "title" to wrap
+ * onto multiple lines.
+ *
+ * (By default, the title of an EditTextPreference is singleLine="true"; see
+ * preference_holo.xml under frameworks/base. But in the "Respond via SMS"
+ * settings UI we want titles to be multi-line, since the customized messages
+ * might be fairly long, and should be able to wrap.)
+ *
+ * TODO: This is pretty cumbersome; it would be nicer for the framework to
+ * either allow modifying the title's attributes in XML, or at least provide
+ * some way from Java (given an EditTextPreference) to reach inside and get a
+ * handle to the "title" TextView.
+ *
+ * TODO: Also, it would reduce clutter if this could be an inner class in
+ * RespondViaSmsManager.java, but then there would be no way to reference the
+ * class from XML. That's because
+ * <com.android.telecomm.MultiLineTitleEditTextPreference ... />
+ * isn't valid XML syntax due to the "$" character. And Preference
+ * elements don't have a "class" attribute, so you can't do something like
+ * <view class="com.android.telecomm.Foo$Bar"> as you can with regular views.
+ */
+public class MultiLineTitleEditTextPreference extends EditTextPreference {
+ public MultiLineTitleEditTextPreference(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ public MultiLineTitleEditTextPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public MultiLineTitleEditTextPreference(Context context) {
+ super(context);
+ }
+
+ // The "title" TextView inside an EditTextPreference defaults to
+ // singleLine="true" (see preference_holo.xml under frameworks/base.)
+ // We override onBindView() purely to look up that TextView and call
+ // setSingleLine(false) on it.
+ @Override
+ protected void onBindView(View view) {
+ super.onBindView(view);
+
+ TextView textView = (TextView) view.findViewById(com.android.internal.R.id.title);
+ if (textView != null) {
+ textView.setSingleLine(false);
+ }
+ }
+}
diff --git a/src/com/android/telecomm/RespondViaSmsManager.java b/src/com/android/telecomm/RespondViaSmsManager.java
new file mode 100644
index 0000000..bf1a2a5
--- /dev/null
+++ b/src/com/android/telecomm/RespondViaSmsManager.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2011 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.android.internal.os.SomeArgs;
+import com.android.internal.telephony.SmsApplication;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.res.Resources;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Message;
+import android.telecomm.Response;
+import android.telephony.TelephonyManager;
+import android.widget.Toast;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Helper class to manage the "Respond via Message" feature for incoming calls.
+ */
+public class RespondViaSmsManager extends CallsManagerListenerBase {
+ private static final String SCHEME_SMSTO = "smsto";
+
+ /** SharedPreferences file name for our persistent settings. */
+ private static final String SHARED_PREFERENCES_NAME = "respond_via_sms_prefs";
+
+ // Preference keys for the 4 "canned responses"; see RespondViaSmsManager$Settings.
+ // Since (for now at least) the number of messages is fixed at 4, and since
+ // SharedPreferences can't deal with arrays anyway, just store the messages
+ // as 4 separate strings.
+ private static final int NUM_CANNED_RESPONSES = 4;
+ private static final String KEY_CANNED_RESPONSE_PREF_1 = "canned_response_pref_1";
+ private static final String KEY_CANNED_RESPONSE_PREF_2 = "canned_response_pref_2";
+ private static final String KEY_CANNED_RESPONSE_PREF_3 = "canned_response_pref_3";
+ private static final String KEY_CANNED_RESPONSE_PREF_4 = "canned_response_pref_4";
+
+ private static final int MSG_CANNED_TEXT_MESSAGES_READY = 1;
+ private static final int MSG_SHOW_SENT_TOAST = 2;
+
+ private static final RespondViaSmsManager sInstance = new RespondViaSmsManager();
+
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_CANNED_TEXT_MESSAGES_READY:
+ SomeArgs args = (SomeArgs) msg.obj;
+ try {
+ Response<Void, List<String>> response =
+ (Response<Void, List<String>>) args.arg1;
+ List<String> textMessages =
+ (List<String>) args.arg2;
+ if (textMessages != null) {
+ response.onResult(null, textMessages);
+ } else {
+ response.onError(null, 0, null);
+ }
+ } finally {
+ args.recycle();
+ }
+ break;
+ case MSG_SHOW_SENT_TOAST:
+ showMessageSentToast((String) msg.obj);
+ break;
+ }
+ }
+ };
+
+ public static RespondViaSmsManager getInstance() { return sInstance; }
+
+ private RespondViaSmsManager() {}
+
+ /**
+ * Read the (customizable) canned responses from SharedPreferences,
+ * or from defaults if the user has never actually brought up
+ * the Settings UI.
+ *
+ * The interface of this method is asynchronous since it does disk I/O.
+ *
+ * @param response An object to receive an async reply, which will be called from
+ * the main thread.
+ */
+ public void loadCannedTextMessages(final Response<Void, List<String>> response) {
+ new Thread() {
+ @Override
+ public void run() {
+ Log.d(RespondViaSmsManager.this, "loadCannedResponses() starting");
+ final SharedPreferences prefs = TelecommApp.getInstance().getSharedPreferences(
+ SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
+ final Resources res = TelecommApp.getInstance().getInstance().getResources();
+
+ final ArrayList<String> textMessages = new ArrayList<String>(NUM_CANNED_RESPONSES);
+
+ // Note the default values here must agree with the corresponding
+ // android:defaultValue attributes in respond_via_sms_settings.xml.
+
+ textMessages.add(0, prefs.getString(KEY_CANNED_RESPONSE_PREF_1,
+ res.getString(R.string.respond_via_sms_canned_response_1)));
+ textMessages.add(1, prefs.getString(KEY_CANNED_RESPONSE_PREF_2,
+ res.getString(R.string.respond_via_sms_canned_response_2)));
+ textMessages.add(2, prefs.getString(KEY_CANNED_RESPONSE_PREF_3,
+ res.getString(R.string.respond_via_sms_canned_response_3)));
+ textMessages.add(3, prefs.getString(KEY_CANNED_RESPONSE_PREF_4,
+ res.getString(R.string.respond_via_sms_canned_response_4)));
+
+ Log.d(RespondViaSmsManager.this,
+ "loadCannedResponses() completed, found responses: %s",
+ textMessages.toString());
+
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = response;
+ args.arg2 = textMessages;
+ mHandler.obtainMessage(MSG_CANNED_TEXT_MESSAGES_READY, args).sendToTarget();
+ }
+ }.start();
+ }
+
+ @Override
+ public void onIncomingCallRejected(Call call, boolean rejectWithMessage, String textMessage) {
+ if (rejectWithMessage) {
+ rejectCallWithMessage(call.getHandle().getSchemeSpecificPart(), textMessage);
+ }
+ }
+
+ private void showMessageSentToast(final String phoneNumber) {
+ // ...and show a brief confirmation to the user (since
+ // otherwise it's hard to be sure that anything actually
+ // happened.)
+ final Resources res = TelecommApp.getInstance().getResources();
+ final String formatString = res.getString(
+ R.string.respond_via_sms_confirmation_format);
+ final String confirmationMsg = String.format(formatString, phoneNumber);
+ Toast.makeText(TelecommApp.getInstance(), confirmationMsg,
+ Toast.LENGTH_LONG).show();
+
+ // TODO: If the device is locked, this toast won't actually ever
+ // be visible! (That's because we're about to dismiss the call
+ // screen, which means that the device will return to the
+ // keyguard. But toasts aren't visible on top of the keyguard.)
+ // Possible fixes:
+ // (1) Is it possible to allow a specific Toast to be visible
+ // on top of the keyguard?
+ // (2) Artificially delay the dismissCallScreen() call by 3
+ // seconds to allow the toast to be seen?
+ // (3) Don't use a toast at all; instead use a transient state
+ // of the InCallScreen (perhaps via the InCallUiState
+ // progressIndication feature), and have that state be
+ // visible for 3 seconds before calling dismissCallScreen().
+ }
+
+ /**
+ * Reject the call with the specified message. If message is null this call is ignored.
+ */
+ private void rejectCallWithMessage(String phoneNumber, String textMessage) {
+ if (textMessage != null) {
+ final ComponentName component =
+ SmsApplication.getDefaultRespondViaMessageApplication(
+ TelecommApp.getInstance(), true /*updateIfNeeded*/);
+ if (component != null) {
+ // Build and send the intent
+ final Uri uri = Uri.fromParts(SCHEME_SMSTO, phoneNumber, null);
+ final Intent intent = new Intent(TelephonyManager.ACTION_RESPOND_VIA_MESSAGE, uri);
+ intent.putExtra(Intent.EXTRA_TEXT, textMessage);
+ mHandler.obtainMessage(MSG_SHOW_SENT_TOAST, phoneNumber).sendToTarget();
+ intent.setComponent(component);
+ TelecommApp.getInstance().startService(intent);
+ }
+ }
+ }
+}
diff --git a/src/com/android/telecomm/RespondViaSmsSettings.java b/src/com/android/telecomm/RespondViaSmsSettings.java
new file mode 100644
index 0000000..1adf45c
--- /dev/null
+++ b/src/com/android/telecomm/RespondViaSmsSettings.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2011 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 android.app.ActionBar;
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.preference.EditTextPreference;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+import android.view.Menu;
+import android.view.MenuItem;
+
+/**
+ * Helper class to manage the "Respond via SMS Message" feature for incoming calls.
+ */
+public class RespondViaSmsSettings {
+ /** SharedPreferences file name for our persistent settings. */
+ private static final String SHARED_PREFERENCES_NAME = "respond_via_sms_prefs";
+
+ // Preference keys for the 4 "canned responses"; see RespondViaSmsManager$Settings.
+ // Since (for now at least) the number of messages is fixed at 4, and since
+ // SharedPreferences can't deal with arrays anyway, just store the messages
+ // as 4 separate strings.
+ private static final int NUM_CANNED_RESPONSES = 4;
+ private static final String KEY_CANNED_RESPONSE_PREF_1 = "canned_response_pref_1";
+ private static final String KEY_CANNED_RESPONSE_PREF_2 = "canned_response_pref_2";
+ private static final String KEY_CANNED_RESPONSE_PREF_3 = "canned_response_pref_3";
+ private static final String KEY_CANNED_RESPONSE_PREF_4 = "canned_response_pref_4";
+ private static final String KEY_PREFERRED_PACKAGE = "preferred_package_pref";
+ private static final String KEY_INSTANT_TEXT_DEFAULT_COMPONENT = "instant_text_def_component";
+
+ // TODO: This class is newly copied into Telecomm (com.android.telecomm) from it previous
+ // location in Telephony (com.android.phone). User's preferences stored in the old location
+ // will be lost. We need code here to migrate KLP -> LMP settings values.
+
+ /**
+ * Settings activity under "Call settings" to let you manage the
+ * canned responses; see respond_via_sms_settings.xml
+ */
+ public static class Settings extends PreferenceActivity
+ implements Preference.OnPreferenceChangeListener {
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ Log.d(this, "Settings: onCreate()...");
+
+ getPreferenceManager().setSharedPreferencesName(SHARED_PREFERENCES_NAME);
+
+ // This preference screen is ultra-simple; it's just 4 plain
+ // <EditTextPreference>s, one for each of the 4 "canned responses".
+ //
+ // The only nontrivial thing we do here is copy the text value of
+ // each of those EditTextPreferences and use it as the preference's
+ // "title" as well, so that the user will immediately see all 4
+ // strings when they arrive here.
+ //
+ // Also, listen for change events (since we'll need to update the
+ // title any time the user edits one of the strings.)
+
+ addPreferencesFromResource(R.xml.respond_via_sms_settings);
+
+ EditTextPreference pref;
+ pref = (EditTextPreference) findPreference(KEY_CANNED_RESPONSE_PREF_1);
+ pref.setTitle(pref.getText());
+ pref.setOnPreferenceChangeListener(this);
+
+ pref = (EditTextPreference) findPreference(KEY_CANNED_RESPONSE_PREF_2);
+ pref.setTitle(pref.getText());
+ pref.setOnPreferenceChangeListener(this);
+
+ pref = (EditTextPreference) findPreference(KEY_CANNED_RESPONSE_PREF_3);
+ pref.setTitle(pref.getText());
+ pref.setOnPreferenceChangeListener(this);
+
+ pref = (EditTextPreference) findPreference(KEY_CANNED_RESPONSE_PREF_4);
+ pref.setTitle(pref.getText());
+ pref.setOnPreferenceChangeListener(this);
+
+ ActionBar actionBar = getActionBar();
+ if (actionBar != null) {
+ // android.R.id.home will be triggered in onOptionsItemSelected()
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ }
+ }
+
+ // Preference.OnPreferenceChangeListener implementation
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ Log.d(this, "onPreferenceChange: key = %s", preference.getKey());
+ Log.d(this, " preference = '%s'", preference);
+ Log.d(this, " newValue = '%s'", newValue);
+
+ EditTextPreference pref = (EditTextPreference) preference;
+
+ // Copy the new text over to the title, just like in onCreate().
+ // (Watch out: onPreferenceChange() is called *before* the
+ // Preference itself gets updated, so we need to use newValue here
+ // rather than pref.getText().)
+ pref.setTitle((String) newValue);
+
+ return true; // means it's OK to update the state of the Preference with the new value
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ final int itemId = item.getItemId();
+ switch (itemId) {
+ case android.R.id.home:
+ goUpToTopLevelSetting(this);
+ return true;
+ case R.id.respond_via_message_reset:
+ // Reset the preferences settings
+ SharedPreferences prefs = getSharedPreferences(
+ SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = prefs.edit();
+ editor.remove(KEY_INSTANT_TEXT_DEFAULT_COMPONENT);
+ editor.apply();
+
+ return true;
+ default:
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.respond_via_message_settings_menu, menu);
+ return super.onCreateOptionsMenu(menu);
+ }
+ }
+
+ /**
+ * Finish current Activity and go up to the top level Settings.
+ */
+ public static void goUpToTopLevelSetting(Activity activity) {
+ Intent intent = new Intent();
+ try {
+ intent.setClassName(
+ activity.createPackageContext("com.android.phone", 0),
+ "com.android.phone.CallFeaturesSetting");
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(RespondViaSmsSettings.class,
+ "Exception building package context com.android.phone", e);
+ return;
+ }
+ intent.setAction(Intent.ACTION_MAIN);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ activity.startActivity(intent);
+ activity.finish();
+ }
+}
diff --git a/src/com/android/telecomm/Ringer.java b/src/com/android/telecomm/Ringer.java
index 5b5beff..0b54a2b 100644
--- a/src/com/android/telecomm/Ringer.java
+++ b/src/com/android/telecomm/Ringer.java
@@ -105,7 +105,7 @@
}
@Override
- public void onIncomingCallRejected(Call call) {
+ public void onIncomingCallRejected(Call call, boolean rejectWithMessage, String textMessage) {
onRespondedToIncomingCall(call);
}