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