RespondViaSmsManager.java revision 524486403a387c324dee5aff7fb78ca784d15255
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.phone; 18 19import android.app.AlertDialog; 20import android.app.Dialog; 21import android.content.Context; 22import android.content.DialogInterface; 23import android.content.Intent; 24import android.net.Uri; 25import android.os.SystemProperties; 26import android.telephony.SmsManager; 27import android.util.Log; 28import android.view.View; 29import android.widget.AdapterView; 30import android.widget.ArrayAdapter; 31import android.widget.ListView; 32 33import com.android.internal.telephony.Call; 34import com.android.internal.telephony.Connection; 35 36import java.util.Arrays; 37 38/** 39 * Helper class to manage the "Respond via SMS" feature for incoming calls. 40 * @see InCallScreen.internalRespondViaSms() 41 */ 42public class RespondViaSmsManager { 43 private static final String TAG = "RespondViaSmsManager"; 44 private static final boolean DBG = true; 45 // STOPSHIP: reduce DBG to 46 // (PhoneApp.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1); 47 48 /** 49 * Reference to the InCallScreen activity that owns us. This may be 50 * null if we haven't been initialized yet *or* after the InCallScreen 51 * activity has been destroyed. 52 */ 53 private InCallScreen mInCallScreen; 54 55 /** 56 * The popup showing the list of canned responses. 57 * 58 * This is an AlertDialog containing a ListView showing the possible 59 * choices. This may be null if the InCallScreen hasn't ever called 60 * showRespondViaSmsPopup() yet, or if the popup was visible once but 61 * then got dismissed. 62 */ 63 private Dialog mPopup; 64 65 /** 66 * RespondViaSmsManager constructor. 67 */ 68 public RespondViaSmsManager() { 69 } 70 71 public void setInCallScreenInstance(InCallScreen inCallScreen) { 72 mInCallScreen = inCallScreen; 73 } 74 75 /** 76 * Brings up the "Respond via SMS" popup for an incoming call. 77 * 78 * @param ringingCall the current incoming call 79 */ 80 public void showRespondViaSmsPopup(Call ringingCall) { 81 if (DBG) log("showRespondViaSmsPopup()..."); 82 83 ListView lv = new ListView(mInCallScreen); 84 85 // Load the canned responses come from an array resource. 86 // TODO: This will eventually come from a SharedPreferences, since 87 // the responses need to be customizable. (Ultimately the 88 // respond_via_sms_canned_responses strings will only be used as 89 // default values.) 90 String[] responses = mInCallScreen.getResources() 91 .getStringArray(R.array.respond_via_sms_canned_responses); 92 93 // And manually add "Custom message..." as the last choice. 94 int numPopupItems = responses.length + 1; 95 String[] popupItems = Arrays.copyOf(responses, numPopupItems); 96 popupItems[numPopupItems - 1] = mInCallScreen.getResources() 97 .getString(R.string.respond_via_sms_custom_message); 98 99 ArrayAdapter<String> adapter = 100 new ArrayAdapter<String>(mInCallScreen, 101 android.R.layout.simple_list_item_1, 102 android.R.id.text1, 103 popupItems); 104 lv.setAdapter(adapter); 105 106 // Create a RespondViaSmsItemClickListener instance to handle item 107 // clicks from the popup. 108 // (Note we create a fresh instance for each incoming call, and 109 // stash away the call's phone number, since we can't necessarily 110 // assume this call will still be ringing when the user finally 111 // chooses a response.) 112 113 Connection c = ringingCall.getLatestConnection(); 114 if (DBG) log("- connection: " + c); 115 116 // TODO: at this point we probably should re-check c.getAddress() 117 // and c.getNumberPresentation() for validity. (i.e. recheck the 118 // same cases in InCallTouchUi.showIncomingCallWidget() where we 119 // should have disallowed the "respond via SMS" feature in the 120 // first place.) 121 122 String phoneNumber = c.getAddress(); 123 if (DBG) log("- phoneNumber: " + phoneNumber); // STOPSHIP: don't log PII 124 lv.setOnItemClickListener(new RespondViaSmsItemClickListener(phoneNumber)); 125 126 AlertDialog.Builder builder = new AlertDialog.Builder(mInCallScreen) 127 .setCancelable(true) 128 .setOnCancelListener(new RespondViaSmsCancelListener()) 129 .setView(lv); 130 mPopup = builder.create(); 131 mPopup.show(); 132 } 133 134 /** 135 * Dismiss the "Respond via SMS" popup if it's visible. 136 * 137 * This is safe to call even if the popup is already dismissed, and 138 * even if you never called showRespondViaSmsPopup() in the first 139 * place. 140 */ 141 public void dismissPopup() { 142 if (mPopup != null) { 143 mPopup.dismiss(); // safe even if already dismissed 144 mPopup = null; 145 } 146 } 147 148 /** 149 * OnItemClickListener for the "Respond via SMS" popup. 150 */ 151 public class RespondViaSmsItemClickListener implements AdapterView.OnItemClickListener { 152 // Phone number to send the SMS to. 153 private String mPhoneNumber; 154 155 public RespondViaSmsItemClickListener(String phoneNumber) { 156 mPhoneNumber = phoneNumber; 157 } 158 159 /** 160 * Handles the user selecting an item from the popup. 161 */ 162 public void onItemClick(AdapterView<?> parent, // The ListView 163 View view, // The TextView that was clicked 164 int position, 165 long id) { 166 if (DBG) log("RespondViaSmsItemClickListener.onItemClick(" + position + ")..."); 167 String message = (String) parent.getItemAtPosition(position); 168 if (DBG) log("- message: '" + message + "'"); 169 170 // The "Custom" choice is a special case. 171 // (For now, it's guaranteed to be the last item.) 172 if (position == (parent.getCount() - 1)) { 173 // Take the user to the standard SMS compose UI. 174 launchSmsCompose(mPhoneNumber); 175 } else { 176 // Send the selected message immediately with no user interaction. 177 sendText(mPhoneNumber, message); 178 } 179 180 PhoneApp.getInstance().dismissCallScreen(); 181 } 182 } 183 184 /** 185 * OnCancelListener for the "Respond via SMS" popup. 186 */ 187 public class RespondViaSmsCancelListener implements DialogInterface.OnCancelListener { 188 public RespondViaSmsCancelListener() { 189 } 190 191 /** 192 * Handles the user canceling the popup, either by touching 193 * outside the popup or by pressing Back. 194 */ 195 public void onCancel(DialogInterface dialog) { 196 if (DBG) log("RespondViaSmsCancelListener.onCancel()..."); 197 198 // If the user cancels the popup, this presumably means that 199 // they didn't actually mean to bring up the "Respond via SMS" 200 // UI in the first place (and instead want to go back to the 201 // state where they can either answer or reject the call.) 202 // So restart the ringer and bring back the regular incoming 203 // call UI. 204 205 // This will have no effect if the incoming call isn't still ringing. 206 PhoneApp.getInstance().notifier.restartRinger(); 207 208 // We hid the MultiWaveView widget way back in 209 // InCallTouchUi.onTrigger(), when the user first selected 210 // the "SMS" trigger. 211 // 212 // To bring it back, just force the entire InCallScreen to 213 // update itself based on the current telephony state. 214 // (Assuming the incoming call is still ringing, this will 215 // cause the incoming call widget to reappear.) 216 mInCallScreen.requestUpdateScreen(); 217 } 218 } 219 220 /** 221 * Sends a text message without any interaction from the user. 222 */ 223 private void sendText(String phoneNumber, String message) { 224 // STOPSHIP: disable all logging of PII (everywhere in this file) 225 if (DBG) log("sendText: number " 226 + phoneNumber + ", message '" + message + "'"); 227 228 // TODO: This code should use the new 229 // com.android.mms.intent.action.SENDTO_NO_CONFIRMATION 230 // intent once change https://android-git.corp.google.com/g/114664 231 // gets checked in. 232 // But use the old-school SmsManager API for now. 233 234 final SmsManager smsManager = SmsManager.getDefault(); 235 smsManager.sendTextMessage(phoneNumber, 236 null /* scAddress; null means "use default" */, 237 message, 238 null /* sentIntent */, 239 null /* deliveryIntent */); 240 } 241 242 /** 243 * Brings up the standard SMS compose UI. 244 */ 245 private void launchSmsCompose(String phoneNumber) { 246 if (DBG) log("launchSmsCompose: number " + phoneNumber); 247 248 // TODO: confirm with SMS guys that this is the correct intent to use. 249 Uri uri = Uri.fromParts(Constants.SCHEME_SMS, phoneNumber, null); 250 Intent intent = new Intent(Intent.ACTION_VIEW, uri); 251 252 if (DBG) log("- Launching SMS compose UI: " + intent); 253 mInCallScreen.startActivity(intent); 254 255 // TODO: One open issue here: if the user selects "Custom message" 256 // for an incoming call while the device was locked, and the user 257 // does *not* have a secure keyguard set, we bring up the 258 // non-secure keyguard at this point :-( 259 // Instead, we should immediately go to the SMS compose UI. 260 // 261 // I *believe* the fix is for the SMS compose activity to set the 262 // FLAG_DISMISS_KEYGUARD window flag (which will cause the 263 // keyguard to be dismissed *only* if it is not a secure lock 264 // keyguard.) 265 // 266 // But it there an equivalent way for me to accomplish that here, 267 // without needing to change the SMS app? 268 // 269 // In any case, I'm pretty sure the SMS UI should *not* to set 270 // FLAG_SHOW_WHEN_LOCKED, since we do want the force the user to 271 // enter their lock pattern or PIN at this point if they have a 272 // secure keyguard set. 273 } 274 275 private void log(String msg) { 276 Log.d(TAG, msg); 277 } 278} 279