package net.hockeyapp.android.utils;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Build;
import android.os.Bundle;
import android.text.TextUtils;
import net.hockeyapp.android.R;
import java.io.UnsupportedEncodingException;
import java.lang.ref.WeakReference;
import java.lang.reflect.Method;
import java.net.URLEncoder;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Util {
public static final String PREFS_FEEDBACK_TOKEN = "net.hockeyapp.android.prefs_feedback_token";
public static final String PREFS_KEY_FEEDBACK_TOKEN = "net.hockeyapp.android.prefs_key_feedback_token";
public static final String PREFS_NAME_EMAIL_SUBJECT = "net.hockeyapp.android.prefs_name_email";
public static final String PREFS_KEY_NAME_EMAIL_SUBJECT = "net.hockeyapp.android.prefs_key_name_email";
public static final String APP_IDENTIFIER_PATTERN = "[0-9a-f]+";
public static final int APP_IDENTIFIER_LENGTH = 32;
public static final String APP_IDENTIFIER_KEY = "net.hockeyapp.android.appIdentifier";
public static final String LOG_IDENTIFIER = "HockeyApp";
private static final String APP_SECRET_KEY = "net.hockeyapp.android.appSecret";
private static final Pattern appIdentifierPattern = Pattern.compile(APP_IDENTIFIER_PATTERN, Pattern.CASE_INSENSITIVE);
private static final String SDK_VERSION_KEY = "net.hockeyapp.android.sdkVersion";
private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
private static final ThreadLocal<DateFormat> DATE_FORMAT_THREAD_LOCAL = new ThreadLocal<DateFormat>() {
@Override
protected DateFormat initialValue() {
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US);
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
return dateFormat;
}
};
/**
* Returns the given param URL-encoded.
*
* @param param a string to encode
* @return the encoded param
*/
public static String encodeParam(String param) {
try {
return URLEncoder.encode(param, "UTF-8");
} catch (UnsupportedEncodingException e) {
// UTF-8 should be available, so just in case
e.printStackTrace();
return "";
}
}
/**
* Returns true if value is a valid email.
*
* @param value a string
* @return true if value is a valid email
*/
public final static boolean isValidEmail(String value) {
return !TextUtils.isEmpty(value) && android.util.Patterns.EMAIL_ADDRESS.matcher(value).matches();
}
/**
* Returns true if the Fragment API is supported (should be on Android 3.0+).
*
* @return true if the Fragment API is supported
*/
@SuppressLint("NewApi")
public static Boolean fragmentsSupported() {
try {
return (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) && classExists("android.app.Fragment");
} catch (NoClassDefFoundError e) {
return false;
}
}
/**
* Returns true if the app runs on large or very large screens (i.e. tablets).
*
* @param weakActivity the context to use
* @return true if the app runs on large or very large screens
*/
public static Boolean runsOnTablet(WeakReference<Activity> weakActivity) {
if (weakActivity != null) {
Activity activity = weakActivity.get();
if (activity != null) {
Configuration configuration = activity.getResources().getConfiguration();
return (((configuration.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) == Configuration.SCREENLAYOUT_SIZE_LARGE) ||
((configuration.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) == Configuration.SCREENLAYOUT_SIZE_XLARGE));
}
}
return false;
}
/**
* Sanitizes an app identifier or throws an exception if it can't be sanitized.
*
* @param appIdentifier the app identifier to sanitize
* @return the sanitized app identifier
* @throws java.lang.IllegalArgumentException if the app identifier can't be sanitized because of unrecoverable input character errors
*/
public static String sanitizeAppIdentifier(String appIdentifier) throws IllegalArgumentException {
if (appIdentifier == null) {
throw new IllegalArgumentException("App ID must not be null.");
}
String sAppIdentifier = appIdentifier.trim();
Matcher matcher = appIdentifierPattern.matcher(sAppIdentifier);
if (sAppIdentifier.length() != APP_IDENTIFIER_LENGTH) {
throw new IllegalArgumentException("App ID length must be " + APP_IDENTIFIER_LENGTH + " characters.");
} else if (!matcher.matches()) {
throw new IllegalArgumentException("App ID must match regex pattern /" + APP_IDENTIFIER_PATTERN + "/i");
}
return sAppIdentifier;
}
/**
* Converts a map of parameters to a HTML form entity.
*
* @param params the parameters
* @return an URL-encoded form string ready for use in a HTTP post
* @throws UnsupportedEncodingException when your system does not know how to handle the UTF-8 charset
*/
public static String getFormString(Map<String, String> params) throws UnsupportedEncodingException {
List<String> protoList = new ArrayList<String>();
for (String key : params.keySet()) {
String value = params.get(key);
key = URLEncoder.encode(key, "UTF-8");
value = URLEncoder.encode(value, "UTF-8");
protoList.add(key + "=" + value);
}
return TextUtils.join("&", protoList);
}
/**
* Helper method to safely check whether a class exists at runtime.
*
* @param className the full-qualified class name to check for
* @return whether the class exists
*/
public static boolean classExists(String className) {
try {
return Class.forName(className) != null;
} catch (ClassNotFoundException e) {
return false;
}
}
/**
* Checks if the Notification.Builder API is supported.
*
* @return if builder API is supported
*/
public static boolean isNotificationBuilderSupported() {
return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) && classExists("android.app.Notification.Builder");
}
/**
* Creates a notification on API levels from 9 to 23
*
* @param context the context to use, e.g. your Activity
* @param pendingIntent the Intent to call
* @param title the title string for the notification
* @param text the text content for the notificationcrash
* @param iconId the icon resource ID for the notification
* @return the created notification
*/
public static Notification createNotification(Context context, PendingIntent pendingIntent, String title, String text, int iconId) {
Notification notification;
if (Util.isNotificationBuilderSupported()) {
notification = buildNotificationWithBuilder(context, pendingIntent, title, text, iconId);
} else {
notification = buildNotificationPreHoneycomb(context, pendingIntent, title, text, iconId);
}
return notification;
}
@SuppressWarnings("deprecation")
private static Notification buildNotificationPreHoneycomb(Context context, PendingIntent pendingIntent, String title, String text, int iconId) {
Notification notification = new Notification(iconId, "", System.currentTimeMillis());
try {
// try to call "setLatestEventInfo" if available
Method m = notification.getClass().getMethod("setLatestEventInfo", Context.class, CharSequence.class, CharSequence.class, PendingIntent.class);
m.invoke(notification, context, title, text, pendingIntent);
} catch (Exception e) {
// do nothing
}
return notification;
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@SuppressWarnings("deprecation")
private static Notification buildNotificationWithBuilder(Context context, PendingIntent pendingIntent, String title, String text, int iconId) {
android.app.Notification.Builder builder = new android.app.Notification.Builder(context)
.setContentTitle(title)
.setContentText(text)
.setContentIntent(pendingIntent)
.setSmallIcon(iconId);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
return builder.getNotification();
} else {
return builder.build();
}
}
/**
* Retrieve the HockeyApp AppIdentifier from the Manifest
*
* @param context usually your Activity
* @return the HockeyApp AppIdentifier
*/
public static String getAppIdentifier(Context context) {
String appIdentifier = getManifestString(context, APP_IDENTIFIER_KEY);
if (TextUtils.isEmpty(appIdentifier)) {
throw new IllegalArgumentException("HockeyApp app identifier was not configured correctly in manifest or build configuration.");
}
return appIdentifier;
}
/**
* Retrieve the HockeyApp appSecret from the Manifest
*
* @param context usually your Activity
* @return the HockeyApp appSecret
*/
public static String getAppSecret(Context context) {
return getManifestString(context, APP_SECRET_KEY);
}
public static String getManifestString(Context context, String key) {
return getBundle(context).getString(key);
}
private static Bundle getBundle(Context context) {
Bundle bundle;
try {
bundle = context.getPackageManager().getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA).metaData;
} catch (PackageManager.NameNotFoundException e) {
throw new RuntimeException(e);
}
return bundle;
}
public static boolean isConnectedToNetwork(Context context) {
try {
ConnectivityManager connectivityManager = (ConnectivityManager) context.getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);
if (connectivityManager != null) {
NetworkInfo activeNetwork = connectivityManager.getActiveNetworkInfo();
return activeNetwork != null && activeNetwork.isConnected();
}
} catch (Exception e) {
HockeyLog.error("Exception thrown when check network is connected:");
e.printStackTrace();
}
return false;
}
public static String getAppName(Context context) {
if (context == null) {
return "";
}
PackageManager packageManager = context.getPackageManager();
ApplicationInfo applicationInfo = null;
try {
applicationInfo = packageManager.getApplicationInfo(context.getApplicationInfo().packageName, 0);
} catch (final PackageManager.NameNotFoundException e) {
}
String appTitle = (applicationInfo != null ? (String) packageManager.getApplicationLabel(applicationInfo)
: context.getString(R.string.hockeyapp_crash_dialog_app_name_fallback));
return appTitle;
}
public static String getSdkVersionFromManifest(Context context) {
return getManifestString(context, SDK_VERSION_KEY);
}
/**
* Sanitizes an app identifier and adds dashes to it so that it conforms to the instrumentation
* key format of Application Insights.
*
* @param appIdentifier the app identifier to sanitize and convert
* @return the converted appIdentifier
* @throws java.lang.IllegalArgumentException if the app identifier can't be converted because
* of unrecoverable input character errors
*/
public static String convertAppIdentifierToGuid(String appIdentifier) throws
IllegalArgumentException {
String sanitizedAppIdentifier = null;
String guid = null;
try {
sanitizedAppIdentifier = sanitizeAppIdentifier(appIdentifier);
} catch (IllegalArgumentException e) {
throw e;
}
if (sanitizedAppIdentifier != null) {
StringBuffer idBuf = new StringBuffer(sanitizedAppIdentifier);
idBuf.insert(20, '-');
idBuf.insert(16, '-');
idBuf.insert(12, '-');
idBuf.insert(8, '-');
guid = idBuf.toString();
}
return guid;
}
/**
* Determines whether the app is running on aan emulator or on a real device.
*
* @return YES if the app is running on an emulator, NO if it is running on a real device
*/
public static boolean isEmulator() {
return Build.BRAND.equalsIgnoreCase("generic");
}
/**
* Convert a date object to an ISO 8601 formatted string
*
* @param date the date object to be formatted
* @return an ISO 8601 string representation of the date
*/
public static String dateToISO8601(Date date) {
Date localDate = date;
if (localDate == null) {
localDate = new Date();
}
return DATE_FORMAT_THREAD_LOCAL.get().format(localDate);
}
/**
* Determines if Session is possible for the current user or not.
*
* @return YES if app runs on at least OS 4.0
*/
public static boolean sessionTrackingSupported() {
return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH);
}
}