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