/* * Copyright (C) 2016 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package com.googlecode.android_scripting.facade.telephony; import com.google.android.mms.ContentType; import com.google.android.mms.InvalidHeaderValueException; import com.google.android.mms.pdu.CharacterSets; import com.google.android.mms.pdu.EncodedStringValue; import com.google.android.mms.pdu.GenericPdu; import com.google.android.mms.pdu.PduBody; import com.google.android.mms.pdu.PduComposer; import com.google.android.mms.pdu.PduHeaders; import com.google.android.mms.pdu.PduParser; import com.google.android.mms.pdu.PduPart; import com.google.android.mms.pdu.PduPersister; import com.google.android.mms.pdu.SendConf; import com.google.android.mms.pdu.SendReq; import com.google.android.mms.MmsException; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.List; import android.app.Activity; import android.app.PendingIntent; import android.app.Service; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.net.Uri; import android.os.Bundle; import android.provider.Telephony.Sms.Intents; import android.provider.Telephony.Mms; import android.telephony.SmsManager; import android.telephony.SmsMessage; import android.telephony.SmsCbMessage; import com.android.internal.telephony.gsm.SmsCbConstants; import com.android.internal.telephony.cdma.sms.SmsEnvelope; import android.telephony.SmsCbEtwsInfo; import android.telephony.SmsCbCmasInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import com.googlecode.android_scripting.Log; import com.googlecode.android_scripting.facade.EventFacade; import com.googlecode.android_scripting.facade.FacadeManager; import com.googlecode.android_scripting.jsonrpc.RpcReceiver; import com.googlecode.android_scripting.rpc.Rpc; import com.googlecode.android_scripting.rpc.RpcDefault; import com.googlecode.android_scripting.rpc.RpcOptional; import com.googlecode.android_scripting.rpc.RpcParameter; import com.googlecode.android_scripting.facade.telephony.TelephonyConstants; //FIXME: Change the build order to use constants defined in here //import com.googlecode.android_scripting.provider.TelephonyTestProvider; /** * Exposes SmsManager functionality. */ public class SmsFacade extends RpcReceiver { static final boolean DBG = false; private final EventFacade mEventFacade; private final SmsManager mSms; private final Context mContext; private final Service mService; private BroadcastReceiver mSmsSendListener; private BroadcastReceiver mSmsIncomingListener; private int mNumExpectedSentEvents; private int mNumExpectedDeliveredEvents; private boolean mListeningIncomingSms; private IntentFilter mEmergencyCBMessage; private BroadcastReceiver mGsmEmergencyCBMessageListener; private BroadcastReceiver mCdmaEmergencyCBMessageListener; private boolean mGsmEmergencyCBListenerRegistered; private boolean mCdmaEmergencyCBListenerRegistered; private boolean mSentReceiversRegistered; private Object lock = new Object(); private BroadcastReceiver mMmsSendListener; private BroadcastReceiver mMmsIncomingListener; private boolean mListeningIncomingMms; TelephonyManager mTelephonyManager; private static final String SMS_MESSAGE_STATUS_DELIVERED_ACTION = "com.googlecode.android_scripting.sms.MESSAGE_STATUS_DELIVERED"; private static final String SMS_MESSAGE_SENT_ACTION = "com.googlecode.android_scripting.sms.MESSAGE_SENT"; private static final String EMERGENCY_CB_MESSAGE_RECEIVED_ACTION = "android.provider.Telephony.SMS_EMERGENCY_CB_RECEIVED"; private static final String MMS_MESSAGE_SENT_ACTION = "com.googlecode.android_scripting.mms.MESSAGE_SENT"; private final int MAX_MESSAGE_LENGTH = 160; private final int INTERNATIONAL_NUMBER_LENGTH = 12; private final int DOMESTIC_NUMBER_LENGTH = 10; private static final String DEFAULT_FROM_PHONE_NUMBER = new String("8675309"); private final int[] mGsmCbMessageIdList = { SmsCbConstants.MESSAGE_ID_ETWS_EARTHQUAKE_WARNING, SmsCbConstants.MESSAGE_ID_ETWS_TSUNAMI_WARNING, SmsCbConstants.MESSAGE_ID_ETWS_EARTHQUAKE_AND_TSUNAMI_WARNING, SmsCbConstants.MESSAGE_ID_ETWS_TEST_MESSAGE, SmsCbConstants.MESSAGE_ID_ETWS_OTHER_EMERGENCY_TYPE, SmsCbConstants.MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL, SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED, SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY, SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED, SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY, SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY, SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED, SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY, SmsCbConstants.MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY, SmsCbConstants.MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST, SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXERCISE }; private final int[] mCdmaCbMessageIdList = { SmsEnvelope.SERVICE_CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT, SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT, SmsEnvelope.SERVICE_CATEGORY_CMAS_SEVERE_THREAT, SmsEnvelope.SERVICE_CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY, SmsEnvelope.SERVICE_CATEGORY_CMAS_TEST_MESSAGE }; public SmsFacade(FacadeManager manager) { super(manager); mService = manager.getService(); mContext = mService; mSms = SmsManager.getDefault(); mEventFacade = manager.getReceiver(EventFacade.class); mSmsSendListener = new SmsSendListener(); mSmsIncomingListener = new SmsIncomingListener(); mNumExpectedSentEvents = 0; mNumExpectedDeliveredEvents = 0; mListeningIncomingSms = false; mGsmEmergencyCBMessageListener = new SmsEmergencyCBMessageListener(); mCdmaEmergencyCBMessageListener = new SmsEmergencyCBMessageListener(); mGsmEmergencyCBListenerRegistered = false; mCdmaEmergencyCBListenerRegistered = false; mSentReceiversRegistered = false; mMmsIncomingListener = new MmsIncomingListener(); mMmsSendListener = new MmsSendListener(); mListeningIncomingMms = false; IntentFilter smsFilter = new IntentFilter(SMS_MESSAGE_SENT_ACTION); smsFilter.addAction(SMS_MESSAGE_STATUS_DELIVERED_ACTION); IntentFilter mmsFilter = new IntentFilter(MMS_MESSAGE_SENT_ACTION); synchronized (lock) { mService.registerReceiver(mSmsSendListener, smsFilter); mService.registerReceiver(mMmsSendListener, mmsFilter); mSentReceiversRegistered = true; } mTelephonyManager = (TelephonyManager) mService.getSystemService(Context.TELEPHONY_SERVICE); } // FIXME: Move to a utility class // FIXME: remove the MODE_WORLD_READABLE once we verify the use case @SuppressWarnings("deprecation") private boolean writeBytesToFile(String fileName, byte[] pdu) { FileOutputStream writer = null; try { writer = mContext.openFileOutput(fileName, Context.MODE_WORLD_READABLE); writer.write(pdu); return true; } catch (final IOException e) { return false; } finally { if (writer != null) { try { writer.close(); } catch (IOException e) { } } } } // FIXME: Move to a utility class private boolean writeBytesToCacheFile(String fileName, byte[] pdu) { File mmsFile = new File(mContext.getCacheDir(), fileName); Log.d(String.format("filename:%s, directory:%s", fileName, mContext.getCacheDir().toString())); FileOutputStream writer = null; try { writer = new FileOutputStream(mmsFile); writer.write(pdu); return true; } catch (final IOException e) { Log.d("writeBytesToCacheFile() failed with " + e.toString()); return false; } finally { if (writer != null) { try { writer.close(); } catch (IOException e) { } } } } @Deprecated @Rpc(description = "Starts tracking incoming SMS.") public void smsStartTrackingIncomingMessage() { Log.d("Using Deprecated smsStartTrackingIncomingMessage!"); smsStartTrackingIncomingSmsMessage(); } @Rpc(description = "Starts tracking incoming SMS.") public void smsStartTrackingIncomingSmsMessage() { mService.registerReceiver(mSmsIncomingListener, new IntentFilter(Intents.SMS_RECEIVED_ACTION)); mListeningIncomingSms = true; } @Deprecated @Rpc(description = "Stops tracking incoming SMS.") public void smsStopTrackingIncomingMessage() { Log.d("Using Deprecated smsStopTrackingIncomingMessage!"); smsStopTrackingIncomingSmsMessage(); } @Rpc(description = "Stops tracking incoming SMS.") public void smsStopTrackingIncomingSmsMessage() { if (mListeningIncomingSms) { mListeningIncomingSms = false; try { mService.unregisterReceiver(mSmsIncomingListener); } catch (Exception e) { Log.e("Tried to unregister nonexistent SMS Listener!"); } } } @Rpc(description = "Starts tracking incoming MMS.") public void smsStartTrackingIncomingMmsMessage() { IntentFilter mmsReceived = new IntentFilter(Intents.MMS_DOWNLOADED_ACTION); mmsReceived.addAction(Intents.WAP_PUSH_RECEIVED_ACTION); mmsReceived.addAction(Intents.DATA_SMS_RECEIVED_ACTION); mService.registerReceiver(mMmsIncomingListener, mmsReceived); mListeningIncomingSms = true; } @Rpc(description = "Stops tracking incoming MMS.") public void smsStopTrackingIncomingMmsMessage() { if (mListeningIncomingMms) { mListeningIncomingMms = false; try { mService.unregisterReceiver(mMmsIncomingListener); } catch (Exception e) { Log.e("Tried to unregister nonexistent MMS Listener!"); } } } // Currently requires 'adb shell su root setenforce 0' @Rpc(description = "Send a multimedia message to a specified number.") public void smsSendMultimediaMessage( @RpcParameter(name = "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) { MmsBuilder mms = new MmsBuilder(); mms.setToPhoneNumber(toPhoneNumber); if (fromPhoneNumber == null) { mTelephonyManager.getLine1Number(); //TODO: b/21592513 - multi-sim awareness } if (DBG) { Log.d(String.format( "Params:toPhoneNumber(%s),subject(%s),message(%s),fromPhoneNumber(%s),filename(%s)", toPhoneNumber, subject, message, (fromPhoneNumber != null) ? fromPhoneNumber : "", (fileName != null) ? fileName : "")); } mms.setFromPhoneNumber((fromPhoneNumber != null) ? fromPhoneNumber : DEFAULT_FROM_PHONE_NUMBER); mms.setSubject(subject); mms.setDate(); mms.addMessageBody(message); mms.setMessageClass(MmsBuilder.MESSAGE_CLASS_PERSONAL); mms.setMessagePriority(MmsBuilder.DEFAULT_PRIORITY); mms.setDeliveryReport(true); mms.setReadReport(true); // Default to 1 week; mms.setExpirySeconds(MmsBuilder.DEFAULT_EXPIRY_TIME); Uri contentUri = null; String randomFileName = "mms." + String.valueOf(System.currentTimeMillis()) + ".dat"; byte[] mmsBytes = mms.build(); if (mmsBytes.length == 0) { Log.e("Failed to build PDU!"); return; } if (writeBytesToCacheFile(randomFileName, mmsBytes) == false) { Log.e("Failed to write PDU to file"); return; } contentUri = (new Uri.Builder()) .authority("com.googlecode.android_scripting.provider.telephonytestprovider") .path("mms/" + randomFileName) .scheme(ContentResolver.SCHEME_CONTENT) .build(); if (contentUri != null) { Log.d(String.format("URI String: %s", contentUri.toString())); SmsManager.getDefault().sendMultimediaMessage(mContext, contentUri, null/* locationUrl */, null/* configOverrides */, PendingIntent.getBroadcast(mService, 0, new Intent(MMS_MESSAGE_SENT_ACTION), 0) ); } else { Log.d("smsSendMultimediaMessage():Content URI String is null"); } } @Rpc(description = "Send a text message to a specified number.") public void smsSendTextMessage( @RpcParameter(name = "phoneNumber") String phoneNumber, @RpcParameter(name = "message") String message, @RpcParameter(name = "deliveryReportRequired") Boolean deliveryReportRequired) { if (message.length() > MAX_MESSAGE_LENGTH) { ArrayList messagesParts = mSms.divideMessage(message); mNumExpectedSentEvents = mNumExpectedDeliveredEvents = messagesParts.size(); ArrayList sentIntents = new ArrayList(); ArrayList deliveredIntents = new ArrayList(); for (int i = 0; i < messagesParts.size(); i++) { sentIntents.add(PendingIntent.getBroadcast(mService, 0, new Intent(SMS_MESSAGE_SENT_ACTION), 0)); if (deliveryReportRequired) { deliveredIntents.add( PendingIntent.getBroadcast(mService, 0, new Intent(SMS_MESSAGE_STATUS_DELIVERED_ACTION), 0)); } } mSms.sendMultipartTextMessage( phoneNumber, null, messagesParts, sentIntents, deliveryReportRequired ? deliveredIntents : null); } else { mNumExpectedSentEvents = mNumExpectedDeliveredEvents = 1; PendingIntent sentIntent = PendingIntent.getBroadcast(mService, 0, new Intent(SMS_MESSAGE_SENT_ACTION), 0); PendingIntent deliveredIntent = PendingIntent.getBroadcast(mService, 0, new Intent(SMS_MESSAGE_STATUS_DELIVERED_ACTION), 0); mSms.sendTextMessage( phoneNumber, null, message, sentIntent, deliveryReportRequired ? deliveredIntent : null); } } @Rpc(description = "Retrieves all messages currently stored on ICC.") public ArrayList smsGetAllMessagesFromIcc() { return SmsManager.getDefault().getAllMessagesFromIcc(); } @Rpc(description = "Starts tracking GSM Emergency CB Messages.") public void smsStartTrackingGsmEmergencyCBMessage() { if (!mGsmEmergencyCBListenerRegistered) { for (int messageId : mGsmCbMessageIdList) { mSms.enableCellBroadcast( messageId, SmsManager.CELL_BROADCAST_RAN_TYPE_GSM); } mEmergencyCBMessage = new IntentFilter(EMERGENCY_CB_MESSAGE_RECEIVED_ACTION); mService.registerReceiver(mGsmEmergencyCBMessageListener, mEmergencyCBMessage); mGsmEmergencyCBListenerRegistered = true; } } @Rpc(description = "Stop tracking GSM Emergency CB Messages") public void smsStopTrackingGsmEmergencyCBMessage() { if (mGsmEmergencyCBListenerRegistered) { mService.unregisterReceiver(mGsmEmergencyCBMessageListener); mGsmEmergencyCBListenerRegistered = false; for (int messageId : mGsmCbMessageIdList) { mSms.disableCellBroadcast( messageId, SmsManager.CELL_BROADCAST_RAN_TYPE_GSM); } } } @Rpc(description = "Starts tracking CDMA Emergency CB Messages") public void smsStartTrackingCdmaEmergencyCBMessage() { if (!mCdmaEmergencyCBListenerRegistered) { for (int messageId : mCdmaCbMessageIdList) { mSms.enableCellBroadcast( messageId, SmsManager.CELL_BROADCAST_RAN_TYPE_CDMA); } mEmergencyCBMessage = new IntentFilter(EMERGENCY_CB_MESSAGE_RECEIVED_ACTION); mService.registerReceiver(mCdmaEmergencyCBMessageListener, mEmergencyCBMessage); mCdmaEmergencyCBListenerRegistered = true; } } @Rpc(description = "Stop tracking CDMA Emergency CB Message.") public void smsStopTrackingCdmaEmergencyCBMessage() { if (mCdmaEmergencyCBListenerRegistered) { mService.unregisterReceiver(mCdmaEmergencyCBMessageListener); mCdmaEmergencyCBListenerRegistered = false; for (int messageId : mCdmaCbMessageIdList) { mSms.disableCellBroadcast( messageId, SmsManager.CELL_BROADCAST_RAN_TYPE_CDMA); } } } private class SmsSendListener extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Bundle event = new Bundle(); event.putString("Type", "SmsDeliverStatus"); String action = intent.getAction(); int resultCode = getResultCode(); if (SMS_MESSAGE_STATUS_DELIVERED_ACTION.equals(action)) { if (resultCode == Activity.RESULT_OK) { if (mNumExpectedDeliveredEvents == 1) { Log.d("SMS Message delivered successfully"); mEventFacade.postEvent(TelephonyConstants.EventSmsDeliverSuccess, event); } if (mNumExpectedDeliveredEvents > 0) { mNumExpectedDeliveredEvents--; } } else { Log.e("SMS Message delivery failed"); // TODO . Need to find the reason for failure from pdu mEventFacade.postEvent(TelephonyConstants.EventSmsDeliverFailure, event); } } else if (SMS_MESSAGE_SENT_ACTION.equals(action)) { if (resultCode == Activity.RESULT_OK) { if (mNumExpectedSentEvents == 1) { event.putString("Type", "SmsSentSuccess"); Log.d("SMS Message sent successfully"); mEventFacade.postEvent(TelephonyConstants.EventSmsSentSuccess, event); } if (mNumExpectedSentEvents > 0) { mNumExpectedSentEvents--; } } else { Log.e("SMS Message send failed"); event.putString("Type", "SmsSentFailure"); switch (resultCode) { case SmsManager.RESULT_ERROR_GENERIC_FAILURE: event.putString("Reason", "GenericFailure"); break; case SmsManager.RESULT_ERROR_RADIO_OFF: event.putString("Reason", "RadioOff"); break; case SmsManager.RESULT_ERROR_NULL_PDU: event.putString("Reason", "NullPdu"); break; case SmsManager.RESULT_ERROR_NO_SERVICE: event.putString("Reason", "NoService"); break; case SmsManager.RESULT_ERROR_LIMIT_EXCEEDED: event.putString("Reason", "LimitExceeded"); break; case SmsManager.RESULT_ERROR_FDN_CHECK_FAILURE: event.putString("Reason", "FdnCheckFailure"); break; default: event.putString("Reason", "Unknown"); break; } mEventFacade.postEvent(TelephonyConstants.EventSmsSentFailure, event); } } } } private class SmsIncomingListener extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (Intents.SMS_RECEIVED_ACTION.equals(action)) { Log.d("New SMS Received"); Bundle extras = intent.getExtras(); int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; if (extras != null) { Bundle event = new Bundle(); event.putString("Type", "NewSmsReceived"); SmsMessage[] msgs = Intents.getMessagesFromIntent(intent); StringBuilder smsMsg = new StringBuilder(); SmsMessage sms = msgs[0]; String sender = sms.getOriginatingAddress(); event.putString("Sender", formatPhoneNumber(sender)); for (int i = 0; i < msgs.length; i++) { sms = msgs[i]; smsMsg.append(sms.getMessageBody()); } event.putString("Text", smsMsg.toString()); // TODO // Need to explore how to get subId information. event.putInt("subscriptionId", subId); mEventFacade.postEvent(TelephonyConstants.EventSmsReceived, event); } } } } private class MmsSendListener extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Bundle event = new Bundle(); String action = intent.getAction(); int resultCode = getResultCode(); event.putString("ResultCode", Integer.toString(resultCode)); if (MMS_MESSAGE_SENT_ACTION.equals(action)) { if (resultCode == Activity.RESULT_OK) { Log.d("MMS Message sent successfully"); mEventFacade.postEvent(TelephonyConstants.EventMmsSentSuccess, event); } else { Log.e(String.format("MMS Message send failed: %d", resultCode)); mEventFacade.postEvent(TelephonyConstants.EventMmsSentFailure, event); } } else { Log.e("MMS Send Listener Received Invalid Event" + intent.toString()); } } } // b/21569494 - Never receiving ANY of these events: requires debugging private class MmsIncomingListener extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Log.d("MmsIncomingListener Received an Intent " + intent.toString()); String action = intent.getAction(); if (Intents.MMS_DOWNLOADED_ACTION.equals(action)) { Log.d("New MMS Downloaded"); mEventFacade.postEvent(TelephonyConstants.EventMmsDownloaded, new Bundle()); } else if (Intents.WAP_PUSH_RECEIVED_ACTION.equals(action)) { Log.d("New Wap Push Received"); mEventFacade.postEvent(TelephonyConstants.EventWapPushReceived, new Bundle()); } else if (Intents.DATA_SMS_RECEIVED_ACTION.equals(action)) { Log.d("New Data SMS Received"); mEventFacade.postEvent(TelephonyConstants.EventDataSmsReceived, new Bundle()); } else { Log.e("MmsIncomingListener Received Unexpected Event" + intent.toString()); } } } String formatPhoneNumber(String phoneNumber) { String senderNumberStr = null; int len = phoneNumber.length(); if (len > 0) { /** * Currently this incomingNumber modification is specific for US numbers. */ if ((INTERNATIONAL_NUMBER_LENGTH == len) && ('+' == phoneNumber.charAt(0))) { senderNumberStr = phoneNumber.substring(1); } else if (DOMESTIC_NUMBER_LENGTH == len) { senderNumberStr = '1' + phoneNumber; } else { senderNumberStr = phoneNumber; } } return senderNumberStr; } private class SmsEmergencyCBMessageListener extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { if (EMERGENCY_CB_MESSAGE_RECEIVED_ACTION.equals(intent.getAction())) { Bundle extras = intent.getExtras(); if (extras != null) { Bundle event = new Bundle(); String eventName = null; SmsCbMessage message = (SmsCbMessage) extras.get("message"); if (message != null) { if (message.isEmergencyMessage()) { event.putString("geographicalScope", getGeographicalScope( message.getGeographicalScope())); event.putInt("serialNumber", message.getSerialNumber()); event.putString("location", message.getLocation().toString()); event.putInt("serviceCategory", message.getServiceCategory()); event.putString("language", message.getLanguageCode()); event.putString("message", message.getMessageBody()); event.putString("priority", getPriority(message.getMessagePriority())); if (message.isCmasMessage()) { // CMAS message eventName = TelephonyConstants.EventCmasReceived; event.putString("cmasMessageClass", getCMASMessageClass( message.getCmasWarningInfo().getMessageClass())); event.putString("cmasCategory", getCMASCategory( message.getCmasWarningInfo().getCategory())); event.putString("cmasResponseType", getCMASResponseType( message.getCmasWarningInfo().getResponseType())); event.putString("cmasSeverity", getCMASSeverity( message.getCmasWarningInfo().getSeverity())); event.putString("cmasUrgency", getCMASUrgency( message.getCmasWarningInfo().getUrgency())); event.putString("cmasCertainty", getCMASCertainty( message.getCmasWarningInfo().getCertainty())); } else if (message.isEtwsMessage()) { // ETWS message eventName = TelephonyConstants.EventEtwsReceived; event.putString("etwsWarningType", getETWSWarningType( message.getEtwsWarningInfo().getWarningType())); event.putBoolean("etwsIsEmergencyUserAlert", message.getEtwsWarningInfo().isEmergencyUserAlert()); event.putBoolean("etwsActivatePopup", message.getEtwsWarningInfo().isPopupAlert()); } else { Log.d("Received message is not CMAS or ETWS"); } if (eventName != null) mEventFacade.postEvent(eventName, event); } } } else { Log.d("Received Emergency CB without extras"); } } } } private static String getETWSWarningType(int type) { switch (type) { case SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE: return "EARTHQUAKE"; case SmsCbEtwsInfo.ETWS_WARNING_TYPE_TSUNAMI: return "TSUNAMI"; case SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE_AND_TSUNAMI: return "EARTHQUAKE_AND_TSUNAMI"; case SmsCbEtwsInfo.ETWS_WARNING_TYPE_TEST_MESSAGE: return "TEST_MESSAGE"; case SmsCbEtwsInfo.ETWS_WARNING_TYPE_OTHER_EMERGENCY: return "OTHER_EMERGENCY"; } return "UNKNOWN"; } private static String getCMASMessageClass(int messageclass) { switch (messageclass) { case SmsCbCmasInfo.CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT: return "PRESIDENTIAL_LEVEL_ALERT"; case SmsCbCmasInfo.CMAS_CLASS_EXTREME_THREAT: return "EXTREME_THREAT"; case SmsCbCmasInfo.CMAS_CLASS_SEVERE_THREAT: return "SEVERE_THREAT"; case SmsCbCmasInfo.CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY: return "CHILD_ABDUCTION_EMERGENCY"; case SmsCbCmasInfo.CMAS_CLASS_REQUIRED_MONTHLY_TEST: return "REQUIRED_MONTHLY_TEST"; case SmsCbCmasInfo.CMAS_CLASS_CMAS_EXERCISE: return "CMAS_EXERCISE"; } return "UNKNOWN"; } private static String getCMASCategory(int category) { switch (category) { case SmsCbCmasInfo.CMAS_CATEGORY_GEO: return "GEOPHYSICAL"; case SmsCbCmasInfo.CMAS_CATEGORY_MET: return "METEOROLOGICAL"; case SmsCbCmasInfo.CMAS_CATEGORY_SAFETY: return "SAFETY"; case SmsCbCmasInfo.CMAS_CATEGORY_SECURITY: return "SECURITY"; case SmsCbCmasInfo.CMAS_CATEGORY_RESCUE: return "RESCUE"; case SmsCbCmasInfo.CMAS_CATEGORY_FIRE: return "FIRE"; case SmsCbCmasInfo.CMAS_CATEGORY_HEALTH: return "HEALTH"; case SmsCbCmasInfo.CMAS_CATEGORY_ENV: return "ENVIRONMENTAL"; case SmsCbCmasInfo.CMAS_CATEGORY_TRANSPORT: return "TRANSPORTATION"; case SmsCbCmasInfo.CMAS_CATEGORY_INFRA: return "INFRASTRUCTURE"; case SmsCbCmasInfo.CMAS_CATEGORY_CBRNE: return "CHEMICAL"; case SmsCbCmasInfo.CMAS_CATEGORY_OTHER: return "OTHER"; } return "UNKNOWN"; } private static String getCMASResponseType(int type) { switch (type) { case SmsCbCmasInfo.CMAS_RESPONSE_TYPE_SHELTER: return "SHELTER"; case SmsCbCmasInfo.CMAS_RESPONSE_TYPE_EVACUATE: return "EVACUATE"; case SmsCbCmasInfo.CMAS_RESPONSE_TYPE_PREPARE: return "PREPARE"; case SmsCbCmasInfo.CMAS_RESPONSE_TYPE_EXECUTE: return "EXECUTE"; case SmsCbCmasInfo.CMAS_RESPONSE_TYPE_MONITOR: return "MONITOR"; case SmsCbCmasInfo.CMAS_RESPONSE_TYPE_AVOID: return "AVOID"; case SmsCbCmasInfo.CMAS_RESPONSE_TYPE_ASSESS: return "ASSESS"; case SmsCbCmasInfo.CMAS_RESPONSE_TYPE_NONE: return "NONE"; } return "UNKNOWN"; } private static String getCMASSeverity(int severity) { switch (severity) { case SmsCbCmasInfo.CMAS_SEVERITY_EXTREME: return "EXTREME"; case SmsCbCmasInfo.CMAS_SEVERITY_SEVERE: return "SEVERE"; } return "UNKNOWN"; } private static String getCMASUrgency(int urgency) { switch (urgency) { case SmsCbCmasInfo.CMAS_URGENCY_IMMEDIATE: return "IMMEDIATE"; case SmsCbCmasInfo.CMAS_URGENCY_EXPECTED: return "EXPECTED"; } return "UNKNOWN"; } private static String getCMASCertainty(int certainty) { switch (certainty) { case SmsCbCmasInfo.CMAS_CERTAINTY_OBSERVED: return "IMMEDIATE"; case SmsCbCmasInfo.CMAS_CERTAINTY_LIKELY: return "LIKELY"; } return "UNKNOWN"; } private static String getGeographicalScope(int scope) { switch (scope) { case SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE: return "CELL_WIDE_IMMEDIATE"; case SmsCbMessage.GEOGRAPHICAL_SCOPE_PLMN_WIDE: return "PLMN_WIDE "; case SmsCbMessage.GEOGRAPHICAL_SCOPE_LA_WIDE: return "LA_WIDE"; case SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE: return "CELL_WIDE"; } return "UNKNOWN"; } private static String getPriority(int priority) { switch (priority) { case SmsCbMessage.MESSAGE_PRIORITY_NORMAL: return "NORMAL"; case SmsCbMessage.MESSAGE_PRIORITY_INTERACTIVE: return "INTERACTIVE"; case SmsCbMessage.MESSAGE_PRIORITY_URGENT: return "URGENT"; case SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY: return "EMERGENCY"; } return "UNKNOWN"; } @Override public void shutdown() { smsStopTrackingIncomingSmsMessage(); smsStopTrackingIncomingMmsMessage(); smsStopTrackingGsmEmergencyCBMessage(); smsStopTrackingCdmaEmergencyCBMessage(); synchronized (lock) { if (mSentReceiversRegistered) { mService.unregisterReceiver(mSmsSendListener); mService.unregisterReceiver(mMmsSendListener); mSentReceiversRegistered = false; } } } private class MmsBuilder { public static final String MESSAGE_CLASS_PERSONAL = PduHeaders.MESSAGE_CLASS_PERSONAL_STR; public static final String MESSAGE_CLASS_ADVERTISEMENT = PduHeaders.MESSAGE_CLASS_ADVERTISEMENT_STR; public static final String MESSAGE_CLASS_INFORMATIONAL = PduHeaders.MESSAGE_CLASS_INFORMATIONAL_STR; public static final String MESSAGE_CLASS_AUTO = PduHeaders.MESSAGE_CLASS_AUTO_STR; public static final int MESSAGE_PRIORITY_LOW = PduHeaders.PRIORITY_LOW; public static final int MESSAGE_PRIORITY_NORMAL = PduHeaders.PRIORITY_LOW; public static final int MESSAGE_PRIORITY_HIGH = PduHeaders.PRIORITY_LOW; private static final int DEFAULT_EXPIRY_TIME = 7 * 24 * 60 * 60; private static final int DEFAULT_PRIORITY = PduHeaders.PRIORITY_NORMAL; private SendReq mRequest; private PduBody mBody; // FIXME: Eventually this should be exposed as a parameter private static final String TEMP_CONTENT_FILE_NAME = "text0.txt"; // Synchronized Multimedia Internet Language // Fragment for compatibility private static final String sSmilText = "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + ""; public MmsBuilder() { mRequest = new SendReq(); mBody = new PduBody(); } public void setFromPhoneNumber(String number) { mRequest.setFrom(new EncodedStringValue(number)); } public void setToPhoneNumber(String number) { mRequest.setTo(new EncodedStringValue[] { new EncodedStringValue(number) }); } public void setToPhoneNumbers(List number) { mRequest.setTo(EncodedStringValue.encodeStrings((String[]) number.toArray())); } public void setSubject(String subject) { mRequest.setSubject(new EncodedStringValue(subject)); } public void setDate() { setDate(System.currentTimeMillis() / 1000); } public void setDate(long time) { mRequest.setDate(time); } public void addMessageBody(String message) { addMessageBody(message, true); } public void setMessageClass(String messageClass) { mRequest.setMessageClass(messageClass.getBytes()); } public void setMessagePriority(int priority) { try { mRequest.setPriority(priority); } catch (InvalidHeaderValueException e) { Log.e("Invalid Header Value "+e.toString()); } } public void setDeliveryReport(boolean report) { try { mRequest.setDeliveryReport((report) ? PduHeaders.VALUE_YES : PduHeaders.VALUE_NO); } catch (InvalidHeaderValueException e) { Log.e("Invalid Header Value "+e.toString()); } } public void setReadReport(boolean report) { try { mRequest.setReadReport((report) ? PduHeaders.VALUE_YES : PduHeaders.VALUE_NO); } catch (InvalidHeaderValueException e) { Log.e("Invalid Header Value "+e.toString()); } } public void setExpirySeconds(int seconds) { mRequest.setExpiry(seconds); } public byte[] build() { mRequest.setBody(mBody); int msgSize = 0; for (int i = 0; i < mBody.getPartsNum(); i++) { msgSize += mBody.getPart(i).getDataLength(); } mRequest.setMessageSize(msgSize); return new PduComposer(mContext, mRequest).make(); } public void addMessageBody(String message, boolean addSmilFragment) { final PduPart part = new PduPart(); part.setCharset(CharacterSets.UTF_8); part.setContentType(ContentType.TEXT_PLAIN.getBytes()); part.setContentLocation("text0".getBytes()); int index = TEMP_CONTENT_FILE_NAME.lastIndexOf("."); String contentId = (index == -1) ? TEMP_CONTENT_FILE_NAME : TEMP_CONTENT_FILE_NAME.substring(0, index); part.setContentId(contentId.getBytes()); part.setContentId("txt".getBytes()); part.setData(message.getBytes()); mBody.addPart(part); if (addSmilFragment) { addSmilTextFragment(TEMP_CONTENT_FILE_NAME); } } private void addSmilTextFragment(String contentFilename) { final String smil = String.format(sSmilText, contentFilename); final PduPart smilPart = new PduPart(); smilPart.setContentId("smil".getBytes()); smilPart.setContentLocation("smil.xml".getBytes()); smilPart.setContentType(ContentType.APP_SMIL.getBytes()); smilPart.setData(smil.getBytes()); mBody.addPart(0, smilPart); } } }