RespondViaSmsManager.java revision 21241167ccc1cfb8e221249e032a8bd3276c525a
1/* 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.server.telecom; 18 19// TODO: Needed for move to system service: import com.android.internal.R; 20import com.android.internal.os.SomeArgs; 21import com.android.internal.telephony.SmsApplication; 22 23import android.content.ComponentName; 24import android.content.Context; 25import android.content.SharedPreferences; 26import android.content.res.Resources; 27import android.os.Handler; 28import android.os.Looper; 29import android.os.Message; 30import android.telecom.Connection; 31import android.telecom.Log; 32import android.telecom.Response; 33import android.telephony.PhoneNumberUtils; 34import android.telephony.SmsManager; 35import android.telephony.SubscriptionManager; 36import android.text.Spannable; 37import android.text.SpannableString; 38import android.text.TextUtils; 39import android.widget.Toast; 40 41import java.util.ArrayList; 42import java.util.List; 43 44/** 45 * Helper class to manage the "Respond via Message" feature for incoming calls. 46 */ 47public class RespondViaSmsManager extends CallsManagerListenerBase { 48 private static final int MSG_SHOW_SENT_TOAST = 2; 49 50 private final CallsManager mCallsManager; 51 private final TelecomSystem.SyncRoot mLock; 52 53 private final Handler mHandler = new Handler(Looper.getMainLooper()) { 54 @Override 55 public void handleMessage(Message msg) { 56 switch (msg.what) { 57 case MSG_SHOW_SENT_TOAST: { 58 SomeArgs args = (SomeArgs) msg.obj; 59 try { 60 String toastMessage = (String) args.arg1; 61 Context context = (Context) args.arg2; 62 showMessageSentToast(toastMessage, context); 63 } finally { 64 args.recycle(); 65 } 66 break; 67 } 68 } 69 } 70 }; 71 72 public RespondViaSmsManager(CallsManager callsManager, TelecomSystem.SyncRoot lock) { 73 mCallsManager = callsManager; 74 mLock = lock; 75 } 76 77 /** 78 * Read the (customizable) canned responses from SharedPreferences, 79 * or from defaults if the user has never actually brought up 80 * the Settings UI. 81 * 82 * The interface of this method is asynchronous since it does disk I/O. 83 * 84 * @param response An object to receive an async reply, which will be called from 85 * the main thread. 86 * @param context The context. 87 */ 88 public void loadCannedTextMessages(final Response<Void, List<String>> response, 89 final Context context) { 90 new Thread() { 91 @Override 92 public void run() { 93 Log.d(RespondViaSmsManager.this, "loadCannedResponses() starting"); 94 95 // This function guarantees that QuickResponses will be in our 96 // SharedPreferences with the proper values considering there may be 97 // old QuickResponses in Telephony pre L. 98 QuickResponseUtils.maybeMigrateLegacyQuickResponses(context); 99 100 final SharedPreferences prefs = context.getSharedPreferences( 101 QuickResponseUtils.SHARED_PREFERENCES_NAME, 102 Context.MODE_PRIVATE | Context.MODE_MULTI_PROCESS); 103 final Resources res = context.getResources(); 104 105 final ArrayList<String> textMessages = new ArrayList<>( 106 QuickResponseUtils.NUM_CANNED_RESPONSES); 107 108 // Note the default values here must agree with the corresponding 109 // android:defaultValue attributes in respond_via_sms_settings.xml. 110 textMessages.add(0, prefs.getString(QuickResponseUtils.KEY_CANNED_RESPONSE_PREF_1, 111 res.getString(R.string.respond_via_sms_canned_response_1))); 112 textMessages.add(1, prefs.getString(QuickResponseUtils.KEY_CANNED_RESPONSE_PREF_2, 113 res.getString(R.string.respond_via_sms_canned_response_2))); 114 textMessages.add(2, prefs.getString(QuickResponseUtils.KEY_CANNED_RESPONSE_PREF_3, 115 res.getString(R.string.respond_via_sms_canned_response_3))); 116 textMessages.add(3, prefs.getString(QuickResponseUtils.KEY_CANNED_RESPONSE_PREF_4, 117 res.getString(R.string.respond_via_sms_canned_response_4))); 118 119 Log.d(RespondViaSmsManager.this, 120 "loadCannedResponses() completed, found responses: %s", 121 textMessages.toString()); 122 123 synchronized (mLock) { 124 response.onResult(null, textMessages); 125 } 126 } 127 }.start(); 128 } 129 130 @Override 131 public void onIncomingCallRejected(Call call, boolean rejectWithMessage, String textMessage) { 132 if (rejectWithMessage 133 && call.getHandle() != null 134 && !call.can(Connection.CAPABILITY_CAN_SEND_RESPONSE_VIA_CONNECTION)) { 135 int subId = mCallsManager.getPhoneAccountRegistrar().getSubscriptionIdForPhoneAccount( 136 call.getTargetPhoneAccount()); 137 rejectCallWithMessage(call.getContext(), call.getHandle().getSchemeSpecificPart(), 138 textMessage, subId, call.getName()); 139 } 140 } 141 142 private void showMessageSentToast(final String phoneNumber, final Context context) { 143 // ...and show a brief confirmation to the user (since 144 // otherwise it's hard to be sure that anything actually 145 // happened.) 146 final Resources res = context.getResources(); 147 final String formatString = res.getString( 148 R.string.respond_via_sms_confirmation_format); 149 final String confirmationMsg = String.format(formatString, phoneNumber); 150 int startingPosition = confirmationMsg.indexOf(phoneNumber); 151 int endingPosition = startingPosition + phoneNumber.length(); 152 153 Spannable styledConfirmationMsg = new SpannableString(confirmationMsg); 154 PhoneNumberUtils.addTtsSpan(styledConfirmationMsg, startingPosition, endingPosition); 155 Toast.makeText(context, styledConfirmationMsg, 156 Toast.LENGTH_LONG).show(); 157 158 // TODO: If the device is locked, this toast won't actually ever 159 // be visible! (That's because we're about to dismiss the call 160 // screen, which means that the device will return to the 161 // keyguard. But toasts aren't visible on top of the keyguard.) 162 // Possible fixes: 163 // (1) Is it possible to allow a specific Toast to be visible 164 // on top of the keyguard? 165 // (2) Artificially delay the dismissCallScreen() call by 3 166 // seconds to allow the toast to be seen? 167 // (3) Don't use a toast at all; instead use a transient state 168 // of the InCallScreen (perhaps via the InCallUiState 169 // progressIndication feature), and have that state be 170 // visible for 3 seconds before calling dismissCallScreen(). 171 } 172 173 /** 174 * Reject the call with the specified message. If message is null this call is ignored. 175 */ 176 private void rejectCallWithMessage(Context context, String phoneNumber, String textMessage, 177 int subId, String contactName) { 178 if (TextUtils.isEmpty(textMessage)) { 179 Log.w(RespondViaSmsManager.this, "Couldn't send SMS message: empty text message. "); 180 return; 181 } 182 if (!SubscriptionManager.isValidSubscriptionId(subId)) { 183 Log.w(RespondViaSmsManager.this, "Couldn't send SMS message: Invalid SubId: " + 184 subId); 185 return; 186 } 187 188 SmsManager smsManager = SmsManager.getSmsManagerForSubscriptionId(subId); 189 try { 190 smsManager.sendTextMessage(phoneNumber, null, textMessage, null /*sentIntent*/, 191 null /*deliveryIntent*/); 192 193 SomeArgs args = SomeArgs.obtain(); 194 args.arg1 = !TextUtils.isEmpty(contactName) ? contactName : phoneNumber; 195 args.arg2 = context; 196 mHandler.obtainMessage(MSG_SHOW_SENT_TOAST, args).sendToTarget(); 197 } catch (IllegalArgumentException e) { 198 Log.w(RespondViaSmsManager.this, "Couldn't send SMS message: " + 199 e.getMessage()); 200 } 201 } 202} 203