1 /*
2  * Copyright (C) 2016 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5  * use this file except in compliance with the License. You may obtain a copy of
6  * 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, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations under
14  * the License.
15  */
16 
17 package com.googlecode.android_scripting.facade.telephony;
18 
19 import com.google.android.mms.ContentType;
20 import com.google.android.mms.InvalidHeaderValueException;
21 import com.google.android.mms.pdu.CharacterSets;
22 import com.google.android.mms.pdu.EncodedStringValue;
23 import com.google.android.mms.pdu.GenericPdu;
24 import com.google.android.mms.pdu.PduBody;
25 import com.google.android.mms.pdu.PduComposer;
26 import com.google.android.mms.pdu.PduHeaders;
27 import com.google.android.mms.pdu.PduParser;
28 import com.google.android.mms.pdu.PduPart;
29 import com.google.android.mms.pdu.PduPersister;
30 import com.google.android.mms.pdu.SendConf;
31 import com.google.android.mms.pdu.SendReq;
32 import com.google.android.mms.MmsException;
33 
34 import java.io.File;
35 import java.io.FileOutputStream;
36 import java.io.IOException;
37 import java.util.ArrayList;
38 import java.util.List;
39 
40 import android.app.Activity;
41 import android.app.PendingIntent;
42 import android.app.Service;
43 import android.content.BroadcastReceiver;
44 import android.content.ContentResolver;
45 import android.content.Context;
46 import android.content.Intent;
47 import android.content.IntentFilter;
48 import android.net.Uri;
49 import android.os.Bundle;
50 import android.provider.Telephony.Sms.Intents;
51 import android.provider.Telephony.Mms;
52 import android.telephony.SmsManager;
53 import android.telephony.SmsMessage;
54 import android.telephony.SmsCbMessage;
55 import com.android.internal.telephony.gsm.SmsCbConstants;
56 import com.android.internal.telephony.cdma.sms.SmsEnvelope;
57 import android.telephony.SmsCbEtwsInfo;
58 import android.telephony.SmsCbCmasInfo;
59 import android.telephony.SubscriptionManager;
60 import android.telephony.TelephonyManager;
61 
62 import com.googlecode.android_scripting.Log;
63 import com.googlecode.android_scripting.facade.EventFacade;
64 import com.googlecode.android_scripting.facade.FacadeManager;
65 import com.googlecode.android_scripting.jsonrpc.RpcReceiver;
66 import com.googlecode.android_scripting.rpc.Rpc;
67 import com.googlecode.android_scripting.rpc.RpcDefault;
68 import com.googlecode.android_scripting.rpc.RpcOptional;
69 import com.googlecode.android_scripting.rpc.RpcParameter;
70 import com.googlecode.android_scripting.facade.telephony.TelephonyConstants;
71 
72 //FIXME: Change the build order to use constants defined in here
73 //import com.googlecode.android_scripting.provider.TelephonyTestProvider;
74 
75 /**
76  * Exposes SmsManager functionality.
77  */
78 public class SmsFacade extends RpcReceiver {
79 
80     static final boolean DBG = false;
81 
82     private final EventFacade mEventFacade;
83     private final SmsManager mSms;
84     private final Context mContext;
85     private final Service mService;
86     private BroadcastReceiver mSmsSendListener;
87     private BroadcastReceiver mSmsIncomingListener;
88     private int mNumExpectedSentEvents;
89     private int mNumExpectedDeliveredEvents;
90     private boolean mListeningIncomingSms;
91     private IntentFilter mEmergencyCBMessage;
92     private BroadcastReceiver mGsmEmergencyCBMessageListener;
93     private BroadcastReceiver mCdmaEmergencyCBMessageListener;
94     private boolean mGsmEmergencyCBListenerRegistered;
95     private boolean mCdmaEmergencyCBListenerRegistered;
96     private boolean mSentReceiversRegistered;
97     private Object lock = new Object();
98 
99     private BroadcastReceiver mMmsSendListener;
100     private BroadcastReceiver mMmsIncomingListener;
101     private boolean mListeningIncomingMms;
102 
103     TelephonyManager mTelephonyManager;
104 
105     private static final String SMS_MESSAGE_STATUS_DELIVERED_ACTION =
106             "com.googlecode.android_scripting.sms.MESSAGE_STATUS_DELIVERED";
107     private static final String SMS_MESSAGE_SENT_ACTION =
108             "com.googlecode.android_scripting.sms.MESSAGE_SENT";
109 
110     private static final String EMERGENCY_CB_MESSAGE_RECEIVED_ACTION =
111             "android.provider.Telephony.SMS_EMERGENCY_CB_RECEIVED";
112 
113     private static final String MMS_MESSAGE_SENT_ACTION =
114             "com.googlecode.android_scripting.mms.MESSAGE_SENT";
115 
116     private final int MAX_MESSAGE_LENGTH = 160;
117     private final int INTERNATIONAL_NUMBER_LENGTH = 12;
118     private final int DOMESTIC_NUMBER_LENGTH = 10;
119 
120     private static final String DEFAULT_FROM_PHONE_NUMBER = new String("8675309");
121 
122     private final int[] mGsmCbMessageIdList = {
123             SmsCbConstants.MESSAGE_ID_ETWS_EARTHQUAKE_WARNING,
124             SmsCbConstants.MESSAGE_ID_ETWS_TSUNAMI_WARNING,
125             SmsCbConstants.MESSAGE_ID_ETWS_EARTHQUAKE_AND_TSUNAMI_WARNING,
126             SmsCbConstants.MESSAGE_ID_ETWS_TEST_MESSAGE,
127             SmsCbConstants.MESSAGE_ID_ETWS_OTHER_EMERGENCY_TYPE,
128             SmsCbConstants.MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL,
129             SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED,
130             SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY,
131             SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED,
132             SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY,
133             SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY,
134             SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED,
135             SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY,
136             SmsCbConstants.MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY,
137             SmsCbConstants.MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST,
138             SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXERCISE
139     };
140 
141     private final int[] mCdmaCbMessageIdList = {
142             SmsEnvelope.SERVICE_CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT,
143             SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT,
144             SmsEnvelope.SERVICE_CATEGORY_CMAS_SEVERE_THREAT,
145             SmsEnvelope.SERVICE_CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY,
146             SmsEnvelope.SERVICE_CATEGORY_CMAS_TEST_MESSAGE
147     };
148 
SmsFacade(FacadeManager manager)149     public SmsFacade(FacadeManager manager) {
150 
151         super(manager);
152         mService = manager.getService();
153         mContext = mService;
154         mSms = SmsManager.getDefault();
155         mEventFacade = manager.getReceiver(EventFacade.class);
156         mSmsSendListener = new SmsSendListener();
157         mSmsIncomingListener = new SmsIncomingListener();
158         mNumExpectedSentEvents = 0;
159         mNumExpectedDeliveredEvents = 0;
160         mListeningIncomingSms = false;
161         mGsmEmergencyCBMessageListener = new SmsEmergencyCBMessageListener();
162         mCdmaEmergencyCBMessageListener = new SmsEmergencyCBMessageListener();
163         mGsmEmergencyCBListenerRegistered = false;
164         mCdmaEmergencyCBListenerRegistered = false;
165         mSentReceiversRegistered = false;
166 
167         mMmsIncomingListener = new MmsIncomingListener();
168         mMmsSendListener = new MmsSendListener();
169 
170         mListeningIncomingMms = false;
171 
172         IntentFilter smsFilter = new IntentFilter(SMS_MESSAGE_SENT_ACTION);
173         smsFilter.addAction(SMS_MESSAGE_STATUS_DELIVERED_ACTION);
174 
175         IntentFilter mmsFilter = new IntentFilter(MMS_MESSAGE_SENT_ACTION);
176 
177         synchronized (lock) {
178             mService.registerReceiver(mSmsSendListener, smsFilter);
179             mService.registerReceiver(mMmsSendListener, mmsFilter);
180             mSentReceiversRegistered = true;
181         }
182 
183         mTelephonyManager =
184                 (TelephonyManager) mService.getSystemService(Context.TELEPHONY_SERVICE);
185     }
186 
187     // FIXME: Move to a utility class
188     // FIXME: remove the MODE_WORLD_READABLE once we verify the use case
189     @SuppressWarnings("deprecation")
writeBytesToFile(String fileName, byte[] pdu)190     private boolean writeBytesToFile(String fileName, byte[] pdu) {
191         FileOutputStream writer = null;
192         try {
193             writer = mContext.openFileOutput(fileName, Context.MODE_WORLD_READABLE);
194             writer.write(pdu);
195             return true;
196         } catch (final IOException e) {
197             return false;
198         } finally {
199             if (writer != null) {
200                 try {
201                     writer.close();
202                 } catch (IOException e) {
203                 }
204             }
205         }
206     }
207 
208     // FIXME: Move to a utility class
writeBytesToCacheFile(String fileName, byte[] pdu)209     private boolean writeBytesToCacheFile(String fileName, byte[] pdu) {
210         File mmsFile = new File(mContext.getCacheDir(), fileName);
211         Log.d(String.format("filename:%s, directory:%s", fileName,
212                 mContext.getCacheDir().toString()));
213         FileOutputStream writer = null;
214         try {
215             writer = new FileOutputStream(mmsFile);
216             writer.write(pdu);
217             return true;
218         } catch (final IOException e) {
219             Log.d("writeBytesToCacheFile() failed with " + e.toString());
220             return false;
221         } finally {
222             if (writer != null) {
223                 try {
224                     writer.close();
225                 } catch (IOException e) {
226                 }
227             }
228         }
229     }
230 
231     @Deprecated
232     @Rpc(description = "Starts tracking incoming SMS.")
smsStartTrackingIncomingMessage()233     public void smsStartTrackingIncomingMessage() {
234         Log.d("Using Deprecated smsStartTrackingIncomingMessage!");
235         smsStartTrackingIncomingSmsMessage();
236     }
237 
238     @Rpc(description = "Starts tracking incoming SMS.")
smsStartTrackingIncomingSmsMessage()239     public void smsStartTrackingIncomingSmsMessage() {
240         mService.registerReceiver(mSmsIncomingListener,
241                 new IntentFilter(Intents.SMS_RECEIVED_ACTION));
242         mListeningIncomingSms = true;
243     }
244 
245     @Deprecated
246     @Rpc(description = "Stops tracking incoming SMS.")
smsStopTrackingIncomingMessage()247     public void smsStopTrackingIncomingMessage() {
248         Log.d("Using Deprecated smsStopTrackingIncomingMessage!");
249         smsStopTrackingIncomingSmsMessage();
250     }
251 
252     @Rpc(description = "Stops tracking incoming SMS.")
smsStopTrackingIncomingSmsMessage()253     public void smsStopTrackingIncomingSmsMessage() {
254         if (mListeningIncomingSms) {
255             mListeningIncomingSms = false;
256             try {
257                 mService.unregisterReceiver(mSmsIncomingListener);
258             } catch (Exception e) {
259                 Log.e("Tried to unregister nonexistent SMS Listener!");
260             }
261         }
262     }
263 
264     @Rpc(description = "Starts tracking incoming MMS.")
smsStartTrackingIncomingMmsMessage()265     public void smsStartTrackingIncomingMmsMessage() {
266         IntentFilter mmsReceived = new IntentFilter(Intents.MMS_DOWNLOADED_ACTION);
267         mmsReceived.addAction(Intents.WAP_PUSH_RECEIVED_ACTION);
268         mmsReceived.addAction(Intents.DATA_SMS_RECEIVED_ACTION);
269         mService.registerReceiver(mMmsIncomingListener, mmsReceived);
270         mListeningIncomingSms = true;
271     }
272 
273     @Rpc(description = "Stops tracking incoming MMS.")
smsStopTrackingIncomingMmsMessage()274     public void smsStopTrackingIncomingMmsMessage() {
275         if (mListeningIncomingMms) {
276             mListeningIncomingMms = false;
277             try {
278                 mService.unregisterReceiver(mMmsIncomingListener);
279             } catch (Exception e) {
280                 Log.e("Tried to unregister nonexistent MMS Listener!");
281             }
282         }
283     }
284 
285     // Currently requires 'adb shell su root setenforce 0'
286     @Rpc(description = "Send a multimedia message to a specified number.")
smsSendMultimediaMessage( @pcParametername = "toPhoneNumber") String toPhoneNumber, @RpcParameter(name = "subject") String subject, @RpcParameter(name = "message") String message, @RpcParameter(name = "fromPhoneNumber") @RpcOptional String fromPhoneNumber, @RpcParameter(name = "fileName") @RpcOptional String fileName)287     public void smsSendMultimediaMessage(
288                         @RpcParameter(name = "toPhoneNumber")
289             String toPhoneNumber,
290                         @RpcParameter(name = "subject")
291             String subject,
292                         @RpcParameter(name = "message")
293             String message,
294             @RpcParameter(name = "fromPhoneNumber")
295             @RpcOptional
296             String fromPhoneNumber,
297             @RpcParameter(name = "fileName")
298             @RpcOptional
299             String fileName) {
300 
301         MmsBuilder mms = new MmsBuilder();
302 
303         mms.setToPhoneNumber(toPhoneNumber);
304         if (fromPhoneNumber == null) {
305             mTelephonyManager.getLine1Number(); //TODO: b/21592513 - multi-sim awareness
306         }
307 
308         if (DBG) {
309             Log.d(String.format(
310                     "Params:toPhoneNumber(%s),subject(%s),message(%s),fromPhoneNumber(%s),filename(%s)",
311                     toPhoneNumber, subject, message,
312                     (fromPhoneNumber != null) ? fromPhoneNumber : "",
313                             (fileName != null) ? fileName : ""));
314         }
315 
316         mms.setFromPhoneNumber((fromPhoneNumber != null) ? fromPhoneNumber : DEFAULT_FROM_PHONE_NUMBER);
317         mms.setSubject(subject);
318         mms.setDate();
319         mms.addMessageBody(message);
320         mms.setMessageClass(MmsBuilder.MESSAGE_CLASS_PERSONAL);
321         mms.setMessagePriority(MmsBuilder.DEFAULT_PRIORITY);
322         mms.setDeliveryReport(true);
323         mms.setReadReport(true);
324         // Default to 1 week;
325         mms.setExpirySeconds(MmsBuilder.DEFAULT_EXPIRY_TIME);
326 
327         Uri contentUri = null;
328 
329         String randomFileName = "mms." + String.valueOf(System.currentTimeMillis()) + ".dat";
330 
331         byte[] mmsBytes = mms.build();
332         if (mmsBytes.length == 0) {
333             Log.e("Failed to build PDU!");
334             return;
335         }
336 
337         if (writeBytesToCacheFile(randomFileName, mmsBytes) == false) {
338             Log.e("Failed to write PDU to file");
339             return;
340         }
341 
342         contentUri = (new Uri.Builder())
343                 .authority("com.googlecode.android_scripting.provider.telephonytestprovider")
344                 .path("mms/" + randomFileName)
345                 .scheme(ContentResolver.SCHEME_CONTENT)
346                 .build();
347 
348         if (contentUri != null) {
349             Log.d(String.format("URI String: %s", contentUri.toString()));
350 
351             SmsManager.getDefault().sendMultimediaMessage(mContext,
352                     contentUri, null/* locationUrl */, null/* configOverrides */,
353                     PendingIntent.getBroadcast(mService, 0,
354                             new Intent(MMS_MESSAGE_SENT_ACTION), 0)
355                     );
356         }
357         else {
358             Log.d("smsSendMultimediaMessage():Content URI String is null");
359         }
360     }
361 
362     @Rpc(description = "Send a text message to a specified number.")
smsSendTextMessage( @pcParametername = "phoneNumber") String phoneNumber, @RpcParameter(name = "message") String message, @RpcParameter(name = "deliveryReportRequired") Boolean deliveryReportRequired)363     public void smsSendTextMessage(
364                         @RpcParameter(name = "phoneNumber")
365             String phoneNumber,
366                         @RpcParameter(name = "message")
367             String message,
368                         @RpcParameter(name = "deliveryReportRequired")
369             Boolean deliveryReportRequired) {
370 
371         if (message.length() > MAX_MESSAGE_LENGTH) {
372             ArrayList<String> messagesParts = mSms.divideMessage(message);
373             mNumExpectedSentEvents = mNumExpectedDeliveredEvents = messagesParts.size();
374             ArrayList<PendingIntent> sentIntents = new ArrayList<PendingIntent>();
375             ArrayList<PendingIntent> deliveredIntents = new ArrayList<PendingIntent>();
376             for (int i = 0; i < messagesParts.size(); i++) {
377                 sentIntents.add(PendingIntent.getBroadcast(mService, 0,
378                         new Intent(SMS_MESSAGE_SENT_ACTION), 0));
379                 if (deliveryReportRequired) {
380                     deliveredIntents.add(
381                             PendingIntent.getBroadcast(mService, 0,
382                                     new Intent(SMS_MESSAGE_STATUS_DELIVERED_ACTION), 0));
383                 }
384             }
385             mSms.sendMultipartTextMessage(
386                     phoneNumber, null, messagesParts,
387                     sentIntents, deliveryReportRequired ? deliveredIntents : null);
388         } else {
389             mNumExpectedSentEvents = mNumExpectedDeliveredEvents = 1;
390             PendingIntent sentIntent = PendingIntent.getBroadcast(mService, 0,
391                     new Intent(SMS_MESSAGE_SENT_ACTION), 0);
392             PendingIntent deliveredIntent = PendingIntent.getBroadcast(mService, 0,
393                     new Intent(SMS_MESSAGE_STATUS_DELIVERED_ACTION), 0);
394             mSms.sendTextMessage(
395                     phoneNumber, null, message, sentIntent,
396                     deliveryReportRequired ? deliveredIntent : null);
397         }
398     }
399 
400     @Rpc(description = "Retrieves all messages currently stored on ICC.")
smsGetAllMessagesFromIcc()401     public ArrayList<SmsMessage> smsGetAllMessagesFromIcc() {
402         return SmsManager.getDefault().getAllMessagesFromIcc();
403     }
404 
405     @Rpc(description = "Starts tracking GSM Emergency CB Messages.")
smsStartTrackingGsmEmergencyCBMessage()406     public void smsStartTrackingGsmEmergencyCBMessage() {
407         if (!mGsmEmergencyCBListenerRegistered) {
408             for (int messageId : mGsmCbMessageIdList) {
409                 mSms.enableCellBroadcast(
410                         messageId,
411                         SmsManager.CELL_BROADCAST_RAN_TYPE_GSM);
412             }
413 
414             mEmergencyCBMessage = new IntentFilter(EMERGENCY_CB_MESSAGE_RECEIVED_ACTION);
415             mService.registerReceiver(mGsmEmergencyCBMessageListener,
416                     mEmergencyCBMessage);
417             mGsmEmergencyCBListenerRegistered = true;
418         }
419     }
420 
421     @Rpc(description = "Stop tracking GSM Emergency CB Messages")
smsStopTrackingGsmEmergencyCBMessage()422     public void smsStopTrackingGsmEmergencyCBMessage() {
423         if (mGsmEmergencyCBListenerRegistered) {
424             mService.unregisterReceiver(mGsmEmergencyCBMessageListener);
425             mGsmEmergencyCBListenerRegistered = false;
426             for (int messageId : mGsmCbMessageIdList) {
427                 mSms.disableCellBroadcast(
428                         messageId,
429                         SmsManager.CELL_BROADCAST_RAN_TYPE_GSM);
430             }
431         }
432     }
433 
434     @Rpc(description = "Starts tracking CDMA Emergency CB Messages")
smsStartTrackingCdmaEmergencyCBMessage()435     public void smsStartTrackingCdmaEmergencyCBMessage() {
436         if (!mCdmaEmergencyCBListenerRegistered) {
437             for (int messageId : mCdmaCbMessageIdList) {
438                 mSms.enableCellBroadcast(
439                         messageId,
440                         SmsManager.CELL_BROADCAST_RAN_TYPE_CDMA);
441             }
442             mEmergencyCBMessage = new IntentFilter(EMERGENCY_CB_MESSAGE_RECEIVED_ACTION);
443             mService.registerReceiver(mCdmaEmergencyCBMessageListener,
444                     mEmergencyCBMessage);
445             mCdmaEmergencyCBListenerRegistered = true;
446         }
447     }
448 
449     @Rpc(description = "Stop tracking CDMA Emergency CB Message.")
smsStopTrackingCdmaEmergencyCBMessage()450     public void smsStopTrackingCdmaEmergencyCBMessage() {
451         if (mCdmaEmergencyCBListenerRegistered) {
452             mService.unregisterReceiver(mCdmaEmergencyCBMessageListener);
453             mCdmaEmergencyCBListenerRegistered = false;
454             for (int messageId : mCdmaCbMessageIdList) {
455                 mSms.disableCellBroadcast(
456                         messageId,
457                         SmsManager.CELL_BROADCAST_RAN_TYPE_CDMA);
458             }
459         }
460     }
461 
462     private class SmsSendListener extends BroadcastReceiver {
463         @Override
onReceive(Context context, Intent intent)464         public void onReceive(Context context, Intent intent) {
465             Bundle event = new Bundle();
466             event.putString("Type", "SmsDeliverStatus");
467             String action = intent.getAction();
468             int resultCode = getResultCode();
469             if (SMS_MESSAGE_STATUS_DELIVERED_ACTION.equals(action)) {
470                 if (resultCode == Activity.RESULT_OK) {
471                     if (mNumExpectedDeliveredEvents == 1) {
472                         Log.d("SMS Message delivered successfully");
473                         mEventFacade.postEvent(TelephonyConstants.EventSmsDeliverSuccess, event);
474                     }
475                     if (mNumExpectedDeliveredEvents > 0) {
476                         mNumExpectedDeliveredEvents--;
477                     }
478                 } else {
479                     Log.e("SMS Message delivery failed");
480                     // TODO . Need to find the reason for failure from pdu
481                     mEventFacade.postEvent(TelephonyConstants.EventSmsDeliverFailure, event);
482                 }
483             } else if (SMS_MESSAGE_SENT_ACTION.equals(action)) {
484                 if (resultCode == Activity.RESULT_OK) {
485                     if (mNumExpectedSentEvents == 1) {
486                         event.putString("Type", "SmsSentSuccess");
487                         Log.d("SMS Message sent successfully");
488                         mEventFacade.postEvent(TelephonyConstants.EventSmsSentSuccess, event);
489                     }
490                     if (mNumExpectedSentEvents > 0) {
491                         mNumExpectedSentEvents--;
492                     }
493                 } else {
494                     Log.e("SMS Message send failed");
495                     event.putString("Type", "SmsSentFailure");
496                     switch (resultCode) {
497                         case SmsManager.RESULT_ERROR_GENERIC_FAILURE:
498                             event.putString("Reason", "GenericFailure");
499                             break;
500                         case SmsManager.RESULT_ERROR_RADIO_OFF:
501                             event.putString("Reason", "RadioOff");
502                             break;
503                         case SmsManager.RESULT_ERROR_NULL_PDU:
504                             event.putString("Reason", "NullPdu");
505                             break;
506                         case SmsManager.RESULT_ERROR_NO_SERVICE:
507                             event.putString("Reason", "NoService");
508                             break;
509                         case SmsManager.RESULT_ERROR_LIMIT_EXCEEDED:
510                             event.putString("Reason", "LimitExceeded");
511                             break;
512                         case SmsManager.RESULT_ERROR_FDN_CHECK_FAILURE:
513                             event.putString("Reason", "FdnCheckFailure");
514                             break;
515                         default:
516                             event.putString("Reason", "Unknown");
517                             break;
518                     }
519                     mEventFacade.postEvent(TelephonyConstants.EventSmsSentFailure, event);
520                 }
521             }
522         }
523     }
524 
525     private class SmsIncomingListener extends BroadcastReceiver {
526         @Override
onReceive(Context context, Intent intent)527         public void onReceive(Context context, Intent intent) {
528             String action = intent.getAction();
529             if (Intents.SMS_RECEIVED_ACTION.equals(action)) {
530                 Log.d("New SMS Received");
531                 Bundle extras = intent.getExtras();
532                 int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
533                 if (extras != null) {
534                     Bundle event = new Bundle();
535                     event.putString("Type", "NewSmsReceived");
536                     SmsMessage[] msgs = Intents.getMessagesFromIntent(intent);
537                     StringBuilder smsMsg = new StringBuilder();
538 
539                     SmsMessage sms = msgs[0];
540                     String sender = sms.getOriginatingAddress();
541                     event.putString("Sender", formatPhoneNumber(sender));
542 
543                     for (int i = 0; i < msgs.length; i++) {
544                         sms = msgs[i];
545                         smsMsg.append(sms.getMessageBody());
546                     }
547                     event.putString("Text", smsMsg.toString());
548                     // TODO
549                     // Need to explore how to get subId information.
550                     event.putInt("subscriptionId", subId);
551                     mEventFacade.postEvent(TelephonyConstants.EventSmsReceived, event);
552                 }
553             }
554         }
555     }
556 
557     private class MmsSendListener extends BroadcastReceiver {
558         @Override
onReceive(Context context, Intent intent)559         public void onReceive(Context context, Intent intent) {
560             Bundle event = new Bundle();
561             String action = intent.getAction();
562             int resultCode = getResultCode();
563             event.putString("ResultCode", Integer.toString(resultCode));
564             if (MMS_MESSAGE_SENT_ACTION.equals(action)) {
565                 if (resultCode == Activity.RESULT_OK) {
566                     Log.d("MMS Message sent successfully");
567                     mEventFacade.postEvent(TelephonyConstants.EventMmsSentSuccess, event);
568                 } else {
569                     Log.e(String.format("MMS Message send failed: %d", resultCode));
570                     mEventFacade.postEvent(TelephonyConstants.EventMmsSentFailure, event);
571                 }
572             } else {
573                 Log.e("MMS Send Listener Received Invalid Event" + intent.toString());
574             }
575         }
576     }
577 
578     // b/21569494 - Never receiving ANY of these events: requires debugging
579     private class MmsIncomingListener extends BroadcastReceiver {
580         @Override
onReceive(Context context, Intent intent)581         public void onReceive(Context context, Intent intent) {
582             Log.d("MmsIncomingListener Received an Intent " + intent.toString());
583             String action = intent.getAction();
584             if (Intents.MMS_DOWNLOADED_ACTION.equals(action)) {
585                 Log.d("New MMS Downloaded");
586                 mEventFacade.postEvent(TelephonyConstants.EventMmsDownloaded, new Bundle());
587             }
588             else if (Intents.WAP_PUSH_RECEIVED_ACTION.equals(action)) {
589                 Log.d("New Wap Push Received");
590                 mEventFacade.postEvent(TelephonyConstants.EventWapPushReceived, new Bundle());
591             }
592             else if (Intents.DATA_SMS_RECEIVED_ACTION.equals(action)) {
593                 Log.d("New Data SMS Received");
594                 mEventFacade.postEvent(TelephonyConstants.EventDataSmsReceived, new Bundle());
595             }
596             else {
597                 Log.e("MmsIncomingListener Received Unexpected Event" + intent.toString());
598             }
599         }
600     }
601 
formatPhoneNumber(String phoneNumber)602     String formatPhoneNumber(String phoneNumber) {
603         String senderNumberStr = null;
604         int len = phoneNumber.length();
605         if (len > 0) {
606             /**
607              * Currently this incomingNumber modification is specific for US numbers.
608              */
609             if ((INTERNATIONAL_NUMBER_LENGTH == len) && ('+' == phoneNumber.charAt(0))) {
610                 senderNumberStr = phoneNumber.substring(1);
611             } else if (DOMESTIC_NUMBER_LENGTH == len) {
612                 senderNumberStr = '1' + phoneNumber;
613             } else {
614                 senderNumberStr = phoneNumber;
615             }
616         }
617         return senderNumberStr;
618     }
619 
620     private class SmsEmergencyCBMessageListener extends BroadcastReceiver {
621         @Override
onReceive(Context context, Intent intent)622         public void onReceive(Context context, Intent intent) {
623             if (EMERGENCY_CB_MESSAGE_RECEIVED_ACTION.equals(intent.getAction())) {
624                 Bundle extras = intent.getExtras();
625                 if (extras != null) {
626                     Bundle event = new Bundle();
627                     String eventName = null;
628                     SmsCbMessage message = (SmsCbMessage) extras.get("message");
629                     if (message != null) {
630                         if (message.isEmergencyMessage()) {
631                             event.putString("geographicalScope", getGeographicalScope(
632                                     message.getGeographicalScope()));
633                             event.putInt("serialNumber", message.getSerialNumber());
634                             event.putString("location", message.getLocation().toString());
635                             event.putInt("serviceCategory", message.getServiceCategory());
636                             event.putString("language", message.getLanguageCode());
637                             event.putString("message", message.getMessageBody());
638                             event.putString("priority", getPriority(message.getMessagePriority()));
639                             if (message.isCmasMessage()) {
640                                 // CMAS message
641                                 eventName = TelephonyConstants.EventCmasReceived;
642                                 event.putString("cmasMessageClass", getCMASMessageClass(
643                                         message.getCmasWarningInfo().getMessageClass()));
644                                 event.putString("cmasCategory", getCMASCategory(
645                                         message.getCmasWarningInfo().getCategory()));
646                                 event.putString("cmasResponseType", getCMASResponseType(
647                                         message.getCmasWarningInfo().getResponseType()));
648                                 event.putString("cmasSeverity", getCMASSeverity(
649                                         message.getCmasWarningInfo().getSeverity()));
650                                 event.putString("cmasUrgency", getCMASUrgency(
651                                         message.getCmasWarningInfo().getUrgency()));
652                                 event.putString("cmasCertainty", getCMASCertainty(
653                                         message.getCmasWarningInfo().getCertainty()));
654                             } else if (message.isEtwsMessage()) {
655                                 // ETWS message
656                                 eventName = TelephonyConstants.EventEtwsReceived;
657                                 event.putString("etwsWarningType", getETWSWarningType(
658                                         message.getEtwsWarningInfo().getWarningType()));
659                                 event.putBoolean("etwsIsEmergencyUserAlert",
660                                         message.getEtwsWarningInfo().isEmergencyUserAlert());
661                                 event.putBoolean("etwsActivatePopup",
662                                         message.getEtwsWarningInfo().isPopupAlert());
663                             } else {
664                                 Log.d("Received message is not CMAS or ETWS");
665                             }
666                             if (eventName != null)
667                                 mEventFacade.postEvent(eventName, event);
668                         }
669                     }
670                 } else {
671                     Log.d("Received  Emergency CB without extras");
672                 }
673             }
674         }
675     }
676 
getETWSWarningType(int type)677     private static String getETWSWarningType(int type) {
678         switch (type) {
679             case SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE:
680                 return "EARTHQUAKE";
681             case SmsCbEtwsInfo.ETWS_WARNING_TYPE_TSUNAMI:
682                 return "TSUNAMI";
683             case SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE_AND_TSUNAMI:
684                 return "EARTHQUAKE_AND_TSUNAMI";
685             case SmsCbEtwsInfo.ETWS_WARNING_TYPE_TEST_MESSAGE:
686                 return "TEST_MESSAGE";
687             case SmsCbEtwsInfo.ETWS_WARNING_TYPE_OTHER_EMERGENCY:
688                 return "OTHER_EMERGENCY";
689         }
690         return "UNKNOWN";
691     }
692 
getCMASMessageClass(int messageclass)693     private static String getCMASMessageClass(int messageclass) {
694         switch (messageclass) {
695             case SmsCbCmasInfo.CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT:
696                 return "PRESIDENTIAL_LEVEL_ALERT";
697             case SmsCbCmasInfo.CMAS_CLASS_EXTREME_THREAT:
698                 return "EXTREME_THREAT";
699             case SmsCbCmasInfo.CMAS_CLASS_SEVERE_THREAT:
700                 return "SEVERE_THREAT";
701             case SmsCbCmasInfo.CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY:
702                 return "CHILD_ABDUCTION_EMERGENCY";
703             case SmsCbCmasInfo.CMAS_CLASS_REQUIRED_MONTHLY_TEST:
704                 return "REQUIRED_MONTHLY_TEST";
705             case SmsCbCmasInfo.CMAS_CLASS_CMAS_EXERCISE:
706                 return "CMAS_EXERCISE";
707         }
708         return "UNKNOWN";
709     }
710 
getCMASCategory(int category)711     private static String getCMASCategory(int category) {
712         switch (category) {
713             case SmsCbCmasInfo.CMAS_CATEGORY_GEO:
714                 return "GEOPHYSICAL";
715             case SmsCbCmasInfo.CMAS_CATEGORY_MET:
716                 return "METEOROLOGICAL";
717             case SmsCbCmasInfo.CMAS_CATEGORY_SAFETY:
718                 return "SAFETY";
719             case SmsCbCmasInfo.CMAS_CATEGORY_SECURITY:
720                 return "SECURITY";
721             case SmsCbCmasInfo.CMAS_CATEGORY_RESCUE:
722                 return "RESCUE";
723             case SmsCbCmasInfo.CMAS_CATEGORY_FIRE:
724                 return "FIRE";
725             case SmsCbCmasInfo.CMAS_CATEGORY_HEALTH:
726                 return "HEALTH";
727             case SmsCbCmasInfo.CMAS_CATEGORY_ENV:
728                 return "ENVIRONMENTAL";
729             case SmsCbCmasInfo.CMAS_CATEGORY_TRANSPORT:
730                 return "TRANSPORTATION";
731             case SmsCbCmasInfo.CMAS_CATEGORY_INFRA:
732                 return "INFRASTRUCTURE";
733             case SmsCbCmasInfo.CMAS_CATEGORY_CBRNE:
734                 return "CHEMICAL";
735             case SmsCbCmasInfo.CMAS_CATEGORY_OTHER:
736                 return "OTHER";
737         }
738         return "UNKNOWN";
739     }
740 
getCMASResponseType(int type)741     private static String getCMASResponseType(int type) {
742         switch (type) {
743             case SmsCbCmasInfo.CMAS_RESPONSE_TYPE_SHELTER:
744                 return "SHELTER";
745             case SmsCbCmasInfo.CMAS_RESPONSE_TYPE_EVACUATE:
746                 return "EVACUATE";
747             case SmsCbCmasInfo.CMAS_RESPONSE_TYPE_PREPARE:
748                 return "PREPARE";
749             case SmsCbCmasInfo.CMAS_RESPONSE_TYPE_EXECUTE:
750                 return "EXECUTE";
751             case SmsCbCmasInfo.CMAS_RESPONSE_TYPE_MONITOR:
752                 return "MONITOR";
753             case SmsCbCmasInfo.CMAS_RESPONSE_TYPE_AVOID:
754                 return "AVOID";
755             case SmsCbCmasInfo.CMAS_RESPONSE_TYPE_ASSESS:
756                 return "ASSESS";
757             case SmsCbCmasInfo.CMAS_RESPONSE_TYPE_NONE:
758                 return "NONE";
759         }
760         return "UNKNOWN";
761     }
762 
getCMASSeverity(int severity)763     private static String getCMASSeverity(int severity) {
764         switch (severity) {
765             case SmsCbCmasInfo.CMAS_SEVERITY_EXTREME:
766                 return "EXTREME";
767             case SmsCbCmasInfo.CMAS_SEVERITY_SEVERE:
768                 return "SEVERE";
769         }
770         return "UNKNOWN";
771     }
772 
getCMASUrgency(int urgency)773     private static String getCMASUrgency(int urgency) {
774         switch (urgency) {
775             case SmsCbCmasInfo.CMAS_URGENCY_IMMEDIATE:
776                 return "IMMEDIATE";
777             case SmsCbCmasInfo.CMAS_URGENCY_EXPECTED:
778                 return "EXPECTED";
779         }
780         return "UNKNOWN";
781     }
782 
getCMASCertainty(int certainty)783     private static String getCMASCertainty(int certainty) {
784         switch (certainty) {
785             case SmsCbCmasInfo.CMAS_CERTAINTY_OBSERVED:
786                 return "IMMEDIATE";
787             case SmsCbCmasInfo.CMAS_CERTAINTY_LIKELY:
788                 return "LIKELY";
789         }
790         return "UNKNOWN";
791     }
792 
getGeographicalScope(int scope)793     private static String getGeographicalScope(int scope) {
794         switch (scope) {
795             case SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE:
796                 return "CELL_WIDE_IMMEDIATE";
797             case SmsCbMessage.GEOGRAPHICAL_SCOPE_PLMN_WIDE:
798                 return "PLMN_WIDE ";
799             case SmsCbMessage.GEOGRAPHICAL_SCOPE_LA_WIDE:
800                 return "LA_WIDE";
801             case SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE:
802                 return "CELL_WIDE";
803         }
804         return "UNKNOWN";
805     }
806 
getPriority(int priority)807     private static String getPriority(int priority) {
808         switch (priority) {
809             case SmsCbMessage.MESSAGE_PRIORITY_NORMAL:
810                 return "NORMAL";
811             case SmsCbMessage.MESSAGE_PRIORITY_INTERACTIVE:
812                 return "INTERACTIVE";
813             case SmsCbMessage.MESSAGE_PRIORITY_URGENT:
814                 return "URGENT";
815             case SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY:
816                 return "EMERGENCY";
817         }
818         return "UNKNOWN";
819     }
820 
821     @Override
shutdown()822     public void shutdown() {
823 
824         smsStopTrackingIncomingSmsMessage();
825         smsStopTrackingIncomingMmsMessage();
826         smsStopTrackingGsmEmergencyCBMessage();
827         smsStopTrackingCdmaEmergencyCBMessage();
828 
829         synchronized (lock) {
830             if (mSentReceiversRegistered) {
831                 mService.unregisterReceiver(mSmsSendListener);
832                 mService.unregisterReceiver(mMmsSendListener);
833                 mSentReceiversRegistered = false;
834             }
835         }
836     }
837 
838     private class MmsBuilder {
839 
840         public static final String MESSAGE_CLASS_PERSONAL =
841                 PduHeaders.MESSAGE_CLASS_PERSONAL_STR;
842 
843         public static final String MESSAGE_CLASS_ADVERTISEMENT =
844                 PduHeaders.MESSAGE_CLASS_ADVERTISEMENT_STR;
845 
846         public static final String MESSAGE_CLASS_INFORMATIONAL =
847                 PduHeaders.MESSAGE_CLASS_INFORMATIONAL_STR;
848 
849         public static final String MESSAGE_CLASS_AUTO =
850                 PduHeaders.MESSAGE_CLASS_AUTO_STR;
851 
852         public static final int MESSAGE_PRIORITY_LOW = PduHeaders.PRIORITY_LOW;
853         public static final int MESSAGE_PRIORITY_NORMAL = PduHeaders.PRIORITY_LOW;
854         public static final int MESSAGE_PRIORITY_HIGH = PduHeaders.PRIORITY_LOW;
855 
856         private static final int DEFAULT_EXPIRY_TIME = 7 * 24 * 60 * 60;
857         private static final int DEFAULT_PRIORITY = PduHeaders.PRIORITY_NORMAL;
858 
859         private SendReq mRequest;
860         private PduBody mBody;
861 
862         // FIXME: Eventually this should be exposed as a parameter
863         private static final String TEMP_CONTENT_FILE_NAME = "text0.txt";
864 
865         // Synchronized Multimedia Internet Language
866         // Fragment for compatibility
867         private static final String sSmilText =
868                 "<smil>" +
869                         "<head>" +
870                         "<layout>" +
871                         "<root-layout/>" +
872                         "<region height=\"100%%\" id=\"Text\" left=\"0%%\"" +
873                         " top=\"0%%\" width=\"100%%\"/>" +
874                         "</layout>" +
875                         "</head>" +
876                         "<body>" +
877                         "<par dur=\"8000ms\">" +
878                         "<text src=\"%s\" region=\"Text\"/>" +
879                         "</par>" +
880                         "</body>" +
881                         "</smil>";
882 
MmsBuilder()883         public MmsBuilder() {
884             mRequest = new SendReq();
885             mBody = new PduBody();
886         }
887 
setFromPhoneNumber(String number)888         public void setFromPhoneNumber(String number) {
889             mRequest.setFrom(new EncodedStringValue(number));
890         }
891 
setToPhoneNumber(String number)892         public void setToPhoneNumber(String number) {
893             mRequest.setTo(new EncodedStringValue[] {
894                     new EncodedStringValue(number) });
895         }
896 
setToPhoneNumbers(List<String> number)897         public void setToPhoneNumbers(List<String> number) {
898             mRequest.setTo(EncodedStringValue.encodeStrings((String[]) number.toArray()));
899         }
900 
setSubject(String subject)901         public void setSubject(String subject) {
902             mRequest.setSubject(new EncodedStringValue(subject));
903         }
904 
setDate()905         public void setDate() {
906             setDate(System.currentTimeMillis() / 1000);
907         }
908 
setDate(long time)909         public void setDate(long time) {
910             mRequest.setDate(time);
911         }
912 
addMessageBody(String message)913         public void addMessageBody(String message) {
914             addMessageBody(message, true);
915         }
916 
setMessageClass(String messageClass)917         public void setMessageClass(String messageClass) {
918             mRequest.setMessageClass(messageClass.getBytes());
919         }
920 
setMessagePriority(int priority)921         public void setMessagePriority(int priority) {
922             try {
923                 mRequest.setPriority(priority);
924             } catch (InvalidHeaderValueException e) {
925                 Log.e("Invalid Header Value "+e.toString());
926             }
927         }
928 
setDeliveryReport(boolean report)929         public void setDeliveryReport(boolean report) {
930             try {
931                 mRequest.setDeliveryReport((report) ? PduHeaders.VALUE_YES : PduHeaders.VALUE_NO);
932             } catch (InvalidHeaderValueException e) {
933                 Log.e("Invalid Header Value "+e.toString());
934             }
935         }
936 
setReadReport(boolean report)937         public void setReadReport(boolean report) {
938             try {
939                 mRequest.setReadReport((report) ? PduHeaders.VALUE_YES : PduHeaders.VALUE_NO);
940             } catch (InvalidHeaderValueException e) {
941                 Log.e("Invalid Header Value "+e.toString());
942             }
943         }
944 
setExpirySeconds(int seconds)945         public void setExpirySeconds(int seconds) {
946             mRequest.setExpiry(seconds);
947         }
948 
build()949         public byte[] build() {
950             mRequest.setBody(mBody);
951 
952             int msgSize = 0;
953             for (int i = 0; i < mBody.getPartsNum(); i++) {
954                 msgSize += mBody.getPart(i).getDataLength();
955             }
956             mRequest.setMessageSize(msgSize);
957 
958             return new PduComposer(mContext, mRequest).make();
959         }
960 
addMessageBody(String message, boolean addSmilFragment)961         public void addMessageBody(String message, boolean addSmilFragment) {
962             final PduPart part = new PduPart();
963             part.setCharset(CharacterSets.UTF_8);
964             part.setContentType(ContentType.TEXT_PLAIN.getBytes());
965             part.setContentLocation("text0".getBytes());
966             int index = TEMP_CONTENT_FILE_NAME.lastIndexOf(".");
967             String contentId = (index == -1) ? TEMP_CONTENT_FILE_NAME
968                     : TEMP_CONTENT_FILE_NAME.substring(0, index);
969             part.setContentId(contentId.getBytes());
970             part.setContentId("txt".getBytes());
971             part.setData(message.getBytes());
972             mBody.addPart(part);
973             if (addSmilFragment) {
974                 addSmilTextFragment(TEMP_CONTENT_FILE_NAME);
975             }
976         }
977 
addSmilTextFragment(String contentFilename)978         private void addSmilTextFragment(String contentFilename) {
979 
980             final String smil = String.format(sSmilText, contentFilename);
981             final PduPart smilPart = new PduPart();
982             smilPart.setContentId("smil".getBytes());
983             smilPart.setContentLocation("smil.xml".getBytes());
984             smilPart.setContentType(ContentType.APP_SMIL.getBytes());
985             smilPart.setData(smil.getBytes());
986             mBody.addPart(0, smilPart);
987         }
988     }
989 
990 }
991