com.kakao.auth.authorization.authcode.KakaoWebViewDialog.java Source code

Java tutorial

Introduction

Here is the source code for com.kakao.auth.authorization.authcode.KakaoWebViewDialog.java

Source

/**
 * Copyright 2014 Daum Kakao Corp.
 *
 * Redistribution and modification in source or binary forms are not permitted without specific prior written permission.
 *
 * 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.kakao.auth.authorization.authcode;

import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.net.http.SslError;
import android.os.Build;
import android.os.Build.VERSION;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.view.Window;
import android.webkit.ConsoleMessage;
import android.webkit.JsResult;
import android.webkit.SslErrorHandler;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.Toast;

import com.kakao.auth.KakaoSDK;
import com.kakao.android.sdk.R;
import com.kakao.auth.Session;
import com.kakao.auth.exception.KakaoWebviewException;
import com.kakao.auth.receiver.SmsReceiver;
import com.kakao.auth.receiver.SmsReceiver.ISmsReceiver;
import com.kakao.network.ServerProtocol;
import com.kakao.util.exception.KakaoException;
import com.kakao.util.exception.KakaoException.ErrorType;
import com.kakao.util.helper.CommonProtocol;
import com.kakao.util.helper.SystemInfo;
import com.kakao.util.helper.log.Logger;

import org.json.JSONException;
import org.json.JSONObject;

import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * @author MJ
 */
public class KakaoWebViewDialog extends Dialog {
    private static SmsReceiver smsReceiver;

    public interface OnWebViewCompleteListener {
        void onComplete(String redirectURL, KakaoException error);
    }

    private static final int DEFAULT_THEME = android.R.style.Theme_NoTitleBar;

    private final String url;
    private final OnWebViewCompleteListener onCompleteListener;
    private final boolean isUsingTimer;
    private WebView webView;
    private ProgressDialog spinner;
    private ImageView crossImageView;
    private FrameLayout contentFrameLayout;
    private volatile AtomicBoolean listenerCalled = new AtomicBoolean(false);
    private boolean isDetached = true;
    private boolean useSmsReceiver;
    private Map<String, String> headers = new HashMap<String, String>();

    // ???  dialog sms ?? ?  .
    public KakaoWebViewDialog(final Context context, final String url, final boolean isUsingTimer,
            boolean useSmsReceiver, final OnWebViewCompleteListener listener) {
        this(context, url, null, isUsingTimer, listener);
        this.useSmsReceiver = useSmsReceiver;
    }

    public KakaoWebViewDialog(final Context context, final String url, Bundle extras, final boolean isUsingTimer,
            final OnWebViewCompleteListener listener) {
        super(context, DEFAULT_THEME);
        this.url = url;
        this.isUsingTimer = isUsingTimer;
        this.onCompleteListener = listener;
        this.headers.put(CommonProtocol.KA_HEADER_KEY, SystemInfo.getKAHeader());
        if (extras != null && extras.size() > 0) {
            for (String key : extras.keySet()) {
                headers.put(key, extras.getString(key));
            }
        }
    }

    @Override
    public void dismiss() {
        if (webView != null) {
            webView.stopLoading();
        }
        if (!isDetached) {
            if (spinner.isShowing()) {
                spinner.dismiss();
            }
            super.dismiss();
        }
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if ((keyCode == KeyEvent.KEYCODE_BACK) && webView.canGoBack()) {
            webView.goBack();
            return true;
        }

        return super.onKeyDown(keyCode, event);
    }

    @Override
    public void onDetachedFromWindow() {
        super.onDetachedFromWindow();

        isDetached = true;

        // ? ? dismiss   ?  .
        // ? ? cancel callback?  ?? .
        sendCancelToListenerIfNeeded();
        unRegisterSmsReceiverIfNeeded();
    }

    @Override
    public void onAttachedToWindow() {
        super.onAttachedToWindow();

        isDetached = false;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().setBackgroundDrawable(new ColorDrawable(android.graphics.Color.argb(128, 0, 0, 0)));

        setOnCancelListener(new OnCancelListener() {
            @Override
            public void onCancel(DialogInterface dialogInterface) {
                sendCancelToListenerIfNeeded();
            }
        });

        spinner = new ProgressDialog(getContext());
        spinner.requestWindowFeature(Window.FEATURE_NO_TITLE);
        //spinner.setMessage(getContext().getString(R.string.core_com_kakao_sdk_loading));
        spinner.setMessage("Loading");
        spinner.setOnCancelListener(new OnCancelListener() {
            @Override
            public void onCancel(DialogInterface dialogInterface) {
                sendCancelToListenerIfNeeded();
                KakaoWebViewDialog.this.dismiss();
            }
        });

        contentFrameLayout = new FrameLayout(getContext());

        createCrossImage();
        int crossWidth = crossImageView.getDrawable().getIntrinsicWidth();
        setUpWebView(crossWidth / 2);

        contentFrameLayout.addView(crossImageView,
                new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
        addContentView(contentFrameLayout, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));

        registerSmsReceiverIfNeeded();
    }

    private void sendSuccessToListener(String redirectURL) {
        if (onCompleteListener != null && !listenerCalled.getAndSet(true)) {
            onCompleteListener.onComplete(redirectURL, null);
        }
    }

    private void sendErrorToListener(Throwable error) {
        if (onCompleteListener != null && !listenerCalled.getAndSet(true)) {
            KakaoException kakaoException;
            if (error instanceof KakaoException) {
                kakaoException = (KakaoException) error;
            } else {
                kakaoException = new KakaoException(error);
            }
            onCompleteListener.onComplete(null, kakaoException);
        }
    }

    private void sendCancelToListenerIfNeeded() {
        sendErrorToListener(new KakaoException(ErrorType.CANCELED_OPERATION,
                "pressed back button or cancel button during requesting auth code."));
    }

    private void createCrossImage() {
        crossImageView = new ImageView(getContext());
        crossImageView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                sendCancelToListenerIfNeeded();
                KakaoWebViewDialog.this.dismiss();
            }
        });

        int id = getContext().getResources().getIdentifier("kakao_close_button", "drawable",
                "com.volthole.guardiansword");
        //Log.d("bk", "R.drawable.kakao_close_button: " + String.valueOf( id ) );
        Drawable crossDrawable = getContext().getResources().getDrawable(id, null);

        crossImageView.setImageDrawable(crossDrawable);
        crossImageView.setVisibility(View.INVISIBLE);
    }

    @SuppressLint("SetJavaScriptEnabled")
    private void setUpWebView(int margin) {
        // TODO xml   ??
        FrameLayout webViewContainer = new FrameLayout(getContext());
        webView = new WebView(getContext());
        webView.setVerticalScrollBarEnabled(false);
        webView.setHorizontalScrollBarEnabled(false);
        webView.setWebViewClient(new DialogWebViewClient());
        webView.setWebChromeClient(new KakaoWebChromeClient());
        webView.getSettings().setJavaScriptEnabled(true);
        webView.loadUrl(url, headers);
        webView.canGoBack();
        webView.setLayoutParams(new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
        webView.setVisibility(View.INVISIBLE);
        webView.getSettings().setSaveFormData(KakaoSDK.getAdapter().getSessionConfig().isSaveFormData());
        webView.getSettings().setSavePassword(false);
        webViewContainer.setPadding(margin, margin, margin, margin);
        webViewContainer.addView(webView);
        contentFrameLayout.addView(webViewContainer);
    }

    private class DialogWebViewClient extends WebViewClient {
        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            Logger.d("Redirect URL: " + url);

            // redirect uri
            if (url.startsWith(Session.REDIRECT_URL_PREFIX) && (url.contains(Session.REDIRECT_URL_POSTFIX)
                    || url.contains(Session.AGEAUTH_REDIRECT_URL_POSTFIX))) {
                sendSuccessToListener(url);
                dismiss();
            } else if (url.contains(ServerProtocol.AUTH_AUTHORITY) || url.contains(ServerProtocol.API_AUTHORITY)
                    || url.contains(ServerProtocol.AGE_AUTH_AUTHORITY)) {
                // ?, ??
                webView.loadUrl(url, headers);
            } else {
                //full browser!!!
                final Context context = getContext();
                try {
                    context.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
                } catch (ActivityNotFoundException e) {
                    Toast.makeText(context, e.getLocalizedMessage(), Toast.LENGTH_SHORT).show();
                }
            }
            return true;
        }

        @Override
        public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
            super.onReceivedError(view, errorCode, description, failingUrl);
            sendErrorToListener(new KakaoWebviewException(errorCode, description, failingUrl));
            dismiss();
        }

        @Override
        public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
            super.onReceivedSslError(view, handler, error);

            sendErrorToListener(new KakaoWebviewException(ERROR_FAILED_SSL_HANDSHAKE, null, null));
            handler.cancel();
            dismiss();
        }

        @Override
        public void onPageStarted(WebView view, String url, Bitmap favicon) {
            Logger.d("Webview loading URL: " + url);
            super.onPageStarted(view, url, favicon);
            if (!isDetached) {
                spinner.show();
            }
        }

        @Override
        public void onPageFinished(WebView view, String url) {
            super.onPageFinished(view, url);
            if (!isDetached) {
                spinner.dismiss();
            }
            contentFrameLayout.setBackgroundColor(Color.TRANSPARENT);
            webView.setVisibility(View.VISIBLE);
            crossImageView.setVisibility(View.VISIBLE);
        }
    }

    /**
     * KakaoWebChromeClient
     */
    private class KakaoWebChromeClient extends WebChromeClient {
        @Override
        public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
            new AlertDialog.Builder(getContext()).setMessage(message)
                    .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {

                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            result.confirm();
                        }
                    }).setCancelable(false).create().show();
            return true;
        }

        @Override
        public boolean onJsConfirm(WebView view, String url, String message, final JsResult result) {

            String msg = null;
            String positive = null;
            String negative = null;

            try {
                JSONObject object = new JSONObject(message);

                msg = object.optString("message");
                positive = object.optString("positive");
                negative = object.optString("negative");

            } catch (JSONException e) {
                Logger.e("JSONException: " + e.getMessage());
            } finally {

                msg = TextUtils.isEmpty(msg) ? message : msg;
                positive = TextUtils.isEmpty(positive) ? getContext().getString(android.R.string.ok) : positive;
                negative = TextUtils.isEmpty(negative) ? getContext().getString(android.R.string.cancel) : negative;

                new AlertDialog.Builder(getContext()).setMessage(msg)
                        .setPositiveButton(positive, new DialogInterface.OnClickListener() {

                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                result.confirm();
                            }
                        }).setNegativeButton(negative, new DialogInterface.OnClickListener() {

                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                result.cancel();
                            }
                        }).setCancelable(false).create().show();
            }

            return true;
        }

        @TargetApi(Build.VERSION_CODES.FROYO)
        @Override
        public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
            Logger.d("KakaoAccountWebView", consoleMessage.message() + " -- (" + consoleMessage.lineNumber() + "/"
                    + consoleMessage.sourceId() + ")");
            return true;
        }

        @Override
        @Deprecated
        public void onConsoleMessage(String message, int lineNumber, String sourceID) {
            Logger.d("KakaoAccountWebView", message + " -- (" + lineNumber + "/" + sourceID + ")");
        }
    }

    @Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);

        if (isUsingTimer && !spinner.isShowing()) {
            if (hasFocus) {
                webView.resumeTimers();
            } else {
                webView.pauseTimers();
            }
        }
    }

    private void registerSmsReceiverIfNeeded() {
        if (!useSmsReceiver) {
            return;
        }

        if (smsReceiver != null) {
            return;
        }
        Logger.d("registerSmsReceiver");

        smsReceiver = new SmsReceiver(new ISmsReceiver() {
            @Override
            public void onCompleteSms(String code) {
                Logger.d("++ onCompleteSms(%s)", code);
                if (!TextUtils.isEmpty(code)) {
                    final String url = String.format(Locale.US, "javascript:insertSms('%s')", code);
                    Logger.d("++ command : " + url);
                    webView.loadUrl(url);
                }
            }
        });

        IntentFilter filter = new IntentFilter();
        filter.addAction(SmsReceiver.ACTION);
        filter.setPriority(999); // ? SMS  ?? SMS  priority 3  .
        getContext().getApplicationContext().registerReceiver(smsReceiver, filter);
    }

    private void unRegisterSmsReceiverIfNeeded() {
        if (smsReceiver != null) {
            try {
                Logger.d("unregisterSmsReceiver");
                getContext().getApplicationContext().unregisterReceiver(smsReceiver);
            } catch (Exception ignore) {
            }
            smsReceiver = null;
        }
    }
}