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