FileDocCategorySizeDatePackage
TransactionService.javaAPI DocAndroid 1.5 API33033Wed May 06 22:42:46 BST 2009com.android.mms.transaction

TransactionService

public class TransactionService extends android.app.Service implements Observer
The 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_ACTION
Used to identify notification intents broadcasted by the TransactionService when a Transaction is completed.
public static final String
ACTION_ONALARM
Action for the Intent which is sent by Alarm service to launch TransactionService.
public static final String
STATE
Used 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_URI
Used 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_URI
Used 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
Constructors Summary
Methods Summary
private voidacquireWakeLock()

        // It's okay to double-acquire this because we are not using it
        // in reference-counted mode.
        mWakeLock.acquire();
    
protected intbeginMmsConnectivity()

        // 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 voidcreateWakeLock()

        // 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 voidendMmsConnectivity()

        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 intgetTransactionType(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 booleanisNetworkAvailable()

        NetworkInfo networkInfo = mConnMgr.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
        return networkInfo.isAvailable();
    
private static booleanisTransientFailure(int type)

        return (type < MmsSms.ERR_TYPE_GENERIC_PERMANENT) && (type > MmsSms.NO_ERROR);
    
private voidlaunchTransaction(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.IBinderonBind(android.content.Intent intent)

        return null;
    
public voidonCreate()


    
       
        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 voidonDestroy()

        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 voidonNetworkUnavailable(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 voidonStart(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 voidreleaseWakeLock()

        // Don't release the wake lock if it hasn't been created and acquired.
        if (mWakeLock != null && mWakeLock.isHeld()) {
            mWakeLock.release();
        }
    
private voidstopSelfIfIdle(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 voidupdate(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);
        }