Java tutorial
/* * Copyright (c) 2016, Apptentive, Inc. All Rights Reserved. * Please refer to the LICENSE file for the terms and conditions * under which redistribution and use of this file is permitted. */ package com.apptentive.android.sdk.util; import android.Manifest; import android.app.Activity; import android.content.ActivityNotFoundException; import android.content.ComponentName; import android.content.Context; import android.content.ContextWrapper; import android.content.Intent; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.res.Resources; import android.database.Cursor; import android.graphics.Color; import android.graphics.Point; import android.graphics.Rect; import android.graphics.Typeface; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.StateListDrawable; import android.net.ConnectivityManager; import android.net.Uri; import android.os.Build; import android.os.Environment; import android.provider.MediaStore; import android.support.annotation.NonNull; import android.support.v4.content.ContextCompat; import android.text.TextUtils; import android.util.TypedValue; import android.view.*; import android.view.inputmethod.InputMethodManager; import android.webkit.URLUtil; import android.widget.ListAdapter; import android.widget.ListView; import com.apptentive.android.sdk.ApptentiveInternal; import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.R; import com.apptentive.android.sdk.model.StoredFile; import java.io.*; import java.lang.reflect.Field; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.*; public class Util { public static int getStatusBarHeight(Window window) { Rect rectangle = new Rect(); window.getDecorView().getWindowVisibleDisplayFrame(rectangle); return rectangle.top; } public static int pixelsToDips(@NonNull Context context, int px) { final float scale = context.getResources().getDisplayMetrics().density; return Math.round(px / scale); } public static float dipsToPixels(@NonNull Context context, float dp) { return context.getResources().getDisplayMetrics().density * dp; } public static float dipsToPixelsFloat(@NonNull Context context, int dp) { final float scale = context.getResources().getDisplayMetrics().density; return dp * scale; } /** * Internal use only. */ public static void hideSoftKeyboard(Context context, View view) { if (context != null && view != null) { InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(view.getWindowToken(), 0); } } public static void showSoftKeyboard(Activity activity, View target) { if (activity != null && activity.getCurrentFocus() != null) { InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE); imm.showSoftInput(target, 0); } } public static boolean isNetworkConnectionPresent() { ConnectivityManager cm = (ConnectivityManager) ApptentiveInternal.getInstance().getApplicationContext() .getSystemService(Context.CONNECTIVITY_SERVICE); return cm != null && cm.getActiveNetworkInfo() != null; } public static void ensureClosed(Closeable stream) { if (stream != null) { try { stream.close(); } catch (IOException e) { // Ignore } } } public static Point getScreenSize(Context context) { Point ret = new Point(); WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); Display display = windowManager.getDefaultDisplay(); // TODO: getWidth(), getHeight(), and getOrientation() are deprecated in API 13 in favor of getSize() and getRotation(). ret.set(display.getWidth(), display.getHeight()); return ret; } public static String trim(String string) { if (string != null) { return string.trim(); } return null; } public static Integer parseCacheControlHeader(String cacheControlHeader) { if (cacheControlHeader != null) { int indexOfOpenBracket = cacheControlHeader.indexOf("["); int indexOfLastBracket = cacheControlHeader.lastIndexOf("]"); cacheControlHeader = cacheControlHeader.substring(indexOfOpenBracket + 1, indexOfLastBracket); String[] cacheControlParts = cacheControlHeader.split(","); for (String part : cacheControlParts) { part = part.trim(); if (part.startsWith("max-age=")) { String[] maxAgeParts = part.split("="); if (maxAgeParts.length == 2) { String expiration = null; try { expiration = maxAgeParts[1]; Integer ret = Integer.parseInt(expiration); return ret; } catch (NumberFormatException e) { ApptentiveLog.e("Error parsing cache expiration as number: %s", e, expiration); } } } } } return null; } public static boolean isEmailValid(String email) { return email.matches("^[^\\s@]+@[^\\s@]+$"); } public static boolean getPackageMetaDataBoolean(Context context, String key) { try { return context.getPackageManager().getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA).metaData.getBoolean(key, false); } catch (PackageManager.NameNotFoundException e) { return false; } } public static Object getPackageMetaData(Context appContext, String key) { try { return appContext.getPackageManager().getApplicationInfo(appContext.getPackageName(), PackageManager.GET_META_DATA).metaData.get(key); } catch (Exception e) { return null; } } /** * <p>This method will allow you to pass in literal strings. You must wrap the string in single quotes in order to ensure it is not modified * by Android. Android will try to coerce the string to a float, Integer, etc., if it looks like one.</p> * <p>Example: <code><meta-data android:name="sdk_distribution" android:value="'1.00'"/></code></p> * <p>This will evaluate to a String "1.00". If you leave off the single quotes, this method will just cast to a String, so the result would be a String "1.0".</p> */ public static String getPackageMetaDataSingleQuotedString(Context appContext, String key) { Object object = getPackageMetaData(appContext, key); if (object == null) { return null; } String ret = object.toString(); if (ret.endsWith("'")) { ret = ret.substring(0, ret.length() - 1); } if (ret.startsWith("'")) { ret = ret.substring(1, ret.length()); } return ret; } public static String stackTraceAsString(Throwable throwable) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); throwable.printStackTrace(pw); return sw.toString(); } public static String getAppVersionName(Context appContext) { try { PackageManager packageManager = appContext.getPackageManager(); PackageInfo packageInfo = packageManager.getPackageInfo(appContext.getPackageName(), 0); return packageInfo.versionName; } catch (PackageManager.NameNotFoundException e) { ApptentiveLog.e("Error getting app version name.", e); } return null; } public static int getAppVersionCode(Context appContext) { try { PackageManager packageManager = appContext.getPackageManager(); PackageInfo packageInfo = packageManager.getPackageInfo(appContext.getPackageName(), 0); return packageInfo.versionCode; } catch (PackageManager.NameNotFoundException e) { ApptentiveLog.e("Error getting app version code.", e); } return -1; } /** * Converts the current time to a double representing seconds, instead of milliseconds. It will have millisecond * precision as fractional seconds. This is the default time format used throughout the Apptentive SDK. * * @return A double representing the current time in seconds. */ public static double currentTimeSeconds() { long millis = System.currentTimeMillis(); double point = (double) millis; return point / 1000; } public static int getUtcOffset() { TimeZone timezone = TimeZone.getDefault(); return timezone.getOffset(System.currentTimeMillis()) / 1000; } public static String getInstallerPackageName(Context context) { try { return context.getPackageManager().getInstallerPackageName(context.getPackageName()); } catch (Exception e) { // Just return. } return null; } public static String readStringFromInputStream(InputStream is, String charEncoding) { Reader reader = null; StringBuilder out = new StringBuilder(); final char[] buf = new char[8196]; try { reader = new InputStreamReader(is, charEncoding); while (true) { int read = reader.read(buf, 0, 8196); if (read < 0) { break; } out.append(buf, 0, read); } } catch (Exception e) { // } finally { Util.ensureClosed(reader); } return out.toString(); } public static Integer getMajorOsVersion() { try { String release = Build.VERSION.RELEASE; String[] parts = release.split("\\."); if (parts != null && parts.length != 0) { return Integer.parseInt(parts[0]); } } catch (Exception e) { ApptentiveLog.w("Error getting major OS version", e); } return -1; } /** * The web standard for colors is RGBA, but Android uses ARGB. This method provides a way to convert RGBA to ARGB. */ public static Integer parseWebColorAsAndroidColor(String input) { // Swap if input is #RRGGBBAA, but not if it is #RRGGBB Boolean swapAlpha = (input.length() == 9); try { Integer ret = Color.parseColor(input); if (swapAlpha) { ret = (ret >>> 8) | ((ret & 0x000000FF) << 24); } return ret; } catch (IllegalArgumentException e) { // } return null; } /** * helper method to set the background depending on the android version * * @param v * @param d */ public static void setBackground(View v, Drawable d) { if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.JELLY_BEAN) { v.setBackgroundDrawable(d); } else { v.setBackground(d); } } /** * helper method to set the background depending on the android version * * @param v * @param drawableRes */ public static void setBackground(View v, int drawableRes) { setBackground(v, getCompatDrawable(v.getContext(), drawableRes)); } /** * helper method to get the drawable by its resource id, specific to the correct android version * * @param c * @param drawableRes * @return */ public static Drawable getCompatDrawable(Context c, int drawableRes) { Drawable d = null; try { d = ContextCompat.getDrawable(c, drawableRes); } catch (Exception ex) { } return d; } public static int getResourceIdFromAttribute(Resources.Theme theme, int attr) { TypedValue tv = new TypedValue(); if (theme.resolveAttribute(attr, tv, true)) { return tv.resourceId; } return 0; } public static int getThemeColor(Context context, int attr) { if (context == null) { return 0; } return getThemeColor(context.getTheme(), attr); } public static int getThemeColor(Resources.Theme theme, int attr) { TypedValue tv = new TypedValue(); if (theme.resolveAttribute(attr, tv, true)) { return tv.data; } return 0; } /** * helper method to get the color by attr (if defined in the style) or by resource. * * @param ctx * @param attr attribute that defines the color * @param res color resource id * @return */ public static int getThemeColorFromAttrOrRes(Context ctx, int attr, int res) { int color = getThemeColor(ctx, attr); // If this color is not styled, use the default from the resource if (color == 0) { color = ContextCompat.getColor(ctx, res); } return color; } /** * helper method to generate the ImageButton background with specified highlight color. * * @param selected_color the color shown as highlight * @return */ public static StateListDrawable getSelectableImageButtonBackground(int selected_color) { ColorDrawable selectedColor = new ColorDrawable(selected_color); StateListDrawable states = new StateListDrawable(); states.addState(new int[] { android.R.attr.state_pressed }, selectedColor); states.addState(new int[] { android.R.attr.state_activated }, selectedColor); return states; } public static int brighter(int color, float factor) { int red = (int) ((Color.red(color) * (1 - factor) / 255 + factor) * 255); int green = (int) ((Color.green(color) * (1 - factor) / 255 + factor) * 255); int blue = (int) ((Color.blue(color) * (1 - factor) / 255 + factor) * 255); return Color.argb(Color.alpha(color), red, green, blue); } public static int dimmer(int color, float factor) { int alpha = (int) (Color.alpha(color) * factor); return Color.argb(alpha, Color.red(color), Color.green(color), Color.blue(color)); } /* Use alpha channel to compute percentage RGB share in blending. * * Background color may be visible only if foreground alpha is smaller than 100% * */ public static int alphaMixColors(int backgroundColor, int foregroundColor) { final byte ALPHA_CHANNEL = 24; final byte RED_CHANNEL = 16; final byte GREEN_CHANNEL = 8; final byte BLUE_CHANNEL = 0; final double ap1 = (double) (backgroundColor >> ALPHA_CHANNEL & 0xff) / 255d; final double ap2 = (double) (foregroundColor >> ALPHA_CHANNEL & 0xff) / 255d; final double ap = ap2 + (ap1 * (1 - ap2)); final double amount1 = (ap1 * (1 - ap2)) / ap; final double amount2 = amount1 / ap; int a = ((int) (ap * 255d)) & 0xff; int r = ((int) (((float) (backgroundColor >> RED_CHANNEL & 0xff) * amount1) + ((float) (foregroundColor >> RED_CHANNEL & 0xff) * amount2))) & 0xff; int g = ((int) (((float) (backgroundColor >> GREEN_CHANNEL & 0xff) * amount1) + ((float) (foregroundColor >> GREEN_CHANNEL & 0xff) * amount2))) & 0xff; int b = ((int) (((float) (backgroundColor & 0xff) * amount1) + ((float) (foregroundColor & 0xff) * amount2))) & 0xff; return a << ALPHA_CHANNEL | r << RED_CHANNEL | g << GREEN_CHANNEL | b << BLUE_CHANNEL; } public static boolean canLaunchIntent(Context context, Intent intent) { if (context == null) { return false; } PackageManager pm = context.getPackageManager(); ComponentName cn = intent.resolveActivity(pm); if (cn != null) { return true; } return false; } public static String classToString(Object object) { if (object == null) { return "null"; } else { return String.format("%s(%s)", object.getClass().getSimpleName(), object); } } public static String getMimeTypeFromUri(Context context, Uri contentUri) { return (context != null) ? context.getContentResolver().getType(contentUri) : null; } public static String getRealFilePathFromUri(Context context, Uri contentUri) { if (!hasPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE)) { return null; } Cursor cursor = context.getContentResolver().query(contentUri, null, null, null, null); if (cursor != null && cursor.moveToFirst()) { String document_id = cursor.getString(0); document_id = document_id.substring(document_id.lastIndexOf(":") + 1); cursor.close(); cursor = context.getContentResolver().query( android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, MediaStore.Images.Media._ID + " = ? ", new String[] { document_id }, null); if (cursor != null && cursor.moveToFirst()) { String path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA)); cursor.close(); return path; } } return null; } public static long getContentCreationTime(Context context, Uri contentUri) { if (!hasPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE)) { return 0; } Cursor cursor = context.getContentResolver().query(contentUri, null, null, null, null); if (cursor != null && cursor.moveToFirst()) { String document_id = cursor.getString(0); document_id = document_id.substring(document_id.lastIndexOf(":") + 1); cursor.close(); cursor = context.getContentResolver().query( android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, MediaStore.Images.Media._ID + " = ? ", new String[] { document_id }, null); if (cursor != null && cursor.moveToFirst()) { long time = cursor.getLong(cursor.getColumnIndex(MediaStore.Images.Media.DATE_ADDED)); cursor.close(); return time; } } return 0; } private static String md5(String s) { try { // Create MD5 Hash MessageDigest digest = java.security.MessageDigest.getInstance("MD5"); digest.update(s.getBytes()); byte messageDigest[] = digest.digest(); // Create Hex String StringBuilder hexString = new StringBuilder(); for (byte aMessageDigest : messageDigest) { hexString.append(Integer.toHexString(0xFF & aMessageDigest)); } return hexString.toString(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } return null; } /* * Generate cached file name use md5 from image originalPath and image created time */ public static String generateCacheFileFullPath(String url, File cacheDir) { String fileName = md5(url); File cacheFile = new File(cacheDir, fileName); return cacheFile.getPath(); } /* * Generate cached file name use md5 from file originalPath and created time */ public static String generateCacheFileFullPath(Uri fileOriginalUri, File cacheDir, long createdTime) { String source = fileOriginalUri.toString() + Long.toString(createdTime); String fileName = md5(source); File cacheFile = new File(cacheDir, fileName); return cacheFile.getPath(); } public static File getDiskCacheDir(Context context) { File appCacheDir = null; if ((Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) || !Environment.isExternalStorageRemovable()) && hasPermission(context, "android.permission.WRITE_EXTERNAL_STORAGE")) { appCacheDir = context.getExternalCacheDir(); } if (appCacheDir == null && context != null) { appCacheDir = context.getCacheDir(); } return appCacheDir; } public static String generateCacheFilePathFromNonceOrPrefix(Context context, String nonce, String prefix) { String fileName = (prefix == null) ? "apptentive-api-file-" + nonce : prefix; File cacheDir = getDiskCacheDir(context); File cacheFile = new File(cacheDir, fileName); return cacheFile.getPath(); } public static boolean hasPermission(Context context, final String permission) { if (context == null) { return false; } int perm = context.checkCallingOrSelfPermission(permission); return perm == PackageManager.PERMISSION_GRANTED; } /** * This function launchs the default app to view the selected file, based on mime type * * @param sourcePath * @param selectedFilePath the full path to the local storage * @param mimeTypeString the mime type of the file to be opened * @return true if file can be viewed */ public static boolean openFileAttachment(final Context context, final String sourcePath, final String selectedFilePath, final String mimeTypeString) { if ((Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) || !Environment.isExternalStorageRemovable()) && hasPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { File selectedFile = new File(selectedFilePath); String selectedFileName = null; if (selectedFile.exists()) { selectedFileName = selectedFile.getName(); final Intent intent = new Intent(); intent.setAction(android.content.Intent.ACTION_VIEW); /* Attachments were downloaded into app private data dir. In order for external app to open * the attachments, the file need to be copied to a download folder that is accessible to public * The folder will be sdcard/Downloads/apptentive-received/<file name> */ File downloadFolder = Environment .getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); File apptentiveSubFolder = new File(downloadFolder, "apptentive-received"); if (!apptentiveSubFolder.exists()) { apptentiveSubFolder.mkdir(); } File tmpfile = new File(apptentiveSubFolder, selectedFileName); String tmpFilePath = tmpfile.getPath(); // If destination file already exists, overwrite it; otherwise, delete all existing files in the same folder first. if (!tmpfile.exists()) { String[] children = apptentiveSubFolder.list(); if (children != null) { for (int i = 0; i < children.length; i++) { new File(apptentiveSubFolder, children[i]).delete(); } } } if (copyFile(selectedFilePath, tmpFilePath) == 0) { return false; } intent.setDataAndType(Uri.fromFile(tmpfile), mimeTypeString); try { context.startActivity(intent); return true; } catch (ActivityNotFoundException e) { ApptentiveLog.e("Activity not found to open attachment: ", e); } } } else { Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(sourcePath)); if (Util.canLaunchIntent(context, browserIntent)) { context.startActivity(browserIntent); } } return false; } /** * This function copies file from one location to another * * @param from the full path to the source file * @param to the full path to the destination file * @return total bytes copied. 0 indicates */ public static int copyFile(String from, String to) { InputStream inStream = null; FileOutputStream fs = null; try { int bytesum = 0; int byteread; File oldfile = new File(from); if (oldfile.exists()) { inStream = new FileInputStream(from); fs = new FileOutputStream(to); byte[] buffer = new byte[1444]; while ((byteread = inStream.read(buffer)) != -1) { bytesum += byteread; fs.write(buffer, 0, byteread); } } return bytesum; } catch (Exception e) { return 0; } finally { Util.ensureClosed(inStream); Util.ensureClosed(fs); } } public static boolean isMimeTypeImage(String mimeType) { if (TextUtils.isEmpty(mimeType)) { return false; } String fileType = mimeType.substring(0, mimeType.indexOf("/")); return (fileType.equalsIgnoreCase("Image")); } /** * This method creates a cached file exactly copying from the input stream. * * @param sourceUrl the source file path or uri string * @param localFilePath the cache file path string * @param mimeType the mimeType of the source inputstream * @return null if failed, otherwise a StoredFile object */ public static StoredFile createLocalStoredFile(String sourceUrl, String localFilePath, String mimeType) { InputStream is = null; try { Context context = ApptentiveInternal.getInstance().getApplicationContext(); if (URLUtil.isContentUrl(sourceUrl) && context != null) { Uri uri = Uri.parse(sourceUrl); is = context.getContentResolver().openInputStream(uri); } else { File file = new File(sourceUrl); is = new FileInputStream(file); } return createLocalStoredFile(is, sourceUrl, localFilePath, mimeType); } catch (FileNotFoundException e) { return null; } finally { ensureClosed(is); } } /** * This method creates a cached file copy from the source input stream. * * @param is the source input stream * @param sourceUrl the source file path or uri string * @param localFilePath the cache file path string * @param mimeType the mimeType of the source inputstream * @return null if failed, otherwise a StoredFile object */ public static StoredFile createLocalStoredFile(InputStream is, String sourceUrl, String localFilePath, String mimeType) { if (is == null) { return null; } // Copy the file contents over. CountingOutputStream cos = null; BufferedOutputStream bos = null; FileOutputStream fos = null; try { File localFile = new File(localFilePath); /* Local cache file name may not be unique, and can be reused, in which case, the previously created * cache file need to be deleted before it is being copied over. */ if (localFile.exists()) { localFile.delete(); } fos = new FileOutputStream(localFile); bos = new BufferedOutputStream(fos); cos = new CountingOutputStream(bos); byte[] buf = new byte[2048]; int count; while ((count = is.read(buf, 0, 2048)) != -1) { cos.write(buf, 0, count); } ApptentiveLog.d("File saved, size = " + (cos.getBytesWritten() / 1024) + "k"); } catch (IOException e) { ApptentiveLog.e("Error creating local copy of file attachment."); return null; } finally { Util.ensureClosed(cos); Util.ensureClosed(bos); Util.ensureClosed(fos); } // Create a StoredFile database entry for this locally saved file. StoredFile storedFile = new StoredFile(); storedFile.setSourceUriOrPath(sourceUrl); storedFile.setLocalFilePath(localFilePath); storedFile.setMimeType(mimeType); return storedFile; } public static Activity castContextToActivity(Context context) { if (context == null) { return null; } else if (context instanceof Activity) { return (Activity) context; } else if (context instanceof ContextWrapper) { return castContextToActivity(((ContextWrapper) context).getBaseContext()); } return null; } /* Utility function to override system default font with an font ttf file from asset. The override * will be applied to the entire application. The ideal place to call this method is from the onCreate() * of the Application. * * Usage: Util.replaceDefaultFont(this, "Tinos-Regular.ttf"); * * @param context The application context the font override will be applied to * @param fontFilePath The file path to the font file in the assets directory */ public static void replaceDefaultFont(Context context, String fontFilePath) { final Typeface newTypeface = Typeface.createFromAsset(context.getAssets(), fontFilePath); TypedValue tv = new TypedValue(); String staticTypefaceFieldName = null; Map<String, Typeface> newMap = null; Resources.Theme apptentiveTheme = context.getResources().newTheme(); ApptentiveInternal.getInstance().updateApptentiveInteractionTheme(apptentiveTheme, context); if (apptentiveTheme == null) { return; } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (apptentiveTheme.resolveAttribute(R.attr.apptentiveFontFamilyDefault, tv, true)) { newMap = new HashMap<String, Typeface>(); newMap.put(tv.string.toString(), newTypeface); } if (apptentiveTheme.resolveAttribute(R.attr.apptentiveFontFamilyMediumDefault, tv, true)) { if (newMap == null) { newMap = new HashMap<String, Typeface>(); } newMap.put(tv.string.toString(), newTypeface); } if (newMap != null) { try { final Field staticField = Typeface.class.getDeclaredField("sSystemFontMap"); staticField.setAccessible(true); staticField.set(null, newMap); } catch (NoSuchFieldException e) { ApptentiveLog.e("Exception replacing system font", e); } catch (IllegalAccessException e) { ApptentiveLog.e("Exception replacing system font", e); } } } else { if (apptentiveTheme.resolveAttribute(R.attr.apptentiveTypefaceDefault, tv, true)) { staticTypefaceFieldName = "DEFAULT"; if (tv.data == context.getResources().getInteger(R.integer.apptentive_typeface_monospace)) { staticTypefaceFieldName = "MONOSPACE"; } else if (tv.data == context.getResources().getInteger(R.integer.apptentive_typeface_serif)) { staticTypefaceFieldName = "SERIF"; } else if (tv.data == context.getResources().getInteger(R.integer.apptentive_typeface_sans)) { staticTypefaceFieldName = "SANS_SERIF"; } try { final Field staticField = Typeface.class.getDeclaredField(staticTypefaceFieldName); staticField.setAccessible(true); staticField.set(null, newTypeface); } catch (NoSuchFieldException e) { ApptentiveLog.e("Exception replacing system font", e); } catch (IllegalAccessException e) { ApptentiveLog.e("Exception replacing system font", e); } } } } }