Blame | Last modification | View Log | RSS feed
/*** Copyright 2010-present Facebook.** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at** http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package com.facebook.internal;import android.content.Context;import android.content.SharedPreferences;import android.content.pm.PackageInfo;import android.content.pm.PackageManager;import android.net.Uri;import android.os.AsyncTask;import android.os.Bundle;import android.os.Parcelable;import android.provider.Settings.Secure;import android.text.TextUtils;import android.util.Log;import android.webkit.CookieManager;import android.webkit.CookieSyncManager;import com.facebook.FacebookException;import com.facebook.Request;import com.facebook.Settings;import com.facebook.model.GraphObject;import org.json.JSONArray;import org.json.JSONException;import org.json.JSONObject;import org.json.JSONTokener;import java.io.*;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;import java.net.HttpURLConnection;import java.net.URLConnection;import java.net.URLDecoder;import java.security.MessageDigest;import java.security.NoSuchAlgorithmException;import java.util.*;import java.util.concurrent.ConcurrentHashMap;/*** 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 APP_SETTINGS_PREFS_STORE = "com.facebook.internal.preferences.APP_SETTINGS";private static final String APP_SETTINGS_PREFS_KEY_FORMAT = "com.facebook.internal.APP_SETTINGS.%s";private static final String APP_SETTING_SUPPORTS_ATTRIBUTION = "supports_attribution";private static final String APP_SETTING_SUPPORTS_IMPLICIT_SDK_LOGGING = "supports_implicit_sdk_logging";private static final String APP_SETTING_NUX_CONTENT = "gdpv4_nux_content";private static final String APP_SETTING_NUX_ENABLED = "gdpv4_nux_enabled";private static final String APP_SETTING_DIALOG_CONFIGS = "android_dialog_configs";private static final String EXTRA_APP_EVENTS_INFO_FORMAT_VERSION = "a1";private static final String DIALOG_CONFIG_DIALOG_NAME_FEATURE_NAME_SEPARATOR = "\\|";private static final String DIALOG_CONFIG_NAME_KEY = "name";private static final String DIALOG_CONFIG_VERSIONS_KEY = "versions";private static final String DIALOG_CONFIG_URL_KEY = "url";private final static String UTF8 = "UTF-8";private static final String[] APP_SETTING_FIELDS = new String[] {APP_SETTING_SUPPORTS_ATTRIBUTION,APP_SETTING_SUPPORTS_IMPLICIT_SDK_LOGGING,APP_SETTING_NUX_CONTENT,APP_SETTING_NUX_ENABLED,APP_SETTING_DIALOG_CONFIGS};private static final String APPLICATION_FIELDS = "fields";// 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;private static Map<String, FetchedAppSettings> fetchedAppSettings =new ConcurrentHashMap<String, FetchedAppSettings>();private static AsyncTask<Void, Void, GraphObject> initialAppSettingsLoadTask;public static class FetchedAppSettings {private boolean supportsAttribution;private boolean supportsImplicitLogging;private String nuxContent;private boolean nuxEnabled;private Map<String, Map<String, DialogFeatureConfig>> dialogConfigMap;private FetchedAppSettings(boolean supportsAttribution,boolean supportsImplicitLogging,String nuxContent,boolean nuxEnabled,Map<String, Map<String, DialogFeatureConfig>> dialogConfigMap) {this.supportsAttribution = supportsAttribution;this.supportsImplicitLogging = supportsImplicitLogging;this.nuxContent = nuxContent;this.nuxEnabled = nuxEnabled;this.dialogConfigMap = dialogConfigMap;}public boolean supportsAttribution() {return supportsAttribution;}public boolean supportsImplicitLogging() {return supportsImplicitLogging;}public String getNuxContent() {return nuxContent;}public boolean getNuxEnabled() {return nuxEnabled;}public Map<String, Map<String, DialogFeatureConfig>> getDialogConfigurations() {return dialogConfigMap;}}public static class DialogFeatureConfig {private static DialogFeatureConfig parseDialogConfig(JSONObject dialogConfigJSON) {String dialogNameWithFeature = dialogConfigJSON.optString(DIALOG_CONFIG_NAME_KEY);if (Utility.isNullOrEmpty(dialogNameWithFeature)) {return null;}String[] components = dialogNameWithFeature.split(DIALOG_CONFIG_DIALOG_NAME_FEATURE_NAME_SEPARATOR);if (components.length != 2) {// We expect the format to be dialogName|FeatureName, where both components are non-empty.return null;}String dialogName = components[0];String featureName = components[1];if (isNullOrEmpty(dialogName) || isNullOrEmpty(featureName)) {return null;}String urlString = dialogConfigJSON.optString(DIALOG_CONFIG_URL_KEY);Uri fallbackUri = null;if (!Utility.isNullOrEmpty(urlString)) {fallbackUri = Uri.parse(urlString);}JSONArray versionsJSON = dialogConfigJSON.optJSONArray(DIALOG_CONFIG_VERSIONS_KEY);int[] featureVersionSpec = parseVersionSpec(versionsJSON);return new DialogFeatureConfig(dialogName, featureName, fallbackUri, featureVersionSpec);}private static int[] parseVersionSpec(JSONArray versionsJSON) {// Null signifies no overrides to the min-version as specified by the SDK.// An empty array would basically turn off the dialog (i.e no supported versions), so DON'T default to that.int[] versionSpec = null;if (versionsJSON != null) {int numVersions = versionsJSON.length();versionSpec = new int[numVersions];for (int i = 0; i < numVersions; i++) {// See if the version was stored directly as an Integerint version = versionsJSON.optInt(i, NativeProtocol.NO_PROTOCOL_AVAILABLE);if (version == NativeProtocol.NO_PROTOCOL_AVAILABLE) {// If not, then see if it was stored as a string that can be parsed out.// If even that fails, then we will leave it as NO_PROTOCOL_AVAILABLEString versionString = versionsJSON.optString(i);if (!isNullOrEmpty(versionString)) {try {version = Integer.parseInt(versionString);} catch (NumberFormatException nfe) {logd(LOG_TAG, nfe);version = NativeProtocol.NO_PROTOCOL_AVAILABLE;}}}versionSpec[i] = version;}}return versionSpec;}private String dialogName;private String featureName;private Uri fallbackUrl;private int[] featureVersionSpec;private DialogFeatureConfig(String dialogName, String featureName, Uri fallbackUrl, int[] featureVersionSpec) {this.dialogName = dialogName;this.featureName = featureName;this.fallbackUrl = fallbackUrl;this.featureVersionSpec = featureVersionSpec;}public String getDialogName() {return dialogName;}public String getFeatureName() {return featureName;}public Uri getFallbackUrl() {return fallbackUrl;}public int[] getVersionSpec() {return featureVersionSpec;}}/*** 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-}** 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)** The method returns an intersect of the two passed in Range-sets* @param range1* @param range2* @return*/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* @param valueIfNullOrEmpty* @return*/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;}static String md5hash(String key) {return hashWithAlgorithm(HASH_ALGORITHM_MD5, key);}static String sha1hash(String key) {return hashWithAlgorithm(HASH_ALGORITHM_SHA1, key);}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);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 happenlogd(LOG_TAG, e);}}}return params;}public static void putObjectInBundle(Bundle bundle, String key, Object value) {if (value instanceof String) {bundle.putString(key, (String) value);} else if (value instanceof Parcelable) {bundle.putParcelable(key, (Parcelable) value);} else if (value instanceof byte[]) {bundle.putByteArray(key, (byte[]) value);} else {throw new FacebookException("attempted to add unsupported type to Bundle");}}public static void closeQuietly(Closeable closeable) {try {if (closeable != null) {closeable.close();}} catch (IOException ioe) {// ignore}}public static void disconnectQuietly(URLConnection connection) {if (connection instanceof HttpURLConnection) {((HttpURLConnection) connection).disconnect();}}public static String getMetadataApplicationId(Context context) {Validate.notNull(context, "context");Settings.loadDefaultsFromMetadata(context);return Settings.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 GraphObject 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 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 (Settings.isDebugEnabled() && tag != null && e != null) {Log.d(tag, e.getClass().getSimpleName() + ": " + e.getMessage());}}public static void logd(String tag, String msg) {if (Settings.isDebugEnabled() && tag != null && msg != null) {Log.d(tag, msg);}}public static void logd(String tag, String msg, Throwable t) {if (Settings.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 void loadAppSettingsAsync(final Context context, final String applicationId) {if (Utility.isNullOrEmpty(applicationId) ||fetchedAppSettings.containsKey(applicationId) ||initialAppSettingsLoadTask != null) {return;}final String settingsKey = String.format(APP_SETTINGS_PREFS_KEY_FORMAT, applicationId);initialAppSettingsLoadTask = new AsyncTask<Void, Void, GraphObject>() {@Overrideprotected GraphObject doInBackground(Void... params) {return getAppSettingsQueryResponse(applicationId);}@Overrideprotected void onPostExecute(GraphObject result) {if (result != null) {JSONObject resultJSON = result.getInnerJSONObject();parseAppSettingsFromJSON(applicationId, resultJSON);SharedPreferences sharedPrefs = context.getSharedPreferences(APP_SETTINGS_PREFS_STORE,Context.MODE_PRIVATE);sharedPrefs.edit().putString(settingsKey, resultJSON.toString()).apply();}initialAppSettingsLoadTask = null;}};initialAppSettingsLoadTask.execute((Void[])null);// Also see if we had a cached copy and use that immediately.SharedPreferences sharedPrefs = context.getSharedPreferences(APP_SETTINGS_PREFS_STORE,Context.MODE_PRIVATE);String settingsJSONString = sharedPrefs.getString(settingsKey, null);if (!isNullOrEmpty(settingsJSONString)) {JSONObject settingsJSON = null;try {settingsJSON = new JSONObject(settingsJSONString);} catch (JSONException je) {logd(LOG_TAG, je);}if (settingsJSON != null) {parseAppSettingsFromJSON(applicationId, settingsJSON);}}}// Note that this method makes a synchronous Graph API call, so should not be called from the main thread.public static FetchedAppSettings queryAppSettings(final String applicationId, final boolean forceRequery) {// Cache the last app checked results.if (!forceRequery && fetchedAppSettings.containsKey(applicationId)) {return fetchedAppSettings.get(applicationId);}GraphObject response = getAppSettingsQueryResponse(applicationId);if (response == null) {return null;}return parseAppSettingsFromJSON(applicationId, response.getInnerJSONObject());}private static FetchedAppSettings parseAppSettingsFromJSON(String applicationId, JSONObject settingsJSON) {FetchedAppSettings result = new FetchedAppSettings(settingsJSON.optBoolean(APP_SETTING_SUPPORTS_ATTRIBUTION, false),settingsJSON.optBoolean(APP_SETTING_SUPPORTS_IMPLICIT_SDK_LOGGING, false),settingsJSON.optString(APP_SETTING_NUX_CONTENT, ""),settingsJSON.optBoolean(APP_SETTING_NUX_ENABLED, false),parseDialogConfigurations(settingsJSON.optJSONObject(APP_SETTING_DIALOG_CONFIGS)));fetchedAppSettings.put(applicationId, result);return result;}// Note that this method makes a synchronous Graph API call, so should not be called from the main thread.private static GraphObject getAppSettingsQueryResponse(String applicationId) {Bundle appSettingsParams = new Bundle();appSettingsParams.putString(APPLICATION_FIELDS, TextUtils.join(",", APP_SETTING_FIELDS));Request request = Request.newGraphPathRequest(null, applicationId, null);request.setSkipClientToken(true);request.setParameters(appSettingsParams);GraphObject response = request.executeAndWait().getGraphObject();return response;}public static DialogFeatureConfig getDialogFeatureConfig(String applicationId, String actionName, String featureName) {if (Utility.isNullOrEmpty(actionName) || Utility.isNullOrEmpty(featureName)) {return null;}FetchedAppSettings settings = fetchedAppSettings.get(applicationId);if (settings != null) {Map<String, DialogFeatureConfig> featureMap = settings.getDialogConfigurations().get(actionName);if (featureMap != null) {return featureMap.get(featureName);}}return null;}private static Map<String, Map<String, DialogFeatureConfig>> parseDialogConfigurations(JSONObject dialogConfigResponse) {HashMap<String, Map<String, DialogFeatureConfig>> dialogConfigMap = new HashMap<String, Map<String, DialogFeatureConfig>>();if (dialogConfigResponse != null) {JSONArray dialogConfigData = dialogConfigResponse.optJSONArray("data");if (dialogConfigData != null) {for (int i = 0; i < dialogConfigData.length(); i++) {DialogFeatureConfig dialogConfig = DialogFeatureConfig.parseDialogConfig(dialogConfigData.optJSONObject(i));if (dialogConfig == null) {continue;}String dialogName = dialogConfig.getDialogName();Map<String, DialogFeatureConfig> featureMap = dialogConfigMap.get(dialogName);if (featureMap == null) {featureMap = new HashMap<String, DialogFeatureConfig>();dialogConfigMap.put(dialogName, featureMap);}featureMap.put(dialogConfig.getFeatureName(), dialogConfig);}}}return dialogConfigMap;}public static boolean safeGetBooleanFromResponse(GraphObject response, String propertyName) {Object result = false;if (response != null) {result = response.getProperty(propertyName);}if (!(result instanceof Boolean)) {result = false;}return (Boolean) result;}public static String safeGetStringFromResponse(GraphObject response, String propertyName) {Object result = "";if (response != null) {result = response.getProperty(propertyName);}if (!(result instanceof String)) {result = "";}return (String) result;}public static JSONObject tryGetJSONObjectFromResponse(GraphObject response, String propertyKey) {if (response == null) {return null;}Object property = response.getProperty(propertyKey);if (!(property instanceof JSONObject)) {return null;}return (JSONObject) property;}public static JSONArray tryGetJSONArrayFromResponse(GraphObject response, String propertyKey) {if (response == null) {return null;}Object property = response.getProperty(propertyKey);if (!(property instanceof JSONArray)) {return null;}return (JSONArray) property;}public static void clearCaches(Context context) {ImageDownloader.clearCache(context);}public static void deleteDirectory(File directoryOrFile) {if (!directoryOrFile.exists()) {return;}if (directoryOrFile.isDirectory()) {for (File child : directoryOrFile.listFiles()) {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;}// Return a hash of the android_id combined with the appid. Intended to dedupe requests on the server side// in order to do counting of users unknown to Facebook. Because we put the appid into the key prior to hashing,// we cannot do correlation of the same user across multiple apps -- this is intentional. When we transition to// the Google advertising ID, we'll get rid of this and always send that up.public static String getHashedDeviceAndAppID(Context context, String applicationId) {String androidId = Secure.getString(context.getContentResolver(), Secure.ANDROID_ID);if (androidId == null) {return null;} else {return sha1hash(androidId + applicationId);}}public static void setAppEventAttributionParameters(GraphObject params,AttributionIdentifiers attributionIdentifiers, String hashedDeviceAndAppId, boolean limitEventUsage) {// Send attributionID if it exists, otherwise send a hashed device+appid specific value as the advertiser_id.if (attributionIdentifiers != null && attributionIdentifiers.getAttributionId() != null) {params.setProperty("attribution", attributionIdentifiers.getAttributionId());}if (attributionIdentifiers != null && attributionIdentifiers.getAndroidAdvertiserId() != null) {params.setProperty("advertiser_id", attributionIdentifiers.getAndroidAdvertiserId());params.setProperty("advertiser_tracking_enabled", !attributionIdentifiers.isTrackingLimited());} else if (hashedDeviceAndAppId != null) {params.setProperty("advertiser_id", hashedDeviceAndAppId);}params.setProperty("application_tracking_enabled", !limitEventUsage);}public static void setAppEventExtendedDeviceInfoParameters(GraphObject params, Context appContext) {JSONArray extraInfoArray = new JSONArray();extraInfoArray.put(EXTRA_APP_EVENTS_INFO_FORMAT_VERSION);// 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);params.setProperty("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();}}}