1 /*
2  * Copyright (C) 2006 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.internal.telephony;
18 
19 import static android.telephony.SmsManager.RESULT_ERROR_GENERIC_FAILURE;
20 import android.app.PendingIntent;
21 import android.app.PendingIntent.CanceledException;
22 import android.net.Uri;
23 import android.os.AsyncResult;
24 import android.os.Message;
25 import android.provider.Telephony.Sms.Intents;
26 import android.telephony.Rlog;
27 
28 import com.android.internal.annotations.VisibleForTesting;
29 import com.android.internal.telephony.cdma.CdmaInboundSmsHandler;
30 import com.android.internal.telephony.cdma.CdmaSMSDispatcher;
31 import com.android.internal.telephony.gsm.GsmInboundSmsHandler;
32 import com.android.internal.telephony.gsm.GsmSMSDispatcher;
33 
34 import java.util.ArrayList;
35 import java.util.HashMap;
36 import java.util.concurrent.atomic.AtomicBoolean;
37 import java.util.concurrent.atomic.AtomicInteger;
38 
39 public class ImsSMSDispatcher extends SMSDispatcher {
40     private static final String TAG = "RIL_ImsSms";
41 
42     private SMSDispatcher mCdmaDispatcher;
43     private SMSDispatcher mGsmDispatcher;
44 
45     private GsmInboundSmsHandler mGsmInboundSmsHandler;
46     private CdmaInboundSmsHandler mCdmaInboundSmsHandler;
47 
48 
49     /** true if IMS is registered and sms is supported, false otherwise.*/
50     private boolean mIms = false;
51     private String mImsSmsFormat = SmsConstants.FORMAT_UNKNOWN;
52 
ImsSMSDispatcher(Phone phone, SmsStorageMonitor storageMonitor, SmsUsageMonitor usageMonitor)53     public ImsSMSDispatcher(Phone phone, SmsStorageMonitor storageMonitor,
54             SmsUsageMonitor usageMonitor) {
55         super(phone, usageMonitor, null);
56         Rlog.d(TAG, "ImsSMSDispatcher created");
57 
58         // Create dispatchers, inbound SMS handlers and
59         // broadcast undelivered messages in raw table.
60         mCdmaDispatcher = new CdmaSMSDispatcher(phone, usageMonitor, this);
61         mGsmInboundSmsHandler = GsmInboundSmsHandler.makeInboundSmsHandler(phone.getContext(),
62                 storageMonitor, phone);
63         mCdmaInboundSmsHandler = CdmaInboundSmsHandler.makeInboundSmsHandler(phone.getContext(),
64                 storageMonitor, phone, (CdmaSMSDispatcher) mCdmaDispatcher);
65         mGsmDispatcher = new GsmSMSDispatcher(phone, usageMonitor, this, mGsmInboundSmsHandler);
66         SmsBroadcastUndelivered.initialize(phone.getContext(),
67             mGsmInboundSmsHandler, mCdmaInboundSmsHandler);
68         InboundSmsHandler.registerNewMessageNotificationActionHandler(phone.getContext());
69 
70         mCi.registerForOn(this, EVENT_RADIO_ON, null);
71         mCi.registerForImsNetworkStateChanged(this, EVENT_IMS_STATE_CHANGED, null);
72     }
73 
74     /* Updates the phone object when there is a change */
75     @Override
updatePhoneObject(Phone phone)76     protected void updatePhoneObject(Phone phone) {
77         Rlog.d(TAG, "In IMS updatePhoneObject ");
78         super.updatePhoneObject(phone);
79         mCdmaDispatcher.updatePhoneObject(phone);
80         mGsmDispatcher.updatePhoneObject(phone);
81         mGsmInboundSmsHandler.updatePhoneObject(phone);
82         mCdmaInboundSmsHandler.updatePhoneObject(phone);
83     }
84 
dispose()85     public void dispose() {
86         mCi.unregisterForOn(this);
87         mCi.unregisterForImsNetworkStateChanged(this);
88         mGsmDispatcher.dispose();
89         mCdmaDispatcher.dispose();
90         mGsmInboundSmsHandler.dispose();
91         mCdmaInboundSmsHandler.dispose();
92     }
93 
94     /**
95      * Handles events coming from the phone stack. Overridden from handler.
96      *
97      * @param msg the message to handle
98      */
99     @Override
handleMessage(Message msg)100     public void handleMessage(Message msg) {
101         AsyncResult ar;
102 
103         switch (msg.what) {
104         case EVENT_RADIO_ON:
105         case EVENT_IMS_STATE_CHANGED: // received unsol
106             mCi.getImsRegistrationState(this.obtainMessage(EVENT_IMS_STATE_DONE));
107             break;
108 
109         case EVENT_IMS_STATE_DONE:
110             ar = (AsyncResult) msg.obj;
111 
112             if (ar.exception == null) {
113                 updateImsInfo(ar);
114             } else {
115                 Rlog.e(TAG, "IMS State query failed with exp "
116                         + ar.exception);
117             }
118             break;
119 
120         default:
121             super.handleMessage(msg);
122         }
123     }
124 
setImsSmsFormat(int format)125     private void setImsSmsFormat(int format) {
126         // valid format?
127         switch (format) {
128             case PhoneConstants.PHONE_TYPE_GSM:
129                 mImsSmsFormat = "3gpp";
130                 break;
131             case PhoneConstants.PHONE_TYPE_CDMA:
132                 mImsSmsFormat = "3gpp2";
133                 break;
134             default:
135                 mImsSmsFormat = "unknown";
136                 break;
137         }
138     }
139 
updateImsInfo(AsyncResult ar)140     private void updateImsInfo(AsyncResult ar) {
141         int[] responseArray = (int[])ar.result;
142 
143         mIms = false;
144         if (responseArray[0] == 1) {  // IMS is registered
145             Rlog.d(TAG, "IMS is registered!");
146             mIms = true;
147         } else {
148             Rlog.d(TAG, "IMS is NOT registered!");
149         }
150 
151         setImsSmsFormat(responseArray[1]);
152 
153         if (("unknown".equals(mImsSmsFormat))) {
154             Rlog.e(TAG, "IMS format was unknown!");
155             // failed to retrieve valid IMS SMS format info, set IMS to unregistered
156             mIms = false;
157         }
158     }
159 
160     @Override
sendData(String destAddr, String scAddr, int destPort, byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent)161     public void sendData(String destAddr, String scAddr, int destPort,
162             byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) {
163         if (isCdmaMo()) {
164             mCdmaDispatcher.sendData(destAddr, scAddr, destPort,
165                     data, sentIntent, deliveryIntent);
166         } else {
167             mGsmDispatcher.sendData(destAddr, scAddr, destPort,
168                     data, sentIntent, deliveryIntent);
169         }
170     }
171 
172     @Override
sendMultipartText(String destAddr, String scAddr, ArrayList<String> parts, ArrayList<PendingIntent> sentIntents, ArrayList<PendingIntent> deliveryIntents, Uri messageUri, String callingPkg, boolean persistMessage)173     public void sendMultipartText(String destAddr, String scAddr,
174             ArrayList<String> parts, ArrayList<PendingIntent> sentIntents,
175             ArrayList<PendingIntent> deliveryIntents, Uri messageUri, String callingPkg,
176             boolean persistMessage) {
177         if (isCdmaMo()) {
178             mCdmaDispatcher.sendMultipartText(destAddr, scAddr,
179                     parts, sentIntents, deliveryIntents, messageUri, callingPkg, persistMessage);
180         } else {
181             mGsmDispatcher.sendMultipartText(destAddr, scAddr,
182                     parts, sentIntents, deliveryIntents, messageUri, callingPkg, persistMessage);
183         }
184     }
185 
186     @Override
sendSms(SmsTracker tracker)187     protected void sendSms(SmsTracker tracker) {
188         //  sendSms is a helper function to other send functions, sendText/Data...
189         //  it is not part of ISms.stub
190         Rlog.e(TAG, "sendSms should never be called from here!");
191     }
192 
193     @Override
sendSmsByPstn(SmsTracker tracker)194     protected void sendSmsByPstn(SmsTracker tracker) {
195         // This function should be defined in Gsm/CdmaDispatcher.
196         Rlog.e(TAG, "sendSmsByPstn should never be called from here!");
197     }
198 
199     @Override
sendText(String destAddr, String scAddr, String text, PendingIntent sentIntent, PendingIntent deliveryIntent, Uri messageUri, String callingPkg, boolean persistMessage)200     public void sendText(String destAddr, String scAddr, String text, PendingIntent sentIntent,
201             PendingIntent deliveryIntent, Uri messageUri, String callingPkg,
202             boolean persistMessage) {
203         Rlog.d(TAG, "sendText");
204         if (isCdmaMo()) {
205             mCdmaDispatcher.sendText(destAddr, scAddr,
206                     text, sentIntent, deliveryIntent, messageUri, callingPkg, persistMessage);
207         } else {
208             mGsmDispatcher.sendText(destAddr, scAddr,
209                     text, sentIntent, deliveryIntent, messageUri, callingPkg, persistMessage);
210         }
211     }
212 
213     @VisibleForTesting
214     @Override
injectSmsPdu(byte[] pdu, String format, PendingIntent receivedIntent)215     public void injectSmsPdu(byte[] pdu, String format, PendingIntent receivedIntent) {
216         Rlog.d(TAG, "ImsSMSDispatcher:injectSmsPdu");
217         try {
218             // TODO We need to decide whether we should allow injecting GSM(3gpp)
219             // SMS pdus when the phone is camping on CDMA(3gpp2) network and vice versa.
220             android.telephony.SmsMessage msg =
221                     android.telephony.SmsMessage.createFromPdu(pdu, format);
222 
223             // Only class 1 SMS are allowed to be injected.
224             if (msg == null ||
225                     msg.getMessageClass() != android.telephony.SmsMessage.MessageClass.CLASS_1) {
226                 if (msg == null) {
227                     Rlog.e(TAG, "injectSmsPdu: createFromPdu returned null");
228                 }
229                 if (receivedIntent != null) {
230                     receivedIntent.send(Intents.RESULT_SMS_GENERIC_ERROR);
231                 }
232                 return;
233             }
234 
235             AsyncResult ar = new AsyncResult(receivedIntent, msg, null);
236 
237             if (format.equals(SmsConstants.FORMAT_3GPP)) {
238                 Rlog.i(TAG, "ImsSMSDispatcher:injectSmsText Sending msg=" + msg +
239                         ", format=" + format + "to mGsmInboundSmsHandler");
240                 mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_INJECT_SMS, ar);
241             } else if (format.equals(SmsConstants.FORMAT_3GPP2)) {
242                 Rlog.i(TAG, "ImsSMSDispatcher:injectSmsText Sending msg=" + msg +
243                         ", format=" + format + "to mCdmaInboundSmsHandler");
244                 mCdmaInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_INJECT_SMS, ar);
245             } else {
246                 // Invalid pdu format.
247                 Rlog.e(TAG, "Invalid pdu format: " + format);
248                 if (receivedIntent != null)
249                     receivedIntent.send(Intents.RESULT_SMS_GENERIC_ERROR);
250             }
251         } catch (Exception e) {
252             Rlog.e(TAG, "injectSmsPdu failed: ", e);
253             try {
254                 if (receivedIntent != null)
255                     receivedIntent.send(Intents.RESULT_SMS_GENERIC_ERROR);
256             } catch (CanceledException ex) {}
257         }
258     }
259 
260     @Override
sendRetrySms(SmsTracker tracker)261     public void sendRetrySms(SmsTracker tracker) {
262         String oldFormat = tracker.mFormat;
263 
264         // newFormat will be based on voice technology
265         String newFormat =
266             (PhoneConstants.PHONE_TYPE_CDMA == mPhone.getPhoneType()) ?
267                     mCdmaDispatcher.getFormat() :
268                         mGsmDispatcher.getFormat();
269 
270         // was previously sent sms format match with voice tech?
271         if (oldFormat.equals(newFormat)) {
272             if (isCdmaFormat(newFormat)) {
273                 Rlog.d(TAG, "old format matched new format (cdma)");
274                 mCdmaDispatcher.sendSms(tracker);
275                 return;
276             } else {
277                 Rlog.d(TAG, "old format matched new format (gsm)");
278                 mGsmDispatcher.sendSms(tracker);
279                 return;
280             }
281         }
282 
283         // format didn't match, need to re-encode.
284         HashMap map = tracker.getData();
285 
286         // to re-encode, fields needed are:  scAddr, destAddr, and
287         //   text if originally sent as sendText or
288         //   data and destPort if originally sent as sendData.
289         if (!( map.containsKey("scAddr") && map.containsKey("destAddr") &&
290                ( map.containsKey("text") ||
291                        (map.containsKey("data") && map.containsKey("destPort"))))) {
292             // should never come here...
293             Rlog.e(TAG, "sendRetrySms failed to re-encode per missing fields!");
294             tracker.onFailed(mContext, RESULT_ERROR_GENERIC_FAILURE, 0/*errorCode*/);
295             return;
296         }
297         String scAddr = (String)map.get("scAddr");
298         String destAddr = (String)map.get("destAddr");
299 
300         SmsMessageBase.SubmitPduBase pdu = null;
301         //    figure out from tracker if this was sendText/Data
302         if (map.containsKey("text")) {
303             Rlog.d(TAG, "sms failed was text");
304             String text = (String)map.get("text");
305 
306             if (isCdmaFormat(newFormat)) {
307                 Rlog.d(TAG, "old format (gsm) ==> new format (cdma)");
308                 pdu = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu(
309                         scAddr, destAddr, text, (tracker.mDeliveryIntent != null), null);
310             } else {
311                 Rlog.d(TAG, "old format (cdma) ==> new format (gsm)");
312                 pdu = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(
313                         scAddr, destAddr, text, (tracker.mDeliveryIntent != null), null);
314             }
315         } else if (map.containsKey("data")) {
316             Rlog.d(TAG, "sms failed was data");
317             byte[] data = (byte[])map.get("data");
318             Integer destPort = (Integer)map.get("destPort");
319 
320             if (isCdmaFormat(newFormat)) {
321                 Rlog.d(TAG, "old format (gsm) ==> new format (cdma)");
322                 pdu = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu(
323                             scAddr, destAddr, destPort.intValue(), data,
324                             (tracker.mDeliveryIntent != null));
325             } else {
326                 Rlog.d(TAG, "old format (cdma) ==> new format (gsm)");
327                 pdu = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(
328                             scAddr, destAddr, destPort.intValue(), data,
329                             (tracker.mDeliveryIntent != null));
330             }
331         }
332 
333         // replace old smsc and pdu with newly encoded ones
334         map.put("smsc", pdu.encodedScAddress);
335         map.put("pdu", pdu.encodedMessage);
336 
337         SMSDispatcher dispatcher = (isCdmaFormat(newFormat)) ?
338                 mCdmaDispatcher : mGsmDispatcher;
339 
340         tracker.mFormat = dispatcher.getFormat();
341         dispatcher.sendSms(tracker);
342     }
343 
344     @Override
sendSubmitPdu(SmsTracker tracker)345     protected void sendSubmitPdu(SmsTracker tracker) {
346         sendRawPdu(tracker);
347     }
348 
349     @Override
getFormat()350     protected String getFormat() {
351         // this function should be defined in Gsm/CdmaDispatcher.
352         Rlog.e(TAG, "getFormat should never be called from here!");
353         return "unknown";
354     }
355 
356     @Override
calculateLength( CharSequence messageBody, boolean use7bitOnly)357     protected GsmAlphabet.TextEncodingDetails calculateLength(
358             CharSequence messageBody, boolean use7bitOnly) {
359         Rlog.e(TAG, "Error! Not implemented for IMS.");
360         return null;
361     }
362 
363     @Override
getNewSubmitPduTracker(String destinationAddress, String scAddress, String message, SmsHeader smsHeader, int format, PendingIntent sentIntent, PendingIntent deliveryIntent, boolean lastPart, AtomicInteger unsentPartCount, AtomicBoolean anyPartFailed, Uri messageUri, String fullMessageText)364     protected SmsTracker getNewSubmitPduTracker(String destinationAddress, String scAddress,
365             String message, SmsHeader smsHeader, int format, PendingIntent sentIntent,
366             PendingIntent deliveryIntent, boolean lastPart,
367             AtomicInteger unsentPartCount, AtomicBoolean anyPartFailed, Uri messageUri,
368             String fullMessageText) {
369         Rlog.e(TAG, "Error! Not implemented for IMS.");
370         return null;
371     }
372 
373     @Override
isIms()374     public boolean isIms() {
375         return mIms;
376     }
377 
378     @Override
getImsSmsFormat()379     public String getImsSmsFormat() {
380         return mImsSmsFormat;
381     }
382 
383     /**
384      * Determines whether or not to use CDMA format for MO SMS.
385      * If SMS over IMS is supported, then format is based on IMS SMS format,
386      * otherwise format is based on current phone type.
387      *
388      * @return true if Cdma format should be used for MO SMS, false otherwise.
389      */
isCdmaMo()390     private boolean isCdmaMo() {
391         if (!isIms()) {
392             // IMS is not registered, use Voice technology to determine SMS format.
393             return (PhoneConstants.PHONE_TYPE_CDMA == mPhone.getPhoneType());
394         }
395         // IMS is registered with SMS support
396         return isCdmaFormat(mImsSmsFormat);
397     }
398 
399     /**
400      * Determines whether or not format given is CDMA format.
401      *
402      * @param format
403      * @return true if format given is CDMA format, false otherwise.
404      */
isCdmaFormat(String format)405     private boolean isCdmaFormat(String format) {
406         return (mCdmaDispatcher.getFormat().equals(format));
407     }
408 }
409