/**
* 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);
}
}