So this video Android 4.4 SMS APIs from #DevBytes explains the recent changes to the SMS APIs in KitKat. They also provide a link with a sample project. http://goo.gl/uQ3Nih
They suggest that you handle the receive of an MMS in a service. Which all looks fine, except they neglect to mention the most undocumented piece. How to actually handle an incoming MMS.
Here is the sample from the project
https://gist.github.com/lawloretienne/8970938
I have tried to "handle the MMS"
https://gist.github.com/lawloretienne/8971050
I can get the extras from the intent but the only meaningful thing that I can extract is the number from which the MMS was sent.
Can anyone point me in the right direction about how to go about this?
I noticed that a WAP_PUSH_MESSAGE contains a few things, a FROM, SUBJECT, and CONTENT_LOCATION.
The content location appears to be the url where the content of the MMS is contained. How can I access this?
Here is an example of that URL
https://atl1mmsget.msg.eng.t-mobile.com/mms/wapenc?location=XXXXXXXXXXX_14zbwk&rid=027
Where the X is a digit in the phone number of the device I am testing on.
It looks like the MMSC (Multimedia Messaging Service Center) for T-Mobile in the U.S. is http://mms.msg.eng.t-mobile.com/mms/wapenc
According to this list : http://www.activexperts.com/xmstoolkit/mmsclist/
There's zero documentation so here's some info to help.
1) com.google.android.mms.pdu from source. You need the Pdu utils.
2) You get the notification push from byte array extra of the incoming mms broadcast (intent.getByteArrayExtra("data")).
3) Parse the notification push into a GenericPdu (new PduParser(rawPdu).parse()).
4) You'll need TransactionSettings to communicate with the carrier's wap server. I get the transaction settings after #5 below. I use:
TransactionSettings transactionSettings = new TransactionSettings(mContext, mConnMgr.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_MMS).getExtraInfo());
5) Force network comm over wifi. I use the following.
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;
}
}
6) You then need to ensure a route to the host.
private 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);
}
}
}
Here's the lookupHost method:
private 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;
}
I also like to use a reflection based method for improved ensureRouteToHost functionality:
private 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);
}
}
7) After ensuring a route to the host you can then need HttpUtls from source. I've heavily modified my implementation using OkHttp for improved communications.
byte[] rawPdu = HttpUtils.httpConnection(mContext, mContentLocation, null, HttpUtils.HTTP_GET_METHOD, mTransactionSettings.isProxySet(), mTransactionSettings.getProxyAddress(), mTransactionSettings.getProxyPort());
8) From the resulting byte array use the PduParser to parge the GenericPdu. Then you can extract the body and cast to a MultimediaMessagePdu.
9) Then you can iterate the parts of the PDU.
There are countless things to consider with MMS. One thing that comes to mind is how annoying Slideshows are, so what I do is detect if there are more than 1 parts in the PDU, then I copy the headers and create separate MultimediaMessagePdu of which I save them to the phone's mms content provider separately. Don't forget to copy the headers especially if you are supporting group messaging. Group messaging is another story because the incomging telephone number in the PDU doesn't tell the whole story (MultimediaMessagePdu.mmpdu()). There's more contacts in the header that you extract using the following code.
private HashSet<String> getRecipients(GenericPdu pdu) {
PduHeaders header = pdu.getPduHeaders();
HashMap<Integer, EncodedStringValue[]> addressMap = new HashMap<Integer, EncodedStringValue[]>(ADDRESS_FIELDS.length);
for (int addrType : ADDRESS_FIELDS) {
EncodedStringValue[] array = null;
if (addrType == PduHeaders.FROM) {
EncodedStringValue v = header.getEncodedStringValue(addrType);
if (v != null) {
array = new EncodedStringValue[1];
array[0] = v;
}
} else {
array = header.getEncodedStringValues(addrType);
}
addressMap.put(addrType, array);
}
HashSet<String> recipients = new HashSet<String>();
loadRecipients(PduHeaders.FROM, recipients, addressMap, false);
loadRecipients(PduHeaders.TO, recipients, addressMap, true);
return recipients;
}
Here's the load recipients method:
private void loadRecipients(int addressType, HashSet<String> recipients, HashMap<Integer, EncodedStringValue[]> addressMap, boolean excludeMyNumber) {
EncodedStringValue[] array = addressMap.get(addressType);
if (array == null) {
return;
}
// If the TO recipients is only a single address, then we can skip loadRecipients when
// we're excluding our own number because we know that address is our own.
if (excludeMyNumber && array.length == 1) {
return;
}
String myNumber = excludeMyNumber ? mTelephonyManager.getLine1Number() : null;
for (EncodedStringValue v : array) {
if (v != null) {
String number = v.getString();
if ((myNumber == null || !PhoneNumberUtils.compare(number, myNumber)) && !recipients.contains(number)) {
// Only add numbers which aren't my own number.
recipients.add(number);
}
}
}
}
Here's how to iterate the MultimediaMessagePdu parts.
private void processPduAttachments() throws Exception {
if (mGenericPdu instanceof MultimediaMessagePdu) {
PduBody body = ((MultimediaMessagePdu) mGenericPdu).getBody();
if (body != null) {
int partsNum = body.getPartsNum();
for (int i = 0; i < partsNum; i++) {
try {
PduPart part = body.getPart(i);
if (part == null || part.getData() == null || part.getContentType() == null || part.getName() == null)
continue;
String partType = new String(part.getContentType());
String partName = new String(part.getName());
Log.d("Part Name: " + partName);
Log.d("Part Type: " + partType);
if (ContentType.isTextType(partType)) {
} else if (ContentType.isImageType(partType)) {
} else if (ContentType.isVideoType(partType)) {
} else if (ContentType.isAudioType(partType)) {
}
} catch (Exception e) {
e.printStackTrace();
// Bad part shouldn't ruin the party for the other parts
}
}
}
} else {
Log.d("Not a MultimediaMessagePdu PDU");
}
}
There's many more considerations such as animated GIF support, which is entirely possible :) Some carriers support acknowledge reports, and delivery reports too, you can most likely neglect these wap communications unless a user really really wants mms delivery reports.
Related
I have to download some data as soon as user logs in to the app. I have put
-the network calls to download this data and
-the database transactions to write it to sqlite
in an intent service. The data is huge so, saving it to sqlite is taking time. But I'm wondering why the intent service is blocking the UI. I can't navigate through the application. If I click on any button nothing happens. If I click again, app just freezes and eventually causes ANR.
I was using Service earlier. As service runs on main thread, I'm using IntentService. I had concurrent AsyncTasks for downloading each set of data. Now I got rid of those AsyncTasks and handling all the network calls in onHandleIntent method. I'm testing on Android device of version 5.1.1(lollipop).
public class MastersSyncService extends IntentService {
private static final String TAG = "MastersSyncService";
private static final int CUSTOMERS_PAGE_LENGTH = 5000;
private Context mContext;
private DataSource dataSource;
private boolean isReset;
private ProgressDialog pDialog;
private int numOfCustReceived, pageNumber, customersVersion;
private AlertDialog alertDialog;
public MastersSyncService() {
super(TAG);
}
#Override
protected void onHandleIntent(Intent intent) {
mContext = MastersSyncService.this;
dataSource = new DataSource(mContext);
if (intent.getExtras() != null) {
isReset = intent.getBooleanExtra("isReset", false);
}
customersVersion = dataSource.customers.getVersion();
if (customersVersion == 0) {
dataSource.customers.truncateTable();
}
downloadPackingDataMasters();
downloadCommoditiesMasters(); //makes a network call and writes some simple data to sqlite
downloadPaymentTypesMasters(); //makes a network call and writes some simple data to sqlite
downloadReasonsMasters(); //makes a network call and writes some simple data to sqlite
downloadMeasurementTypesMasters(); //makes a network call and writes some simple data to sqlite
downloadDocketsMasters(); //makes a network call and writes some simple data to sqlite
downloadContractsMasters(); //makes a network call and writes some simple data to sqlite
downloadBookingModesMasters(); //makes a network call and writes some simple data to sqlite
downloadPincodeMasters(); //makes a network call and writes some simple data to sqlite
downloadCustomersMasters(); //this method has a recursive network call with pagination. Takes a lot of time
}
public void downloadCustomersMasters() {
try {
Globals.lastErrMsg = "";
String url = VXUtils.getCustomersUrl(customersVersion, pageNumber);
Utils.logD("Log 1");
InputStream inputStream = HttpRequest.getInputStreamFromUrlRaw(url, mContext);
if (inputStream != null) {
pageNumber++;
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
dataSource.beginTransaction();
String line = null;
while ((line = reader.readLine()) != null) {
if (Utils.isValidString(line)) {
numOfCustReceived++;
Utils.logD("line from InputStream: " + line);
parseCustomer(line);
}
}
dataSource.endTransaction();
} else {
numOfCustReceived = 0;
Utils.logD(TAG + " InputStream is null");
}
} catch (IOException ioe) {
ioe.printStackTrace();
System.out.println("Exception while reading input " + ioe);
} catch (Exception e) {
e.printStackTrace();
if (!Utils.isValidString(Globals.lastErrMsg))
Globals.lastErrMsg = e.toString();
if (Globals.lastErrMsg.equalsIgnoreCase("null"))
Globals.lastErrMsg = getString(R.string.server_not_reachable);
}
if (numOfCustReceived >= CUSTOMERS_PAGE_LENGTH) {
downloadCustomersMasters();
} else {
if (!Utils.isValidString(Globals.lastErrMsg) && pDialog != null && pDialog.isShowing()) {
Utils.updateTimeStamp(mContext, Constants.CUSTOMERS_DATE_PREF);
}
}
}
public void downloadPackingDataMasters() {
try {
Globals.lastErrMsg = "";
int version = dataSource.packingData.getVersion();
String url = VXUtils.getPackingDataUrl(version); //, imei, versionCode + ""
Utils.logD("Log 1");
PackingDataResponse response = (PackingDataResponse) HttpRequest
.getInputStreamFromUrl(url, PackingDataResponse.class,
mContext);
if (response != null) {
Utils.logD("Log 4");
Utils.logD(response.toString());
if (response.isStatus()) {
List<PackingDataModel> data = response.getData();
if (Utils.isValidArrayList((ArrayList<?>) data)) {
if (isReset)
dataSource.packingData.truncateTable();
int val = dataSource.packingData.savePackingData(data, version);
}
} else {
Globals.lastErrMsg = response.getMessage();
}
}
} catch (Exception e) {
if (!Utils.isValidString(Globals.lastErrMsg))
Globals.lastErrMsg = e.toString();
if (Globals.lastErrMsg.equalsIgnoreCase("null"))
Globals.lastErrMsg = getString(R.string.server_not_reachable);
}
if (!Utils.isValidString(Globals.lastErrMsg)) {
Utils.updateTimeStamp(mContext, Constants.PACKING_DATE_PREF);
}
}
private final char DEFAULT_QUOTE_CHARACTER = '"';
private final char DEFAULT_SEPARATOR_CHARACTER = ',';
private void parseCustomer(String line) {
char quotechar = DEFAULT_QUOTE_CHARACTER;
char separator = DEFAULT_SEPARATOR_CHARACTER;
if (line == null) {
return;
}
List<String> tokensOnThisLine = new ArrayList<String>();
StringBuffer sb = new StringBuffer();
boolean inQuotes = false;
for (int i = 0; i < line.length(); i++) {
char c = line.charAt(i);
if (c == quotechar) {
if (inQuotes && line.length() > (i + 1)
&& line.charAt(i + 1) == quotechar) {
sb.append(line.charAt(i + 1));
i++;
} else {
inQuotes = !inQuotes;
if (i > 2 && line.charAt(i - 1) != separator
&& line.length() > (i + 1)
&& line.charAt(i + 1) != separator) {
sb.append(c);
}
}
} else if (c == separator && !inQuotes) {
if (Utils.isValidString(sb.toString())) {
String str = sb.toString().trim();
tokensOnThisLine.add(str);
} else {
tokensOnThisLine.add(sb.toString());
}
sb = new StringBuffer();
} else {
sb.append(c);
}
}
if (Utils.isValidString(sb.toString())) {
String str = sb.toString().trim();
tokensOnThisLine.add(str);
} else {
tokensOnThisLine.add(sb.toString());
}
int customerId = Integer.parseInt(tokensOnThisLine.get(0));
String custName = tokensOnThisLine.get(2);
Utils.logD("CUST: " + customerId + " " + custName);
dataSource.customers.saveCustomerDirect(
customerId,
tokensOnThisLine.get(1),
custName,
tokensOnThisLine.get(3),
tokensOnThisLine.get(4),
tokensOnThisLine.get(5),
tokensOnThisLine.get(6),
tokensOnThisLine.get(7),
Integer.parseInt(tokensOnThisLine.get(8)),
Integer.parseInt(tokensOnThisLine.get(9)),
Integer.parseInt(tokensOnThisLine.get(10)),
Integer.parseInt(tokensOnThisLine.get(11)),
Integer.parseInt(tokensOnThisLine.get(12)));
}
Downloading data is taking several minutes. I can't make the user wait for such long time. Please suggest how to make the UI interactive while the data is being downloaded in the background. Or please suggest ways to save huge number of records to sqlite quickly
Sending Email in Android using JavaMail API without using the default/built-in app
Using this tutorial, I've loaded up the code into a sample android project and imported the libraries. Changed the parameters in the lines:
send.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// TODO Auto-generated method stub
try {
GMailSender sender = new GMailSender("sender#gmail.com", "sender_password");
sender.sendMail("This is Subject", "This is Body", "sender#gmail.com", "recipient#gmail.com");
} catch (Exception e) {
Log.e("SendMail", e.getMessage(), e);
}
}
});
Wanted to test it out and in this code, the try block of code gets executed successfully when I press the button, but I don't receive the mail, nor do I get any errors. Since there's no readme or any guidelines as to how to use this code, I have no choice but to ask what I'm doing wrong.
Just to clear the confusion, I've put the senders email instead of sender#gmail.com, same goes for password and recipient#gmail.com.
I've also added the INTERNET permission to the manifest.
If you want to use mailgun instead you can do it like this:
public void sendEmailInBackground(final String subject, final String body, final String... toAddress) {
AsyncTask task = new AsyncTask() {
#Override
protected Object doInBackground(Object[] objects) {
String hostname = "smpt.mailgun.org";
int port = 25;
String login = "login";
String password = "password";
String from = "from#example.com";
AuthenticatingSMTPClient client = null;
try {
client = new AuthenticatingSMTPClient();
// optionally set a timeout to have a faster feedback on errors
client.setDefaultTimeout(10 * 1000);
// you connect to the SMTP server
client.connect(hostname, port);
// you say helo and you specify the host you are connecting from, could be anything
client.ehlo("localhost");
// if your host accepts STARTTLS, we're good everything will be encrypted, otherwise we're done here
if (client.execTLS()) {
client.auth(AuthenticatingSMTPClient.AUTH_METHOD.LOGIN, login, password);
checkReply(client);
client.setSender(from);
checkReply(client);
String address = "";
if (toAddress != null) {
for (String to : toAddress) {
if(to != null && to.length() > 0) {
client.addRecipient(to);
if (address.length() == 0) {
address += ",";
}
address += to;
}
}
}
if(address.length() == 0){
logger.warning("No address specified for mail message");
return null;
}
checkReply(client);
Writer writer = client.sendMessageData();
if (writer != null) {
SimpleSMTPHeader header = new SimpleSMTPHeader(from, address, subject);
writer.write(header.toString());
writer.write(body);
writer.close();
if (!client.completePendingCommand()) {// failure
throw new IOException("Failure to send the email " + client.getReply() + client.getReplyString());
}
} else {
throw new IOException("Failure to send the email " + client.getReply() + client.getReplyString());
}
} else {
throw new IOException("STARTTLS was not accepted " + client.getReply() + client.getReplyString());
}
} catch (IOException | NoSuchAlgorithmException | InvalidKeyException | InvalidKeySpecException e) {
logger.severe("Error sending email",e);
} finally {
if (client != null) {
try {
client.logout();
client.disconnect();
} catch (Exception e) {
logger.warning("Error closing email client: " + e.getMessage());
}
}
}
return null;
}
};
task.execute();
}
private static void checkReply(SMTPClient sc) throws IOException {
if (SMTPReply.isNegativeTransient(sc.getReplyCode())) {
throw new IOException("Transient SMTP error " + sc.getReplyString());
} else if (SMTPReply.isNegativePermanent(sc.getReplyCode())) {
throw new IOException("Permanent SMTP error " + sc.getReplyString());
}
}
So this video Android 4.4 SMS APIs from #DevBytes explains the recent changes to the SMS APIs in KitKat. They also provide a link with a sample project. http://goo.gl/uQ3Nih
They suggest that you handle the receive of an MMS in a service. Which all looks fine, except they neglect to mention the most undocumented piece. How to actually handle an incoming MMS.
Here is the sample from the project
https://gist.github.com/lawloretienne/8970938
I have tried to "handle the MMS"
https://gist.github.com/lawloretienne/8971050
I can get the extras from the intent but the only meaningful thing that I can extract is the number from which the MMS was sent.
Can anyone point me in the right direction about how to go about this?
I noticed that a WAP_PUSH_MESSAGE contains a few things, a FROM, SUBJECT, and CONTENT_LOCATION.
The content location appears to be the url where the content of the MMS is contained. How can I access this?
Here is an example of that URL
https://atl1mmsget.msg.eng.t-mobile.com/mms/wapenc?location=XXXXXXXXXXX_14zbwk&rid=027
Where the X is a digit in the phone number of the device I am testing on.
It looks like the MMSC (Multimedia Messaging Service Center) for T-Mobile in the U.S. is http://mms.msg.eng.t-mobile.com/mms/wapenc
According to this list : http://www.activexperts.com/xmstoolkit/mmsclist/
There's zero documentation so here's some info to help.
1) com.google.android.mms.pdu from source. You need the Pdu utils.
2) You get the notification push from byte array extra of the incoming mms broadcast (intent.getByteArrayExtra("data")).
3) Parse the notification push into a GenericPdu (new PduParser(rawPdu).parse()).
4) You'll need TransactionSettings to communicate with the carrier's wap server. I get the transaction settings after #5 below. I use:
TransactionSettings transactionSettings = new TransactionSettings(mContext, mConnMgr.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_MMS).getExtraInfo());
5) Force network comm over wifi. I use the following.
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;
}
}
6) You then need to ensure a route to the host.
private 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);
}
}
}
Here's the lookupHost method:
private 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;
}
I also like to use a reflection based method for improved ensureRouteToHost functionality:
private 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);
}
}
7) After ensuring a route to the host you can then need HttpUtls from source. I've heavily modified my implementation using OkHttp for improved communications.
byte[] rawPdu = HttpUtils.httpConnection(mContext, mContentLocation, null, HttpUtils.HTTP_GET_METHOD, mTransactionSettings.isProxySet(), mTransactionSettings.getProxyAddress(), mTransactionSettings.getProxyPort());
8) From the resulting byte array use the PduParser to parge the GenericPdu. Then you can extract the body and cast to a MultimediaMessagePdu.
9) Then you can iterate the parts of the PDU.
There are countless things to consider with MMS. One thing that comes to mind is how annoying Slideshows are, so what I do is detect if there are more than 1 parts in the PDU, then I copy the headers and create separate MultimediaMessagePdu of which I save them to the phone's mms content provider separately. Don't forget to copy the headers especially if you are supporting group messaging. Group messaging is another story because the incomging telephone number in the PDU doesn't tell the whole story (MultimediaMessagePdu.mmpdu()). There's more contacts in the header that you extract using the following code.
private HashSet<String> getRecipients(GenericPdu pdu) {
PduHeaders header = pdu.getPduHeaders();
HashMap<Integer, EncodedStringValue[]> addressMap = new HashMap<Integer, EncodedStringValue[]>(ADDRESS_FIELDS.length);
for (int addrType : ADDRESS_FIELDS) {
EncodedStringValue[] array = null;
if (addrType == PduHeaders.FROM) {
EncodedStringValue v = header.getEncodedStringValue(addrType);
if (v != null) {
array = new EncodedStringValue[1];
array[0] = v;
}
} else {
array = header.getEncodedStringValues(addrType);
}
addressMap.put(addrType, array);
}
HashSet<String> recipients = new HashSet<String>();
loadRecipients(PduHeaders.FROM, recipients, addressMap, false);
loadRecipients(PduHeaders.TO, recipients, addressMap, true);
return recipients;
}
Here's the load recipients method:
private void loadRecipients(int addressType, HashSet<String> recipients, HashMap<Integer, EncodedStringValue[]> addressMap, boolean excludeMyNumber) {
EncodedStringValue[] array = addressMap.get(addressType);
if (array == null) {
return;
}
// If the TO recipients is only a single address, then we can skip loadRecipients when
// we're excluding our own number because we know that address is our own.
if (excludeMyNumber && array.length == 1) {
return;
}
String myNumber = excludeMyNumber ? mTelephonyManager.getLine1Number() : null;
for (EncodedStringValue v : array) {
if (v != null) {
String number = v.getString();
if ((myNumber == null || !PhoneNumberUtils.compare(number, myNumber)) && !recipients.contains(number)) {
// Only add numbers which aren't my own number.
recipients.add(number);
}
}
}
}
Here's how to iterate the MultimediaMessagePdu parts.
private void processPduAttachments() throws Exception {
if (mGenericPdu instanceof MultimediaMessagePdu) {
PduBody body = ((MultimediaMessagePdu) mGenericPdu).getBody();
if (body != null) {
int partsNum = body.getPartsNum();
for (int i = 0; i < partsNum; i++) {
try {
PduPart part = body.getPart(i);
if (part == null || part.getData() == null || part.getContentType() == null || part.getName() == null)
continue;
String partType = new String(part.getContentType());
String partName = new String(part.getName());
Log.d("Part Name: " + partName);
Log.d("Part Type: " + partType);
if (ContentType.isTextType(partType)) {
} else if (ContentType.isImageType(partType)) {
} else if (ContentType.isVideoType(partType)) {
} else if (ContentType.isAudioType(partType)) {
}
} catch (Exception e) {
e.printStackTrace();
// Bad part shouldn't ruin the party for the other parts
}
}
}
} else {
Log.d("Not a MultimediaMessagePdu PDU");
}
}
There's many more considerations such as animated GIF support, which is entirely possible :) Some carriers support acknowledge reports, and delivery reports too, you can most likely neglect these wap communications unless a user really really wants mms delivery reports.
Following the last section in the GCM: Getting Started guide, there's some book-keeping to be done after receiving the results.
Quoting from the guide:
It's now necessary to parse the result and take the proper action in the following cases:
If the message was created but the result returned a canonical registration ID, it's necessary to replace the current registration
ID with the canonical one.
If the returned error is NotRegistered, it's necessary to remove that registration ID, because the application was uninstalled from
the device.
Here's a code snippet that handles these 2 conditions:
if (result.getMessageId() != null) {
String canonicalRegId = result.getCanonicalRegistrationId();
if (canonicalRegId != null) {
// same device has more than on registration ID: update database
}
} else {
String error = result.getErrorCodeName();
if (error.equals(Constants.ERROR_NOT_REGISTERED)) {
// application has been removed from device - unregister database
}
}
The guide above refers to a single result, and not to the multicast case.
I'm not sure how to handle the multicast case:
ArrayList<String> devices = new ArrayList<String>();
for (String d : relevantDevices) {
devices.add(d);
}
Sender sender = new Sender(myApiKey);
Message message = new Message.Builder().addData("hello", "world").build();
try {
MulticastResult result = sender.send(message, devices, 5);
for (Result r : result.getResults()) {
if (r.getMessageId() != null) {
String canonicalRegId = r.getCanonicalRegistrationId();
if (canonicalRegId != null) {
// same device has more than on registration ID: update database
// BUT WHICH DEVICE IS IT?
}
} else {
String error = r.getErrorCodeName();
if (error.equals(Constants.ERROR_NOT_REGISTERED)) {
// application has been removed from device - unregister database
// BUT WHICH DEVICE IS IT?
}
}
}
} catch (IOException ex) {
Log.err(TAG, "sending message failed", ex);
}
I submit a list of devices, and receive back a list of results.
The Result object doesn't contain the registration id, but only a canonical id if the first is obsolete.
It is undocumented if the two lists are co-related (ie. preserves order and size).
How can I be sure which result refer to which device?
-- UPDATE
I've pasted a snippet of the solution in a separate answer below
The results are in the order of your registration_id array that you sent to GCM server. e.g. if your registration_ids are:
[id1, id4, id7, id8]
Then the results array you get will have same order for id1, id4, id7, and id8.
You just need to parse each result accordingly, e.g. if the 2nd result has 'message_id' and 'registration_id' of 'id9', you know 'id4' is now obsolete and should be replaced by id9.
For the readers convenience, here is a snippet that handles response for multiple devices
public void sendMessageToMultipleDevices(String key, String value, ArrayList<String> devices) {
Sender sender = new Sender(myApiKey);
Message message = new Message.Builder().addData(key, value).build();
try {
MulticastResult result = sender.send(message, devices, 5);
MTLog.info(TAG, "result " + result.toString());
for (int i = 0; i < result.getTotal(); i++) {
Result r = result.getResults().get(i);
if (r.getMessageId() != null) {
String canonicalRegId = r.getCanonicalRegistrationId();
if (canonicalRegId != null) {
// devices.get(i) has more than on registration ID: update database
}
} else {
String error = r.getErrorCodeName();
if (error.equals(Constants.ERROR_NOT_REGISTERED)) {
// application has been removed from devices.get(i) - unregister database
}
}
}
} catch (IOException ex) {
MTLog.err(TAG, "sending message failed", ex);
}
}
This solution is done by google developer sample GCM Demo application
note the asyncSend for multicasting handle
List<GcmUsers> devices=SearchRegisterdDevicesByCourseCommand.execute(instructorId, courseId);
String status;
if ( devices.equals(Collections.<GcmUsers>emptyList())) {
status = "Message ignored as there is no device registered!";
} else {
// NOTE: check below is for demonstration purposes; a real application
// could always send a multicast, even for just one recipient
if (devices.size() == 1) {
// send a single message using plain post
GcmUsers gcmUsers = devices.get(0);
Message message = new Message.Builder().build();
Result result = sender.send(message, gcmUsers.getGcmRegid(), 5);
status = "Sent message to one device: " + result;
} else {
// send a multicast message using JSON
// must split in chunks of 1000 devices (GCM limit)
int total = devices.size();
List<String> partialDevices = new ArrayList<String>(total);
int counter = 0;
int tasks = 0;
for (GcmUsers device : devices) {
counter++;
partialDevices.add(device.getGcmRegid());
int partialSize = partialDevices.size();
if (partialSize == MULTICAST_SIZE || counter == total) {
asyncSend(partialDevices);
partialDevices.clear();
tasks++;
}
}
status = "Asynchronously sending " + tasks + " multicast messages to " +
total + " devices";
}
}
req.setAttribute(HomeServlet.ATTRIBUTE_STATUS, status.toString());
private void asyncSend(List<String> partialDevices) {
// make a copy
final List<String> devices = new ArrayList<String>(partialDevices);
threadPool.execute(new Runnable() {
public void run() {
Message message = new Message.Builder().build();
MulticastResult multicastResult;
try {
multicastResult = sender.send(message, devices, 5);
} catch (IOException e) {
logger.log(Level.SEVERE, "Error posting messages", e);
return;
}
List<Result> results = multicastResult.getResults();
// analyze the results
for (int i = 0; i < devices.size(); i++) {
String regId = devices.get(i);
Result result = results.get(i);
String messageId = result.getMessageId();
if (messageId != null) {
logger.fine("Succesfully sent message to device: " + regId +
"; messageId = " + messageId);
String canonicalRegId = result.getCanonicalRegistrationId();
if (canonicalRegId != null) {
// same device has more than on registration id: update it
logger.info("canonicalRegId " + canonicalRegId);
Datastore.updateRegistration(regId, canonicalRegId);
}
} else {
String error = result.getErrorCodeName();
if (error.equals(Constants.ERROR_NOT_REGISTERED)) {
// application has been removed from device - unregister it
logger.info("Unregistered device: " + regId);
Datastore.unregister(regId);
} else {
logger.severe("Error sending message to " + regId + ": " + error);
}
}
}
}});
}
I can access Hotmail with Javamail.jar on Android. I want to receive a mail's attachment with Hotmail, this code is working for Gmail but with Hotmail is not working. Why it is not filling the disposition, so "dosyaIsmiEkDurumu" is not filling and is falling into the catch because of that.
Is there other code samples for access attachments on Hotmail, or why am I doing wrong with this code?
public void EkDosyaIsim(Part part) throws IOException,
MessagingException {
if (part.isMimeType("multipart/*")) {
Multipart mp = (Multipart) part.getContent();
for (int i = 0; i < mp.getCount(); i++) {
System.out.println("64 : [" + mp.getCount() + "] δΈͺ/n");
BodyPart mpart = mp.getBodyPart(i);
String disposition = mpart.getDisposition();
if ((disposition != null)
&& ((disposition.equals("ATTACHMENT")) || (disposition
.equals("INLINE")))) {
dosyaIsmiEkDurumu = mpart.getFileName();
if(dosyaIsmiEkDurumu != null)
{
dosyaIsmiDizi[k] = dosyaIsmiEkDurumu;
k++;
}
if (dosyaIsmiEkDurumu.toLowerCase().indexOf("gb2312") != -1) {
dosyaIsmiEkDurumu = MimeUtility.decodeText(dosyaIsmiEkDurumu);
}
} else if (mpart.isMimeType("multipart/*")) {
EkDosyaIsim(mpart);
} else
{
if ((dosyaIsmiEkDurumu != null)
&& (dosyaIsmiEkDurumu.toLowerCase().indexOf("GB2312") != -1)) {
dosyaIsmiEkDurumu = MimeUtility.decodeText(dosyaIsmiEkDurumu);
}
}
}
} else if (part.isMimeType("message/rfc822"))
{
EkDosyaIsim((Part) part.getContent());
}
}
The disposition is "advice", it's not guaranteed to be there even in messages that have attachments. See the JavaMail FAQ for more information on handling messages with attachments.