blob: 8ad1257caaf77d0e5852add851a1e279fefb7ee2 [file] [log] [blame]
Christine Chenee09a492013-08-06 16:02:29 -07001/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.phone;
18
Christine Chenee09a492013-08-06 16:02:29 -070019import android.content.ComponentName;
20import android.content.Context;
Christine Chenee09a492013-08-06 16:02:29 -070021import android.content.Intent;
22import android.content.SharedPreferences;
Christine Chenee09a492013-08-06 16:02:29 -070023import android.content.res.Resources;
Christine Chenee09a492013-08-06 16:02:29 -070024import android.net.Uri;
25import android.os.Handler;
Christine Chen32f4a8d2013-09-18 21:11:17 -070026import android.os.Looper;
27import android.os.Message;
Yorke Lee814da302013-08-30 16:01:07 -070028import android.telephony.PhoneNumberUtils;
Christine Chenee09a492013-08-06 16:02:29 -070029import android.telephony.TelephonyManager;
Yorke Lee814da302013-08-30 16:01:07 -070030import android.text.TextUtils;
Christine Chenee09a492013-08-06 16:02:29 -070031import android.util.Log;
Christine Chen32f4a8d2013-09-18 21:11:17 -070032import android.widget.Toast;
Christine Chenee09a492013-08-06 16:02:29 -070033
34import com.android.internal.telephony.Call;
35import com.android.internal.telephony.Connection;
36import com.android.internal.telephony.PhoneConstants;
David Braun35272072013-09-24 17:19:26 -070037import com.android.internal.telephony.SmsApplication;
Christine Chenee09a492013-08-06 16:02:29 -070038
39import java.util.ArrayList;
Christine Chenee09a492013-08-06 16:02:29 -070040
41/**
42 * Helper class to manage the "Respond via Message" feature for incoming calls.
43 *
44 * @see com.android.phone.InCallScreen.internalRespondViaSms()
45 */
46public class RejectWithTextMessageManager {
Christine Chenee09a492013-08-06 16:02:29 -070047 private static final String TAG = RejectWithTextMessageManager.class.getSimpleName();
48 private static final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 2);
49
Christine Chenee09a492013-08-06 16:02:29 -070050 /** SharedPreferences file name for our persistent settings. */
51 private static final String SHARED_PREFERENCES_NAME = "respond_via_sms_prefs";
52
David Braun35272072013-09-24 17:19:26 -070053 // Preference keys for the 4 "canned responses"; see RespondViaSmsManager$Settings.
Christine Chenee09a492013-08-06 16:02:29 -070054 // Since (for now at least) the number of messages is fixed at 4, and since
55 // SharedPreferences can't deal with arrays anyway, just store the messages
56 // as 4 separate strings.
57 private static final int NUM_CANNED_RESPONSES = 4;
58 private static final String KEY_CANNED_RESPONSE_PREF_1 = "canned_response_pref_1";
59 private static final String KEY_CANNED_RESPONSE_PREF_2 = "canned_response_pref_2";
60 private static final String KEY_CANNED_RESPONSE_PREF_3 = "canned_response_pref_3";
61 private static final String KEY_CANNED_RESPONSE_PREF_4 = "canned_response_pref_4";
Christine Chenee09a492013-08-06 16:02:29 -070062
63 /**
64 * Read the (customizable) canned responses from SharedPreferences,
65 * or from defaults if the user has never actually brought up
66 * the Settings UI.
67 *
68 * This method does disk I/O (reading the SharedPreferences file)
69 * so don't call it from the main thread.
70 *
71 * @see com.android.phone.RejectWithTextMessageManager.Settings
72 */
Chiao Cheng6c6b2722013-08-22 18:35:54 -070073 public static ArrayList<String> loadCannedResponses() {
Christine Chenee09a492013-08-06 16:02:29 -070074 if (DBG) log("loadCannedResponses()...");
75
76 final SharedPreferences prefs = PhoneGlobals.getInstance().getSharedPreferences(
77 SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
78 final Resources res = PhoneGlobals.getInstance().getResources();
79
80 final ArrayList<String> responses = new ArrayList<String>(NUM_CANNED_RESPONSES);
81
82 // Note the default values here must agree with the corresponding
83 // android:defaultValue attributes in respond_via_sms_settings.xml.
84
85 responses.add(0, prefs.getString(KEY_CANNED_RESPONSE_PREF_1,
86 res.getString(R.string.respond_via_sms_canned_response_1)));
87 responses.add(1, prefs.getString(KEY_CANNED_RESPONSE_PREF_2,
88 res.getString(R.string.respond_via_sms_canned_response_2)));
89 responses.add(2, prefs.getString(KEY_CANNED_RESPONSE_PREF_3,
90 res.getString(R.string.respond_via_sms_canned_response_3)));
91 responses.add(3, prefs.getString(KEY_CANNED_RESPONSE_PREF_4,
92 res.getString(R.string.respond_via_sms_canned_response_4)));
93 return responses;
94 }
95
David Braun35272072013-09-24 17:19:26 -070096 private static void showMessageSentToast(final String phoneNumber) {
97 // ...and show a brief confirmation to the user (since
98 // otherwise it's hard to be sure that anything actually
99 // happened.)
100 // Ugly hack to show a toaster from a service.
101 (new Thread(new Runnable() {
102 @Override
103 public void run() {
104 Looper.prepare();
105 Handler innerHandler = new Handler() {
106 @Override
107 public void handleMessage(Message message) {
108 final Resources res = PhoneGlobals.getInstance().getResources();
109 final String formatString = res.getString(
110 R.string.respond_via_sms_confirmation_format);
111 final String confirmationMsg = String.format(formatString, phoneNumber);
112 Toast.makeText(PhoneGlobals.getInstance(), confirmationMsg,
113 Toast.LENGTH_LONG).show();
114 }
Christine Chen32f4a8d2013-09-18 21:11:17 -0700115
David Braun35272072013-09-24 17:19:26 -0700116 @Override
117 public void dispatchMessage(Message message) {
118 handleMessage(message);
119 }
120 };
Christine Chen32f4a8d2013-09-18 21:11:17 -0700121
David Braun35272072013-09-24 17:19:26 -0700122 Message message = innerHandler.obtainMessage();
123 innerHandler.dispatchMessage(message);
124 Looper.loop();
125 }
126 })).start();
Christine Chenee09a492013-08-06 16:02:29 -0700127
Christine Chenee09a492013-08-06 16:02:29 -0700128 // TODO: If the device is locked, this toast won't actually ever
129 // be visible! (That's because we're about to dismiss the call
130 // screen, which means that the device will return to the
131 // keyguard. But toasts aren't visible on top of the keyguard.)
132 // Possible fixes:
133 // (1) Is it possible to allow a specific Toast to be visible
134 // on top of the keyguard?
David Braun35272072013-09-24 17:19:26 -0700135 // (2) Artificially delay the dismissCallScreen() call by 3
Christine Chenee09a492013-08-06 16:02:29 -0700136 // seconds to allow the toast to be seen?
137 // (3) Don't use a toast at all; instead use a transient state
138 // of the InCallScreen (perhaps via the InCallUiState
139 // progressIndication feature), and have that state be
140 // visible for 3 seconds before calling dismissCallScreen().
141 }
142
143 /**
David Braun35272072013-09-24 17:19:26 -0700144 * Reject the call with the specified message (or launch messaging UX if null message)
Christine Chenee09a492013-08-06 16:02:29 -0700145 */
David Braun35272072013-09-24 17:19:26 -0700146 public static void rejectCallWithMessage(Call call, String message) {
147 Connection conn = call.getLatestConnection();
148 if (conn != null) {
149 final String phoneNumber = conn.getAddress();
150 final ComponentName component =
151 SmsApplication.getDefaultRespondViaMessageApplication(
152 PhoneGlobals.getInstance(), true /*updateIfNeeded*/);
153 if (component != null) {
154 // Build and send the intent
155 final Uri uri = Uri.fromParts(Constants.SCHEME_SMSTO, phoneNumber, null);
156 final Intent intent = new Intent(TelephonyManager.ACTION_RESPOND_VIA_MESSAGE, uri);
157 if (message != null) {
158 intent.putExtra(Intent.EXTRA_TEXT, message);
159 showMessageSentToast(phoneNumber);
160 } else {
161 intent.putExtra("exit_on_sent", true);
162 intent.putExtra("showUI", true);
163 }
164 intent.setComponent(component);
165 PhoneGlobals.getInstance().startService(intent);
Christine Chenee09a492013-08-06 16:02:29 -0700166 }
Christine Chenee09a492013-08-06 16:02:29 -0700167 }
168 }
169
Yorke Lee814da302013-08-30 16:01:07 -0700170 /**
171 * @return true if the "Respond via SMS" feature should be enabled
172 * for the specified incoming call.
173 *
174 * The general rule is that we *do* allow "Respond via SMS" except for
175 * the few (relatively rare) cases where we know for sure it won't
176 * work, namely:
177 * - a bogus or blank incoming number
178 * - a call from a SIP address
179 * - a "call presentation" that doesn't allow the number to be revealed
180 *
181 * In all other cases, we allow the user to respond via SMS.
182 *
183 * Note that this behavior isn't perfect; for example we have no way
184 * to detect whether the incoming call is from a landline (with most
185 * networks at least), so we still enable this feature even though
186 * SMSes to that number will silently fail.
187 */
188 public static boolean allowRespondViaSmsForCall(
189 com.android.services.telephony.common.Call call, Connection conn) {
190 if (DBG) log("allowRespondViaSmsForCall(" + call + ")...");
191
192 // First some basic sanity checks:
193 if (call == null) {
194 Log.w(TAG, "allowRespondViaSmsForCall: null ringingCall!");
195 return false;
196 }
197 if (!(call.getState() == com.android.services.telephony.common.Call.State.INCOMING) &&
198 !(call.getState() ==
199 com.android.services.telephony.common.Call.State.CALL_WAITING)) {
200 // The call is in some state other than INCOMING or WAITING!
201 // (This should almost never happen, but it *could*
202 // conceivably happen if the ringing call got disconnected by
203 // the network just *after* we got it from the CallManager.)
204 Log.w(TAG, "allowRespondViaSmsForCall: ringingCall not ringing! state = "
205 + call.getState());
206 return false;
207 }
208
209 if (conn == null) {
210 // The call doesn't have any connections! (Again, this can
211 // happen if the ringing call disconnects at the exact right
212 // moment, but should almost never happen in practice.)
213 Log.w(TAG, "allowRespondViaSmsForCall: null Connection!");
214 return false;
215 }
216
217 // Check the incoming number:
218 final String number = conn.getAddress();
219 if (DBG) log("- number: '" + number + "'");
220 if (TextUtils.isEmpty(number)) {
221 Log.w(TAG, "allowRespondViaSmsForCall: no incoming number!");
222 return false;
223 }
224 if (PhoneNumberUtils.isUriNumber(number)) {
225 // The incoming number is actually a URI (i.e. a SIP address),
226 // not a regular PSTN phone number, and we can't send SMSes to
227 // SIP addresses.
228 // (TODO: That might still be possible eventually, though. Is
229 // there some SIP-specific equivalent to sending a text message?)
230 Log.i(TAG, "allowRespondViaSmsForCall: incoming 'number' is a SIP address.");
231 return false;
232 }
233
234 // Finally, check the "call presentation":
235 int presentation = conn.getNumberPresentation();
236 if (DBG) log("- presentation: " + presentation);
237 if (presentation == PhoneConstants.PRESENTATION_RESTRICTED) {
238 // PRESENTATION_RESTRICTED means "caller-id blocked".
239 // The user isn't allowed to see the number in the first
240 // place, so obviously we can't let you send an SMS to it.
241 Log.i(TAG, "allowRespondViaSmsForCall: PRESENTATION_RESTRICTED.");
242 return false;
243 }
244
David Braun35272072013-09-24 17:19:26 -0700245 // Is there a valid SMS application on the phone?
246 if (SmsApplication.getDefaultRespondViaMessageApplication(PhoneGlobals.getInstance(),
247 true /*updateIfNeeded*/) == null) {
Yorke Lee814da302013-08-30 16:01:07 -0700248 return false;
249 }
250
251 // TODO: with some carriers (in certain countries) you *can* actually
252 // tell whether a given number is a mobile phone or not. So in that
253 // case we could potentially return false here if the incoming call is
254 // from a land line.
255
256 // If none of the above special cases apply, it's OK to enable the
257 // "Respond via SMS" feature.
258 return true;
259 }
260
Christine Chenee09a492013-08-06 16:02:29 -0700261 private static void log(String msg) {
262 Log.d(TAG, msg);
263 }
264}