Last active
January 2, 2016 18:39
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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