/**
* 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.login;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import com.facebook.AccessTokenSource;
import com.facebook.CustomTabMainActivity;
import com.facebook.FacebookException;
import com.facebook.FacebookOperationCanceledException;
import com.facebook.FacebookRequestError;
import com.facebook.FacebookSdk;
import com.facebook.FacebookServiceException;
import com.facebook.internal.FetchedAppSettings;
import com.facebook.internal.FetchedAppSettingsManager;
import com.facebook.internal.ServerProtocol;
import com.facebook.internal.Utility;
import com.facebook.internal.Validate;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class CustomTabLoginMethodHandler extends WebLoginMethodHandler {
private static final int CUSTOM_TAB_REQUEST_CODE = 1;
private static final int CHALLENGE_LENGTH = 20;
private static final int API_EC_DIALOG_CANCEL = 4201;
private static final String CUSTOM_TABS_SERVICE_ACTION =
"android.support.customtabs.action.CustomTabsService";
private static final String[] CHROME_PACKAGES = {
"com.android.chrome",
"com.chrome.beta",
"com.chrome.dev",
};
private String currentPackage;
private String expectedChallenge;
CustomTabLoginMethodHandler(LoginClient loginClient) {
super(loginClient);
expectedChallenge = Utility.generateRandomString(CHALLENGE_LENGTH);
}
@Override
String getNameForLogging() {
return "custom_tab";
}
@Override
AccessTokenSource getTokenSource() {
return AccessTokenSource.CHROME_CUSTOM_TAB;
}
@Override
protected String getSSODevice() {
return "chrome_custom_tab";
}
@Override
boolean tryAuthorize(final LoginClient.Request request) {
if (!isCustomTabsAllowed()) {
return false;
}
Bundle parameters = getParameters(request);
parameters = addExtraParameters(parameters, request);
Activity activity = loginClient.getActivity();
Intent intent = new Intent(activity, CustomTabMainActivity.class);
intent.putExtra(CustomTabMainActivity.EXTRA_PARAMS, parameters);
intent.putExtra(CustomTabMainActivity.EXTRA_CHROME_PACKAGE, getChromePackage());
loginClient.getFragment().startActivityForResult(intent, CUSTOM_TAB_REQUEST_CODE);
return true;
}
private boolean isCustomTabsAllowed() {
return isCustomTabsEnabled()
&& getChromePackage() != null
&& Validate.hasCustomTabRedirectActivity(FacebookSdk.getApplicationContext());
}
private boolean isCustomTabsEnabled() {
final String appId = Utility.getMetadataApplicationId(loginClient.getActivity());
final FetchedAppSettings settings = FetchedAppSettingsManager.getAppSettingsWithoutQuery(appId);
return settings != null && settings.getCustomTabsEnabled();
}
private String getChromePackage() {
if (currentPackage != null) {
return currentPackage;
}
Context context = loginClient.getActivity();
Intent serviceIntent = new Intent(CUSTOM_TABS_SERVICE_ACTION);
List<ResolveInfo> resolveInfos =
context.getPackageManager().queryIntentServices(serviceIntent, 0);
if (resolveInfos != null) {
Set<String> chromePackages = new HashSet<>(Arrays.asList(CHROME_PACKAGES));
for (ResolveInfo resolveInfo : resolveInfos) {
ServiceInfo serviceInfo = resolveInfo.serviceInfo;
if (serviceInfo != null && chromePackages.contains(serviceInfo.packageName)) {
currentPackage = serviceInfo.packageName;
return currentPackage;
}
}
}
return null;
}
@Override
boolean onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode != CUSTOM_TAB_REQUEST_CODE) {
return super.onActivityResult(requestCode, resultCode, data);
}
LoginClient.Request request = loginClient.getPendingRequest();
if (resultCode == Activity.RESULT_OK) {
onCustomTabComplete(data.getStringExtra(CustomTabMainActivity.EXTRA_URL), request);
return true;
}
super.onComplete(request, null, new FacebookOperationCanceledException());
return false;
}
private void onCustomTabComplete(String url, LoginClient.Request request) {
if (url != null && url.startsWith(CustomTabMainActivity.getRedirectUrl())) {
Uri uri = Uri.parse(url);
Bundle values = Utility.parseUrlQueryString(uri.getQuery());
values.putAll(Utility.parseUrlQueryString(uri.getFragment()));
if (!validateChallengeParam(values)) {
super.onComplete(request, null, new FacebookException("Invalid state parameter"));
return;
}
String error = values.getString("error");
if (error == null) {
error = values.getString("error_type");
}
String errorMessage = values.getString("error_msg");
if (errorMessage == null) {
errorMessage = values.getString("error_message");
}
if (errorMessage == null) {
errorMessage = values.getString("error_description");
}
String errorCodeString = values.getString("error_code");
int errorCode = FacebookRequestError.INVALID_ERROR_CODE;
if (!Utility.isNullOrEmpty(errorCodeString)) {
try {
errorCode = Integer.parseInt(errorCodeString);
} catch (NumberFormatException ex) {
errorCode = FacebookRequestError.INVALID_ERROR_CODE;
}
}
if (Utility.isNullOrEmpty(error) && Utility.isNullOrEmpty(errorMessage)
&& errorCode == FacebookRequestError.INVALID_ERROR_CODE) {
super.onComplete(request, values, null);
} else if (error != null && (error.equals("access_denied") ||
error.equals("OAuthAccessDeniedException"))) {
super.onComplete(request, null, new FacebookOperationCanceledException());
} else if (errorCode == API_EC_DIALOG_CANCEL) {
super.onComplete(request, null, new FacebookOperationCanceledException());
} else {
FacebookRequestError requestError =
new FacebookRequestError(errorCode, error, errorMessage);
super.onComplete(
request,
null,
new FacebookServiceException(requestError, errorMessage));
}
}
}
@Override
protected void putChallengeParam(JSONObject param) throws JSONException {
param.put(LoginLogger.EVENT_PARAM_CHALLENGE, expectedChallenge);
}
private boolean validateChallengeParam(Bundle values) {
try {
String stateString = values.getString(ServerProtocol.DIALOG_PARAM_STATE);
if (stateString == null) {
return false;
}
JSONObject state = new JSONObject(stateString);
String challenge = state.getString(LoginLogger.EVENT_PARAM_CHALLENGE);
return challenge.equals(expectedChallenge);
} catch (JSONException e) {
return false;
}
}
@Override
public int describeContents() {
return 0;
}
CustomTabLoginMethodHandler(Parcel source) {
super(source);
expectedChallenge = source.readString();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeString(expectedChallenge);
}
public static final Parcelable.Creator<CustomTabLoginMethodHandler> CREATOR =
new Parcelable.Creator() {
@Override
public CustomTabLoginMethodHandler createFromParcel(Parcel source) {
return new CustomTabLoginMethodHandler(source);
}
@Override
public CustomTabLoginMethodHandler[] newArray(int size) {
return new CustomTabLoginMethodHandler[size];
}
};
}