TransactionServicepublic class TransactionService extends android.app.Service implements ObserverThe TransactionService of the MMS Client is responsible for handling requests
to initiate client-transactions sent from:
- The Proxy-Relay (Through Push messages)
- The composer/viewer activities of the MMS Client (Through intents)
The TransactionService runs locally in the same process as the application.
It contains a HandlerThread to which messages are posted from the
intent-receivers of this application.
IMPORTANT: This is currently the only instance in the system in
which simultaneous connectivity to both the mobile data network and
a Wi-Fi network is allowed. This makes the code for handling network
connectivity somewhat different than it is in other applications. In
particular, we want to be able to send or receive MMS messages when
a Wi-Fi connection is active (which implies that there is no connection
to the mobile data network). This has two main consequences:
- Testing for current network connectivity ({@link android.net.NetworkInfo#isConnected()} is
not sufficient. Instead, the correct test is for network availability
({@link android.net.NetworkInfo#isAvailable()}).
- If the mobile data network is not in the connected state, but it is available,
we must initiate setup of the mobile data connection, and defer handling
the MMS transaction until the connection is established.
|
Fields Summary |
---|
private static final String | TAG | public static final String | TRANSACTION_COMPLETED_ACTIONUsed to identify notification intents broadcasted by the
TransactionService when a Transaction is completed. | public static final String | ACTION_ONALARMAction for the Intent which is sent by Alarm service to launch
TransactionService. | public static final String | STATEUsed as extra key in notification intents broadcasted by the TransactionService
when a Transaction is completed (TRANSACTION_COMPLETED_ACTION intents).
Allowed values for this key are: TransactionState.INITIALIZED,
TransactionState.SUCCESS, TransactionState.FAILED. | public static final String | STATE_URIUsed as extra key in notification intents broadcasted by the TransactionService
when a Transaction is completed (TRANSACTION_COMPLETED_ACTION intents).
Allowed values for this key are any valid content uri. | public static final String | CONTENT_URIUsed as extra key in notification intents broadcasted by the TransactionService
when a Transaction is completed (TRANSACTION_COMPLETED_ACTION intents).
Allowed values for this key are the Uri's of stored messages relevant
for the completed Transaction,
i.e.: Uri of DeliveryInd for DeliveryTransaction,
NotificationInd for NotificationTransaction,
ReadOrigInd for ReadOrigTransaction,
null for ReadRecTransaction,
RetrieveConf for RetrieveTransaction,
SendReq for SendTransaction. | private static final int | EVENT_TRANSACTION_REQUEST | private static final int | EVENT_DATA_STATE_CHANGED | private static final int | EVENT_CONTINUE_MMS_CONNECTIVITY | private static final int | EVENT_HANDLE_NEXT_PENDING_TRANSACTION | private static final int | EVENT_QUIT | private static final int | TOAST_MSG_QUEUED | private static final int | TOAST_DOWNLOAD_LATER | private static final int | TOAST_NONE | private static final int | APN_EXTENSION_WAIT | private ServiceHandler | mServiceHandler | private android.os.Looper | mServiceLooper | private final ArrayList | mProcessing | private final ArrayList | mPending | private android.net.ConnectivityManager | mConnMgr | private android.net.NetworkConnectivityListener | mConnectivityListener | private PowerManager.WakeLock | mWakeLock | public android.os.Handler | mToastHandler |
Methods Summary |
---|
private void | acquireWakeLock()
// It's okay to double-acquire this because we are not using it
// in reference-counted mode.
mWakeLock.acquire();
| protected int | beginMmsConnectivity()
// Take a wake lock so we don't fall asleep before the message is downloaded.
createWakeLock();
int result = mConnMgr.startUsingNetworkFeature(
ConnectivityManager.TYPE_MOBILE, Phone.FEATURE_ENABLE_MMS);
switch (result) {
case Phone.APN_ALREADY_ACTIVE:
case Phone.APN_REQUEST_STARTED:
acquireWakeLock();
return result;
}
throw new IOException("Cannot establish MMS connectivity");
| private synchronized void | createWakeLock()
// Create a new wake lock if we haven't made one yet.
if (mWakeLock == null) {
PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MMS Connectivity");
mWakeLock.setReferenceCounted(false);
}
| protected void | endMmsConnectivity()
try {
// cancel timer for renewal of lease
mServiceHandler.removeMessages(EVENT_CONTINUE_MMS_CONNECTIVITY);
if (mConnMgr != null) {
mConnMgr.stopUsingNetworkFeature(
ConnectivityManager.TYPE_MOBILE, Phone.FEATURE_ENABLE_MMS);
}
} finally {
releaseWakeLock();
}
| private int | getTransactionType(int msgType)
switch (msgType) {
case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND:
return Transaction.RETRIEVE_TRANSACTION;
case PduHeaders.MESSAGE_TYPE_READ_REC_IND:
return Transaction.READREC_TRANSACTION;
case PduHeaders.MESSAGE_TYPE_SEND_REQ:
return Transaction.SEND_TRANSACTION;
default:
Log.w(TAG, "Unrecognized MESSAGE_TYPE: " + msgType);
return -1;
}
| private boolean | isNetworkAvailable()
NetworkInfo networkInfo = mConnMgr.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
return networkInfo.isAvailable();
| private static boolean | isTransientFailure(int type)
return (type < MmsSms.ERR_TYPE_GENERIC_PERMANENT) && (type > MmsSms.NO_ERROR);
| private void | launchTransaction(int serviceId, TransactionBundle txnBundle, boolean noNetwork)
if (noNetwork) {
Log.w(TAG, "launchTransaction: no network error!");
onNetworkUnavailable(serviceId, txnBundle.getTransactionType());
return;
}
Message msg = mServiceHandler.obtainMessage(EVENT_TRANSACTION_REQUEST);
msg.arg1 = serviceId;
msg.obj = txnBundle;
if (Log.isLoggable(MmsApp.LOG_TAG, Log.VERBOSE)) {
Log.v(TAG, "Sending: " + msg);
}
mServiceHandler.sendMessage(msg);
| public android.os.IBinder | onBind(android.content.Intent intent)
return null;
| public void | onCreate()
if (Log.isLoggable(MmsApp.LOG_TAG, Log.VERBOSE)) {
Log.v(TAG, "Creating TransactionService");
}
// Start up the thread running the service. Note that we create a
// separate thread because the service normally runs in the process's
// main thread, which we don't want to block.
HandlerThread thread = new HandlerThread("TransactionService");
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
mConnectivityListener = new NetworkConnectivityListener();
mConnectivityListener.registerHandler(mServiceHandler, EVENT_DATA_STATE_CHANGED);
mConnectivityListener.startListening(this);
| public void | onDestroy()
if (Log.isLoggable(MmsApp.LOG_TAG, Log.VERBOSE)) {
Log.v(TAG, "Destroying TransactionService");
}
if (!mPending.isEmpty()) {
Log.w(TAG, "TransactionService exiting with transaction still pending");
}
releaseWakeLock();
mConnectivityListener.unregisterHandler(mServiceHandler);
mConnectivityListener.stopListening();
mConnectivityListener = null;
mServiceHandler.sendEmptyMessage(EVENT_QUIT);
| private void | onNetworkUnavailable(int serviceId, int transactionType)
int toastType = TOAST_NONE;
if (transactionType == Transaction.RETRIEVE_TRANSACTION) {
toastType = TOAST_DOWNLOAD_LATER;
} else if (transactionType == Transaction.SEND_TRANSACTION) {
toastType = TOAST_MSG_QUEUED;
}
if (toastType != TOAST_NONE) {
mToastHandler.sendEmptyMessage(toastType);
}
stopSelf(serviceId);
| public void | onStart(android.content.Intent intent, int startId)
if (Log.isLoggable(MmsApp.LOG_TAG, Log.VERBOSE)) {
Log.v(TAG, "onStart: #" + startId + ": " + intent.getExtras());
}
mConnMgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
boolean noNetwork = !isNetworkAvailable();
if (ACTION_ONALARM.equals(intent.getAction()) || (intent.getExtras() == null)) {
// Scan database to find all pending operations.
Cursor cursor = PduPersister.getPduPersister(this).getPendingMessages(
System.currentTimeMillis());
if (cursor != null) {
try {
if (cursor.getCount() == 0) {
if (Log.isLoggable(MmsApp.LOG_TAG, Log.VERBOSE)) {
Log.v(TAG, "onStart: " +
"No pending messages. Stopping service.");
}
RetryScheduler.setRetryAlarm(this);
stopSelfIfIdle(startId);
return;
}
int columnIndexOfMsgId = cursor.getColumnIndexOrThrow(
PendingMessages.MSG_ID);
int columnIndexOfMsgType = cursor.getColumnIndexOrThrow(
PendingMessages.MSG_TYPE);
while (cursor.moveToNext()) {
int msgType = cursor.getInt(columnIndexOfMsgType);
int transactionType = getTransactionType(msgType);
if (noNetwork) {
onNetworkUnavailable(startId, transactionType);
return;
}
switch (transactionType) {
case -1:
break;
case Transaction.RETRIEVE_TRANSACTION:
// If it's a transiently failed transaction,
// we should retry it in spite of current
// downloading mode.
int failureType = cursor.getInt(
cursor.getColumnIndexOrThrow(
PendingMessages.ERROR_TYPE));
if (!isTransientFailure(failureType)) {
break;
}
// fall-through
default:
Uri uri = ContentUris.withAppendedId(
Mms.CONTENT_URI,
cursor.getLong(columnIndexOfMsgId));
TransactionBundle args = new TransactionBundle(
transactionType, uri.toString());
// FIXME: We use the same startId for all MMs.
launchTransaction(startId, args, false);
break;
}
}
} finally {
cursor.close();
}
} else {
if (Log.isLoggable(MmsApp.LOG_TAG, Log.VERBOSE)) {
Log.v(TAG, "onStart: " +
"No pending messages. Stopping service.");
}
RetryScheduler.setRetryAlarm(this);
stopSelfIfIdle(startId);
}
} else {
if (Log.isLoggable(MmsApp.LOG_TAG, Log.VERBOSE)) {
Log.v(TAG, "onStart: launch transaction...");
}
// For launching NotificationTransaction and test purpose.
TransactionBundle args = new TransactionBundle(intent.getExtras());
launchTransaction(startId, args, noNetwork);
}
| private void | releaseWakeLock()
// Don't release the wake lock if it hasn't been created and acquired.
if (mWakeLock != null && mWakeLock.isHeld()) {
mWakeLock.release();
}
| private void | stopSelfIfIdle(int startId)
synchronized (mProcessing) {
if (mProcessing.isEmpty() && mPending.isEmpty()) {
if (Log.isLoggable(MmsApp.LOG_TAG, Log.VERBOSE)) {
Log.v(TAG, "stopSelfIfIdle: STOP!");
}
stopSelf(startId);
}
}
| public void | update(Observable observable)Handle status change of Transaction (The Observable).
Transaction transaction = (Transaction) observable;
int serviceId = transaction.getServiceId();
try {
synchronized (mProcessing) {
mProcessing.remove(transaction);
if (mPending.size() > 0) {
Message msg = mServiceHandler.obtainMessage(
EVENT_HANDLE_NEXT_PENDING_TRANSACTION,
transaction.getConnectionSettings());
mServiceHandler.sendMessage(msg);
}
else {
endMmsConnectivity();
}
}
Intent intent = new Intent(TRANSACTION_COMPLETED_ACTION);
TransactionState state = transaction.getState();
int result = state.getState();
intent.putExtra(STATE, result);
switch (result) {
case TransactionState.SUCCESS:
if (Log.isLoggable(MmsApp.LOG_TAG, Log.VERBOSE)) {
Log.v(TAG, "Transaction complete: " + serviceId);
}
intent.putExtra(STATE_URI, state.getContentUri());
// Notify user in the system-wide notification area.
switch (transaction.getType()) {
case Transaction.NOTIFICATION_TRANSACTION:
case Transaction.RETRIEVE_TRANSACTION:
MessagingNotification.updateNewMessageIndicator(this, true);
MessagingNotification.updateDownloadFailedNotification(this);
break;
case Transaction.SEND_TRANSACTION:
RateController.getInstance().update();
break;
}
break;
case TransactionState.FAILED:
if (Log.isLoggable(MmsApp.LOG_TAG, Log.VERBOSE)) {
Log.v(TAG, "Transaction failed: " + serviceId);
}
break;
default:
if (Log.isLoggable(MmsApp.LOG_TAG, Log.VERBOSE)) {
Log.v(TAG, "Transaction state unknown: " +
serviceId + " " + result);
}
break;
}
// Broadcast the result of the transaction.
sendBroadcast(intent);
} finally {
transaction.detach(this);
stopSelf(serviceId);
}
|
|