diff options
Diffstat (limited to 'telephony/java/com/android/internal/telephony/SMSDispatcher.java')
-rw-r--r-- | telephony/java/com/android/internal/telephony/SMSDispatcher.java | 840 |
1 files changed, 840 insertions, 0 deletions
diff --git a/telephony/java/com/android/internal/telephony/SMSDispatcher.java b/telephony/java/com/android/internal/telephony/SMSDispatcher.java new file mode 100644 index 000000000000..9947dc046015 --- /dev/null +++ b/telephony/java/com/android/internal/telephony/SMSDispatcher.java @@ -0,0 +1,840 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * 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.android.internal.telephony; + +import android.app.Activity; +import android.app.PendingIntent; +import android.app.AlertDialog; +import android.app.PendingIntent.CanceledException; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.content.DialogInterface; +import android.content.res.Resources; +import android.database.Cursor; +import android.database.SQLException; +import android.net.Uri; +import android.os.AsyncResult; +import android.os.Handler; +import android.os.Message; +import android.provider.Telephony; +import android.provider.Telephony.Sms.Intents; +import android.telephony.SmsMessage; +import android.telephony.ServiceState; +import android.util.Config; +import android.util.Log; +import android.view.WindowManager; + +import com.android.internal.telephony.CommandsInterface; +import com.android.internal.telephony.SmsMessageBase; +import com.android.internal.telephony.SmsResponse; +import com.android.internal.util.HexDump; + +import java.io.ByteArrayOutputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Random; + +import com.android.internal.R; + +import static android.telephony.SmsManager.RESULT_ERROR_GENERIC_FAILURE; +import static android.telephony.SmsManager.RESULT_ERROR_NO_SERVICE; +import static android.telephony.SmsManager.RESULT_ERROR_NULL_PDU; +import static android.telephony.SmsManager.RESULT_ERROR_RADIO_OFF; + + +public abstract class SMSDispatcher extends Handler { + private static final String TAG = "SMS"; + + /** Default checking period for SMS sent without user permit */ + private static final int DEFAULT_SMS_CHECK_PERIOD = 3600000; + + /** Default number of SMS sent in checking period without user permit */ + private static final int DEFAULT_SMS_MAX_ALLOWED = 100; + + /** Default timeout for SMS sent query */ + private static final int DEFAULT_SMS_TIMOUEOUT = 6000; + + protected static final int WAP_PDU_TYPE_PUSH = 0x06; + + protected static final int WAP_PDU_TYPE_CONFIRMED_PUSH = 0x07; + + protected static final byte DRM_RIGHTS_XML = (byte)0xca; + + protected static final String DRM_RIGHTS_XML_MIME_TYPE = "application/vnd.oma.drm.rights+xml"; + + protected static final byte DRM_RIGHTS_WBXML = (byte)0xcb; + + protected static final String DRM_RIGHTS_WBXML_MIME_TYPE = + "application/vnd.oma.drm.rights+wbxml"; + + protected static final byte WAP_SI_MIME_PORT = (byte)0xae; + + protected static final String WAP_SI_MIME_TYPE = "application/vnd.wap.sic"; + + protected static final byte WAP_SL_MIME_PORT = (byte)0xb0; + + protected static final String WAP_SL_MIME_TYPE = "application/vnd.wap.slc"; + + protected static final byte WAP_CO_MIME_PORT = (byte)0xb2; + + protected static final String WAP_CO_MIME_TYPE = "application/vnd.wap.coc"; + + protected static final int WAP_PDU_SHORT_LENGTH_MAX = 30; + + protected static final int WAP_PDU_LENGTH_QUOTE = 31; + + protected static final String MMS_MIME_TYPE = "application/vnd.wap.mms-message"; + + protected static final String[] RAW_PROJECTION = new String[] { + "pdu", + "sequence", + }; + + static final int MAIL_SEND_SMS = 1; + + static final protected int EVENT_NEW_SMS = 1; + + static final protected int EVENT_SEND_SMS_COMPLETE = 2; + + /** Retry sending a previously failed SMS message */ + static final protected int EVENT_SEND_RETRY = 3; + + /** Status report received */ + static final protected int EVENT_NEW_SMS_STATUS_REPORT = 5; + + /** SIM/RUIM storage is full */ + static final protected int EVENT_ICC_FULL = 6; + + /** SMS confirm required */ + static final protected int EVENT_POST_ALERT = 7; + + /** Send the user confirmed SMS */ + static final protected int EVENT_SEND_CONFIRMED_SMS = 8; + + /** Alert is timeout */ + static final protected int EVENT_ALERT_TIMEOUT = 9; + + protected Phone mPhone; + protected Context mContext; + protected ContentResolver mResolver; + protected CommandsInterface mCm; + + protected final Uri mRawUri = Uri.withAppendedPath(Telephony.Sms.CONTENT_URI, "raw"); + + /** Maximum number of times to retry sending a failed SMS. */ + private static final int MAX_SEND_RETRIES = 3; + /** Delay before next send attempt on a failed SMS, in milliseconds. */ + private static final int SEND_RETRY_DELAY = 2000; // ms + + /** + * Message reference for a CONCATENATED_8_BIT_REFERENCE or + * CONCATENATED_16_BIT_REFERENCE message set. Should be + * incremented for each set of concatenated messages. + */ + protected static int sConcatenatedRef; + + private SmsCounter mCounter; + + private SmsTracker mSTracker; + + private static SmsMessage mSmsMessage; + private static SmsMessageBase mSmsMessageBase; + private SmsMessageBase.SubmitPduBase mSubmitPduBase; + + /** + * Implement the per-application based SMS control, which only allows + * a limit on the number of SMS/MMS messages an app can send in checking + * period. + */ + private class SmsCounter { + private int mCheckPeriod; + private int mMaxAllowed; + private HashMap<String, ArrayList<Long>> mSmsStamp; + + /** + * Create SmsCounter + * @param mMax is the number of SMS allowed without user permit + * @param mPeriod is the checking period + */ + SmsCounter(int mMax, int mPeriod) { + mMaxAllowed = mMax; + mCheckPeriod = mPeriod; + mSmsStamp = new HashMap<String, ArrayList<Long>> (); + } + + boolean check(String appName) { + if (!mSmsStamp.containsKey(appName)) { + mSmsStamp.put(appName, new ArrayList<Long>()); + } + + return isUnderLimit(mSmsStamp.get(appName)); + } + + private boolean isUnderLimit(ArrayList<Long> sent) { + Long ct = System.currentTimeMillis(); + + Log.d(TAG, "SMS send size=" + sent.size() + "time=" + ct); + + while (sent.size() > 0 && (ct - sent.get(0)) > mCheckPeriod ) { + sent.remove(0); + } + + if (sent.size() < mMaxAllowed) { + sent.add(ct); + return true; + } + return false; + } + } + + protected SMSDispatcher(PhoneBase phone) { + mPhone = phone; + mContext = phone.getContext(); + mResolver = mContext.getContentResolver(); + mCm = phone.mCM; + mSTracker = null; + mCounter = new SmsCounter(DEFAULT_SMS_MAX_ALLOWED, + DEFAULT_SMS_CHECK_PERIOD); + + mCm.setOnNewSMS(this, EVENT_NEW_SMS, null); + mCm.setOnSmsStatus(this, EVENT_NEW_SMS_STATUS_REPORT, null); + mCm.setOnIccSmsFull(this, EVENT_ICC_FULL, null); + + // Don't always start message ref at 0. + sConcatenatedRef = new Random().nextInt(256); + } + + public void dispose() { + mCm.unSetOnNewSMS(this); + mCm.unSetOnSmsStatus(this); + mCm.unSetOnIccSmsFull(this); + } + + protected void finalize() { + Log.d(TAG, "SMSDispatcher finalized"); + } + + + /* TODO: Need to figure out how to keep track of status report routing in a + * persistent manner. If the phone process restarts (reboot or crash), + * we will lose this list and any status reports that come in after + * will be dropped. + */ + /** Sent messages awaiting a delivery status report. */ + protected final ArrayList<SmsTracker> deliveryPendingList = new ArrayList<SmsTracker>(); + + /** + * Handles events coming from the phone stack. Overridden from handler. + * + * @param msg the message to handle + */ + @Override + public void handleMessage(Message msg) { + AsyncResult ar; + + switch (msg.what) { + case EVENT_NEW_SMS: + // A new SMS has been received by the device + if (Config.LOGD) { + Log.d(TAG, "New SMS Message Received"); + } + + SmsMessage sms; + + ar = (AsyncResult) msg.obj; + + // FIXME only acknowledge on store + acknowledgeLastIncomingSms(true, null); + + if (ar.exception != null) { + Log.e(TAG, "Exception processing incoming SMS", + ar.exception); + return; + } + + sms = (SmsMessage) ar.result; + dispatchMessage(sms.mWrappedSmsMessage); + + break; + + case EVENT_SEND_SMS_COMPLETE: + // An outbound SMS has been successfully transferred, or failed. + handleSendComplete((AsyncResult) msg.obj); + break; + + case EVENT_SEND_RETRY: + sendSms((SmsTracker) msg.obj); + break; + + case EVENT_NEW_SMS_STATUS_REPORT: + handleStatusReport((AsyncResult)msg.obj); + break; + + case EVENT_ICC_FULL: + handleIccFull(); + break; + + case EVENT_POST_ALERT: + handleReachSentLimit((SmsTracker)(msg.obj)); + break; + + case EVENT_ALERT_TIMEOUT: + ((AlertDialog)(msg.obj)).dismiss(); + msg.obj = null; + mSTracker = null; + break; + + case EVENT_SEND_CONFIRMED_SMS: + if (mSTracker!=null) { + Log.d(TAG, "Ready to send SMS again."); + sendSms(mSTracker); + mSTracker = null; + } + break; + } + } + + /** + * Called when SIM_FULL message is received from the RIL. Notifies interested + * parties that SIM storage for SMS messages is full. + */ + private void handleIccFull(){ + // broadcast SIM_FULL intent + Intent intent = new Intent(Intents.SIM_FULL_ACTION); + mPhone.getContext().sendBroadcast(intent, "android.permission.RECEIVE_SMS"); + } + + /** + * Called when a status report is received. This should correspond to + * a previously successful SEND. + * + * @param ar AsyncResult passed into the message handler. ar.result should + * be a String representing the status report PDU, as ASCII hex. + */ + protected abstract void handleStatusReport(AsyncResult ar); + + /** + * Called when SMS send completes. Broadcasts a sentIntent on success. + * On failure, either sets up retries or broadcasts a sentIntent with + * the failure in the result code. + * + * @param ar AsyncResult passed into the message handler. ar.result should + * an SmsResponse instance if send was successful. ar.userObj + * should be an SmsTracker instance. + */ + protected void handleSendComplete(AsyncResult ar) { + SmsTracker tracker = (SmsTracker) ar.userObj; + PendingIntent sentIntent = tracker.mSentIntent; + + if (ar.exception == null) { + if (Config.LOGD) { + Log.d(TAG, "SMS send complete. Broadcasting " + + "intent: " + sentIntent); + } + + if (tracker.mDeliveryIntent != null) { + // Expecting a status report. Add it to the list. + int messageRef = ((SmsResponse)ar.result).messageRef; + tracker.mMessageRef = messageRef; + deliveryPendingList.add(tracker); + } + + if (sentIntent != null) { + try { + sentIntent.send(Activity.RESULT_OK); + } catch (CanceledException ex) {} + } + } else { + if (Config.LOGD) { + Log.d(TAG, "SMS send failed"); + } + + int ss = mPhone.getServiceState().getState(); + + if (ss != ServiceState.STATE_IN_SERVICE) { + handleNotInService(ss, tracker); + } else if ((((CommandException)(ar.exception)).getCommandError() + == CommandException.Error.SMS_FAIL_RETRY) && + tracker.mRetryCount < MAX_SEND_RETRIES) { + // Retry after a delay if needed. + // TODO: According to TS 23.040, 9.2.3.6, we should resend + // with the same TP-MR as the failed message, and + // TP-RD set to 1. However, we don't have a means of + // knowing the MR for the failed message (EF_SMSstatus + // may or may not have the MR corresponding to this + // message, depending on the failure). Also, in some + // implementations this retry is handled by the baseband. + tracker.mRetryCount++; + Message retryMsg = obtainMessage(EVENT_SEND_RETRY, tracker); + sendMessageDelayed(retryMsg, SEND_RETRY_DELAY); + } else if (tracker.mSentIntent != null) { + // Done retrying; return an error to the app. + try { + tracker.mSentIntent.send(RESULT_ERROR_GENERIC_FAILURE); + } catch (CanceledException ex) {} + } + } + } + + /** + * Handles outbound message when the phone is not in service. + * + * @param ss Current service state. Valid values are: + * OUT_OF_SERVICE + * EMERGENCY_ONLY + * POWER_OFF + * @param tracker An SmsTracker for the current message. + */ + protected void handleNotInService(int ss, SmsTracker tracker) { + if (tracker.mSentIntent != null) { + try { + if (ss == ServiceState.STATE_POWER_OFF) { + tracker.mSentIntent.send(RESULT_ERROR_RADIO_OFF); + } else { + tracker.mSentIntent.send(RESULT_ERROR_NO_SERVICE); + } + } catch (CanceledException ex) {} + } + } + + /** + * Dispatches an incoming SMS messages. + * + * @param sms the incoming message from the phone + */ + protected abstract void dispatchMessage(SmsMessageBase sms); + + + /** + * If this is the last part send the parts out to the application, otherwise + * the part is stored for later processing. + */ + protected void processMessagePart(SmsMessageBase sms, int referenceNumber, + int sequence, int count, int destinationPort) { + // Lookup all other related parts + StringBuilder where = new StringBuilder("reference_number ="); + where.append(referenceNumber); + where.append(" AND address = ?"); + String[] whereArgs = new String[] {sms.getOriginatingAddress()}; + + byte[][] pdus = null; + Cursor cursor = null; + try { + cursor = mResolver.query(mRawUri, RAW_PROJECTION, where.toString(), whereArgs, null); + int cursorCount = cursor.getCount(); + if (cursorCount != count - 1) { + // We don't have all the parts yet, store this one away + ContentValues values = new ContentValues(); + values.put("date", new Long(sms.getTimestampMillis())); + values.put("pdu", HexDump.toHexString(sms.getPdu())); + values.put("address", sms.getOriginatingAddress()); + values.put("reference_number", referenceNumber); + values.put("count", count); + values.put("sequence", sequence); + if (destinationPort != -1) { + values.put("destination_port", destinationPort); + } + mResolver.insert(mRawUri, values); + + return; + } + + // All the parts are in place, deal with them + int pduColumn = cursor.getColumnIndex("pdu"); + int sequenceColumn = cursor.getColumnIndex("sequence"); + + pdus = new byte[count][]; + for (int i = 0; i < cursorCount; i++) { + cursor.moveToNext(); + int cursorSequence = (int)cursor.getLong(sequenceColumn); + pdus[cursorSequence - 1] = HexDump.hexStringToByteArray( + cursor.getString(pduColumn)); + } + // This one isn't in the DB, so add it + pdus[sequence - 1] = sms.getPdu(); + + // Remove the parts from the database + mResolver.delete(mRawUri, where.toString(), whereArgs); + } catch (SQLException e) { + Log.e(TAG, "Can't access multipart SMS database", e); + return; // TODO: NACK the message or something, don't just discard. + } finally { + if (cursor != null) cursor.close(); + } + + // Dispatch the PDUs to applications + switch (destinationPort) { + case SmsHeader.PORT_WAP_PUSH: { + // Build up the data stream + ByteArrayOutputStream output = new ByteArrayOutputStream(); + for (int i = 0; i < count; i++) { + SmsMessage msg = SmsMessage.createFromPdu(pdus[i]); + byte[] data = msg.getUserData(); + output.write(data, 0, data.length); + } + + // Handle the PUSH + dispatchWapPdu(output.toByteArray()); + break; + } + + case -1: + // The messages were not sent to a port + dispatchPdus(pdus); + break; + + default: + // The messages were sent to a port, so concoct a URI for it + dispatchPortAddressedPdus(pdus, destinationPort); + break; + } + } + + /** + * Dispatches standard PDUs to interested applications + * + * @param pdus The raw PDUs making up the message + */ + protected void dispatchPdus(byte[][] pdus) { + Intent intent = new Intent(Intents.SMS_RECEIVED_ACTION); + intent.putExtra("pdus", pdus); + mPhone.getContext().sendBroadcast( + intent, "android.permission.RECEIVE_SMS"); + } + + /** + * Dispatches port addressed PDUs to interested applications + * + * @param pdus The raw PDUs making up the message + * @param port The destination port of the messages + */ + protected void dispatchPortAddressedPdus(byte[][] pdus, int port) { + Uri uri = Uri.parse("sms://localhost:" + port); + Intent intent = new Intent(Intents.DATA_SMS_RECEIVED_ACTION, uri); + intent.putExtra("pdus", pdus); + mPhone.getContext().sendBroadcast( + intent, "android.permission.RECEIVE_SMS"); + } + + /** + * Dispatches inbound messages that are in the WAP PDU format. See + * wap-230-wsp-20010705-a section 8 for details on the WAP PDU format. + * + * @param pdu The WAP PDU, made up of one or more SMS PDUs + */ + protected void dispatchWapPdu(byte[] pdu) { + int index = 0; + int transactionId = pdu[index++] & 0xFF; + int pduType = pdu[index++] & 0xFF; + int headerLength = 0; + + if ((pduType != WAP_PDU_TYPE_PUSH) && + (pduType != WAP_PDU_TYPE_CONFIRMED_PUSH)) { + Log.w(TAG, "Received non-PUSH WAP PDU. Type = " + pduType); + return; + } + + /** + * Parse HeaderLen(unsigned integer). + * From wap-230-wsp-20010705-a section 8.1.2 + * The maximum size of a uintvar is 32 bits. + * So it will be encoded in no more than 5 octets. + */ + int temp = 0; + do { + temp = pdu[index++]; + headerLength = headerLength << 7; + headerLength |= temp & 0x7F; + } while ((temp & 0x80) != 0); + + int headerStartIndex = index; + + /** + * Parse Content-Type. + * From wap-230-wsp-20010705-a section 8.4.2.24 + * + * Content-type-value = Constrained-media | Content-general-form + * Content-general-form = Value-length Media-type + * Media-type = (Well-known-media | Extension-Media) *(Parameter) + * Value-length = Short-length | (Length-quote Length) + * Short-length = <Any octet 0-30> (octet <= WAP_PDU_SHORT_LENGTH_MAX) + * Length-quote = <Octet 31> (WAP_PDU_LENGTH_QUOTE) + * Length = Uintvar-integer + */ + // Parse Value-length. + if ((pdu[index] & 0xff) <= WAP_PDU_SHORT_LENGTH_MAX) { + // Short-length. + index++; + } else if (pdu[index] == WAP_PDU_LENGTH_QUOTE) { + // Skip Length-quote. + index++; + // Skip Length. + // Now we assume 8bit is enough to store the content-type length. + index++; + } + String mimeType; + switch (pdu[headerStartIndex]) { + case DRM_RIGHTS_XML: + mimeType = DRM_RIGHTS_XML_MIME_TYPE; + break; + case DRM_RIGHTS_WBXML: + mimeType = DRM_RIGHTS_WBXML_MIME_TYPE; + break; + case WAP_SI_MIME_PORT: + // application/vnd.wap.sic + mimeType = WAP_SI_MIME_TYPE; + break; + case WAP_SL_MIME_PORT: + mimeType = WAP_SL_MIME_TYPE; + break; + case WAP_CO_MIME_PORT: + mimeType = WAP_CO_MIME_TYPE; + break; + default: + int start = index; + + // Skip text-string. + // Now we assume the mimetype is Extension-Media. + while (pdu[index++] != '\0') { + ; + } + mimeType = new String(pdu, start, index-start-1); + break; + } + + // XXX Skip the remainder of the header for now + int dataIndex = headerStartIndex + headerLength; + byte[] data; + if (pdu[headerStartIndex] == WAP_CO_MIME_PORT) { + // because GsmSMSDispatcher can't parse push headers "Content-Location" and + // X-Wap-Content-URI, so pass the whole push to CO application. + data = pdu; + } else { + data = new byte[pdu.length - dataIndex]; + System.arraycopy(pdu, dataIndex, data, 0, data.length); + } + + // Notify listeners about the WAP PUSH + Intent intent = new Intent(Intents.WAP_PUSH_RECEIVED_ACTION); + intent.setType(mimeType); + intent.putExtra("transactionId", transactionId); + intent.putExtra("pduType", pduType); + intent.putExtra("data", data); + + if (mimeType.equals(MMS_MIME_TYPE)) { + mPhone.getContext().sendBroadcast( + intent, "android.permission.RECEIVE_MMS"); + } else { + mPhone.getContext().sendBroadcast( + intent, "android.permission.RECEIVE_WAP_PUSH"); + } + } + + /** + * Send a multi-part text based SMS. + * + * @param destinationAddress the address to send the message to + * @param scAddress is the service center address or null to use + * the current default SMSC + * @param parts an <code>ArrayList</code> of strings that, in order, + * comprise the original message + * @param sentIntents if not null, an <code>ArrayList</code> of + * <code>PendingIntent</code>s (one for each message part) that is + * broadcast when the corresponding message part has been sent. + * The result code will be <code>Activity.RESULT_OK<code> for success, + * or one of these errors: + * <code>RESULT_ERROR_GENERIC_FAILURE</code> + * <code>RESULT_ERROR_RADIO_OFF</code> + * <code>RESULT_ERROR_NULL_PDU</code>. + * The per-application based SMS control checks sentIntent. If sentIntent + * is NULL the caller will be checked against all unknown applicaitons, + * which cause smaller number of SMS to be sent in checking period. + * @param deliveryIntents if not null, an <code>ArrayList</code> of + * <code>PendingIntent</code>s (one for each message part) that is + * broadcast when the corresponding message part has been delivered + * to the recipient. The raw pdu of the status report is in the + * extended data ("pdu"). + */ + protected abstract void sendMultipartText(String destinationAddress, String scAddress, + ArrayList<String> parts, ArrayList<PendingIntent> sentIntents, + ArrayList<PendingIntent> deliveryIntents); + + /** + * Send a SMS + * + * @param smsc the SMSC to send the message through, or NULL for the + * defatult SMSC + * @param pdu the raw PDU to send + * @param sentIntent if not NULL this <code>Intent</code> is + * broadcast when the message is sucessfully sent, or failed. + * The result code will be <code>Activity.RESULT_OK<code> for success, + * or one of these errors: + * <code>RESULT_ERROR_GENERIC_FAILURE</code> + * <code>RESULT_ERROR_RADIO_OFF</code> + * <code>RESULT_ERROR_NULL_PDU</code>. + * The per-application based SMS control checks sentIntent. If sentIntent + * is NULL the caller will be checked against all unknown applicaitons, + * which cause smaller number of SMS to be sent in checking period. + * @param deliveryIntent if not NULL this <code>Intent</code> is + * broadcast when the message is delivered to the recipient. The + * raw pdu of the status report is in the extended data ("pdu"). + */ + protected void sendRawPdu(byte[] smsc, byte[] pdu, PendingIntent sentIntent, + PendingIntent deliveryIntent) { + if (pdu == null) { + if (sentIntent != null) { + try { + sentIntent.send(RESULT_ERROR_NULL_PDU); + } catch (CanceledException ex) {} + } + return; + } + + HashMap<String, Object> map = new HashMap<String, Object>(); + map.put("smsc", smsc); + map.put("pdu", pdu); + + SmsTracker tracker = new SmsTracker(map, sentIntent, + deliveryIntent); + int ss = mPhone.getServiceState().getState(); + + if (ss != ServiceState.STATE_IN_SERVICE) { + handleNotInService(ss, tracker); + } else { + String appName = getAppNameByIntent(sentIntent); + if (mCounter.check(appName)) { + sendSms(tracker); + } else { + sendMessage(obtainMessage(EVENT_POST_ALERT, tracker)); + } + } + } + + /** + * Post an alert while SMS needs user confirm. + * + * An SmsTracker for the current message. + */ + protected void handleReachSentLimit(SmsTracker tracker) { + + Resources r = Resources.getSystem(); + + String appName = getAppNameByIntent(tracker.mSentIntent); + + AlertDialog d = new AlertDialog.Builder(mContext) + .setTitle(r.getString(R.string.sms_control_title)) + .setMessage(appName + " " + r.getString(R.string.sms_control_message)) + .setPositiveButton(r.getString(R.string.sms_control_yes), mListener) + .setNegativeButton(r.getString(R.string.sms_control_no), null) + .create(); + + d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); + d.show(); + + mSTracker = tracker; + sendMessageDelayed ( obtainMessage(EVENT_ALERT_TIMEOUT, d), + DEFAULT_SMS_TIMOUEOUT); + } + + protected String getAppNameByIntent(PendingIntent intent) { + Resources r = Resources.getSystem(); + return (intent != null) ? intent.getTargetPackage() + : r.getString(R.string.sms_control_default_app_name); + } + + /** + * Send the message along to the radio. + * + * @param tracker holds the SMS message to send + */ + protected abstract void sendSms(SmsTracker tracker); + + /** + * Activate or deactivate cell broadcast SMS. + * + * @param activate + * 0 = activate, 1 = deactivate + * @param response + * Callback message is empty on completion + */ + protected abstract void activateCellBroadcastSms(int activate, Message response); + + /** + * Query the current configuration of cell broadcast SMS. + * + * @param response + * Callback message contains the configuration from the modem on completion + * @see #setCellBroadcastConfig + */ + protected abstract void getCellBroadcastSmsConfig(Message response); + + /** + * Configure cell broadcast SMS. + * + * @param configValuesArray + * The first element defines the number of triples that follow. + * A triple is made up of the service category, the language identifier + * and a boolean that specifies whether the category is set active. + * @param response + * Callback message is empty on completion + */ + protected abstract void setCellBroadcastConfig(int[] configValuesArray, Message response); + + /** + * Send an acknowledge message. + * @param success indicates that last message was successfully received. + * @param response callback message sent when operation completes. + */ + protected abstract void acknowledgeLastIncomingSms(boolean success, Message response); + + /** + * Keeps track of an SMS that has been sent to the RIL, until it it has + * successfully been sent, or we're done trying. + * + */ + static protected class SmsTracker { + // fields need to be public for derived SmsDispatchers + public HashMap mData; + public int mRetryCount; + public int mMessageRef; + + public PendingIntent mSentIntent; + public PendingIntent mDeliveryIntent; + + SmsTracker(HashMap data, PendingIntent sentIntent, + PendingIntent deliveryIntent) { + mData = data; + mSentIntent = sentIntent; + mDeliveryIntent = deliveryIntent; + mRetryCount = 0; + } + } + + private DialogInterface.OnClickListener mListener = + new DialogInterface.OnClickListener() { + + public void onClick(DialogInterface dialog, int which) { + if (which == DialogInterface.BUTTON1) { + Log.d(TAG, "click YES to send out sms"); + sendMessage(obtainMessage(EVENT_SEND_CONFIRMED_SMS)); + } + } + }; +} + |