/** * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. * * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, * copy, modify, and distribute this software in source code or binary form for use * in connection with the web services and APIs provided by Facebook. * * As with any software that integrates with the Facebook platform, your use of * this software is subject to the Facebook Developer Principles and Policies * [http://developers.facebook.com/policy/]. This copyright notice shall be * included in all copies or substantial portions of the software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ package com.facebook.internal; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.database.Cursor; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.os.Parcel; import android.os.StatFs; import android.provider.OpenableColumns; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.DisplayMetrics; import android.util.Log; import android.view.Display; import android.view.WindowManager; import android.webkit.CookieManager; import android.webkit.CookieSyncManager; import com.facebook.AccessToken; import com.facebook.FacebookException; import com.facebook.FacebookSdk; import com.facebook.GraphRequest; import com.facebook.GraphResponse; import com.facebook.HttpMethod; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.json.JSONTokener; import java.io.BufferedInputStream; import java.io.Closeable; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.math.BigInteger; import java.net.HttpURLConnection; import java.net.URLConnection; import java.net.URLDecoder; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Random; import java.util.Set; import java.util.TimeZone; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.regex.Pattern; /** * com.facebook.internal is solely for the use of other packages within the Facebook SDK for * Android. Use of any of the classes in this package is unsupported, and they may be modified or * removed without warning at any time. */ public final class Utility { static final String LOG_TAG = "FacebookSDK"; private static final String HASH_ALGORITHM_MD5 = "MD5"; private static final String HASH_ALGORITHM_SHA1 = "SHA-1"; private static final String URL_SCHEME = "https"; private static final String EXTRA_APP_EVENTS_INFO_FORMAT_VERSION = "a2"; private final static String UTF8 = "UTF-8"; // This is the default used by the buffer streams, but they trace a warning if you do not // specify. public static final int DEFAULT_STREAM_BUFFER_SIZE = 8192; // Refresh extended device info every 30 minutes private static final int REFRESH_TIME_FOR_EXTENDED_DEVICE_INFO_MILLIS = 30 * 60 * 1000; private static final String noCarrierConstant = "NoCarrier"; private static final int GINGERBREAD_MR1 = 10; private static int numCPUCores = 0; private static long timestampOfLastCheck = -1; private static long totalExternalStorageGB = -1; private static long availableExternalStorageGB = -1; private static String deviceTimezoneAbbreviation = ""; private static String deviceTimeZoneName = ""; private static String carrierName = noCarrierConstant; /** * Each array represents a set of closed or open Range, like so: [0,10,50,60] - Ranges are * {0-9}, {50-59} [20] - Ranges are {20-} [30,40,100] - Ranges are {30-39}, {100-} * <p/> * All Ranges in the array have a closed lower bound. Only the last Range in each array may be * open. It is assumed that the passed in arrays are sorted with ascending order. It is assumed * that no two elements in a given are equal (i.e. no 0-length ranges) * <p/> * The method returns an intersect of the two passed in Range-sets * * @param range1 The first range * @param range2 The second range * @return The intersection of the two ranges. */ public static int[] intersectRanges(int[] range1, int[] range2) { if (range1 == null) { return range2; } else if (range2 == null) { return range1; } int[] outputRange = new int[range1.length + range2.length]; int outputIndex = 0; int index1 = 0, lower1, upper1; int index2 = 0, lower2, upper2; while (index1 < range1.length && index2 < range2.length) { int newRangeLower = Integer.MIN_VALUE, newRangeUpper = Integer.MAX_VALUE; lower1 = range1[index1]; upper1 = Integer.MAX_VALUE; lower2 = range2[index2]; upper2 = Integer.MAX_VALUE; if (index1 < range1.length - 1) { upper1 = range1[index1 + 1]; } if (index2 < range2.length - 1) { upper2 = range2[index2 + 1]; } if (lower1 < lower2) { if (upper1 > lower2) { newRangeLower = lower2; if (upper1 > upper2) { newRangeUpper = upper2; index2 += 2; } else { newRangeUpper = upper1; index1 += 2; } } else { index1 += 2; } } else { if (upper2 > lower1) { newRangeLower = lower1; if (upper2 > upper1) { newRangeUpper = upper1; index1 += 2; } else { newRangeUpper = upper2; index2 += 2; } } else { index2 += 2; } } if (newRangeLower != Integer.MIN_VALUE) { outputRange[outputIndex++] = newRangeLower; if (newRangeUpper != Integer.MAX_VALUE) { outputRange[outputIndex++] = newRangeUpper; } else { // If we reach an unbounded/open range, then we know we're done. break; } } } return Arrays.copyOf(outputRange, outputIndex); } // Returns true iff all items in subset are in superset, treating null and // empty collections as // the same. public static <T> boolean isSubset(Collection<T> subset, Collection<T> superset) { if ((superset == null) || (superset.size() == 0)) { return ((subset == null) || (subset.size() == 0)); } HashSet<T> hash = new HashSet<T>(superset); for (T t : subset) { if (!hash.contains(t)) { return false; } } return true; } public static <T> boolean isNullOrEmpty(Collection<T> c) { return (c == null) || (c.size() == 0); } public static boolean isNullOrEmpty(String s) { return (s == null) || (s.length() == 0); } /** * Use this when you want to normalize empty and null strings * This way, Utility.areObjectsEqual can used for comparison, where a null string is to be * treated the same as an empty string. * * @param s The string to coerce * @param valueIfNullOrEmpty The value if s is null or empty. * @return The original string s if it's not null or empty, otherwise the valueIfNullOrEmpty */ public static String coerceValueIfNullOrEmpty(String s, String valueIfNullOrEmpty) { if (isNullOrEmpty(s)) { return valueIfNullOrEmpty; } return s; } public static <T> Collection<T> unmodifiableCollection(T... ts) { return Collections.unmodifiableCollection(Arrays.asList(ts)); } public static <T> ArrayList<T> arrayList(T... ts) { ArrayList<T> arrayList = new ArrayList<T>(ts.length); for (T t : ts) { arrayList.add(t); } return arrayList; } public static <T> HashSet<T> hashSet(T... ts) { HashSet<T> hashSet = new HashSet<T>(ts.length); for (T t : ts) { hashSet.add(t); } return hashSet; } public static String md5hash(String key) { return hashWithAlgorithm(HASH_ALGORITHM_MD5, key); } public static String sha1hash(String key) { return hashWithAlgorithm(HASH_ALGORITHM_SHA1, key); } public static String sha1hash(byte[] bytes) { return hashWithAlgorithm(HASH_ALGORITHM_SHA1, bytes); } private static String hashWithAlgorithm(String algorithm, String key) { return hashWithAlgorithm(algorithm, key.getBytes()); } private static String hashWithAlgorithm(String algorithm, byte[] bytes) { MessageDigest hash; try { hash = MessageDigest.getInstance(algorithm); } catch (NoSuchAlgorithmException e) { return null; } return hashBytes(hash, bytes); } private static String hashBytes(MessageDigest hash, byte[] bytes) { hash.update(bytes); byte[] digest = hash.digest(); StringBuilder builder = new StringBuilder(); for (int b : digest) { builder.append(Integer.toHexString((b >> 4) & 0xf)); builder.append(Integer.toHexString((b >> 0) & 0xf)); } return builder.toString(); } public static Uri buildUri(String authority, String path, Bundle parameters) { Uri.Builder builder = new Uri.Builder(); builder.scheme(URL_SCHEME); builder.authority(authority); builder.path(path); if (parameters != null) { for (String key : parameters.keySet()) { Object parameter = parameters.get(key); if (parameter instanceof String) { builder.appendQueryParameter(key, (String) parameter); } } } return builder.build(); } public static Bundle parseUrlQueryString(String queryString) { Bundle params = new Bundle(); if (!isNullOrEmpty(queryString)) { String array[] = queryString.split("&"); for (String parameter : array) { String keyValuePair[] = parameter.split("="); try { if (keyValuePair.length == 2) { params.putString( URLDecoder.decode(keyValuePair[0], UTF8), URLDecoder.decode(keyValuePair[1], UTF8)); } else if (keyValuePair.length == 1) { params.putString( URLDecoder.decode(keyValuePair[0], UTF8), ""); } } catch (UnsupportedEncodingException e) { // shouldn't happen logd(LOG_TAG, e); } } } return params; } public static void putNonEmptyString(Bundle b, String key, String value) { if (!Utility.isNullOrEmpty(value)) { b.putString(key, value); } } public static void putCommaSeparatedStringList(Bundle b, String key, List<String> list) { if (list != null) { StringBuilder builder = new StringBuilder(); for (String string : list) { builder.append(string); builder.append(","); } String commaSeparated = ""; if (builder.length() > 0) { commaSeparated = builder.substring(0, builder.length() - 1); } b.putString(key, commaSeparated); } } public static void putUri(Bundle b, String key, Uri uri) { if (uri != null) { Utility.putNonEmptyString(b, key, uri.toString()); } } public static boolean putJSONValueInBundle(Bundle bundle, String key, Object value) { if (value == null) { bundle.remove(key); } else if (value instanceof Boolean) { bundle.putBoolean(key, (boolean) value); } else if (value instanceof boolean[]) { bundle.putBooleanArray(key, (boolean[]) value); } else if (value instanceof Double) { bundle.putDouble(key, (double) value); } else if (value instanceof double[]) { bundle.putDoubleArray(key, (double[]) value); } else if (value instanceof Integer) { bundle.putInt(key, (int) value); } else if (value instanceof int[]) { bundle.putIntArray(key, (int[]) value); } else if (value instanceof Long) { bundle.putLong(key, (long) value); } else if (value instanceof long[]) { bundle.putLongArray(key, (long[]) value); } else if (value instanceof String) { bundle.putString(key, (String) value); } else if (value instanceof JSONArray) { bundle.putString(key, value.toString()); } else if (value instanceof JSONObject) { bundle.putString(key, value.toString()); } else { return false; } return true; } public static void closeQuietly(Closeable closeable) { try { if (closeable != null) { closeable.close(); } } catch (IOException ioe) { // ignore } } public static void disconnectQuietly(URLConnection connection) { if (connection != null && connection instanceof HttpURLConnection) { ((HttpURLConnection) connection).disconnect(); } } public static String getMetadataApplicationId(Context context) { Validate.notNull(context, "context"); FacebookSdk.sdkInitialize(context); return FacebookSdk.getApplicationId(); } static Map<String, Object> convertJSONObjectToHashMap(JSONObject jsonObject) { HashMap<String, Object> map = new HashMap<String, Object>(); JSONArray keys = jsonObject.names(); for (int i = 0; i < keys.length(); ++i) { String key; try { key = keys.getString(i); Object value = jsonObject.get(key); if (value instanceof JSONObject) { value = convertJSONObjectToHashMap((JSONObject) value); } map.put(key, value); } catch (JSONException e) { } } return map; } // Returns either a JSONObject or JSONArray representation of the 'key' property of // 'jsonObject'. public static Object getStringPropertyAsJSON( JSONObject jsonObject, String key, String nonJSONPropertyKey ) throws JSONException { Object value = jsonObject.opt(key); if (value != null && value instanceof String) { JSONTokener tokener = new JSONTokener((String) value); value = tokener.nextValue(); } if (value != null && !(value instanceof JSONObject || value instanceof JSONArray)) { if (nonJSONPropertyKey != null) { // Facebook sometimes gives us back a non-JSON value such as // literal "true" or "false" as a result. // If we got something like that, we present it to the caller as a JSONObject // with a single property. We only do this if the caller wants that behavior. jsonObject = new JSONObject(); jsonObject.putOpt(nonJSONPropertyKey, value); return jsonObject; } else { throw new FacebookException("Got an unexpected non-JSON object."); } } return value; } public static String readStreamToString(InputStream inputStream) throws IOException { BufferedInputStream bufferedInputStream = null; InputStreamReader reader = null; try { bufferedInputStream = new BufferedInputStream(inputStream); reader = new InputStreamReader(bufferedInputStream); StringBuilder stringBuilder = new StringBuilder(); final int bufferSize = 1024 * 2; char[] buffer = new char[bufferSize]; int n = 0; while ((n = reader.read(buffer)) != -1) { stringBuilder.append(buffer, 0, n); } return stringBuilder.toString(); } finally { closeQuietly(bufferedInputStream); closeQuietly(reader); } } public static int copyAndCloseInputStream(InputStream inputStream, OutputStream outputStream) throws IOException { BufferedInputStream bufferedInputStream = null; int totalBytes = 0; try { bufferedInputStream = new BufferedInputStream(inputStream); byte[] buffer = new byte[8192]; int bytesRead; while ((bytesRead = bufferedInputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, bytesRead); totalBytes += bytesRead; } } finally { if (bufferedInputStream != null) { bufferedInputStream.close(); } if (inputStream != null) { inputStream.close(); } } return totalBytes; } public static boolean stringsEqualOrEmpty(String a, String b) { boolean aEmpty = TextUtils.isEmpty(a); boolean bEmpty = TextUtils.isEmpty(b); if (aEmpty && bEmpty) { // Both null or empty, they match. return true; } if (!aEmpty && !bEmpty) { // Both non-empty, check equality. return a.equals(b); } // One empty, one non-empty, can't match. return false; } private static void clearCookiesForDomain(Context context, String domain) { // This is to work around a bug where CookieManager may fail to instantiate if // CookieSyncManager has never been created. CookieSyncManager syncManager = CookieSyncManager.createInstance(context); syncManager.sync(); CookieManager cookieManager = CookieManager.getInstance(); String cookies = cookieManager.getCookie(domain); if (cookies == null) { return; } String[] splitCookies = cookies.split(";"); for (String cookie : splitCookies) { String[] cookieParts = cookie.split("="); if (cookieParts.length > 0) { String newCookie = cookieParts[0].trim() + "=;expires=Sat, 1 Jan 2000 00:00:01 UTC;"; cookieManager.setCookie(domain, newCookie); } } cookieManager.removeExpiredCookie(); } public static void clearFacebookCookies(Context context) { // setCookie acts differently when trying to expire cookies between builds of Android that // are using Chromium HTTP stack and those that are not. Using both of these domains to // ensure it works on both. clearCookiesForDomain(context, "facebook.com"); clearCookiesForDomain(context, ".facebook.com"); clearCookiesForDomain(context, "https://facebook.com"); clearCookiesForDomain(context, "https://.facebook.com"); } public static void logd(String tag, Exception e) { if (FacebookSdk.isDebugEnabled() && tag != null && e != null) { Log.d(tag, e.getClass().getSimpleName() + ": " + e.getMessage()); } } public static void logd(String tag, String msg) { if (FacebookSdk.isDebugEnabled() && tag != null && msg != null) { Log.d(tag, msg); } } public static void logd(String tag, String msg, Throwable t) { if (FacebookSdk.isDebugEnabled() && !isNullOrEmpty(tag)) { Log.d(tag, msg, t); } } public static <T> boolean areObjectsEqual(T a, T b) { if (a == null) { return b == null; } return a.equals(b); } public static boolean hasSameId(JSONObject a, JSONObject b) { if (a == null || b == null || !a.has("id") || !b.has("id")) { return false; } if (a.equals(b)) { return true; } String idA = a.optString("id"); String idB = b.optString("id"); if (idA == null || idB == null) { return false; } return idA.equals(idB); } public static String safeGetStringFromResponse(JSONObject response, String propertyName) { return response != null ? response.optString(propertyName, "") : ""; } public static JSONObject tryGetJSONObjectFromResponse(JSONObject response, String propertyKey) { return response != null ? response.optJSONObject(propertyKey) : null; } public static JSONArray tryGetJSONArrayFromResponse(JSONObject response, String propertyKey) { return response != null ? response.optJSONArray(propertyKey) : null; } public static void clearCaches(Context context) { ImageDownloader.clearCache(context); } public static void deleteDirectory(File directoryOrFile) { if (!directoryOrFile.exists()) { return; } if (directoryOrFile.isDirectory()) { final File[] children = directoryOrFile.listFiles(); if (children != null) { for (final File child : children) { deleteDirectory(child); } } } directoryOrFile.delete(); } public static <T> List<T> asListNoNulls(T... array) { ArrayList<T> result = new ArrayList<T>(); for (T t : array) { if (t != null) { result.add(t); } } return result; } public static List<String> jsonArrayToStringList(JSONArray jsonArray) throws JSONException { ArrayList<String> result = new ArrayList<>(); for (int i = 0; i < jsonArray.length(); i++) { result.add(jsonArray.getString(i)); } return result; } public static Set<String> jsonArrayToSet(JSONArray jsonArray) throws JSONException { Set<String> result = new HashSet<>(); for (int i = 0; i < jsonArray.length(); i++) { result.add(jsonArray.getString(i)); } return result; } public static void setAppEventAttributionParameters( JSONObject params, AttributionIdentifiers attributionIdentifiers, String anonymousAppDeviceGUID, boolean limitEventUsage) throws JSONException { if (attributionIdentifiers != null && attributionIdentifiers.getAttributionId() != null) { params.put("attribution", attributionIdentifiers.getAttributionId()); } if (attributionIdentifiers != null && attributionIdentifiers.getAndroidAdvertiserId() != null) { params.put("advertiser_id", attributionIdentifiers.getAndroidAdvertiserId()); params.put("advertiser_tracking_enabled", !attributionIdentifiers.isTrackingLimited()); } if (attributionIdentifiers != null && attributionIdentifiers.getAndroidInstallerPackage() != null) { params.put("installer_package", attributionIdentifiers.getAndroidInstallerPackage()); } params.put("anon_id", anonymousAppDeviceGUID); params.put("application_tracking_enabled", !limitEventUsage); } public static void setAppEventExtendedDeviceInfoParameters( JSONObject params, Context appContext ) throws JSONException { JSONArray extraInfoArray = new JSONArray(); extraInfoArray.put(EXTRA_APP_EVENTS_INFO_FORMAT_VERSION); Utility.refreshPeriodicExtendedDeviceInfo(appContext); // Application Manifest info: String pkgName = appContext.getPackageName(); int versionCode = -1; String versionName = ""; try { PackageInfo pi = appContext.getPackageManager().getPackageInfo(pkgName, 0); versionCode = pi.versionCode; versionName = pi.versionName; } catch (PackageManager.NameNotFoundException e) { // Swallow } // Application Manifest info: extraInfoArray.put(pkgName); extraInfoArray.put(versionCode); extraInfoArray.put(versionName); // OS/Device info extraInfoArray.put(Build.VERSION.RELEASE); extraInfoArray.put(Build.MODEL); // Locale Locale locale; try { locale = appContext.getResources().getConfiguration().locale; } catch (Exception e) { locale = Locale.getDefault(); } extraInfoArray.put(locale.getLanguage() + "_" + locale.getCountry()); // Time zone extraInfoArray.put(deviceTimezoneAbbreviation); // Carrier extraInfoArray.put(carrierName); // Screen dimensions int width = 0; int height = 0; double density = 0; try { WindowManager wm = (WindowManager) appContext.getSystemService(Context.WINDOW_SERVICE); if (wm != null) { Display display = wm.getDefaultDisplay(); DisplayMetrics displayMetrics = new DisplayMetrics(); display.getMetrics(displayMetrics); width = displayMetrics.widthPixels; height = displayMetrics.heightPixels; density = displayMetrics.density; } } catch (Exception e) { // Swallow } extraInfoArray.put(width); extraInfoArray.put(height); extraInfoArray.put(String.format("%.2f", density)); // CPU Cores extraInfoArray.put(refreshBestGuessNumberOfCPUCores()); // External Storage extraInfoArray.put(totalExternalStorageGB); extraInfoArray.put(availableExternalStorageGB); extraInfoArray.put(deviceTimeZoneName); params.put("extinfo", extraInfoArray.toString()); } public static Method getMethodQuietly( Class<?> clazz, String methodName, Class<?>... parameterTypes) { try { return clazz.getMethod(methodName, parameterTypes); } catch (NoSuchMethodException ex) { return null; } } public static Method getMethodQuietly( String className, String methodName, Class<?>... parameterTypes) { try { Class<?> clazz = Class.forName(className); return getMethodQuietly(clazz, methodName, parameterTypes); } catch (ClassNotFoundException ex) { return null; } } public static Object invokeMethodQuietly(Object receiver, Method method, Object... args) { try { return method.invoke(receiver, args); } catch (IllegalAccessException ex) { return null; } catch (InvocationTargetException ex) { return null; } } /** * Returns the name of the current activity if the context is an activity, otherwise return * "unknown" */ public static String getActivityName(Context context) { if (context == null) { return "null"; } else if (context == context.getApplicationContext()) { return "unknown"; } else { return context.getClass().getSimpleName(); } } public interface Predicate<T> { public boolean apply(T item); } public static <T> List<T> filter(final List<T> target, final Predicate<T> predicate) { if (target == null) { return null; } final List<T> list = new ArrayList<T>(); for (T item : target) { if (predicate.apply(item)) { list.add(item); } } return (list.size() == 0 ? null : list); } public interface Mapper<T, K> { public K apply(T item); } public static <T, K> List<K> map(final List<T> target, final Mapper<T, K> mapper) { if (target == null) { return null; } final List<K> list = new ArrayList<K>(); for (T item : target) { final K mappedItem = mapper.apply(item); if (mappedItem != null) { list.add(mappedItem); } } return (list.size() == 0 ? null : list); } public static String getUriString(final Uri uri) { return (uri == null ? null : uri.toString()); } public static boolean isWebUri(final Uri uri) { return (uri != null) && ("http".equalsIgnoreCase(uri.getScheme()) || "https".equalsIgnoreCase(uri.getScheme()) || "fbstaging".equalsIgnoreCase(uri.getScheme())); } public static boolean isContentUri(final Uri uri) { return (uri != null) && ("content".equalsIgnoreCase(uri.getScheme())); } public static boolean isFileUri(final Uri uri) { return (uri != null) && ("file".equalsIgnoreCase(uri.getScheme())); } public static long getContentSize(final Uri contentUri) { Cursor cursor = null; try { cursor = FacebookSdk .getApplicationContext() .getContentResolver() .query(contentUri, null, null, null, null); int sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE); cursor.moveToFirst(); return cursor.getLong(sizeIndex); } finally { if (cursor != null) { cursor.close(); } } } public static Date getBundleLongAsDate(Bundle bundle, String key, Date dateBase) { if (bundle == null) { return null; } long secondsFromBase; Object secondsObject = bundle.get(key); if (secondsObject instanceof Long) { secondsFromBase = (Long) secondsObject; } else if (secondsObject instanceof String) { try { secondsFromBase = Long.parseLong((String) secondsObject); } catch (NumberFormatException e) { return null; } } else { return null; } if (secondsFromBase == 0) { return new Date(Long.MAX_VALUE); } else { return new Date(dateBase.getTime() + (secondsFromBase * 1000L)); } } public static void writeStringMapToParcel(Parcel parcel, final Map<String, String> map) { if (map == null) { // 0 is for empty map, -1 to indicate null parcel.writeInt(-1); } else { parcel.writeInt(map.size()); for (Map.Entry<String, String> entry : map.entrySet()) { parcel.writeString(entry.getKey()); parcel.writeString(entry.getValue()); } } } public static Map<String, String> readStringMapFromParcel(Parcel parcel) { int size = parcel.readInt(); if (size < 0) { return null; } Map<String, String> map = new HashMap<>(); for (int i = 0; i < size; i++) { map.put(parcel.readString(), parcel.readString()); } return map; } public static boolean isCurrentAccessToken(AccessToken token) { return token != null ? token.equals(AccessToken.getCurrentAccessToken()) : false; } public interface GraphMeRequestWithCacheCallback { void onSuccess(JSONObject userInfo); void onFailure(FacebookException error); } public static void getGraphMeRequestWithCacheAsync( final String accessToken, final GraphMeRequestWithCacheCallback callback) { JSONObject cachedValue = ProfileInformationCache.getProfileInformation(accessToken); if (cachedValue != null) { callback.onSuccess(cachedValue); return; } GraphRequest.Callback graphCallback = new GraphRequest.Callback() { @Override public void onCompleted(GraphResponse response) { if (response.getError() != null) { callback.onFailure(response.getError().getException()); } else { ProfileInformationCache.putProfileInformation( accessToken, response.getJSONObject()); callback.onSuccess(response.getJSONObject()); } } }; GraphRequest graphRequest = getGraphMeRequestWithCache(accessToken); graphRequest.setCallback(graphCallback); graphRequest.executeAsync(); } public static JSONObject awaitGetGraphMeRequestWithCache( final String accessToken) { JSONObject cachedValue = ProfileInformationCache.getProfileInformation(accessToken); if (cachedValue != null) { return cachedValue; } GraphRequest graphRequest = getGraphMeRequestWithCache(accessToken); GraphResponse response = graphRequest.executeAndWait(); if (response.getError() != null) { return null; } return response.getJSONObject(); } private static GraphRequest getGraphMeRequestWithCache( final String accessToken) { Bundle parameters = new Bundle(); parameters.putString("fields", "id,name,first_name,middle_name,last_name,link"); parameters.putString("access_token", accessToken); GraphRequest graphRequest = new GraphRequest( null, "me", parameters, HttpMethod.GET, null); return graphRequest; } /** * Return our best guess at the available number of cores. Will always return at least 1. * @return The minimum number of CPU cores */ private static int refreshBestGuessNumberOfCPUCores() { // If we have calculated this before, return that value if (numCPUCores > 0) { return numCPUCores; } // Enumerate all available CPU files and try to count the number of CPU cores. try { File cpuDir = new File("/sys/devices/system/cpu/"); File[] cpuFiles = cpuDir.listFiles(new FilenameFilter() { @Override public boolean accept(File dir, String fileName) { return Pattern.matches("cpu[0-9]+", fileName); } }); if (cpuFiles != null) { numCPUCores = cpuFiles.length; } } catch (Exception e) { } // If enumerating and counting the CPU cores fails, use the runtime. Fallback to 1 if // that returns bogus values. if (numCPUCores <= 0) { numCPUCores = Math.max(Runtime.getRuntime().availableProcessors(), 1); } return numCPUCores; } private static void refreshPeriodicExtendedDeviceInfo(Context appContext) { if (timestampOfLastCheck == -1 || (System.currentTimeMillis() - timestampOfLastCheck) >= Utility.REFRESH_TIME_FOR_EXTENDED_DEVICE_INFO_MILLIS) { timestampOfLastCheck = System.currentTimeMillis(); Utility.refreshTimezone(); Utility.refreshCarrierName(appContext); Utility.refreshTotalExternalStorage(); Utility.refreshAvailableExternalStorage(); } } private static void refreshTimezone() { try { TimeZone tz = TimeZone.getDefault(); deviceTimezoneAbbreviation = tz.getDisplayName( tz.inDaylightTime(new Date()), TimeZone.SHORT ); deviceTimeZoneName = tz.getID(); } catch (Exception e) { } } /** * Get and cache the carrier name since this won't change during the lifetime of the app. * @return The carrier name */ private static void refreshCarrierName(Context appContext) { if (carrierName.equals(noCarrierConstant)) { try { TelephonyManager telephonyManager = ((TelephonyManager) appContext.getSystemService(Context.TELEPHONY_SERVICE)); carrierName = telephonyManager.getNetworkOperatorName(); } catch (Exception e) { } } } /** * @return whether there is external storage: */ private static boolean externalStorageExists() { return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()); } // getAvailableBlocks/getBlockSize deprecated but required pre-API v18 @SuppressWarnings("deprecation") private static void refreshAvailableExternalStorage() { try { if (externalStorageExists()) { File path = Environment.getExternalStorageDirectory(); StatFs stat = new StatFs(path.getPath()); availableExternalStorageGB = (long)stat.getAvailableBlocks() * (long)stat.getBlockSize(); } availableExternalStorageGB = Utility.convertBytesToGB(availableExternalStorageGB); } catch (Exception e) { // Swallow } } // getAvailableBlocks/getBlockSize deprecated but required pre-API v18 @SuppressWarnings("deprecation") private static void refreshTotalExternalStorage() { try { if (externalStorageExists()) { File path = Environment.getExternalStorageDirectory(); StatFs stat = new StatFs(path.getPath()); totalExternalStorageGB = (long)stat.getBlockCount() * (long)stat.getBlockSize(); } totalExternalStorageGB = Utility.convertBytesToGB(totalExternalStorageGB); } catch (Exception e) { // Swallow } } private static long convertBytesToGB(double bytes) { return Math.round(bytes / (1024.0 * 1024.0 * 1024.0)); } /** * Internal helper class that is used to hold two different permission lists (granted and * declined) */ public static class PermissionsPair { List<String> grantedPermissions; List<String> declinedPermissions; public PermissionsPair(List<String> grantedPermissions, List<String> declinedPermissions) { this.grantedPermissions = grantedPermissions; this.declinedPermissions = declinedPermissions; } public List<String> getGrantedPermissions() { return grantedPermissions; } public List<String> getDeclinedPermissions() { return declinedPermissions; } } public static PermissionsPair handlePermissionResponse(JSONObject result) throws JSONException { JSONObject permissions = result.getJSONObject("permissions"); JSONArray data = permissions.getJSONArray("data"); List<String> grantedPermissions = new ArrayList<>(data.length()); List<String> declinedPermissions = new ArrayList<>(data.length()); for (int i = 0; i < data.length(); ++i) { JSONObject object = data.optJSONObject(i); String permission = object.optString("permission"); if (permission == null || permission.equals("installed")) { continue; } String status = object.optString("status"); if (status == null) { continue; } if (status.equals("granted")) { grantedPermissions.add(permission); } else if (status.equals("declined")) { declinedPermissions.add(permission); } } return new PermissionsPair(grantedPermissions, declinedPermissions); } public static String generateRandomString(int length) { Random r = new Random(); return new BigInteger(length * 5, r).toString(32); } }