745 lines
27 KiB
Java
745 lines
27 KiB
Java
/*
|
|
* 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.provider.Settings;
|
|
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.telephony.WapPushOverSms;
|
|
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_COUNT = 100;
|
|
|
|
/** Default timeout for SMS sent query */
|
|
private static final int DEFAULT_SMS_TIMOUEOUT = 6000;
|
|
|
|
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 WapPushOverSms mWapPush;
|
|
|
|
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;
|
|
/** single part SMS */
|
|
private static final int SINGLE_PART_SMS = 1;
|
|
|
|
/**
|
|
* 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>> ();
|
|
}
|
|
|
|
/**
|
|
* Check to see if an application allow to send new SMS messages
|
|
*
|
|
* @param appName is the application sending sms
|
|
* @param smsWaiting is the number of new sms wants to be sent
|
|
* @return true if application is allowed to send the requested number
|
|
* of new sms messages
|
|
*/
|
|
boolean check(String appName, int smsWaiting) {
|
|
if (!mSmsStamp.containsKey(appName)) {
|
|
mSmsStamp.put(appName, new ArrayList<Long>());
|
|
}
|
|
|
|
return isUnderLimit(mSmsStamp.get(appName), smsWaiting);
|
|
}
|
|
|
|
private boolean isUnderLimit(ArrayList<Long> sent, int smsWaiting) {
|
|
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() + smsWaiting) <= mMaxAllowed) {
|
|
for (int i = 0; i < smsWaiting; i++ ) {
|
|
sent.add(ct);
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
protected SMSDispatcher(PhoneBase phone) {
|
|
mPhone = phone;
|
|
mWapPush = new WapPushOverSms(phone);
|
|
mContext = phone.getContext();
|
|
mResolver = mContext.getContentResolver();
|
|
mCm = phone.mCM;
|
|
mSTracker = null;
|
|
|
|
int check_period = Settings.Gservices.getInt(mResolver,
|
|
Settings.Gservices.SMS_OUTGOING_CEHCK_INTERVAL_MS,
|
|
DEFAULT_SMS_CHECK_PERIOD);
|
|
int max_count = Settings.Gservices.getInt(mResolver,
|
|
Settings.Gservices.SMS_OUTGOING_CEHCK_MAX_COUNT,
|
|
DEFAULT_SMS_MAX_COUNT);
|
|
mCounter = new SmsCounter(max_count, 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. Exception:" + 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) {
|
|
if (isMultipartTracker(mSTracker)) {
|
|
sendMultipartSms(mSTracker);
|
|
} else {
|
|
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
|
|
mWapPush.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");
|
|
}
|
|
|
|
|
|
/**
|
|
* 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, SINGLE_PART_SMS)) {
|
|
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);
|
|
|
|
/**
|
|
* Send the multi-part SMS based on multipart Sms tracker
|
|
*
|
|
* @param tracker holds the multipart Sms tracker ready to be sent
|
|
*/
|
|
protected abstract void sendMultipartSms (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);
|
|
|
|
/**
|
|
* Check if a SmsTracker holds multi-part Sms
|
|
*
|
|
* @param tracker a SmsTracker could hold a multi-part Sms
|
|
* @return true for tracker holds Multi-parts Sms
|
|
*/
|
|
private boolean isMultipartTracker (SmsTracker tracker) {
|
|
HashMap map = tracker.mData;
|
|
return ( map.get("parts") != null);
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
}
|
|
|
|
protected SmsTracker SmsTrackerFactory(HashMap data, PendingIntent sentIntent,
|
|
PendingIntent deliveryIntent) {
|
|
return new SmsTracker(data, sentIntent, deliveryIntent);
|
|
}
|
|
|
|
private DialogInterface.OnClickListener mListener =
|
|
new DialogInterface.OnClickListener() {
|
|
|
|
public void onClick(DialogInterface dialog, int which) {
|
|
if (which == DialogInterface.BUTTON_POSITIVE) {
|
|
Log.d(TAG, "click YES to send out sms");
|
|
sendMessage(obtainMessage(EVENT_SEND_CONFIRMED_SMS));
|
|
}
|
|
}
|
|
};
|
|
}
|