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