Skip to content

Instantly share code, notes, and snippets.

@nseidm1
Last active January 2, 2016 18:39
Show Gist options
  • Save nseidm1/8345290 to your computer and use it in GitHub Desktop.
Save nseidm1/8345290 to your computer and use it in GitHub Desktop.
A tremendously simplified, and increasingly reliable implementation of the AOSP messaging app's MMS TransactionService.
/*
* Copyright (C) 2007-2008 Esmertec AG.
* Copyright (C) 2007-2008 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.concentriclivers.mms.com.android.mms.transaction;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.LinkedList;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.Cursor;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.PowerManager;
import android.provider.Telephony.Mms;
import android.provider.Telephony.MmsSms.PendingMessages;
import android.widget.Toast;
import com.android.camera.BuildConfig;
import com.concentriclivers.mms.com.android.internal.telephony.Phone;
import com.concentriclivers.mms.com.android.mms.R;
import com.concentriclivers.mms.com.android.mms.transaction.simple.VegasMMS;
import com.concentriclivers.mms.com.android.mms.transaction.simple.VegasTransaction;
import com.concentriclivers.mms.com.android.mms.ui.managers.ExecutorManager;
import com.concentriclivers.mms.com.google.android.mms.pdu.PduHeaders;
import com.concentriclivers.mms.com.google.android.mms.pdu.PduPersister;
public class TransactionService extends Service {
private ConnectivityManager mConnMgr;
private PowerManager mPowerManager;
private PowerManager.WakeLock mWakeLock;
private ConnectivityBroadcastReceiver mReceiver;
private boolean mTransactionsProcessing = false;
private Handler mHandler = new Handler(Looper.getMainLooper());
private final LinkedList<Transaction> mProcessing = new LinkedList<Transaction>();
private final LinkedList<VegasMMS> mVegasProcessing = new LinkedList<VegasMMS>();
public static final String VEGAS_MMS = "VEGAS_MMS";
private static final String MMS_SENT = "com.project.vegassms.MMS_SENT";
private static final String MMS_SENT_MESSAGE_ID = "message_id";
@Override
public void onCreate() {
mConnMgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
mPowerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MMS Connectivity");
mWakeLock.setReferenceCounted(false);
mReceiver = new ConnectivityBroadcastReceiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
registerReceiver(mReceiver, intentFilter);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d("onStartCommand()");
//Vegas mms are not persisted to disk
//and are only parceled and shipped here
if (intent != null) {
VegasMMS vegasMMS = intent.getParcelableExtra(VEGAS_MMS);
if (vegasMMS != null) {
Log.d("Adding Vegas MMS");
mVegasProcessing.add(vegasMMS);
}
} else {
Log.d("onStartCommand() - intent is null");
}
//Here's where regular mms messages are retrieved from their persisted state in the database
retrievePendingTransactions();
return Service.START_STICKY;
}
private void retrievePendingTransactions() {
ExecutorManager.sInstance.mSingleThread.execute(new Runnable() {
@Override
public void run() {
Cursor cursor = PduPersister.getPduPersister(TransactionService.this).getPendingMessages(System.currentTimeMillis());
try {
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);
Uri uri = ContentUris.withAppendedId(Mms.CONTENT_URI, cursor.getLong(columnIndexOfMsgId));
TransactionBundle args = new TransactionBundle(transactionType, uri.toString());
processTransaction(args);
}
if (mProcessing.size() > 0 || mVegasProcessing.size() > 0)
mReceiver.onReceive(null, null);
} finally {
if(cursor != null && !cursor.isClosed())
cursor.close();
}
}
});
}
@Override
public void onDestroy() {
unregisterReceiver(mReceiver);
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
private void processTransaction(TransactionBundle args) {
Transaction transaction = null;
switch (args.getTransactionType()) {
case Transaction.SEND_TRANSACTION:
transaction = new SendTransaction(TransactionService.this, args.getUri());
break;
case Transaction.READREC_TRANSACTION:
transaction = new ReadRecTransaction(TransactionService.this, args.getUri());
break;
}
if (transaction != null && !mProcessing.contains(transaction))
mProcessing.add(transaction);
}
private class ConnectivityBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if ((mProcessing.size() == 0 && mVegasProcessing.size() == 0) || !beginMmsConnectivity()) {
Log.d("Connectivity unavailable or no transactions to process");
return;
}
Log.d("Proceeding the process transactions, connectivity is available");
TransactionSettings transactionSettings = new TransactionSettings(TransactionService.this, mConnMgr.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_MMS).getExtraInfo());
if (transactionSettings.getMmscUrl() != null && !transactionSettings.getMmscUrl().equals(""))
processTransactions(transactionSettings);
}
};
private void processTransactions(final TransactionSettings transactionSettings) {
if (mTransactionsProcessing)
return;
mTransactionsProcessing = true;
ExecutorManager.sInstance.mSingleThread.execute(new Runnable() {
@Override
public void run() {
Log.d("wakelock acquired");
mWakeLock.acquire();
stop_executing:{
while(mProcessing.size() > 0) {
Transaction transaction = mProcessing.pop();
transaction.mTransactionSettings = transactionSettings;
if (transaction.failureCount > 3) {
mHandler.post(new Runnable() {
@Override
public void run() {
Toast.makeText(TransactionService.this, R.string.send_failed, Toast.LENGTH_SHORT).show();
}
});
continue;
}
try {
Log.d("executing transaction");
erth(transactionSettings.getMmscUrl(), transactionSettings);
transaction.execute();
Log.d("execution complete");
} catch (Exception e) {
Log.d("exception sending transaction");
transaction.failureCount++;
mProcessing.add(transaction);
mTransactionsProcessing = false;
e.printStackTrace();
mHandler.post(new Runnable() {
@Override
public void run() {
Toast.makeText(TransactionService.this, R.string.sending_failed, Toast.LENGTH_SHORT).show();
}
});
break stop_executing;
}
}
while(mVegasProcessing.size() > 0) {
VegasTransaction vegasTransaction = new VegasTransaction(TransactionService.this);
vegasTransaction.mTransactionSettings = transactionSettings;
VegasMMS vegasMMS = mVegasProcessing.poll();
if (vegasMMS.failureCount > 3) {
mHandler.post(new Runnable() {
@Override
public void run() {
Toast.makeText(TransactionService.this, R.string.send_failed, Toast.LENGTH_SHORT).show();
}
});
continue;
}
try {
Log.d("sending vegas mms");
erth(transactionSettings.getMmscUrl(), transactionSettings);
vegasTransaction.sendNewMessage(vegasMMS);
Intent intent = new Intent(MMS_SENT);
intent.putExtra(MMS_SENT_MESSAGE_ID, vegasMMS.getMessageId());
sendBroadcast(intent);
Log.d("sending complete");
} catch (Exception e) {
Log.d("exception sending vegas mms");
vegasMMS.failureCount++;
mVegasProcessing.add(vegasMMS);
mTransactionsProcessing = false;
e.printStackTrace();
mHandler.post(new Runnable() {
@Override
public void run() {
Toast.makeText(TransactionService.this, R.string.sending_failed, Toast.LENGTH_SHORT).show();
}
});
break stop_executing;
}
}
}
if (mWakeLock != null && mWakeLock.isHeld())
mWakeLock.release();
endMmsConnectivity();
Log.d("wakelock disabled");
mTransactionsProcessing = false;
}
});
}
private boolean beginMmsConnectivity() {
try {
int result = mConnMgr.startUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE, Phone.FEATURE_ENABLE_MMS);
NetworkInfo info = mConnMgr.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_MMS);
boolean isAvailable = info != null && info.isConnected() && result == Phone.APN_ALREADY_ACTIVE && !Phone.REASON_VOICE_CALL_ENDED.equals(info.getReason());
return isAvailable;
} catch(Exception e) {
return false;
}
}
protected void endMmsConnectivity() {
Log.d("endMmsConnectivity()");
mConnMgr.stopUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE, Phone.FEATURE_ENABLE_MMS);
}
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:
return -1;
}
}
private static class Log {
public static void d(String msg) {
if (BuildConfig.DEBUG)
android.util.Log.d(TransactionService.class.getSimpleName(), msg);
}
}
public static void ensureRouteToHostFancy(ConnectivityManager cm, String url, TransactionSettings settings) throws IOException, NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Method m = cm.getClass().getMethod("requestRouteToHostAddress", new Class[] { int.class, InetAddress.class });
InetAddress inetAddr;
if (settings.isProxySet()) {
String proxyAddr = settings.getProxyAddress();
try {
inetAddr = InetAddress.getByName(proxyAddr);
} catch (UnknownHostException e) {
throw new IOException("Cannot establish route for " + url + ": Unknown proxy " + proxyAddr);
}
if (!(Boolean) m.invoke(cm, new Object[] { ConnectivityManager.TYPE_MOBILE_MMS, inetAddr }))
throw new IOException("Cannot establish route to proxy " + inetAddr);
} else {
Uri uri = Uri.parse(url);
try {
inetAddr = InetAddress.getByName(uri.getHost());
} catch (UnknownHostException e) {
throw new IOException("Cannot establish route for " + url + ": Unknown host");
}
if (!(Boolean) m.invoke(cm, new Object[] { ConnectivityManager.TYPE_MOBILE_MMS, inetAddr }))
throw new IOException("Cannot establish route to " + inetAddr + " for " + url);
}
}
private void erth(String url, TransactionSettings settings) throws IOException {
try {
ensureRouteToHostFancy(mConnMgr, url, settings);
} catch (Exception e) {
e.printStackTrace();
ensureRouteToHost(mConnMgr, url, settings);
}
}
public static void ensureRouteToHost(ConnectivityManager cm, String url, TransactionSettings settings) throws IOException {
int inetAddr;
if (settings.isProxySet()) {
String proxyAddr = settings.getProxyAddress();
inetAddr = lookupHost(proxyAddr);
if (inetAddr == -1) {
throw new IOException("Cannot establish route for " + url + ": Unknown host");
} else {
if (!cm.requestRouteToHost(ConnectivityManager.TYPE_MOBILE_MMS, inetAddr))
throw new IOException("Cannot establish route to proxy " + inetAddr);
}
} else {
Uri uri = Uri.parse(url);
inetAddr = lookupHost(uri.getHost());
if (inetAddr == -1) {
throw new IOException("Cannot establish route for " + url + ": Unknown host");
} else {
if (!cm.requestRouteToHost(ConnectivityManager.TYPE_MOBILE_MMS, inetAddr))
throw new IOException("Cannot establish route to " + inetAddr + " for " + url);
}
}
}
public static int lookupHost(String hostname) {
InetAddress inetAddress;
try {
inetAddress = InetAddress.getByName(hostname);
} catch (UnknownHostException e) {
return -1;
}
byte[] addrBytes;
int addr;
addrBytes = inetAddress.getAddress();
addr = ((addrBytes[3] & 0xff) << 24) | ((addrBytes[2] & 0xff) << 16) | ((addrBytes[1] & 0xff) << 8) | (addrBytes[0] & 0xff);
return addr;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment