package au.com.newint.newinternationalist;
import android.annotation.TargetApi;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.StatFs;
import android.preference.PreferenceManager;
import android.provider.Settings;
import android.support.v4.graphics.drawable.RoundedBitmapDrawable;
import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory;
import android.util.Base64;
import android.util.DisplayMetrics;
import android.util.Log;
import com.google.ads.conversiontracking.AdWordsConversionReporter;
import com.google.android.gms.analytics.GoogleAnalytics;
import com.google.android.gms.analytics.HitBuilders;
import com.google.android.gms.analytics.Tracker;
import com.google.firebase.analytics.FirebaseAnalytics;
import com.google.firebase.crash.FirebaseCrash;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.TrueFileFilter;
import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.Console;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLDecoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.Properties;
import java.util.logging.Logger;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
import javax.net.ssl.HttpsURLConnection;
import au.com.newint.newinternationalist.util.IabHelper;
import au.com.newint.newinternationalist.util.Purchase;
import au.com.newint.newinternationalist.util.SkuDetails;
/**
* Created by New Internationalist on 26/02/15.
*/
public class Helpers {
public static final String LOGIN_USERNAME_KEY = "newintLogin" ;
public static final String LOGIN_PASSWORD_KEY = "newintHarmless" ;
public static final String TWELVE_MONTH_SUBSCRIPTION_ID = "12monthauto";
public static final String ONE_MONTH_SUBSCRIPTION_ID = "1monthauto";
public static final int GOOGLE_PLAY_REQUEST_CODE = 5000;
public static final int GOOGLE_PLAY_MAX_SKU_LIST_SIZE = 16;
public static final String GOOGLE_PLAY_APP_URL = "https://play.google.com/store/apps/details?id=" + ((MainActivity.applicationContext.getPackageName() == null) ? "" : MainActivity.applicationContext.getPackageName());
public static final boolean debugMode = BuildConfig.DEBUG;
public static boolean emulator = Build.FINGERPRINT.contains("generic");
private static FirebaseAnalytics mFirebaseAnalytics;
public static float screenHeight() {
DisplayMetrics displayMetrics = MainActivity.applicationContext.getResources().getDisplayMetrics();
return MainActivity.applicationContext.getResources().getDisplayMetrics().heightPixels / displayMetrics.density;
}
public static float screenWidth() {
DisplayMetrics displayMetrics = MainActivity.applicationContext.getResources().getDisplayMetrics();
return MainActivity.applicationContext.getResources().getDisplayMetrics().widthPixels / displayMetrics.density;
}
public static RoundedBitmapDrawable roundDrawableFromBitmap(Bitmap bitmap) {
RoundedBitmapDrawable roundedBitmapDrawable = RoundedBitmapDrawableFactory.create(MainActivity.applicationResources, bitmap);
roundedBitmapDrawable.setAntiAlias(true);
roundedBitmapDrawable.setCornerRadius(Math.max(bitmap.getWidth(), bitmap.getHeight()) / 2.0f);
return roundedBitmapDrawable;
}
public static File getStorageDirectory() {
return getStorageDirectory(Helpers.getFromPrefs(MainActivity.applicationContext.getResources().getString(R.string.use_external_storage), false));
}
public static File getStorageDirectory(boolean userRequestsExternalStorage) {
File externalStorage = MainActivity.applicationContext.getExternalFilesDir(null);
boolean emulated = Environment.isExternalStorageEmulated();
boolean mounted = Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
if (Build.VERSION.SDK_INT >= 21) {
// If API is >= 21 check to see if external SD card is present, not emulated and mounted
File[] externalFilesDirs = MainActivity.applicationContext.getExternalFilesDirs(null);
if (externalFilesDirs != null && externalFilesDirs.length > 0) {
for (File dir : externalFilesDirs) {
if (dir != null && !Environment.isExternalStorageEmulated(dir) && Environment.getExternalStorageState(dir).equals(Environment.MEDIA_MOUNTED)) {
emulated = false;
mounted = true;
externalStorage = dir;
break;
}
}
}
}
if (userRequestsExternalStorage && externalStorage != null && !emulated && mounted) {
// This device has external storage, so use that to store data
return externalStorage;
} else {
// This device doesn't have external storage, so use internal
return MainActivity.applicationContext.getFilesDir();
}
}
public static String getSiteURL() {
return getVariableFromConfig("SITE_URL");
}
public static String getVariableFromConfig(String string) {
Resources resources = MainActivity.applicationContext.getResources();
AssetManager assetManager = resources.getAssets();
try {
InputStream inputStream = assetManager.open("config.properties");
Properties properties = new Properties();
properties.load(inputStream);
return properties.getProperty(string);
} catch (IOException e) {
Log.e("Properties", "Failed to open config property file");
return null;
}
}
public static void saveToPrefs(String key, String value) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(MainActivity.applicationContext);
final SharedPreferences.Editor editor = prefs.edit();
editor.putString(key,value);
editor.apply();
}
public static void saveToPrefs(String key, boolean value) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(MainActivity.applicationContext);
final SharedPreferences.Editor editor = prefs.edit();
editor.putBoolean(key, value);
editor.apply();
}
public static String getFromPrefs(String key, String defaultValue) {
// Default value will be returned of no value found or error occurred.
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(MainActivity.applicationContext);
try {
return sharedPrefs.getString(key, defaultValue);
} catch (Exception e) {
e.printStackTrace();
return defaultValue; // Nothing for key or error, defaultValue returned
}
}
public static boolean getFromPrefs(String key, boolean defaultValue) {
// Default value will be returned of no value found or error occurred.
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(MainActivity.applicationContext);
try {
return sharedPrefs.getBoolean(key, defaultValue);
} catch (Exception e) {
e.printStackTrace();
return defaultValue; // Nothing for key or error, defaultValue returned
}
}
public static String getDeveloperPayload() {
// We don't want to collect any extra data
return "";
}
private static byte[] hexStringToByteArray(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i+1), 16));
}
return data;
}
private static byte[] getKey() {
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
// we know that the device ID isn't very secure but this is just to obscure naive attacks
byte[] id = hexStringToByteArray(Settings.Secure.ANDROID_ID);
md.update(id, 0, id.length);
return md.digest();
} catch (Exception e) {
Log.e("Helper.getKey","error digesting ANDROID_ID: "+e);
return null;
}
}
public static void savePassword(String value) {
SecretKeySpec skeySpec = new SecretKeySpec(getKey(), "AES");
Cipher cipher = null;
try {
cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
byte[] encryptedBytes = cipher.doFinal(value.getBytes("UTF-8"));
String encryptedString = Base64.encodeToString(encryptedBytes, Base64.DEFAULT);
saveToPrefs(LOGIN_PASSWORD_KEY, encryptedString);
} catch (Exception e) {
// TODO: let the user know
Log.e("Helper.getPassword","Error encrypting password "+e);
}
}
public static String getPassword(String defaultValue) {
byte[] encryptedBytes = Base64.decode(getFromPrefs(LOGIN_PASSWORD_KEY, defaultValue), Base64.DEFAULT);
SecretKeySpec skeySpec = new SecretKeySpec(getKey(), "AES");
try {
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
byte[] decrypted = cipher.doFinal(encryptedBytes);
return new String(decrypted,"UTF-8");
} catch (Exception e) {
// TODO: let the user know?
Log.e("Helper.getPassword","Error decrypting password "+e);
return defaultValue;
}
}
public static String wrapInHTML(String htmlToWrap) {
// Load CSS from file and wrap it in HTML
return "<html><head><link href='bootstrap.css' type='text/css' rel='stylesheet'/><link href='article-body.css' type='text/css' rel='stylesheet'/></head><body>" + htmlToWrap + "</body></html>";
}
public static String capitalize(final String line) {
return Character.toUpperCase(line.charAt(0)) + line.substring(1);
}
public static String singleIssuePurchaseID(int magazineNumber) {
return Integer.toString(magazineNumber) + "single";
}
public static boolean isSubscriptionValid(Date purchaseDate, int numberOfMonths) {
Date todaysDate = new Date();
return subscriptionExpiryDate(purchaseDate, numberOfMonths).after(todaysDate);
}
public static Date subscriptionExpiryDate(Date purchaseDate, int numberOfMonths) {
// Add numberOfMonths to determine expiry date
Calendar calendar = Calendar.getInstance();
calendar.setTime(purchaseDate);
calendar.add(Calendar.MONTH, numberOfMonths);
return calendar.getTime();
}
public static IabHelper setupIabHelper(Context context) {
String base64EncodedPublicKey = "";
String publicKey = getVariableFromConfig("PUBLIC_KEY");
if (publicKey != null) {
try {
base64EncodedPublicKey = au.com.newint.newinternationalist.util.Base64.encode(URLDecoder.decode(publicKey, "UTF-8").getBytes("ISO8859_1"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
return new IabHelper(context, base64EncodedPublicKey);
}
public static int calculateInSampleSize(
BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
public static Bitmap scaledBitmapDecode(byte[] bytes, int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
}
// not actually synchronized any more
public static Bitmap bitmapDecode(byte[] bytes) {
//Helpers.debugLog("bitmapDecode", "start");
Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
//Helpers.debugLog("bitmapDecode", "end");
return bitmap;
}
public static void sendGoogleAnalytics(String screenName) {
boolean allowAnonymousStatistics = getFromPrefs(MainActivity.applicationContext.getResources().getString(R.string.allow_anonymous_statistics_key), false);
if (allowAnonymousStatistics) {
// Obtain the FirebaseAnalytics instance.
mFirebaseAnalytics = FirebaseAnalytics.getInstance(MainActivity.applicationContext);
try {
Bundle bundle = new Bundle();
// bundle.putString(FirebaseAnalytics.Param.ITEM_ID, id);
bundle.putString(FirebaseAnalytics.Param.ITEM_NAME, "pageView");
bundle.putString(FirebaseAnalytics.Param.CONTENT_TYPE, screenName);
mFirebaseAnalytics.logEvent(FirebaseAnalytics.Event.SELECT_CONTENT, bundle);
} catch (Exception e) {
Log.e("FirebaseAnalytics", "ERROR: Failed to log event. " + e);
}
}
}
public static void sendGoogleAnalyticsEvent(String category, String action, String label) {
sendGoogleAnalyticsEvent(category, action, label, "0");
}
public static void sendGoogleAnalyticsEvent(String category, String action, String label, String value) {
boolean allowAnonymousStatistics = getFromPrefs(MainActivity.applicationContext.getResources().getString(R.string.allow_anonymous_statistics_key), false);
if (allowAnonymousStatistics) {
// Send analytics event
// try {
// App.tracker.send(new HitBuilders.EventBuilder()
// .setCategory(category)
// .setAction(action)
// .setLabel(label)
// .setValue((long) Float.parseFloat(value))
// .build());
// } catch (Exception e) {
// Helpers.debugLog("Analytics", "ERROR: Couldn't send inapp purchase analytics - " + e);
// }
mFirebaseAnalytics = FirebaseAnalytics.getInstance(MainActivity.applicationContext);
try {
Bundle bundle = new Bundle();
bundle.putString(FirebaseAnalytics.Param.ITEM_CATEGORY, category);
bundle.putString(FirebaseAnalytics.Param.ITEM_NAME, action);
bundle.putString(FirebaseAnalytics.Param.CONTENT_TYPE, label);
bundle.putString(FirebaseAnalytics.Param.VALUE, value);
mFirebaseAnalytics.logEvent(FirebaseAnalytics.Event.ECOMMERCE_PURCHASE, bundle);
} catch (Exception e) {
Log.e("FirebaseAnalytics", "ERROR: Failed to log event. " + e);
}
}
}
public static void sendGoogleAdwordsConversion(SkuDetails productPurchased) {
boolean allowAnonymousStatistics = getFromPrefs(MainActivity.applicationContext.getResources().getString(R.string.allow_anonymous_statistics_key), false);
if (allowAnonymousStatistics) {
// Send conversion
String purchasePrice = "0";
if (productPurchased != null) {
purchasePrice = productPurchased.getPrice();
}
try {
AdWordsConversionReporter.reportWithConversionId(MainActivity.applicationContext,
Helpers.getVariableFromConfig("ADWORDS_ID"),
Helpers.getVariableFromConfig("ADWORDS_KEY"),
purchasePrice, true);
} catch (Exception e) {
Helpers.debugLog("Analytics", "ERROR: Couldn't send inapp purchase adwords conversion - " + e);
}
}
}
public static void registerGoogleConversionsReferrer(Intent intent) {
boolean allowAnonymousStatistics = getFromPrefs(MainActivity.applicationContext.getResources().getString(R.string.allow_anonymous_statistics_key), false);
if (allowAnonymousStatistics) {
AdWordsConversionReporter.registerReferrer(MainActivity.applicationContext, intent.getData());
}
}
public static boolean moveDirectoryToDirectory(File sourceDirectory, File targetDirectory) {
try {
FileUtils.copyDirectoryToDirectory(sourceDirectory, targetDirectory.getParentFile());
FileUtils.deleteDirectory(sourceDirectory);
} catch (IOException e) {
Helpers.debugLog("moveDirectoryToDirectory", "IOException: "+e.toString());
return false;
}
return true;
}
public static long bytesAvailable(File f) {
StatFs stat = new StatFs(f.getPath());
if (Build.VERSION.SDK_INT >= 18) {
return stat.getAvailableBytes();
} else {
return stat.getAvailableBlocks() * stat.getBlockSize();
}
//return (long)stat.getBlockSizeLong() * (long)stat.getAvailableBlocksLong();
}
public static long directorySize(File target) {
long sum = 0;
Iterator<File> fileIterator = FileUtils.iterateFiles(target, TrueFileFilter.INSTANCE, TrueFileFilter.INSTANCE);
while(fileIterator.hasNext()) {
File f = fileIterator.next();
sum += f.length();
}
return sum;
}
public static void debugLog(String tag, String msg) {
if (BuildConfig.DEBUG) {
Log.i(tag,msg);
}
}
public static void restartApp(Context context) {
Log.e("Helpers", "Restarting app");
Intent restartIntent = context.getPackageManager()
.getLaunchIntentForPackage(context.getPackageName());
restartIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, restartIntent, PendingIntent.FLAG_ONE_SHOT);
AlarmManager manager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
manager.set(AlarmManager.RTC, System.currentTimeMillis() + 500, pendingIntent);
restartIntent.putExtra("EXIT", true);
context.startActivity(restartIntent);
}
public static String getNotificationTitle() {
return "New Internationalist magazine";
}
public static void crash(String exceptionMessage) {
FirebaseCrash.report(new Exception(exceptionMessage));
}
public static void crashLog(String message) {
FirebaseCrash.log(message);
}
public static void sendPushRegistrationToServer(final String token) {
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
// Try posting to the server
URL url = null;
try {
String pushRegistrationsString = "";
if (BuildConfig.DEBUG) {
// Local debug site
pushRegistrationsString = getVariableFromConfig("DEBUG_SITE_URL") + "push_registrations";
} else {
// Real server
pushRegistrationsString = Helpers.getSiteURL() + "push_registrations";
}
Helpers.debugLog("PushRegistrations", "Sending token: " + token + ", to server: " + pushRegistrationsString);
url = new URL(pushRegistrationsString);
HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
try {
urlConnection.setDoOutput(true);
urlConnection.setChunkedStreamingMode(0);
urlConnection.setRequestMethod("POST");
OutputStream out = new BufferedOutputStream(urlConnection.getOutputStream());
Uri.Builder builder = new Uri.Builder()
.appendQueryParameter("token", token)
.appendQueryParameter("device", "android");
String query = builder.build().getEncodedQuery();
BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(out, "UTF-8"));
writer.write(query);
writer.flush();
writer.close();
Helpers.debugLog("PushRegistrations", "Response from server: " + urlConnection.getResponseCode());
// InputStream in = new BufferedInputStream(urlConnection.getInputStream());
// readStream(in);
} finally {
urlConnection.disconnect();
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@Override
protected void onPostExecute(Void unused) {
//
}
}.execute();
}
public static void changeFontSize(Float fontChange, Context context) {
if (context != null && Build.VERSION.SDK_INT >= 23) {
Boolean canWriteSettings = Settings.System.canWrite(context);
if (canWriteSettings) {
// Get system font scale, check it's between 0.5 and 2.0
Float currentFontScale = context.getResources().getConfiguration().fontScale;
Float newFontScale = currentFontScale + fontChange;
if (newFontScale < 0.5f || newFontScale > 2.0f) {
// Don't change it, and use the currentFontScale
newFontScale = currentFontScale;
}
if (fontChange == 1.0f) {
// Reset to the default
newFontScale = fontChange;
}
Helpers.debugLog("ChangeFontSize", String.format("Changing from %f to %f", currentFontScale, newFontScale));
Settings.System.putFloat(context.getContentResolver(),
Settings.System.FONT_SCALE, newFontScale);
} else {
// Request permission
Intent writeIntent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS);
context.startActivity(writeIntent);
}
} else if (context != null) {
// Take the user to font size settings
Intent settingsIntent = new Intent(Settings.ACTION_DISPLAY_SETTINGS);
context.startActivity(settingsIntent);
}
}
}