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