/** * 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.Intent; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.webkit.WebView; import com.facebook.FacebookException; import com.facebook.widget.FacebookDialog; import com.facebook.widget.WebDialog; import org.json.JSONException; import org.json.JSONObject; /** * 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. * * This dialog is used as a fallback when a native FacebookDialog could not be displayed. The primary reason for * this separation is to keep this approach for internal use only until we stabilize the API. */ public class FacebookWebFallbackDialog extends WebDialog { private static final String TAG = FacebookWebFallbackDialog.class.getName(); private static final int OS_BACK_BUTTON_RESPONSE_TIMEOUT_MILLISECONDS = 1500; private boolean waitingForDialogToClose; public static boolean presentWebFallback(final Context context, String dialogUrl, String applicationId, final FacebookDialog.PendingCall appCall, final FacebookDialog.Callback callback) { if (Utility.isNullOrEmpty(dialogUrl)) { return false; } String redirectUrl = String.format("fb%s://bridge/", applicationId); // Show the webdialog. FacebookWebFallbackDialog fallbackWebDialog = new FacebookWebFallbackDialog( context, dialogUrl, redirectUrl); fallbackWebDialog.setOnCompleteListener(new WebDialog.OnCompleteListener() { @Override public void onComplete(Bundle values, FacebookException error) { Intent dummyIntent = new Intent(); dummyIntent.putExtras(values == null ? new Bundle() : values); FacebookDialog.handleActivityResult( context, appCall, appCall.getRequestCode(), dummyIntent, callback); } }); fallbackWebDialog.show(); return true; } private FacebookWebFallbackDialog(Context context, String url, String expectedRedirectUrl) { super(context, url); setExpectedRedirectUrl(expectedRedirectUrl); } @Override protected Bundle parseResponseUri(String url) { Uri responseUri = Uri.parse(url); Bundle queryParams = Utility.parseUrlQueryString(responseUri.getQuery()); // Convert Bridge args to the format that the Native dialog code understands. String bridgeArgsJSONString = queryParams.getString(ServerProtocol.FALLBACK_DIALOG_PARAM_BRIDGE_ARGS); queryParams.remove(ServerProtocol.FALLBACK_DIALOG_PARAM_BRIDGE_ARGS); if (!Utility.isNullOrEmpty(bridgeArgsJSONString)) { Bundle bridgeArgs; try { JSONObject bridgeArgsJSON = new JSONObject(bridgeArgsJSONString); bridgeArgs = BundleJSONConverter.convertToBundle(bridgeArgsJSON); queryParams.putBundle(NativeProtocol.EXTRA_PROTOCOL_BRIDGE_ARGS, bridgeArgs); } catch (JSONException je) { Utility.logd(TAG, "Unable to parse bridge_args JSON", je); } } // Convert Method results to the format that the Native dialog code understands. String methodResultsJSONString = queryParams.getString(ServerProtocol.FALLBACK_DIALOG_PARAM_METHOD_RESULTS); queryParams.remove(ServerProtocol.FALLBACK_DIALOG_PARAM_METHOD_RESULTS); if (!Utility.isNullOrEmpty(methodResultsJSONString)) { methodResultsJSONString = Utility.isNullOrEmpty(methodResultsJSONString) ? "{}" : methodResultsJSONString; Bundle methodResults; try { JSONObject methodArgsJSON = new JSONObject(methodResultsJSONString); methodResults = BundleJSONConverter.convertToBundle(methodArgsJSON); queryParams.putBundle(NativeProtocol.EXTRA_PROTOCOL_METHOD_RESULTS, methodResults); } catch (JSONException je) { Utility.logd(TAG, "Unable to parse bridge_args JSON", je); } } // The web host does not send a numeric version back. Put the latest known version in there so NativeProtocol // can continue parsing the response. queryParams.remove(ServerProtocol.FALLBACK_DIALOG_PARAM_VERSION); queryParams.putInt(NativeProtocol.EXTRA_PROTOCOL_VERSION, NativeProtocol.getLatestKnownVersion()); return queryParams; } @Override public void dismiss() { WebView webView = getWebView(); if (isListenerCalled() || webView == null || !webView.isShown()) { // If the listener has been called, or if the WebView isn't visible, we cannot give the dialog a chance // to respond. So defer to the parent implementation. super.dismiss(); return; } // If we have already notified the dialog to close, then ignore this request to dismiss. The timer will // honor the request. if (waitingForDialogToClose) { return; } waitingForDialogToClose = true; // Now fire off the event that will tell the dialog to wind down. String eventJS = "(function() {" + " var event = document.createEvent('Event');" + " event.initEvent('fbPlatformDialogMustClose',true,true);" + " document.dispatchEvent(event);" + "})();"; webView.loadUrl("javascript:" + eventJS); // Set up a timeout for the dialog to respond. If the timer expires, we need to honor the user's desire to // dismiss the dialog. Handler handler = new Handler(Looper.getMainLooper()); handler.postDelayed( new Runnable() { @Override public void run() { if (!isListenerCalled()) { // If we get here, then the dialog did not close quickly enough. So we need to honor the user's // wish to cancel. sendCancelToListener(); } } }, OS_BACK_BUTTON_RESPONSE_TIMEOUT_MILLISECONDS); } }