/*
* Copyright (C) 2015 QK Labs
*
* 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.moez.QKSMS.mmssms;
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.Uri;
import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.provider.Telephony;
import android.telephony.SmsMessage;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.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.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
*/
public class Utils {
private static final String TAG = "Utils";
private static final boolean LOCAL_LOGV = false;
/**
* 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]*$";
/**
* 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();
}
/**
* Enable mobile connection for a specific address
*
* @param address the address to enable
* @return true for success, else false
*/
public static void forceMobileConnectionForAddress(ConnectivityManager mConnMgr, String address) {
//find the host name to route
String hostName = extractAddressFromUrl(address);
if (TextUtils.isEmpty(hostName)) hostName = address;
//create a route for the specified address
int hostAddress = lookupHost(hostName);
mConnMgr.requestRouteToHost(ConnectivityManager.TYPE_MOBILE_MMS, hostAddress);
}
/**
* Function for getting the weird auth token used to send or receive google voice messages
*
* @param account is the string of the account name to get the auth token for
* @param context is the context of the activity or service
* @return a string of the auth token to be saved for later
* @throws java.io.IOException
* @throws android.accounts.OperationCanceledException
* @throws android.accounts.AuthenticatorException
*/
public static String getAuthToken(String account, Context context) throws IOException, OperationCanceledException, AuthenticatorException {
Bundle bundle = AccountManager.get(context).getAuthToken(new Account(account, "com.google"), "grandcentral", true, null, null).getResult();
return bundle.getString(AccountManager.KEY_AUTHTOKEN);
}
/**
* This method extracts from address the hostname
*
* @param url eg. http://some.where.com:8080/sync
* @return some.where.com
*/
public static String extractAddressFromUrl(String url) {
String urlToProcess = null;
//find protocol
int protocolEndIndex = url.indexOf("://");
if (protocolEndIndex > 0) {
urlToProcess = url.substring(protocolEndIndex + 3);
} else {
urlToProcess = url;
}
// If we have port number in the address we strip everything
// after the port number
int pos = urlToProcess.indexOf(':');
if (pos >= 0) {
urlToProcess = urlToProcess.substring(0, pos);
}
// If we have resource location in the address then we strip
// everything after the '/'
pos = urlToProcess.indexOf('/');
if (pos >= 0) {
urlToProcess = urlToProcess.substring(0, pos);
}
// If we have ? in the address then we strip
// everything after the '?'
pos = urlToProcess.indexOf('?');
if (pos >= 0) {
urlToProcess = urlToProcess.substring(0, pos);
}
return urlToProcess;
}
/**
* Transform host name in int value used by ConnectivityManager.requestRouteToHost
* method
*
* @param hostname
* @return -1 if the host doesn't exists, elsewhere its translation
* to an integer
*/
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;
}
/**
* 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);
connMgr.startUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE_HIPRI, "enableMMS");
if (LOCAL_LOGV) Log.v(TAG, "ensuring route to host");
int inetAddr;
if (proxy != null && !proxy.equals("")) {
String proxyAddr = proxy;
inetAddr = lookupHost(proxyAddr);
if (inetAddr == -1) {
throw new IOException("Cannot establish route for " + url + ": Unknown host");
} else {
if (!connMgr.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 (!connMgr.requestRouteToHost(
ConnectivityManager.TYPE_MOBILE_MMS, inetAddr)) {
throw new IOException("Cannot establish route to " + inetAddr + " for " + url);
}
}
}
}
/**
* 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);
// Make sure to return FALSE instead of null, or else you will get a NPE when you try
// to access the boolean value of this.
return Boolean.FALSE;
}
}
/**
* 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) {
if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean("pref_key_auto_data", true)) {
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);
}
}
}
/**
* 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<>();
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) {
long threadId = getThreadId(context, recipients);
Random random = new Random();
return threadId == -1 ? random.nextLong() : threadId;
}
/**
* Gets the current thread_id or -1 if none found
* @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, -1 if none found
*/
public static long getThreadId(Context context, String recipient) {
Set<String> recipients = new HashSet<>();
recipients.add(recipient);
return getThreadId(context, recipients);
}
/**
* Gets the current thread_id or -1 if none found
* @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, -1 if none found
*/
public static long getThreadId(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()) {
return cursor.getLong(0);
} else {
}
} finally {
cursor.close();
}
}
return -1;
}
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);
sendSettings.setAccount(null);
sendSettings.setRnrSe(null);
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) {
return !hasKitKat() || context.getPackageName().equals(Telephony.Sms.getDefaultSmsPackage(context));
}
}