/** * Copyright 2012 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; import android.annotation.SuppressLint; import android.content.Context; import android.net.Uri; import android.os.Bundle; import android.os.Parcelable; import android.text.TextUtils; import android.view.View; import android.view.animation.AlphaAnimation; import android.webkit.CookieManager; import android.webkit.CookieSyncManager; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.json.JSONTokener; import java.io.*; import java.net.HttpURLConnection; import java.net.URLConnection; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.text.Collator; import java.util.*; import java.util.Map.Entry; final class Utility { static final String LOG_TAG = "FacebookSDK"; private static final String HASH_ALGORITHM_MD5 = "MD5"; private static final String URL_SCHEME = "http"; // This is the default used by the buffer streams, but they trace a warning if you do not specify. static final int DEFAULT_STREAM_BUFFER_SIZE = 8192; // Returns true iff all items in subset are in superset, treating null and // empty collections as // the same. 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; } static <T> boolean isNullOrEmpty(Collection<T> c) { return (c == null) || (c.size() == 0); } static boolean isNullOrEmpty(String s) { return (s == null) || (s.length() == 0); } static <T> Collection<T> unmodifiableCollection(T... ts) { return Collections.unmodifiableCollection(Arrays.asList(ts)); } 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) { MessageDigest hash = null; try { hash = MessageDigest.getInstance(HASH_ALGORITHM_MD5); } catch (NoSuchAlgorithmException e) { return null; } hash.update(key.getBytes()); 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(); } 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(); } 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"); } } static void closeQuietly(Closeable closeable) { try { if (closeable != null) { closeable.close(); } } catch (IOException ioe) { // ignore } } static void disconnectQuietly(HttpURLConnection connection) { if (connection != null) { connection.disconnect(); } } static void disconnectQuietly(URLConnection connection) { if (connection instanceof HttpURLConnection) { ((HttpURLConnection)connection).disconnect(); } } static String convertCamelCaseToLowercaseWithUnderscores(String string) { string = string.replaceAll("([a-z])([A-Z])", "$1_$2"); return string.toLowerCase(); } static void jsonObjectClear(JSONObject jsonObject) { @SuppressWarnings("unchecked") Iterator<String> keys = (Iterator<String>) jsonObject.keys(); while (keys.hasNext()) { keys.next(); keys.remove(); } } static boolean jsonObjectContainsValue(JSONObject jsonObject, Object value) { @SuppressWarnings("unchecked") Iterator<String> keys = (Iterator<String>) jsonObject.keys(); while (keys.hasNext()) { Object thisValue = jsonObject.opt(keys.next()); if (thisValue != null && thisValue.equals(value)) { return true; } } return false; } private final static class JSONObjectEntry implements Entry<String, Object> { private final String key; private final Object value; JSONObjectEntry(String key, Object value) { this.key = key; this.value = value; } @SuppressLint("FieldGetter") @Override public String getKey() { return this.key; } @Override public Object getValue() { return this.value; } @Override public Object setValue(Object object) { throw new UnsupportedOperationException("JSONObjectEntry is immutable"); } } static Set<Entry<String, Object>> jsonObjectEntrySet(JSONObject jsonObject) { HashSet<Entry<String, Object>> result = new HashSet<Entry<String, Object>>(); @SuppressWarnings("unchecked") Iterator<String> keys = (Iterator<String>) jsonObject.keys(); while (keys.hasNext()) { String key = keys.next(); Object value = jsonObject.opt(key); result.add(new JSONObjectEntry(key, value)); } return result; } static Set<String> jsonObjectKeySet(JSONObject jsonObject) { HashSet<String> result = new HashSet<String>(); @SuppressWarnings("unchecked") Iterator<String> keys = (Iterator<String>) jsonObject.keys(); while (keys.hasNext()) { result.add(keys.next()); } return result; } static void jsonObjectPutAll(JSONObject jsonObject, Map<String, Object> map) { Set<Entry<String, Object>> entrySet = map.entrySet(); for (Entry<String, Object> entry : entrySet) { try { jsonObject.putOpt(entry.getKey(), entry.getValue()); } catch (JSONException e) { throw new IllegalArgumentException(e); } } } static Collection<Object> jsonObjectValues(JSONObject jsonObject) { ArrayList<Object> result = new ArrayList<Object>(); @SuppressWarnings("unchecked") Iterator<String> keys = (Iterator<String>) jsonObject.keys(); while (keys.hasNext()) { result.add(jsonObject.opt(keys.next())); } return result; } 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'. 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; } 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); } } static int compareGraphObjects(GraphObject a, GraphObject b, Collection<String> sortFields, Collator collator) { for (String sortField : sortFields) { String sa = (String) a.getProperty(sortField); String sb = (String) b.getProperty(sortField); if (sa != null && sb != null) { int result = collator.compare(sa, sb); if (result != 0) { return result; } } else if (!(sa == null && sb == null)) { return (sa == null) ? -1 : 1; } } return 0; } static void setAlpha(View view, float alpha) { // Set the alpha appropriately (setAlpha is API >= 11, this technique works on all API levels). AlphaAnimation alphaAnimation = new AlphaAnimation(alpha, alpha); alphaAnimation.setDuration(0); alphaAnimation.setFillAfter(true); view.startAnimation(alphaAnimation); } 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"); } }