Java tutorial
/* * Copyright (c) 2015. Basagee Yun. (www.basagee.tk) * * 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.nbplus.hybrid; import android.annotation.TargetApi; import android.app.Activity; import android.app.DownloadManager; import android.content.DialogInterface; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.drawable.Drawable; import android.net.Uri; import android.net.http.SslError; import android.os.Build; import android.os.Environment; import android.provider.ContactsContract; import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; import android.telephony.PhoneNumberUtils; import android.util.Log; import android.webkit.ConsoleMessage; import android.webkit.CookieManager; import android.webkit.CookieSyncManager; import android.webkit.DownloadListener; import android.webkit.GeolocationPermissions; import android.webkit.JavascriptInterface; import android.webkit.JsResult; import android.webkit.MimeTypeMap; import android.webkit.PermissionRequest; import android.webkit.SslErrorHandler; import android.webkit.WebChromeClient; import android.webkit.WebSettings; import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.Toast; import com.google.i18n.phonenumbers.NumberParseException; import com.google.i18n.phonenumbers.PhoneNumberUtil; import com.google.i18n.phonenumbers.Phonenumber; import com.nbplus.progress.ProgressDialogFragment; import org.apache.http.util.EncodingUtils; import org.basdroid.common.DeviceUtils; import org.basdroid.common.NetworkUtils; import org.basdroid.common.PhoneState; import org.basdroid.common.R; import org.basdroid.common.StorageUtils; import org.basdroid.common.StringUtils; import java.io.File; import java.util.Arrays; /** * Created by basagee on 2015. 4. 30.. */ public abstract class BasicWebViewClient extends WebViewClient { private static final String TAG = BasicWebViewClient.class.getSimpleName(); protected static final String JAVASCRIPT_IF_NAME = "nbplus"; protected WebView mWebView; protected Activity mContext; protected BroadcastWebChromeClient mWebChromeClient; protected boolean mPageLoadSuccess = false; protected DownloadManager mDownloadManager; protected ProgressDialogFragment mProgressDialogFragment; protected String mAlertTitleString; protected String mConfirmTitleString; /** * Created by basagee on 2015. 4. 30.. */ class BroadcastWebChromeClient extends WebChromeClient { @Override public void onProgressChanged(WebView view, int newProgress) { super.onProgressChanged(view, newProgress); } public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) { callback.invoke(origin, true, false); } @Override public void onPermissionRequest(final PermissionRequest request) { // Show a grant or deny dialog to the user mContext.runOnUiThread(new Runnable() { @TargetApi(Build.VERSION_CODES.LOLLIPOP) @Override public void run() { Log.d(TAG, "onPermissionRequest() grant"); //if(request.getOrigin().toString().equals("https://apprtc-m.appspot.com/")) { request.grant(request.getResources()); //} else { // request.deny(); //} } }); // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { // // On accept or deny call // request.grant(request.getResources()); // // or // // request.deny(); // } } public boolean onJsAlert(WebView view, String url, String message, final android.webkit.JsResult result) { new AlertDialog.Builder(mContext).setTitle(mAlertTitleString).setMessage(message) .setPositiveButton(android.R.string.ok, new AlertDialog.OnClickListener() { 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) { new AlertDialog.Builder(mContext).setTitle(mConfirmTitleString).setMessage(message) .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { result.confirm(); } }).setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { result.cancel(); } }).create().show(); return true; } } public WebView getWebView() { return mWebView; } /** * ??. * @param activity : context * @param view : ?? */ public BasicWebViewClient(Activity activity, WebView view, String alertTitleString, String confirmTitleString) { mWebView = view; mContext = activity; // This will handle downloading. It requires Gingerbread, though mDownloadManager = (DownloadManager) mContext.getSystemService(mContext.DOWNLOAD_SERVICE); mWebChromeClient = new BroadcastWebChromeClient(); // Enable remote debugging via chrome://inspect if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { mWebView.setWebContentsDebuggingEnabled(true); } mWebView.setWebChromeClient(mWebChromeClient); WebSettings webSettings = mWebView.getSettings(); webSettings.setGeolocationEnabled(true); webSettings.setJavaScriptCanOpenWindowsAutomatically(true); webSettings.setJavaScriptEnabled(true); webSettings.setDomStorageEnabled(true); webSettings.setDatabaseEnabled(true); // Use WideViewport and Zoom out if there is no viewport defined webSettings.setUseWideViewPort(true); webSettings.setLoadWithOverviewMode(true); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { webSettings.setMediaPlaybackRequiresUserGesture(false); } // Enable pinch to zoom without the zoom buttons webSettings.setBuiltInZoomControls(true); if (Build.VERSION.SDK_INT > Build.VERSION_CODES.HONEYCOMB) { // Hide the zoom controls for HONEYCOMB+ webSettings.setDisplayZoomControls(false); } webSettings.setAppCacheEnabled(true); mWebView.clearCache(true); webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { // Sets whether the WebView should allow third party cookies to be set. // Allowing third party cookies is a per WebView policy and can be set differently on different WebView instances. // Apps that target KITKAT or below default to allowing third party cookies. // Apps targeting LOLLIPOP or later default to disallowing third party cookies. CookieManager cookieManager = CookieManager.getInstance(); cookieManager.setAcceptCookie(true); cookieManager.setAcceptThirdPartyCookies(mWebView, true); } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { mWebView.getSettings().setTextZoom(100); } if (StringUtils.isEmptyString(alertTitleString)) { mAlertTitleString = activity.getString(R.string.default_webview_alert_title); } else { mAlertTitleString = alertTitleString; } if (StringUtils.isEmptyString(confirmTitleString)) { mConfirmTitleString = activity.getString(R.string.default_webview_confirm_title); } else { mConfirmTitleString = confirmTitleString; } mWebView.setDownloadListener(new DownloadListener() { public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) { Intent intent = new Intent(Intent.ACTION_VIEW); intent.setData(Uri.parse(url)); mContext.startActivity(intent); } }); Log.d(TAG, ">> user agent = " + mWebView.getSettings().getUserAgentString()); } public void setBackgroundResource(int resId) { mWebView.setBackgroundColor(Color.TRANSPARENT); mWebView.setBackgroundResource(resId); } public void setBackgroundTransparent() { mWebView.setBackgroundColor(Color.TRANSPARENT); } public void setPreAuthorizePermission(String url) { // mWebView.preauthorizePermission(Uri.parse(url), PermissionRequest.RESOURCE_AUDIO_CAPTURE | PermissionRequest.RESOURCE_VIDEO_CAPTURE); } // public void stopMediaStream() { // /** // * When the application falls into the background we want to stop the media stream // * such that the camera is free to use by other apps. // */ // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // mWebView.evaluateJavascript("if(window.localStream){window.localStream.stop();}", null); // } // } public void setBackground(Drawable drawable) { mWebView.setBackgroundColor(Color.TRANSPARENT); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { mWebView.setBackgroundDrawable(drawable); } else { mWebView.setBackground(drawable); } } private static final String[] DOCUMENT_MIMETYPE = new String[] { "application/pdf", "application/msword", "application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "application/vnd.openxmlformats-officedocument.wordprocessingml.template", "application/vnd.ms-excel", "application/vnd.ms-excel", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "application/vnd.openxmlformats-officedocument.spreadsheetml.template", "application/vnd.ms-powerpoint", "application/vnd.ms-powerpoint", "application/vnd.ms-powerpoint", "application/vnd.openxmlformats-officedocument.presentationml.presentation", "application/vnd.openxmlformats-officedocument.presentationml.template", "application/vnd.openxmlformats-officedocument.presentationml.slideshow" }; // url = file path or whatever suitable URL you want. public String getMimeType(String url) { String type = null; String extension = MimeTypeMap.getFileExtensionFromUrl(url); if (extension != null) { type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); } return type; } public boolean isDocumentMimeType(String url) { String mimeType = getMimeType(url); if (!StringUtils.isEmptyString(mimeType) && Arrays.asList(DOCUMENT_MIMETYPE).contains(mimeType)) { return true; } return false; } /* * 2016.07.21 baagee@gmail.com * ? ? . */ // @Override // public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { // handler.proceed(); // SSL ? ?? ? ! // } @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { // for excel download if (isDocumentMimeType(url)) { Log.d(TAG, "This url is document mimetype = " + url); if (StorageUtils.isExternalStorageWritable()) { Uri source = Uri.parse(url); // Make a new request pointing to the mp3 url DownloadManager.Request request = new DownloadManager.Request(source); // Use the same file name for the destination File destinationFile = new File( Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), source.getLastPathSegment()); request.setDestinationUri(Uri.fromFile(destinationFile)); // Add it to the manager mDownloadManager.enqueue(request); Toast.makeText(mContext, R.string.downloads_requested, Toast.LENGTH_SHORT).show(); } else { AlertDialog.Builder builder = new AlertDialog.Builder(mContext); builder.setPositiveButton("?", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { //closeWebApplication(); } }); builder.setMessage(R.string.downloads_path_check); builder.show(); } return true; } if (url.startsWith("tel:")) { // phone call if (!PhoneState.hasPhoneCallAbility(mContext)) { Log.d(TAG, ">> This device has not phone call ability !!!"); return true; } mContext.startActivity(new Intent(Intent.ACTION_DIAL, Uri.parse(url))); } else if (url.startsWith("mailto:")) { url = url.replaceFirst("mailto:", ""); url = url.trim(); Intent i = new Intent(Intent.ACTION_SEND); i.setType("plain/text").putExtra(Intent.EXTRA_EMAIL, new String[] { url }); mContext.startActivity(i); } else if (url.startsWith("geo:")) { Intent searchAddress = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); mContext.startActivity(searchAddress); } else { // URL ?? ? ??? . dismissProgressDialog(); loadWebUrl(url); showProgressDialog(); } return true; } @JavascriptInterface public void postUrl(String url, String data) { Intent intent = new Intent(Intent.ACTION_VIEW).addCategory(Intent.CATEGORY_BROWSABLE) .setPackage("com.android.chrome") // open only chrome .setData(Uri.parse(url)); mContext.startActivity(intent); } public abstract void loadWebUrl(String url); @JavascriptInterface public abstract void updateIoTDevices(); @JavascriptInterface public abstract void onUpdateIoTDevices(String iotDevices); @JavascriptInterface public abstract boolean registerGcm(); @JavascriptInterface public abstract boolean unRegisterGcm(); public abstract void onRegistered(String gcmRegToken); public abstract void onUnRegistered(); @Override public void onPageStarted(WebView view, String url, Bitmap favicon) { super.onPageStarted(view, url, favicon); } @Override public void onPageFinished(WebView view, String url) { Log.d(TAG, "WebView client onPageFinished = " + url); super.onPageFinished(view, url); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { CookieSyncManager.getInstance().sync(); } dismissProgressDialog(); mPageLoadSuccess = true; } @Override public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { mPageLoadSuccess = false; Log.e(TAG, "WebView client onReceivedError code = " + errorCode); switch (errorCode) { case ERROR_AUTHENTICATION: // ? ? ?? case ERROR_BAD_URL: // ? URL case ERROR_CONNECT: // case ERROR_FAILED_SSL_HANDSHAKE: // SSL handshake case ERROR_FILE: // ? ? case ERROR_FILE_NOT_FOUND: // ?? ? case ERROR_HOST_LOOKUP: // ? ? ? case ERROR_IO: // ? ? case ERROR_PROXY_AUTHENTICATION: // ?? ? ?? case ERROR_REDIRECT_LOOP: // ? case ERROR_TIMEOUT: // case ERROR_TOO_MANY_REQUESTS: // ? ? ? case ERROR_UNKNOWN: // ? case ERROR_UNSUPPORTED_AUTH_SCHEME: // ?? ?? case ERROR_UNSUPPORTED_SCHEME: AlertDialog.Builder builder = new AlertDialog.Builder(mContext); builder.setPositiveButton("?", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { closeWebApplication(); } }); builder.setMessage(R.string.webview_page_loading_error); builder.show(); dismissProgressDialog(); // Default behaviour super.onReceivedError(view, errorCode, description, failingUrl); } } //////////////////////////////// /** * ? ? ??? Native ? . * window.nbplus.{methodName} ? ?. */ /** * ?? UUID . 40bytes SHA-1 value */ @JavascriptInterface public abstract String getDeviceId(); @JavascriptInterface public String getApplicationPackageName() { return mContext.getApplicationContext().getPackageName(); } /** * ? ? ?. */ @JavascriptInterface public boolean isPhoneCallAbility() { return PhoneState.hasPhoneCallAbility(mContext); } @JavascriptInterface public boolean isNetworkAvailable() { return NetworkUtils.isConnected(mContext); } @JavascriptInterface public String getIpAddress() { return NetworkUtils.getIPAddress(true); } @JavascriptInterface public void onNetworkStatusChanged(boolean connected) { String ipAddress = ""; if (connected) { ipAddress = getIpAddress(); } mWebView.loadUrl("javascript:window.onNetworkStatusChanged(" + connected + ", " + ipAddress + ");"); } /** * ? . * @param message : * @param duration : LENGTH_LONG === 1, LENGTH_SHORT === 0 */ @JavascriptInterface public void toast(String message, int duration) { if (duration != Toast.LENGTH_LONG) { duration = Toast.LENGTH_SHORT; } Toast.makeText(mContext, message, duration).show(); } @JavascriptInterface public String getLineNumber() { Log.d(TAG, "getLineNumber() called"); String phoneNumberStr = PhoneState.getLineNumber1(mContext); Log.d(TAG, ">>> PhoneState.getLineNumber1 = " + phoneNumberStr); if (StringUtils.isEmptyString(phoneNumberStr)) { return null; } if (PhoneNumberUtils.isGlobalPhoneNumber(phoneNumberStr)) { PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance(); Phonenumber.PhoneNumber phoneNumberProto; try { phoneNumberProto = phoneUtil.parse(phoneNumberStr, "KR"); return phoneUtil.format(phoneNumberProto, PhoneNumberUtil.PhoneNumberFormat.NATIONAL) .replace("-", "").replace(" ", "").replace("(", "").replace(")", ""); } catch (NumberParseException e) { Log.e(TAG, "NumberParseException was thrown: " + e.toString()); return phoneNumberStr; } } else { return phoneNumberStr; } } /** * ? ? . */ @JavascriptInterface public abstract void closeWebApplication(); //////////////////////////////// /** * ? ? ?? Native ? ? . * ? ?? function ? ? ? ? . * */ public void onOrientationChanged(int orientation) { // Use loadUrl("javascript:...") (API Level 1-18) // or evaluateJavascript() (API Level 19+) to evaluate your own JavaScript in the context of the currently-loaded Web page mWebView.loadUrl("javascript:window.onOrientationChanged(" + orientation + ");"); } public void onBackPressed() { if (!mPageLoadSuccess) { closeWebApplication(); } else { // Use loadUrl("javascript:...") (API Level 1-18) // or evaluateJavascript() (API Level 19+) to evaluate your own JavaScript in the context of the currently-loaded Web page mWebView.loadUrl("javascript:window.onBackPressed();"); } } // progress bar protected void showProgressDialog() { try { dismissProgressDialog(); mProgressDialogFragment = ProgressDialogFragment.newInstance(); mProgressDialogFragment.show(((AppCompatActivity) mContext).getSupportFragmentManager(), "progress_dialog"); } catch (Exception e) { e.printStackTrace(); } } protected void dismissProgressDialog() { try { if (mProgressDialogFragment != null) { mProgressDialogFragment.dismiss(); } mProgressDialogFragment = null; } catch (Exception e) { e.printStackTrace(); } } }