package com.klinker.android.send_message;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.preference.PreferenceManager;
import android.provider.Telephony;
import android.telephony.SmsManager;
import android.telephony.SmsMessage;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import com.android.mms.service_alt.MmsNetworkManager;
import com.android.mms.service_alt.exception.MmsNetworkException;
import com.klinker.android.logger.Log;
import com.google.android.mms.util_alt.SqliteWrapper;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Common methods to be used for data connectivity/sending messages ect
*
* @author Jake Klinker
*/
public class Utils {
/**
* characters to compare against when checking for 160 character sending compatibility
*/
public static final String GSM_CHARACTERS_REGEX = "^[A-Za-z0-9 \\r\\n@Ł$ĽčéůěňÇŘřĹĺ\u0394_\u03A6\u0393\u039B\u03A9\u03A0\u03A8\u03A3\u0398\u039EĆćßÉ!\"#$%&'()*+,\\-./:;<=>?ĄÄÖŃܧżäöńüŕ^{}\\\\\\[~\\]|\u20AC]*$";
private static final String TAG = "Utils";
public static final int DEFAULT_SUBSCRIPTION_ID = 1;
/**
* Gets the current users phone number
*
* @param context is the context of the activity or service
* @return a string of the phone number on the device
*/
public static String getMyPhoneNumber(Context context) {
TelephonyManager mTelephonyMgr;
mTelephonyMgr = (TelephonyManager)
context.getSystemService(Context.TELEPHONY_SERVICE);
return mTelephonyMgr.getLine1Number();
}
public interface Task<T> {
T run() throws IOException;
}
public static <T> T ensureRouteToMmsNetwork(Context context, String url, String proxy, Task<T> task) throws IOException {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
return ensureRouteToMmsNetworkMarshmallow(context, task);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
return ensureRouteToMmsNetworkLollipop(context, task);
} else {
ensureRouteToHost(context, url, proxy);
return task.run();
}
}
private static <T> T ensureRouteToMmsNetworkMarshmallow(Context context, Task<T> task) throws IOException {
final MmsNetworkManager networkManager = new MmsNetworkManager(context.getApplicationContext(), Utils.getDefaultSubscriptionId());
final ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
Network network = null;
try {
network = networkManager.acquireNetwork();
connectivityManager.bindProcessToNetwork(network);
return task.run();
} catch (MmsNetworkException e) {
throw new IOException(e);
} finally {
if (network != null) {
connectivityManager.bindProcessToNetwork(null);
}
networkManager.releaseNetwork();
}
}
private static <T> T ensureRouteToMmsNetworkLollipop(Context context, Task<T> task) throws IOException {
final MmsNetworkManager networkManager = new MmsNetworkManager(context.getApplicationContext(), Utils.getDefaultSubscriptionId());
Network network = null;
try {
network = networkManager.acquireNetwork();
ConnectivityManager.setProcessDefaultNetwork(network);
return task.run();
} catch (MmsNetworkException e) {
throw new IOException(e);
} finally {
if (network != null) {
ConnectivityManager.setProcessDefaultNetwork(null);
}
networkManager.releaseNetwork();
}
}
/**
* Ensures that the host MMSC is reachable
*
* @param context is the context of the activity or service
* @param url is the MMSC to check
* @param proxy is the proxy of the APN to check
* @throws java.io.IOException when route cannot be established
*/
public static void ensureRouteToHost(Context context, String url, String proxy) throws IOException {
ConnectivityManager connMgr =
(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
InetAddress inetAddr;
if (proxy != null && proxy.trim().length() != 0) {
try {
inetAddr = InetAddress.getByName(proxy);
} catch (UnknownHostException e) {
throw new IOException("Cannot establish route for " + url +
": Unknown proxy " + proxy);
}
try {
Method requestRoute = ConnectivityManager.class.getMethod("requestRouteToHostAddress", Integer.TYPE, InetAddress.class);
if (!((Boolean) requestRoute.invoke(connMgr, ConnectivityManager.TYPE_MOBILE_MMS, inetAddr))) {
throw new IOException("Cannot establish route to proxy " + inetAddr);
}
} catch (Exception e) {
Log.e(TAG, "Cannot establishh route to proxy " + inetAddr, e);
}
} 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");
}
try {
Method requestRoute = ConnectivityManager.class.getMethod("requestRouteToHostAddress", Integer.TYPE, InetAddress.class);
if (!((Boolean) requestRoute.invoke(connMgr, ConnectivityManager.TYPE_MOBILE_MMS, inetAddr))) {
throw new IOException("Cannot establish route to proxy " + inetAddr);
}
} catch (Exception e) {
Log.e(TAG, "Cannot establishh route to proxy " + inetAddr + " for " + url, e);
}
}
}
/**
* Checks whether or not mobile data is enabled and returns the result
*
* @param context is the context of the activity or service
* @return true if data is enabled or false if disabled
*/
public static Boolean isMobileDataEnabled(Context context) {
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
try {
Class<?> c = Class.forName(cm.getClass().getName());
Method m = c.getDeclaredMethod("getMobileDataEnabled");
m.setAccessible(true);
return (Boolean) m.invoke(cm);
} catch (Exception e) {
Log.e(TAG, "exception thrown", e);
return null;
}
}
/**
* Checks mobile data enabled based on telephonymanager
*
* @param telephonyManager the telephony manager
*/
public static boolean isDataEnabled(TelephonyManager telephonyManager) {
try {
Class<?> c = telephonyManager.getClass();
Method m = c.getMethod("getDataEnabled");
return (boolean) m.invoke(telephonyManager);
} catch (Exception e) {
Log.e(TAG, "exception thrown", e);
return true;
}
}
/**
* Checks mobile data enabled based on telephonymanager and sim card
*
* @param telephonyManager the telephony manager
* @param subId the sim card id
*/
public static boolean isDataEnabled(TelephonyManager telephonyManager, int subId) {
try {
Class<?> c = telephonyManager.getClass();
Method m = c.getMethod("getDataEnabled", int.class);
return (boolean) m.invoke(telephonyManager, subId);
} catch (Exception e) {
Log.e(TAG, "exception thrown", e);
return isDataEnabled(telephonyManager);
}
}
/**
* Toggles mobile data
*
* @param context is the context of the activity or service
* @param enabled is whether to enable or disable data
*/
public static void setMobileDataEnabled(Context context, boolean enabled) {
String methodName;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
try {
ConnectivityManager conman = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
Class conmanClass = Class.forName(conman.getClass().getName());
Field iConnectivityManagerField = conmanClass.getDeclaredField("mService");
iConnectivityManagerField.setAccessible(true);
Object iConnectivityManager = iConnectivityManagerField.get(conman);
Class iConnectivityManagerClass = Class.forName(iConnectivityManager.getClass().getName());
Method setMobileDataEnabledMethod = iConnectivityManagerClass.getDeclaredMethod("setMobileDataEnabled", Boolean.TYPE);
setMobileDataEnabledMethod.setAccessible(true);
setMobileDataEnabledMethod.invoke(iConnectivityManager, enabled);
} catch (Exception e) {
Log.e(TAG, "exception thrown", e);
}
} else {
// TODO find a better way to do this on lollipop!
// This will actually not work due to no permission for android.permission.MODIFY_PHONE_STATE, which
// is a system level permission and cannot be accessed for third party apps.
try {
TelephonyManager tm = (TelephonyManager) context
.getSystemService(Context.TELEPHONY_SERVICE);
Class c = Class.forName(tm.getClass().getName());
Method m = c.getDeclaredMethod("getITelephony");
m.setAccessible(true);
Object telephonyService = m.invoke(tm);
c = Class.forName(telephonyService.getClass().getName());
m = c.getDeclaredMethod("setDataEnabled", Boolean.TYPE);
m.setAccessible(true);
m.invoke(telephonyService, enabled);
} catch (Exception e) {
Log.e(TAG, "error enabling data on lollipop", e);
}
}
}
/**
* Gets the number of pages in the SMS based on settings and the length of string
*
* @param settings is the settings object to check against
* @param text is the text from the message object to be sent
* @return the number of pages required to hold message
*/
public static int getNumPages(Settings settings, String text) {
if (settings.getStripUnicode()) {
text = StripAccents.stripAccents(text);
}
int[] data = SmsMessage.calculateLength(text, false);
return data[0];
}
/**
* Gets the current thread_id or creates a new one for the given recipient
* @param context is the context of the activity or service
* @param recipient is the person message is being sent to
* @return the thread_id to use in the database
*/
public static long getOrCreateThreadId(Context context, String recipient) {
Set<String> recipients = new HashSet<String>();
recipients.add(recipient);
return getOrCreateThreadId(context, recipients);
}
/**
* Gets the current thread_id or creates a new one for the given recipient
* @param context is the context of the activity or service
* @param recipients is the set of people message is being sent to
* @return the thread_id to use in the database
*/
public static long getOrCreateThreadId(
Context context, Set<String> recipients) {
Uri.Builder uriBuilder = Uri.parse("content://mms-sms/threadID").buildUpon();
for (String recipient : recipients) {
if (isEmailAddress(recipient)) {
recipient = extractAddrSpec(recipient);
}
uriBuilder.appendQueryParameter("recipient", recipient);
}
Uri uri = uriBuilder.build();
Cursor cursor = SqliteWrapper.query(context, context.getContentResolver(),
uri, new String[]{"_id"}, null, null, null);
if (cursor != null) {
try {
if (cursor.moveToFirst()) {
long id = cursor.getLong(0);
cursor.close();
return id;
} else {
}
} finally {
cursor.close();
}
}
Random random = new Random();
return random.nextLong();
//throw new IllegalArgumentException("Unable to find or allocate a thread ID.");
}
public static boolean doesThreadIdExist(Context context, long threadId) {
Uri uri = Uri.parse("content://mms-sms/conversations/" + threadId + "/");
Cursor cursor = context.getContentResolver().query(uri, new String[] {"_id"}, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
cursor.close();
return true;
} else {
return false;
}
}
private static boolean isEmailAddress(String address) {
if (TextUtils.isEmpty(address)) {
return false;
}
String s = extractAddrSpec(address);
Matcher match = EMAIL_ADDRESS_PATTERN.matcher(s);
return match.matches();
}
private static final Pattern EMAIL_ADDRESS_PATTERN
= Pattern.compile(
"[a-zA-Z0-9\\+\\.\\_\\%\\-]{1,256}" +
"\\@" +
"[a-zA-Z0-9][a-zA-Z0-9\\-]{0,64}" +
"(" +
"\\." +
"[a-zA-Z0-9][a-zA-Z0-9\\-]{0,25}" +
")+"
);
private static final Pattern NAME_ADDR_EMAIL_PATTERN =
Pattern.compile("\\s*(\"[^\"]*\"|[^<>\"]+)\\s*<([^<>]+)>\\s*");
private static String extractAddrSpec(String address) {
Matcher match = NAME_ADDR_EMAIL_PATTERN.matcher(address);
if (match.matches()) {
return match.group(2);
}
return address;
}
/**
* Gets the default settings from a shared preferences file associated with your app
* @param context is the context of the activity or service
* @return the settings object to send with
*/
public static Settings getDefaultSendSettings(Context context) {
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
Settings sendSettings = new Settings();
sendSettings.setMmsc(sharedPrefs.getString("mmsc_url", ""));
sendSettings.setProxy(sharedPrefs.getString("mms_proxy", ""));
sendSettings.setPort(sharedPrefs.getString("mms_port", ""));
sendSettings.setAgent(sharedPrefs.getString("mms_agent", ""));
sendSettings.setUserProfileUrl(sharedPrefs.getString("mms_user_agent_profile_url", ""));
sendSettings.setUaProfTagName(sharedPrefs.getString("mms_user_agent_tag_name", ""));
sendSettings.setGroup(sharedPrefs.getBoolean("group_message", true));
sendSettings.setDeliveryReports(sharedPrefs.getBoolean("delivery_reports", false));
sendSettings.setSplit(sharedPrefs.getBoolean("split_sms", false));
sendSettings.setSplitCounter(sharedPrefs.getBoolean("split_counter", false));
sendSettings.setStripUnicode(sharedPrefs.getBoolean("strip_unicode", false));
sendSettings.setSignature(sharedPrefs.getString("signature", ""));
sendSettings.setSendLongAsMms(true);
sendSettings.setSendLongAsMmsAfter(3);
return sendSettings;
}
/**
* Determines whether or not the user has Android 4.4 KitKat
* @return true if version code on device is >= kitkat
*/
public static boolean hasKitKat() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
}
/**
* Determines whether or not the app is the default SMS app on a device
* @param context
* @return true if app is default
*/
public static boolean isDefaultSmsApp(Context context) {
if (hasKitKat()) {
return context.getPackageName().equals(Telephony.Sms.getDefaultSmsPackage(context));
}
return true;
}
/**
* Determins whether or not the app has enabled MMS over WiFi
* @param context
* @return true if enabled
*/
public static boolean isMmsOverWifiEnabled(Context context) {
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean("mms_over_wifi", false);
}
public static int getDefaultSubscriptionId() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
return SmsManager.getDefaultSmsSubscriptionId();
} else {
return DEFAULT_SUBSCRIPTION_ID;
}
}
}