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