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
17package com.googlecode.android_scripting.facade.telephony;
18
19import android.app.Activity;
20import android.app.PendingIntent;
21import android.app.Service;
22import android.content.BroadcastReceiver;
23import android.content.ContentResolver;
24import android.content.Context;
25import android.content.Intent;
26import android.content.IntentFilter;
27import android.net.Uri;
28import android.os.Bundle;
29import android.provider.Telephony.Sms.Intents;
30import android.provider.Telephony.Mms;
31import android.telephony.SmsCbCmasInfo;
32import android.telephony.SmsCbEtwsInfo;
33import android.telephony.SmsCbMessage;
34import android.telephony.SmsManager;
35import android.telephony.SmsMessage;
36import android.telephony.SubscriptionManager;
37import android.telephony.TelephonyManager;
38
39import com.android.internal.telephony.cdma.sms.SmsEnvelope;
40import com.android.internal.telephony.gsm.SmsCbConstants;
41
42import com.google.android.mms.ContentType;
43import com.google.android.mms.InvalidHeaderValueException;
44import com.google.android.mms.pdu.CharacterSets;
45import com.google.android.mms.pdu.EncodedStringValue;
46import com.google.android.mms.pdu.PduBody;
47import com.google.android.mms.pdu.PduComposer;
48import com.google.android.mms.pdu.PduHeaders;
49import com.google.android.mms.pdu.PduPart;
50import com.google.android.mms.pdu.SendReq;
51import com.googlecode.android_scripting.Log;
52import com.googlecode.android_scripting.facade.EventFacade;
53import com.googlecode.android_scripting.facade.FacadeManager;
54import com.googlecode.android_scripting.jsonrpc.RpcReceiver;
55import com.googlecode.android_scripting.rpc.Rpc;
56import com.googlecode.android_scripting.rpc.RpcDefault;
57import com.googlecode.android_scripting.rpc.RpcOptional;
58import com.googlecode.android_scripting.rpc.RpcParameter;
59import com.googlecode.android_scripting.facade.telephony.TelephonyConstants;
60
61import java.io.File;
62import java.io.FileOutputStream;
63import java.io.IOException;
64import java.util.ArrayList;
65import java.util.List;
66
67//FIXME: Change the build order to use constants defined in here
68//import com.googlecode.android_scripting.provider.TelephonyTestProvider;
69
70/**
71 * Exposes SmsManager functionality.
72 */
73public class SmsFacade extends RpcReceiver {
74
75    static final boolean DBG = false;
76
77    private final EventFacade mEventFacade;
78    private final SmsManager mSms;
79    private final Context mContext;
80    private final Service mService;
81    private BroadcastReceiver mSmsSendListener;
82    private BroadcastReceiver mSmsIncomingListener;
83    private int mNumExpectedSentEvents;
84    private int mNumExpectedDeliveredEvents;
85    private boolean mListeningIncomingSms;
86    private IntentFilter mEmergencyCBMessage;
87    private BroadcastReceiver mGsmEmergencyCBMessageListener;
88    private BroadcastReceiver mCdmaEmergencyCBMessageListener;
89    private boolean mGsmEmergencyCBListenerRegistered;
90    private boolean mCdmaEmergencyCBListenerRegistered;
91    private boolean mSentReceiversRegistered;
92    private Object lock = new Object();
93
94    private BroadcastReceiver mMmsSendListener;
95    private BroadcastReceiver mMmsIncomingListener;
96    private boolean mListeningIncomingMms;
97
98    TelephonyManager mTelephonyManager;
99
100    private static final String SMS_MESSAGE_STATUS_DELIVERED_ACTION =
101            "com.googlecode.android_scripting.sms.MESSAGE_STATUS_DELIVERED";
102    private static final String SMS_MESSAGE_SENT_ACTION =
103            "com.googlecode.android_scripting.sms.MESSAGE_SENT";
104
105    private static final String EMERGENCY_CB_MESSAGE_RECEIVED_ACTION =
106            "android.provider.Telephony.SMS_EMERGENCY_CB_RECEIVED";
107
108    private static final String MMS_MESSAGE_SENT_ACTION =
109            "com.googlecode.android_scripting.mms.MESSAGE_SENT";
110
111    private final int MAX_MESSAGE_LENGTH = 160;
112    private final int INTERNATIONAL_NUMBER_LENGTH = 12;
113    private final int DOMESTIC_NUMBER_LENGTH = 10;
114
115    private static final String DEFAULT_FROM_PHONE_NUMBER = new String("8675309");
116
117    private final int[] mGsmCbMessageIdList = {
118            SmsCbConstants.MESSAGE_ID_ETWS_EARTHQUAKE_WARNING,
119            SmsCbConstants.MESSAGE_ID_ETWS_TSUNAMI_WARNING,
120            SmsCbConstants.MESSAGE_ID_ETWS_EARTHQUAKE_AND_TSUNAMI_WARNING,
121            SmsCbConstants.MESSAGE_ID_ETWS_TEST_MESSAGE,
122            SmsCbConstants.MESSAGE_ID_ETWS_OTHER_EMERGENCY_TYPE,
123            SmsCbConstants.MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL,
124            SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED,
125            SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY,
126            SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED,
127            SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY,
128            SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY,
129            SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED,
130            SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY,
131            SmsCbConstants.MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY,
132            SmsCbConstants.MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST,
133            SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXERCISE
134    };
135
136    private final int[] mCdmaCbMessageIdList = {
137            SmsEnvelope.SERVICE_CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT,
138            SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT,
139            SmsEnvelope.SERVICE_CATEGORY_CMAS_SEVERE_THREAT,
140            SmsEnvelope.SERVICE_CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY,
141            SmsEnvelope.SERVICE_CATEGORY_CMAS_TEST_MESSAGE
142    };
143
144    public SmsFacade(FacadeManager manager) {
145
146        super(manager);
147        mService = manager.getService();
148        mContext = mService;
149        mSms = SmsManager.getDefault();
150        mEventFacade = manager.getReceiver(EventFacade.class);
151        mSmsSendListener = new SmsSendListener();
152        mSmsIncomingListener = new SmsIncomingListener();
153        mNumExpectedSentEvents = 0;
154        mNumExpectedDeliveredEvents = 0;
155        mListeningIncomingSms = false;
156        mGsmEmergencyCBMessageListener = new SmsEmergencyCBMessageListener();
157        mCdmaEmergencyCBMessageListener = new SmsEmergencyCBMessageListener();
158        mGsmEmergencyCBListenerRegistered = false;
159        mCdmaEmergencyCBListenerRegistered = false;
160        mSentReceiversRegistered = false;
161
162        mMmsIncomingListener = new MmsIncomingListener();
163        mMmsSendListener = new MmsSendListener();
164
165        mListeningIncomingMms = false;
166
167        IntentFilter smsFilter = new IntentFilter(SMS_MESSAGE_SENT_ACTION);
168        smsFilter.addAction(SMS_MESSAGE_STATUS_DELIVERED_ACTION);
169
170        IntentFilter mmsFilter = new IntentFilter(MMS_MESSAGE_SENT_ACTION);
171
172        synchronized (lock) {
173            mService.registerReceiver(mSmsSendListener, smsFilter);
174            mService.registerReceiver(mMmsSendListener, mmsFilter);
175            mSentReceiversRegistered = true;
176        }
177
178        mTelephonyManager =
179                (TelephonyManager) mService.getSystemService(Context.TELEPHONY_SERVICE);
180    }
181
182    // FIXME: Move to a utility class
183    // FIXME: remove the MODE_WORLD_READABLE once we verify the use case
184    @SuppressWarnings("deprecation")
185    private boolean writeBytesToFile(String fileName, byte[] pdu) {
186        FileOutputStream writer = null;
187        try {
188            writer = mContext.openFileOutput(fileName, Context.MODE_WORLD_READABLE);
189            writer.write(pdu);
190            return true;
191        } catch (final IOException e) {
192            return false;
193        } finally {
194            if (writer != null) {
195                try {
196                    writer.close();
197                } catch (IOException e) {
198                }
199            }
200        }
201    }
202
203    // FIXME: Move to a utility class
204    private boolean writeBytesToCacheFile(String fileName, byte[] pdu) {
205        File mmsFile = new File(mContext.getCacheDir(), fileName);
206        Log.d(String.format("filename:%s, directory:%s", fileName,
207                mContext.getCacheDir().toString()));
208        FileOutputStream writer = null;
209        try {
210            writer = new FileOutputStream(mmsFile);
211            writer.write(pdu);
212            return true;
213        } catch (final IOException e) {
214            Log.d("writeBytesToCacheFile() failed with " + e.toString());
215            return false;
216        } finally {
217            if (writer != null) {
218                try {
219                    writer.close();
220                } catch (IOException e) {
221                }
222            }
223        }
224    }
225
226    @Deprecated
227    @Rpc(description = "Starts tracking incoming SMS.")
228    public void smsStartTrackingIncomingMessage() {
229        Log.d("Using Deprecated smsStartTrackingIncomingMessage!");
230        smsStartTrackingIncomingSmsMessage();
231    }
232
233    @Rpc(description = "Starts tracking incoming SMS.")
234    public void smsStartTrackingIncomingSmsMessage() {
235        mService.registerReceiver(mSmsIncomingListener,
236                new IntentFilter(Intents.SMS_RECEIVED_ACTION));
237        mListeningIncomingSms = true;
238    }
239
240    @Deprecated
241    @Rpc(description = "Stops tracking incoming SMS.")
242    public void smsStopTrackingIncomingMessage() {
243        Log.d("Using Deprecated smsStopTrackingIncomingMessage!");
244        smsStopTrackingIncomingSmsMessage();
245    }
246
247    @Rpc(description = "Stops tracking incoming SMS.")
248    public void smsStopTrackingIncomingSmsMessage() {
249        if (mListeningIncomingSms) {
250            mListeningIncomingSms = false;
251            try {
252                mService.unregisterReceiver(mSmsIncomingListener);
253            } catch (Exception e) {
254                Log.e("Tried to unregister nonexistent SMS Listener!");
255            }
256        }
257    }
258
259    @Rpc(description = "Starts tracking incoming MMS.")
260    public void smsStartTrackingIncomingMmsMessage() {
261        IntentFilter mmsReceived = new IntentFilter(Intents.MMS_DOWNLOADED_ACTION);
262        mmsReceived.addAction(Intents.WAP_PUSH_RECEIVED_ACTION);
263        mmsReceived.addAction(Intents.DATA_SMS_RECEIVED_ACTION);
264        mService.registerReceiver(mMmsIncomingListener, mmsReceived);
265        mListeningIncomingSms = true;
266    }
267
268    @Rpc(description = "Stops tracking incoming MMS.")
269    public void smsStopTrackingIncomingMmsMessage() {
270        if (mListeningIncomingMms) {
271            mListeningIncomingMms = false;
272            try {
273                mService.unregisterReceiver(mMmsIncomingListener);
274            } catch (Exception e) {
275                Log.e("Tried to unregister nonexistent MMS Listener!");
276            }
277        }
278    }
279
280    // Currently requires 'adb shell su root setenforce 0'
281    @Rpc(description = "Send a multimedia message to a specified number.")
282    public void smsSendMultimediaMessage(
283                        @RpcParameter(name = "toPhoneNumber")
284            String toPhoneNumber,
285                        @RpcParameter(name = "subject")
286            String subject,
287                        @RpcParameter(name = "message")
288            String message,
289            @RpcParameter(name = "fromPhoneNumber")
290            @RpcOptional
291            String fromPhoneNumber,
292            @RpcParameter(name = "fileName")
293            @RpcOptional
294            String fileName) {
295
296        MmsBuilder mms = new MmsBuilder();
297
298        mms.setToPhoneNumber(toPhoneNumber);
299        if (fromPhoneNumber == null) {
300            mTelephonyManager.getLine1Number(); //TODO: b/21592513 - multi-sim awareness
301        }
302
303        if (DBG) {
304            Log.d(String.format(
305                    "Params:toPhoneNumber(%s),subject(%s),message(%s),fromPhoneNumber(%s),filename(%s)",
306                    toPhoneNumber, subject, message,
307                    (fromPhoneNumber != null) ? fromPhoneNumber : "",
308                            (fileName != null) ? fileName : ""));
309        }
310
311        mms.setFromPhoneNumber((fromPhoneNumber != null) ? fromPhoneNumber : DEFAULT_FROM_PHONE_NUMBER);
312        mms.setSubject(subject);
313        mms.setDate();
314        mms.addMessageBody(message);
315        mms.setMessageClass(MmsBuilder.MESSAGE_CLASS_PERSONAL);
316        mms.setMessagePriority(MmsBuilder.DEFAULT_PRIORITY);
317        mms.setDeliveryReport(true);
318        mms.setReadReport(true);
319        // Default to 1 week;
320        mms.setExpirySeconds(MmsBuilder.DEFAULT_EXPIRY_TIME);
321
322        String randomFileName = "mms." + String.valueOf(System.currentTimeMillis()) + ".dat";
323
324        byte[] mmsBytes = mms.build();
325        if (mmsBytes.length == 0) {
326            Log.e("Failed to build PDU!");
327            return;
328        }
329
330        if (writeBytesToCacheFile(randomFileName, mmsBytes) == false) {
331            Log.e("Failed to write PDU to file " + randomFileName);
332            return;
333        }
334
335        Uri contentUri = (new Uri.Builder())
336                          .authority(
337                          "com.googlecode.android_scripting.facade.telephony.MmsFileProvider")
338                          .path(randomFileName)
339                          .scheme(ContentResolver.SCHEME_CONTENT)
340                          .build();
341
342        if (contentUri != null) {
343            Log.d(String.format("URI String: %s", contentUri.toString()));
344            SmsManager.getDefault().sendMultimediaMessage(mContext,
345                    contentUri, null/* locationUrl */, null/* configOverrides */,
346                    PendingIntent.getBroadcast(mService, 0,
347                            new Intent(MMS_MESSAGE_SENT_ACTION), 0)
348                    );
349        }
350        else {
351            Log.d("smsSendMultimediaMessage():Content URI String is null");
352        }
353    }
354
355    @Rpc(description = "Send a text message to a specified number.")
356    public void smsSendTextMessage(
357                        @RpcParameter(name = "phoneNumber")
358            String phoneNumber,
359                        @RpcParameter(name = "message")
360            String message,
361                        @RpcParameter(name = "deliveryReportRequired")
362            Boolean deliveryReportRequired) {
363
364        if (message.length() > MAX_MESSAGE_LENGTH) {
365            ArrayList<String> messagesParts = mSms.divideMessage(message);
366            mNumExpectedSentEvents = mNumExpectedDeliveredEvents = messagesParts.size();
367            ArrayList<PendingIntent> sentIntents = new ArrayList<PendingIntent>();
368            ArrayList<PendingIntent> deliveredIntents = new ArrayList<PendingIntent>();
369            for (int i = 0; i < messagesParts.size(); i++) {
370                sentIntents.add(PendingIntent.getBroadcast(mService, 0,
371                        new Intent(SMS_MESSAGE_SENT_ACTION), 0));
372                if (deliveryReportRequired) {
373                    deliveredIntents.add(
374                            PendingIntent.getBroadcast(mService, 0,
375                                    new Intent(SMS_MESSAGE_STATUS_DELIVERED_ACTION), 0));
376                }
377            }
378            mSms.sendMultipartTextMessage(
379                    phoneNumber, null, messagesParts,
380                    sentIntents, deliveryReportRequired ? deliveredIntents : null);
381        } else {
382            mNumExpectedSentEvents = mNumExpectedDeliveredEvents = 1;
383            PendingIntent sentIntent = PendingIntent.getBroadcast(mService, 0,
384                    new Intent(SMS_MESSAGE_SENT_ACTION), 0);
385            PendingIntent deliveredIntent = PendingIntent.getBroadcast(mService, 0,
386                    new Intent(SMS_MESSAGE_STATUS_DELIVERED_ACTION), 0);
387            mSms.sendTextMessage(
388                    phoneNumber, null, message, sentIntent,
389                    deliveryReportRequired ? deliveredIntent : null);
390        }
391    }
392
393    @Rpc(description = "Retrieves all messages currently stored on ICC.")
394    public ArrayList<SmsMessage> smsGetAllMessagesFromIcc() {
395        return SmsManager.getDefault().getAllMessagesFromIcc();
396    }
397
398    @Rpc(description = "Starts tracking GSM Emergency CB Messages.")
399    public void smsStartTrackingGsmEmergencyCBMessage() {
400        if (!mGsmEmergencyCBListenerRegistered) {
401            for (int messageId : mGsmCbMessageIdList) {
402                mSms.enableCellBroadcast(
403                        messageId,
404                        SmsManager.CELL_BROADCAST_RAN_TYPE_GSM);
405            }
406
407            mEmergencyCBMessage = new IntentFilter(EMERGENCY_CB_MESSAGE_RECEIVED_ACTION);
408            mService.registerReceiver(mGsmEmergencyCBMessageListener,
409                    mEmergencyCBMessage);
410            mGsmEmergencyCBListenerRegistered = true;
411        }
412    }
413
414    @Rpc(description = "Stop tracking GSM Emergency CB Messages")
415    public void smsStopTrackingGsmEmergencyCBMessage() {
416        if (mGsmEmergencyCBListenerRegistered) {
417            mService.unregisterReceiver(mGsmEmergencyCBMessageListener);
418            mGsmEmergencyCBListenerRegistered = false;
419            for (int messageId : mGsmCbMessageIdList) {
420                mSms.disableCellBroadcast(
421                        messageId,
422                        SmsManager.CELL_BROADCAST_RAN_TYPE_GSM);
423            }
424        }
425    }
426
427    @Rpc(description = "Starts tracking CDMA Emergency CB Messages")
428    public void smsStartTrackingCdmaEmergencyCBMessage() {
429        if (!mCdmaEmergencyCBListenerRegistered) {
430            for (int messageId : mCdmaCbMessageIdList) {
431                mSms.enableCellBroadcast(
432                        messageId,
433                        SmsManager.CELL_BROADCAST_RAN_TYPE_CDMA);
434            }
435            mEmergencyCBMessage = new IntentFilter(EMERGENCY_CB_MESSAGE_RECEIVED_ACTION);
436            mService.registerReceiver(mCdmaEmergencyCBMessageListener,
437                    mEmergencyCBMessage);
438            mCdmaEmergencyCBListenerRegistered = true;
439        }
440    }
441
442    @Rpc(description = "Stop tracking CDMA Emergency CB Message.")
443    public void smsStopTrackingCdmaEmergencyCBMessage() {
444        if (mCdmaEmergencyCBListenerRegistered) {
445            mService.unregisterReceiver(mCdmaEmergencyCBMessageListener);
446            mCdmaEmergencyCBListenerRegistered = false;
447            for (int messageId : mCdmaCbMessageIdList) {
448                mSms.disableCellBroadcast(
449                        messageId,
450                        SmsManager.CELL_BROADCAST_RAN_TYPE_CDMA);
451            }
452        }
453    }
454
455    private class SmsSendListener extends BroadcastReceiver {
456        @Override
457        public void onReceive(Context context, Intent intent) {
458            Bundle event = new Bundle();
459            event.putString("Type", "SmsDeliverStatus");
460            String action = intent.getAction();
461            int resultCode = getResultCode();
462            if (SMS_MESSAGE_STATUS_DELIVERED_ACTION.equals(action)) {
463                if (resultCode == Activity.RESULT_OK) {
464                    if (mNumExpectedDeliveredEvents == 1) {
465                        Log.d("SMS Message delivered successfully");
466                        mEventFacade.postEvent(TelephonyConstants.EventSmsDeliverSuccess, event);
467                    }
468                    if (mNumExpectedDeliveredEvents > 0) {
469                        mNumExpectedDeliveredEvents--;
470                    }
471                } else {
472                    Log.e("SMS Message delivery failed");
473                    // TODO . Need to find the reason for failure from pdu
474                    mEventFacade.postEvent(TelephonyConstants.EventSmsDeliverFailure, event);
475                }
476            } else if (SMS_MESSAGE_SENT_ACTION.equals(action)) {
477                if (resultCode == Activity.RESULT_OK) {
478                    if (mNumExpectedSentEvents == 1) {
479                        event.putString("Type", "SmsSentSuccess");
480                        Log.d("SMS Message sent successfully");
481                        mEventFacade.postEvent(TelephonyConstants.EventSmsSentSuccess, event);
482                    }
483                    if (mNumExpectedSentEvents > 0) {
484                        mNumExpectedSentEvents--;
485                    }
486                } else {
487                    Log.e("SMS Message send failed");
488                    event.putString("Type", "SmsSentFailure");
489                    switch (resultCode) {
490                        case SmsManager.RESULT_ERROR_GENERIC_FAILURE:
491                            event.putString("Reason", "GenericFailure");
492                            break;
493                        case SmsManager.RESULT_ERROR_RADIO_OFF:
494                            event.putString("Reason", "RadioOff");
495                            break;
496                        case SmsManager.RESULT_ERROR_NULL_PDU:
497                            event.putString("Reason", "NullPdu");
498                            break;
499                        case SmsManager.RESULT_ERROR_NO_SERVICE:
500                            event.putString("Reason", "NoService");
501                            break;
502                        case SmsManager.RESULT_ERROR_LIMIT_EXCEEDED:
503                            event.putString("Reason", "LimitExceeded");
504                            break;
505                        case SmsManager.RESULT_ERROR_FDN_CHECK_FAILURE:
506                            event.putString("Reason", "FdnCheckFailure");
507                            break;
508                        default:
509                            event.putString("Reason", "Unknown");
510                            break;
511                    }
512                    mEventFacade.postEvent(TelephonyConstants.EventSmsSentFailure, event);
513                }
514            }
515        }
516    }
517
518    private class SmsIncomingListener extends BroadcastReceiver {
519        @Override
520        public void onReceive(Context context, Intent intent) {
521            String action = intent.getAction();
522            if (Intents.SMS_RECEIVED_ACTION.equals(action)) {
523                Log.d("New SMS Received");
524                Bundle extras = intent.getExtras();
525                int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
526                if (extras != null) {
527                    Bundle event = new Bundle();
528                    event.putString("Type", "NewSmsReceived");
529                    SmsMessage[] msgs = Intents.getMessagesFromIntent(intent);
530                    StringBuilder smsMsg = new StringBuilder();
531
532                    SmsMessage sms = msgs[0];
533                    String sender = sms.getOriginatingAddress();
534                    event.putString("Sender", formatPhoneNumber(sender));
535
536                    for (int i = 0; i < msgs.length; i++) {
537                        sms = msgs[i];
538                        smsMsg.append(sms.getMessageBody());
539                    }
540                    event.putString("Text", smsMsg.toString());
541                    // TODO
542                    // Need to explore how to get subId information.
543                    event.putInt("subscriptionId", subId);
544                    mEventFacade.postEvent(TelephonyConstants.EventSmsReceived, event);
545                }
546            }
547        }
548    }
549
550    private class MmsSendListener extends BroadcastReceiver {
551        @Override
552        public void onReceive(Context context, Intent intent) {
553            Bundle event = new Bundle();
554            event.putString("Type", "MmsDeliverStatus");
555            String action = intent.getAction();
556            int resultCode = getResultCode();
557            event.putString("ResultCode", Integer.toString(resultCode));
558            if (MMS_MESSAGE_SENT_ACTION.equals(action)) {
559                if (resultCode == Activity.RESULT_OK) {
560                    Log.d("MMS Message sent successfully");
561                    mEventFacade.postEvent(TelephonyConstants.EventMmsSentSuccess, event);
562                } else {
563                    Log.e(String.format("MMS Message send failed: %d", resultCode));
564                    mEventFacade.postEvent(TelephonyConstants.EventMmsSentFailure, event);
565                }
566            } else {
567                Log.e("MMS Send Listener Received Invalid Event" + intent.toString());
568            }
569        }
570    }
571
572    // add mms matching after mms message parser is added in sl4a. b/34276948
573    private class MmsIncomingListener extends BroadcastReceiver {
574        @Override
575        public void onReceive(Context context, Intent intent) {
576            Log.d("MmsIncomingListener Received an Intent " + intent.toString());
577            String action = intent.getAction();
578            if (Intents.MMS_DOWNLOADED_ACTION.equals(action)) {
579                Log.d("New MMS Downloaded");
580                Bundle event = new Bundle();
581                event.putString("Type", "NewMmsReceived");
582                mEventFacade.postEvent(TelephonyConstants.EventMmsDownloaded, event);
583            }
584            else if (Intents.WAP_PUSH_RECEIVED_ACTION.equals(action)) {
585                Log.d("New Wap Push Received");
586                Bundle event = new Bundle();
587                event.putString("Type", "NewWapPushReceived");
588                mEventFacade.postEvent(TelephonyConstants.EventWapPushReceived, event);
589            }
590            if (Intents.DATA_SMS_RECEIVED_ACTION.equals(action)) {
591                Log.d("New Data SMS Received");
592                Bundle event = new Bundle();
593                event.putString("Type", "NewDataSMSReceived");
594                mEventFacade.postEvent(TelephonyConstants.EventDataSmsReceived, event);
595            }
596            else {
597                Log.e("MmsIncomingListener Received Unexpected Event" + intent.toString());
598            }
599        }
600    }
601
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
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
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
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
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
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
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
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
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
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
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
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
883        public MmsBuilder() {
884            mRequest = new SendReq();
885            mBody = new PduBody();
886        }
887
888        public void setFromPhoneNumber(String number) {
889            mRequest.setFrom(new EncodedStringValue(number));
890        }
891
892        public void setToPhoneNumber(String number) {
893            mRequest.setTo(new EncodedStringValue[] {
894                    new EncodedStringValue(number) });
895        }
896
897        public void setToPhoneNumbers(List<String> number) {
898            mRequest.setTo(EncodedStringValue.encodeStrings((String[]) number.toArray()));
899        }
900
901        public void setSubject(String subject) {
902            mRequest.setSubject(new EncodedStringValue(subject));
903        }
904
905        public void setDate() {
906            setDate(System.currentTimeMillis() / 1000);
907        }
908
909        public void setDate(long time) {
910            mRequest.setDate(time);
911        }
912
913        public void addMessageBody(String message) {
914            addMessageBody(message, true);
915        }
916
917        public void setMessageClass(String messageClass) {
918            mRequest.setMessageClass(messageClass.getBytes());
919        }
920
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
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
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
945        public void setExpirySeconds(int seconds) {
946            mRequest.setExpiry(seconds);
947        }
948
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
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(TEMP_CONTENT_FILE_NAME.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
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