package com.smartandroid.sa.aq;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import org.json.JSONObject;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.content.Intent;
import android.content.SharedPreferences.Editor;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.pm.Signature;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.util.Log;
import android.webkit.CookieManager;
import android.webkit.CookieSyncManager;
import android.webkit.WebView;
import android.webkit.WebViewClient;
public class FacebookHandle extends AccountHandle{
private String appId;
private Activity act;
private WebDialog dialog;
private String token;
private String permissions;
private String message;
private static final String OAUTH_ENDPOINT = "https://graph.facebook.com/oauth/authorize";
private static final String REDIRECT_URI = "https://www.facebook.com/connect/login_success.html";
private static final String CANCEL_URI = "fbconnect:cancel";
private boolean first;
private boolean sso;
private int requestId;
public FacebookHandle(Activity act, String appId, String permissions) {
this(act, appId, permissions, null);
}
public FacebookHandle(Activity act, String appId, String permissions, String accessToken) {
this.appId = appId;
this.act = act;
this.permissions = permissions;
this.token = accessToken;
if(token == null && permissionOk(permissions, fetchPermission())){
token = fetchToken();
}
first = token == null;
}
public String getToken(){
return token;
}
public static String getToken(Context context){
return PreferenceManager.getDefaultSharedPreferences(context).getString(FB_TOKEN, null);
}
public FacebookHandle sso(int requestId){
this.sso = true;
this.requestId = requestId;
return this;
}
private boolean permissionOk(String permissions, String old){
if(permissions == null) return true;
if(old == null) return false;
String[] splits = old.split("[,\\s]+");
Set<String> oldSet = new HashSet<String>(Arrays.asList(splits));
splits = permissions.split("[,\\s]+");
for(int i = 0; i < splits.length; i++){
if(!oldSet.contains(splits[i])){
AQUtility.debug("perm mismatch");
return false;
}
}
return true;
}
public FacebookHandle message(String message){
this.message = message;
return this;
}
public FacebookHandle setLoadingMessage(int resId){
this.message = act.getString(resId);
return this;
}
private void dismiss(){
if(dialog != null){
new AQuery(act).dismiss(dialog);
dialog = null;
}
}
private void show(){
if(dialog != null){
new AQuery(act).show(dialog);
}
}
private void hide(){
if(dialog != null){
try{
dialog.hide();
}catch(Exception e){
AQUtility.debug(e);
}
}
}
private void failure(){
failure("cancel");
}
private void failure(String message){
dismiss();
failure(act, AjaxStatus.AUTH_ERROR, message);
}
protected void auth() {
if(act.isFinishing()) return;
boolean ok = sso();
AQUtility.debug("authing", ok);
if(!ok){
webAuth();
}
}
private boolean sso(){
if(!sso) return false;
return startSingleSignOn(act, appId, permissions, requestId);
}
private void webAuth() {
AQUtility.debug("web auth");
Bundle parameters = new Bundle();
parameters.putString("client_id", appId);
parameters.putString("type", "user_agent");
if(permissions != null){
parameters.putString("scope", permissions);
}
parameters.putString("redirect_uri", REDIRECT_URI);
String url = OAUTH_ENDPOINT + "?" + encodeUrl(parameters);
FbWebViewClient client = new FbWebViewClient();
dialog = new WebDialog(act, url, client);
dialog.setLoadingMessage(message);
dialog.setOnCancelListener(client);
show();
if(!first || token != null){
AQUtility.debug("auth hide");
hide();
}
dialog.load();
AQUtility.debug("auth started");
}
private static final String FB_TOKEN = "aq.fb.token";
private static final String FB_PERMISSION = "aq.fb.permission";
private String fetchToken(){
return PreferenceManager.getDefaultSharedPreferences(act).getString(FB_TOKEN, null);
}
private String fetchPermission(){
return PreferenceManager.getDefaultSharedPreferences(act).getString(FB_PERMISSION, null);
}
private void storeToken(String token, String permission){
Editor editor = PreferenceManager.getDefaultSharedPreferences(act).edit();
editor.putString(FB_TOKEN, token).putString(FB_PERMISSION, permission);//.commit();
AQUtility.apply(editor);
}
private class FbWebViewClient extends WebViewClient implements OnCancelListener {
private boolean checkDone(String url){
if(url.startsWith(REDIRECT_URI)) {
Bundle values = parseUrl(url);
String error = values.getString("error_reason");
AQUtility.debug("error", error);
if(error == null) {
token = extractToken(url);
}
if(token != null){
dismiss();
storeToken(token, permissions);
first = false;
authenticated(token);
success(act);
}else{
failure();
}
return true;
}else if(url.startsWith(CANCEL_URI)) {
AQUtility.debug("cancelled");
failure();
return true;
}
return false;
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
AQUtility.debug("return url: " + url);
return checkDone(url);
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
AQUtility.debug("started", url);
if(checkDone(url)){
}else{
super.onPageStarted(view, url, favicon);
}
}
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
show();
AQUtility.debug("finished", url);
}
@Override
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
failure();
}
@Override
public void onCancel(DialogInterface dialog) {
failure();
}
}
private String extractToken(String url) {
Uri uri = Uri.parse(url.replace('#', '?'));
String token = uri.getQueryParameter("access_token");
AQUtility.debug("token", token);
return token;
}
private static String encodeUrl(Bundle parameters) {
if (parameters == null) {
return "";
}
StringBuilder sb = new StringBuilder();
boolean first = true;
for (String key : parameters.keySet()) {
if (first)
first = false;
else
sb.append("&");
sb.append(key + "=" + parameters.getString(key));
}
return sb.toString();
}
private static Bundle decodeUrl(String s) {
Bundle params = new Bundle();
if (s != null) {
String array[] = s.split("&");
for (String parameter : array) {
String v[] = parameter.split("=");
params.putString(v[0], v[1]);
}
}
return params;
}
private static Bundle parseUrl(String url) {
try {
URL u = new URL(url);
Bundle b = decodeUrl(u.getQuery());
b.putAll(decodeUrl(u.getRef()));
return b;
} catch (MalformedURLException e) {
return new Bundle();
}
}
//03-17 17:05:40.594: W/AQuery(23190): error:{"error":{"message":"Error validating access token: User 1318428934 has not authorized application 155734287864315.","type":"OAuthException","code":190}}
@Override
public boolean expired(AbstractAjaxCallback<?, ?> cb, AjaxStatus status) {
int code = status.getCode();
if(code == 200) return false;
String error = status.getError();
if(error != null && error.contains("OAuthException")){
AQUtility.debug("fb token expired");
return true;
}
String url = cb.getUrl();
if(code == 400 && (url.endsWith("/likes") || url.endsWith("/comments") || url.endsWith("/checkins"))){
return false;
}
if(code == 403 && (url.endsWith("/feed") || url.contains("method=delete"))){
return false;
}
return code == 400 || code == 401 || code == 403;
}
@Override
public boolean reauth(final AbstractAjaxCallback<?, ?> cb) {
AQUtility.debug("reauth requested");
token = null;
AQUtility.post(new Runnable() {
@Override
public void run() {
auth(cb);
}
});
return false;
}
@Override
public String getNetworkUrl(String url){
if(url.indexOf('?') == -1){
url = url + "?";
}else{
url = url + "&";
}
url = url + "access_token=" + token;
return url;
}
@Override
public String getCacheUrl(String url){
return getNetworkUrl(url);
}
@Override
public boolean authenticated() {
return token != null;
}
@Override
public void unauth(){
token = null;
CookieSyncManager.createInstance(act);
CookieManager.getInstance().removeAllCookie();
storeToken(null, null);
}
private boolean startSingleSignOn(Activity activity, String applicationId, String permissions, int activityCode) {
boolean didSucceed = true;
Intent intent = new Intent();
intent.setClassName("com.facebook.katana", "com.facebook.katana.ProxyAuth");
intent.putExtra("client_id", applicationId);
if(permissions != null) {
intent.putExtra("scope", permissions);
}
if(!validateAppSignatureForIntent(activity, intent)){
return false;
}
try {
activity.startActivityForResult(intent, activityCode);
} catch (ActivityNotFoundException e) {
didSucceed = false;
}
return didSucceed;
}
private static Boolean hasSSO;
public boolean isSSOAvailable(){
if(hasSSO == null){
Intent intent = new Intent();
intent.setClassName("com.facebook.katana", "com.facebook.katana.ProxyAuth");
hasSSO = validateAppSignatureForIntent(act, intent);
}
return hasSSO;
}
protected void authenticated(String token){
}
public void ajaxProfile(AjaxCallback<JSONObject> cb){
ajaxProfile(cb, 0);
}
public void ajaxProfile(AjaxCallback<JSONObject> cb, long expire){
String url = "https://graph.facebook.com/me";
AQuery aq = new AQuery(act);
aq.auth(this).ajax(url, JSONObject.class, expire, cb);
}
private boolean validateAppSignatureForIntent(Context context, Intent intent) {
PackageManager pm = context.getPackageManager();
ResolveInfo resolveInfo = pm.resolveActivity(intent, 0);
if(resolveInfo == null){
return false;
}
String packageName = resolveInfo.activityInfo.packageName;
PackageInfo packageInfo;
try {
packageInfo = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
} catch (NameNotFoundException e) {
return false;
}
for(Signature signature : packageInfo.signatures) {
if(signature.toCharsString().equals(FB_APP_SIGNATURE)) {
return true;
}
}
return false;
}
public void onActivityResult(int requestCode, int resultCode, Intent data) {
AQUtility.debug("on result", resultCode);
// Successfully redirected.
if (resultCode == Activity.RESULT_OK) {
// Check OAuth 2.0/2.10 error code.
String error = data.getStringExtra("error");
if (error == null) {
error = data.getStringExtra("error_type");
}
// A Facebook error occurred.
if(error != null) {
AQUtility.debug("error", error);
if(error.equals("service_disabled") || error.equals("AndroidAuthKillSwitchException")) {
webAuth();
}else{
String description = data.getStringExtra("error_description");
AQUtility.debug("fb error", description);
Log.e("fb error", description);
failure(description);
}
// No errors.
}else{
token = data.getStringExtra("access_token");
AQUtility.debug("onComplete", token);
if(token != null){
storeToken(token, permissions);
first = false;
authenticated(token);
success(act);
}else{
failure();
}
}
// An error occurred before we could be redirected.
}else if (resultCode == Activity.RESULT_CANCELED) {
failure();
}
}
public static final String FB_APP_SIGNATURE =
"30820268308201d102044a9c4610300d06092a864886f70d0101040500307a310"
+ "b3009060355040613025553310b30090603550408130243413112301006035504"
+ "07130950616c6f20416c746f31183016060355040a130f46616365626f6f6b204"
+ "d6f62696c653111300f060355040b130846616365626f6f6b311d301b06035504"
+ "03131446616365626f6f6b20436f72706f726174696f6e3020170d30393038333"
+ "13231353231365a180f32303530303932353231353231365a307a310b30090603"
+ "55040613025553310b30090603550408130243413112301006035504071309506"
+ "16c6f20416c746f31183016060355040a130f46616365626f6f6b204d6f62696c"
+ "653111300f060355040b130846616365626f6f6b311d301b06035504031314466"
+ "16365626f6f6b20436f72706f726174696f6e30819f300d06092a864886f70d01"
+ "0101050003818d0030818902818100c207d51df8eb8c97d93ba0c8c1002c928fa"
+ "b00dc1b42fca5e66e99cc3023ed2d214d822bc59e8e35ddcf5f44c7ae8ade50d7"
+ "e0c434f500e6c131f4a2834f987fc46406115de2018ebbb0d5a3c261bd97581cc"
+ "fef76afc7135a6d59e8855ecd7eacc8f8737e794c60a761c536b72b11fac8e603"
+ "f5da1a2d54aa103b8a13c0dbc10203010001300d06092a864886f70d010104050"
+ "0038181005ee9be8bcbb250648d3b741290a82a1c9dc2e76a0af2f2228f1d9f9c"
+ "4007529c446a70175c5a900d5141812866db46be6559e2141616483998211f4a6"
+ "73149fb2232a10d247663b26a9031e15f84bc1c74d141ff98a02d76f85b2c8ab2"
+ "571b6469b232d8e768a7f7ca04f7abe4a775615916c07940656b58717457b42bd"
+ "928a2";
}