blob: dd4fe170dd12ef859b2383d05abee9ffa7b1ae0e [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 Braun85e30702013-10-05 19:33:00 -0700144 * Reject the call with the specified message. If message is null this call is ignored.
Christine Chenee09a492013-08-06 16:02:29 -0700145 */
Christine Chen89ee4722013-10-07 16:10:36 -0700146 public static void rejectCallWithMessage(String phoneNumber, String message) {
147 if (message != null) {
David Braun35272072013-09-24 17:19:26 -0700148 final ComponentName component =
149 SmsApplication.getDefaultRespondViaMessageApplication(
150 PhoneGlobals.getInstance(), true /*updateIfNeeded*/);
151 if (component != null) {
152 // Build and send the intent
153 final Uri uri = Uri.fromParts(Constants.SCHEME_SMSTO, phoneNumber, null);
154 final Intent intent = new Intent(TelephonyManager.ACTION_RESPOND_VIA_MESSAGE, uri);
David Braun85e30702013-10-05 19:33:00 -0700155 intent.putExtra(Intent.EXTRA_TEXT, message);
156 showMessageSentToast(phoneNumber);
David Braun35272072013-09-24 17:19:26 -0700157 intent.setComponent(component);
158 PhoneGlobals.getInstance().startService(intent);
Christine Chenee09a492013-08-06 16:02:29 -0700159 }
Christine Chenee09a492013-08-06 16:02:29 -0700160 }
161 }
162
Yorke Lee814da302013-08-30 16:01:07 -0700163 /**
164 * @return true if the "Respond via SMS" feature should be enabled
165 * for the specified incoming call.
166 *
167 * The general rule is that we *do* allow "Respond via SMS" except for
168 * the few (relatively rare) cases where we know for sure it won't
169 * work, namely:
170 * - a bogus or blank incoming number
171 * - a call from a SIP address
172 * - a "call presentation" that doesn't allow the number to be revealed
173 *
174 * In all other cases, we allow the user to respond via SMS.
175 *
176 * Note that this behavior isn't perfect; for example we have no way
177 * to detect whether the incoming call is from a landline (with most
178 * networks at least), so we still enable this feature even though
179 * SMSes to that number will silently fail.
180 */
181 public static boolean allowRespondViaSmsForCall(
182 com.android.services.telephony.common.Call call, Connection conn) {
183 if (DBG) log("allowRespondViaSmsForCall(" + call + ")...");
184
185 // First some basic sanity checks:
186 if (call == null) {
187 Log.w(TAG, "allowRespondViaSmsForCall: null ringingCall!");
188 return false;
189 }
190 if (!(call.getState() == com.android.services.telephony.common.Call.State.INCOMING) &&
191 !(call.getState() ==
192 com.android.services.telephony.common.Call.State.CALL_WAITING)) {
193 // The call is in some state other than INCOMING or WAITING!
194 // (This should almost never happen, but it *could*
195 // conceivably happen if the ringing call got disconnected by
196 // the network just *after* we got it from the CallManager.)
197 Log.w(TAG, "allowRespondViaSmsForCall: ringingCall not ringing! state = "
198 + call.getState());
199 return false;
200 }
201
202 if (conn == null) {
203 // The call doesn't have any connections! (Again, this can
204 // happen if the ringing call disconnects at the exact right
205 // moment, but should almost never happen in practice.)
206 Log.w(TAG, "allowRespondViaSmsForCall: null Connection!");
207 return false;
208 }
209
210 // Check the incoming number:
211 final String number = conn.getAddress();
212 if (DBG) log("- number: '" + number + "'");
213 if (TextUtils.isEmpty(number)) {
214 Log.w(TAG, "allowRespondViaSmsForCall: no incoming number!");
215 return false;
216 }
217 if (PhoneNumberUtils.isUriNumber(number)) {
218 // The incoming number is actually a URI (i.e. a SIP address),
219 // not a regular PSTN phone number, and we can't send SMSes to
220 // SIP addresses.
221 // (TODO: That might still be possible eventually, though. Is
222 // there some SIP-specific equivalent to sending a text message?)
223 Log.i(TAG, "allowRespondViaSmsForCall: incoming 'number' is a SIP address.");
224 return false;
225 }
226
227 // Finally, check the "call presentation":
228 int presentation = conn.getNumberPresentation();
229 if (DBG) log("- presentation: " + presentation);
230 if (presentation == PhoneConstants.PRESENTATION_RESTRICTED) {
231 // PRESENTATION_RESTRICTED means "caller-id blocked".
232 // The user isn't allowed to see the number in the first
233 // place, so obviously we can't let you send an SMS to it.
234 Log.i(TAG, "allowRespondViaSmsForCall: PRESENTATION_RESTRICTED.");
235 return false;
236 }
237
David Braun35272072013-09-24 17:19:26 -0700238 // Is there a valid SMS application on the phone?
239 if (SmsApplication.getDefaultRespondViaMessageApplication(PhoneGlobals.getInstance(),
240 true /*updateIfNeeded*/) == null) {
Yorke Lee814da302013-08-30 16:01:07 -0700241 return false;
242 }
243
244 // TODO: with some carriers (in certain countries) you *can* actually
245 // tell whether a given number is a mobile phone or not. So in that
246 // case we could potentially return false here if the incoming call is
247 // from a land line.
248
249 // If none of the above special cases apply, it's OK to enable the
250 // "Respond via SMS" feature.
251 return true;
252 }
253
Christine Chenee09a492013-08-06 16:02:29 -0700254 private static void log(String msg) {
255 Log.d(TAG, msg);
256 }
257}