/*
Copyright 2012-2013, Polyvi Inc. (http://polyvi.github.io/openxface)
This program is distributed under the terms of the GNU General Public License.
This file is part of xFace.
xFace is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
xFace is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with xFace. If not, see <http://www.gnu.org/licenses/>.
*/
package com.polyvi.xface.view;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebChromeClient.CustomViewCallback;
import android.widget.FrameLayout;
import com.polyvi.xface.app.XApplication;
import com.polyvi.xface.app.XWhiteList;
import com.polyvi.xface.core.XConfiguration;
import com.polyvi.xface.core.XISystemContext;
import com.polyvi.xface.core.XJSNativeBridge;
import com.polyvi.xface.event.XIWebAppEventListener;
import com.polyvi.xface.plugin.api.XPluginBase;
import com.polyvi.xface.util.XFileUtils;
import com.polyvi.xface.util.XLog;
import com.polyvi.xface.util.XStringUtils;
import com.polyvi.xface.util.XStrings;
import com.polyvi.xface.util.XUtils;
/**
* application对应的WebView封装,用于显示application
*/
public class XAppWebView extends WebView{
public static final String CLASS_NAME = "XAppWebView";
public static final int EMPTPY_VIEW_ID = Integer.MAX_VALUE;
private static final String JS_INTERFACE_NAME = "_addJavaInterface";
private int mViewId; /** < view id,每个view的id唯一 */
private XIWebAppEventListener mWebAppEventListener; /** app事件监听器 */
private XTouchEventHandler mTouchEventHandler; /** < 用来处理touch事件,目前处理了双击时的缩放 */
private XISystemContext mSystemContext;
private boolean mIsJsInitFinshed = false;/**< js是否加载完成的标志*/
/** 该视图是否有效 */
private boolean mIsValid;
private XApplication mOwnerApp;
/** 自定义HTML5视频视图 */
private View mCustomVideoView;
private CustomViewCallback mCustomViewCallback;
/** HTML5视频视图的布局 */
static final FrameLayout.LayoutParams COVER_SCREEN_GRAVITY_CENTER =
new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT,
Gravity.CENTER);
public XAppWebView(XISystemContext systemContext,
XApplication app) {
super(systemContext.getContext());
mOwnerApp = app;
mViewId = XUtils.generateRandomId();
mTouchEventHandler = new XTouchEventHandler();
mSystemContext = systemContext;
init();
}
/**
* view的初始化 主要设置一些view的回调函数以及设置view本身的属性
*/
private void init() {
WebSettings settings = getSettings();
settings.setJavaScriptEnabled(true);
settings.setJavaScriptCanOpenWindowsAutomatically(true);
settings.setDomStorageEnabled(true);
settings.setDatabaseEnabled(true);
String databasePath = this.getContext().getApplicationContext()
.getDir("database", Context.MODE_PRIVATE).getPath();
settings.setDatabasePath(databasePath);
// 用于实现用户手动的缩放
settings.setBuiltInZoomControls(false);
// 使viewport中的width标签生效
settings.setUseWideViewPort(true);
settings.setLoadWithOverviewMode(true);
settings.setNeedInitialFocus(false);
setWebChromeClient(new XWebChromeClient(getContext(), this));
setWebViewClient(new XWebViewClient(mSystemContext, this));
this.requestFocus();
this.setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY);
// TODO: 其它的初始化工作
}
public void exposeJsInterface( XJSNativeBridge js )
{
//当在2.3的模拟器上使用的时候 会有问题 所以不使用addJavaInterface
if(Build.VERSION.RELEASE.startsWith("2.3") && Build.MANUFACTURER.equals("unknown"))
{
return;
}
this.addJavascriptInterface(js, JS_INTERFACE_NAME);
}
/**
* 处理双击以及多次连击时自动放大的情况
*
* @param event 交互事件
*
* @return 如果事件被处理了返回true,否则返回false
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
if( event.getAction() == MotionEvent.ACTION_DOWN) {
mOwnerApp.resetIdleWatcher();
}
mTouchEventHandler.handleTouchEvent(event, this);
return super.onTouchEvent(event);
}
/**
* 加载app
*/
private void loadApp(String url) {
/**白名单检查*/
XWhiteList whiteList = mOwnerApp.getAppInfo().getWhiteList();
if(!url.startsWith("file://") &&
(null != whiteList && !whiteList.isUrlWhiteListed(url))) {
XLog.e(CLASS_NAME, "url is not in white list");
return;
}
loadTimeoutCheck();
this.loadUrl(url);
this.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
// 重载onLongClick,阻止Android 4.0系统中出现默认text selection行为.
// 经过测试,不影响touchstart, touchmove, touchend等events
return true;
}
});
}
private void loadTimeoutCheck() {
String timeoutValue = XConfiguration.getInstance().readLoadUrlTimeout();
if (!XStringUtils.isEmptyString(timeoutValue)) {
final int loadUrlTimeoutValue = Integer.parseInt(timeoutValue);
final Runnable timeoutCheck = new Runnable() {
public void run() {
try {
synchronized (this) {
wait(loadUrlTimeoutValue);
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (!isJsInitFinished()) {
mSystemContext.runOnUiThread(new Runnable() {
public void run() {
mSystemContext
.toast(XStrings
.getInstance()
.getString(
XStrings.NO_XFACE_JS_MESSAGE));
mSystemContext.waitingDialogForAppStartFinished();
setVisibility(View.VISIBLE);
requestFocus();
}
});
}
}
}
};
Thread thread = new Thread(timeoutCheck);
thread.start();
}
}
public int getViewId() {
return mViewId;
}
/**
* 设置适配是否完成标志
* @param adapt
* adapt为true则表示适配完成,false表示适配未完成这时不响应双击时将viewport设置为false
*/
public void setAdapated(boolean adapt)
{
mTouchEventHandler.setAdapated(adapt);
}
public void setJsInitFinished(boolean isFinished){
mIsJsInitFinshed = isFinished;
}
public boolean isJsInitFinished(){
return mIsJsInitFinshed;
}
public void setValid(boolean isValid) {
this.mIsValid = isValid;
}
public boolean isValid() {
return mIsValid;
}
public void loadUrl(String url, boolean openExternal,boolean clearHistory,Context context) {
XLog.d(CLASS_NAME, "loadUrl(%s, %b, %b, HashMap", url, openExternal, clearHistory);
// If clearing history
if (clearHistory) {
this.clearHistory();
}
// 通过app方式加载url
if (!openExternal) {
XWhiteList whiteList = mOwnerApp.getAppInfo().getWhiteList();
if (url.startsWith("file://") || whiteList.isUrlWhiteListed(url)) {
// 加载app
this.loadApp(url);
return;
}
else {
XLog.w(CLASS_NAME, "loadUrl: Cannot load URL by app, Loading into browser instead. (URL=" + url + ")");
}
}
// 由于前面加载url未成功所以通过浏览器方式加载url
try {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.parse(url), XFileUtils.getMIMEType(url));
context.startActivity(intent);
} catch (android.content.ActivityNotFoundException e) {
XLog.e(CLASS_NAME, "Error loading url " + url, e);
}
}
public boolean backHistory() {
if (super.canGoBack()) {
super.goBack();
return true;
}
return false;
}
public void clearHistory() {
super.clearHistory();
}
public void clearCache(boolean includeDiskFile) {
super.clearCache(includeDiskFile);
}
public void loadApp(String url, boolean showWaiting) {
loadApp(url);
if( showWaiting ) {
mSystemContext.waitingDialogForAppStart();
}
}
public void bindJSNativeBridge(
ConcurrentHashMap<String, XPluginBase> plugin) {
Iterator<Map.Entry<String, XPluginBase>> iter = plugin.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<String, XPluginBase> entry = iter.next();
this.addJavascriptInterface(entry.getValue(), entry.getKey());
}
}
/**
* 获取ownerApp
* @return
*/
public XApplication getOwnerApp() {
return mOwnerApp;
}
public void registerAppEventListener(XIWebAppEventListener listener) {
mWebAppEventListener =listener;
}
public void unRegisterAppEventListener(XIWebAppEventListener listener) {
mWebAppEventListener = null;
}
public XIWebAppEventListener getAppEventListener() {
return mWebAppEventListener;
}
/**
* 显示HTML5视频自定义视图
*
* @param view
* @param callback
*/
public void showCustomView(View view, CustomViewCallback callback) {
// 如果存在一个view,则立即消除新建的view
XLog.d(CLASS_NAME, "Showing Custom Video View");
if (mCustomVideoView != null) {
callback.onCustomViewHidden();
return;
}
mCustomVideoView = view;
mCustomViewCallback = callback;
// 在它的container中增加CustomVideoView
ViewGroup parent = (ViewGroup) this.getParent();
parent.addView(view, COVER_SCREEN_GRAVITY_CENTER);
// 隐藏CustomVideoView
this.setVisibility(View.GONE);
// 显示container
parent.setVisibility(View.VISIBLE);
parent.bringToFront();
}
/**
* 隐藏HTML5视频自定义视图
*/
public boolean hideCustomView() {
XLog.d(CLASS_NAME, "Hidding Custom Video View");
if (mCustomVideoView == null) {
return false;
}
// 隐藏mCustomVideoView
mCustomVideoView.setVisibility(View.GONE);
// 从mCustomVideoView的container中删除它
ViewGroup parent = (ViewGroup) this.getParent();
parent.removeView(mCustomVideoView);
mCustomVideoView = null;
mCustomViewCallback.onCustomViewHidden();
// 显示content view.
this.setVisibility(View.VISIBLE);
return true;
}
}