/**
* 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.internal;
import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.webkit.WebView;
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.
* <p/>
* 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 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 cancel() {
WebView webView = getWebView();
// If the page hasn't loaded, or the listener is already called, then we can't interrupt
// this cancellation. Either the JS won't be ready to consume the event, or the listener
// has already processed a result.
// So let's just handle this cancellation in the standard way.
if (!isPageFinished()
|| isListenerCalled()
|| webView == null
|| !webView.isShown()) {
super.cancel();
return;
}
// Return right away if we have already queued up the delayed-cancel call.
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 we get here, then the dialog did not close quickly enough.
// So we need to honor the user's wish to cancel and we should do
// so without allowing interruptions.
FacebookWebFallbackDialog.super.cancel();
}
},
OS_BACK_BUTTON_RESPONSE_TIMEOUT_MILLISECONDS);
}
}