/* 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.app; import java.io.File; import java.util.Hashtable; import java.util.Iterator; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Pattern; import android.content.Context; import android.view.View; import android.view.ViewGroup; import android.webkit.WebSettings; import com.polyvi.xface.XSecurityPolicy; import com.polyvi.xface.XStartParams; import com.polyvi.xface.core.XAppRunningMode; import com.polyvi.xface.core.XConfiguration; import com.polyvi.xface.core.XIResourceFilter; import com.polyvi.xface.core.XISystemContext; import com.polyvi.xface.core.XIdleWatcher; import com.polyvi.xface.core.XJSNativeBridge; import com.polyvi.xface.core.XLocalMode; import com.polyvi.xface.core.XNativeToJsMessageQueue; import com.polyvi.xface.event.XAppEventHandler; import com.polyvi.xface.event.XSystemEventCenter; import com.polyvi.xface.extension.XExtensionContext; import com.polyvi.xface.extension.XExtensionManager; import com.polyvi.xface.extension.XExtensionResult; import com.polyvi.xface.plugin.api.XIWebContext; import com.polyvi.xface.plugin.api.XPluginBase; import com.polyvi.xface.plugin.api.XPluginLoader; import com.polyvi.xface.util.XConstant; 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.view.XAppWebView; /** * 用于描述一个app应用,包含app的描述信息、状态及UI界面 */ public class XApplication implements XIApplication, XIWebContext { private static final String CLASS_NAME = XApplication.class.getName(); /** 所有应用图标所在目录的名称 */ public static final String APPS_ICON_DIR_NAME = "app_icons"; public static final String TAG_EXT_PERMISSIONS = "all"; /** 系统上下文环境 */ private XISystemContext mSysContext; /** app对应的视图 */ private XAppWebView mAppView; /** 应用描述信息 */ private XAppInfo mAppInfo; /** app的workspace */ private String mWorkSpace = ""; /** 用于存放App的通信数据 */ private Map<String, Object> mDatas; /** 用于加载外部扩展 */ private XPluginLoader mPluginLoader; private boolean mIsOverrideBackbutton = false; private boolean mIsOverrideVolumeButtonDown = false; private boolean mIsOverrideVolumeButtonUp = false; private XJSNativeBridge mJsInterface; private XNativeToJsMessageQueue mJsMessageQueue; /** < 默认为本地运行模式 */ private XAppRunningMode mRunningMode = new XLocalMode(); /** app事件处理器 */ private XAppEventHandler mEvtHandler; /** 扩展管理组件 */ private XExtensionManager mExtensionManager; /** 监视app是否处于空闲状态 */ private XIdleWatcher mWatcher; /** app的安全策略 */ private XSecurityPolicy mSecurityPolicy; public XApplication(XAppInfo appInfo) { updateAppInfo(appInfo); } public XApplication(String appId) { mAppInfo = new XAppInfo(); mAppInfo.setAppId(appId); } public void init(XISystemContext sysContext, XExtensionContext ec) { mSysContext = sysContext; mDatas = new Hashtable<String, Object>(); initWorkSpace(); mPluginLoader = new XPluginLoader(this); mExtensionManager = new XExtensionManager(this, ec); mExtensionManager.loadExtensions(); initEventHandler(); } /** * 初始化事件监听器 */ protected void initEventHandler() { if (null == mEvtHandler && (null != mExtensionManager)) { mEvtHandler = new XAppEventHandler(this, mExtensionManager); } } /** * 设置事件处理器 * * @param handler */ public void setEventHandler(XAppEventHandler handler) { if (null != mEvtHandler) { XSystemEventCenter.getInstance().unregisterReceiver(mEvtHandler); } mEvtHandler = handler; } /** * 获取事件处理器 * * @return */ public XAppEventHandler getEventHandler() { return mEvtHandler; } /** * 获取app对应的view */ public XAppWebView getView() { return mAppView; } /** * 获取应用描述信息 * * @return */ public XAppInfo getAppInfo() { return mAppInfo; } /** * 设置应用配置信息 */ public void updateAppInfo(XAppInfo appInfo) { this.mAppInfo = appInfo; initAppRunningMode(); } /** * 获取应用id */ public String getAppId() { return mAppInfo.getAppId(); } /** * 获取应用视图id * * @return 应用id */ public int getViewId() { return mAppView == null ? XAppWebView.EMPTPY_VIEW_ID : mAppView .getViewId(); } /** * 将app加载到appView上面显示 * * @param url * [in] 应用的url */ public void loadAppIntoView(String url) { mAppView.loadApp(url, false); } public void loadAppIntoView(String url, boolean showWaiting) { mAppView.loadApp(url, showWaiting); } /** * 获取app的图片url * * @return url */ public String getAppIconUrl() { return mRunningMode.getIconUrl(mAppInfo); } /** * 设置app运行模式 * * @param mode * [in] app运行模式 */ public void setAppRunningMode(XAppRunningMode mode) { if (mode != null) mRunningMode = mode; } /** * 初始化app的运行模式 根据app的配置文件指定运行模式 */ private void initAppRunningMode() { setAppRunningMode(XAppRunningMode.createAppRunningMode(mAppInfo .getRunModeConfig())); } /** * 初始化应用程序的工作目录,若不存在,创建该目录,然后设置工作目录为其他用户可读 * */ private void initWorkSpace() { mWorkSpace = XConfiguration.getInstance().getAppInstallDir() + getAppId() + File.separator + XConstant.APP_WORK_DIR_NAME; File appWorkDir = new File(mWorkSpace); if (!appWorkDir.exists()) { appWorkDir.mkdirs(); } // 设置工作目录的权限为其它用户可执行 setWorkSpaceExecutableByOther(); } /** * 返回该应用程序的工作目录 * * @return 应用程序的工作目录 */ public String getWorkSpace() { return mWorkSpace; } /** * 设置工作目录的权限为其它用户可执行 */ private void setWorkSpaceExecutableByOther() { XFileUtils.setPermission(XFileUtils.EXECUTABLE_BY_OTHER, XConfiguration .getInstance().getAppInstallDir()); XFileUtils.setPermission(XFileUtils.EXECUTABLE_BY_OTHER, XConfiguration .getInstance().getAppInstallDir() + getAppId()); XFileUtils.setPermission(XFileUtils.EXECUTABLE_BY_OTHER, mWorkSpace); } /** * 返回存放该应用程序数据的目录,若不存在,创建该目录 应用对该目录没有读写权限 * * @return 存放该应用程序数据的目录 */ public String getDataDir() { String dataDirPath = XConfiguration.getInstance().getAppInstallDir() + getAppId() + File.separator + XConstant.APP_DATA_DIR_NAME; File appDataDir = new File(dataDirPath); if (!appDataDir.exists()) { appDataDir.mkdirs(); } return dataDirPath; } /** * 获取资源迭代器 * * @param filter * 安全资源过滤器 * @return */ public Iterator<byte[]> getResourceIterator(XIResourceFilter filter) { return mRunningMode.createResourceIterator(this, filter); } /** * 当前app是否是活动状态的 * */ public boolean isActive() { return null != mAppView; } /** * 设置backbutton是否被重写 * * @param overrideBackbutton * 为true表示要重写 为false表示不重写 * */ public void setOverrideBackbutton(boolean overrideBackbutton) { mIsOverrideBackbutton = overrideBackbutton; } /** * 设置volume button down是否被重写 * * @param overrideVolumeButtonDown * 为true表示要重写 为false表示不重写 * */ public void setOverrideVolumeButtonDown(boolean overrideVolumeButtonDown) { mIsOverrideVolumeButtonDown = overrideVolumeButtonDown; } /** * 设置volume button up是否被重写 * * @param overrideVolumeButtonUp * 为true表示要重写 为false表示不重写 * */ public void setOverrideVolumeButtonUp(boolean overrideVolumeButtonUp) { mIsOverrideVolumeButtonUp = overrideVolumeButtonUp; } /** * 得到backbutton是否被重写 * * @return 返回true表示被重写 返回false表示没有重写 * */ public boolean isOverrideBackbutton() { return mIsOverrideBackbutton; } /** * 得到volume button down是否被重写 * * @return 返回true表示被重写 返回false表示没有重写 * */ public boolean isOverrideVolumeButtonDown() { return mIsOverrideVolumeButtonDown; } /** * 得到volume button up是否被重写 * * @return 返回true表示被重写 返回false表示没有重写 * */ public boolean isOverrideVolumeButtonUp() { return mIsOverrideVolumeButtonUp; } /** * 存放app的数据的数据 * * @param key * @param value */ public void setData(String key, Object value) { mDatas.put(key, value); } /** * 删除数据 * * @param key */ public void removeData(String key) { mDatas.remove(key); } /** * 获得数据 * * @param key * 键值 * @return */ public Object getData(String key) { return mDatas.get(key); } /** * 创建app对应的所暴露给js的接口 注意,每个app仅仅唯一对应一个exec接口(与之对应的是么个app中的每个扩展对应一个exec接口) * * @param em * @return */ public XJSNativeBridge createExposedJsInterface() { mJsMessageQueue = new XNativeToJsMessageQueue(mAppView, mSysContext); return mJsInterface = new XJSNativeBridge(mExtensionManager, mJsMessageQueue); } public String getIntalledDir() { return XConfiguration.getInstance().getAppInstallDir() + mAppInfo.getAppId() + File.separator; } /** * 把传过来的URL通过app的方式加载或者在浏览器上加载,这取决于参数openExternal的值. * * @param url * 要加载的URL * @param openExternal * url加载方式<br/> * - true 通过浏览器加载<br/> * - false 通过app的方式加载<br/> * @param clearHistory * 是否清除加载以前URL留下的的历史记录 <br/> * - true 表示清除以前加载URL留下的的历史记录<br/> * - false 表示不清除以前加载URL留下的的历史记录<br/> * @param params * 新app的参数,目前未用 * @param context * 应用context */ public void loadUrl(String url, boolean openExternal, boolean clearHistory, Context context) { mAppView.loadUrl(url, openExternal, clearHistory, context); } /** * 返回到前一页面,函数功能就和android上的back按钮相同。 */ public void backHistory() { mAppView.backHistory(); } /** * 清理页面浏览历史. */ public void clearHistory() { mAppView.clearHistory(); } /** * 清理页面缓存. * @param includeDiskFile 是否包含磁盘文件 */ public void clearCache(boolean includeDiskFile) { mAppView.clearCache(includeDiskFile); } /** * 卸载应用的缓存数据,例如:离线应用的缓存、http缓存、localStorage信息 * * @param context */ public void releaseData(Context context) { // TODO: 要清除http cache mRunningMode.clearAppData(this, context); } /** * 加载错误显示页面 */ public void loadErrorPage() { String errorPageUrl = XConstant.FILE_SCHEME + getDataDir() + File.separator + XConstant.ERROR_PAGE_NAME; loadAppIntoView(errorPageUrl); } @Override public boolean start(XStartParams params) { String pageEntry = null; String startData = null; if (null != params) { pageEntry = params.pageEntry; startData = params.data; } if (!XStringUtils.isEmptyString(pageEntry)) { mAppInfo.setEntry(pageEntry); } if (!XStringUtils.isEmptyString(startData)) { setData(XConstant.TAG_APP_START_PARAMS, startData); } createView(); loadPlugin(mSysContext, mExtensionManager.getExtensionContext()); mAppView.bindJSNativeBridge(getPlugins()); mEvtHandler.registerSystemEventReceiver(); checkEngineVersionRequired(this); if(null == mSecurityPolicy) { //安全策略为空,则采用系统默认的安全策略 mSecurityPolicy = mSysContext.getSecurityPolicy(); } mRunningMode.loadApp(this, mSecurityPolicy); return true; } @Override public boolean close() { if (null != mWatcher) { mWatcher.stop(); } mEvtHandler.unRegisterSystemEventReceiver(); unloadPlugins(); // TODO:关闭app事件不需要分发给Ext,Ext仅仅关注页面切换 if (null != mExtensionManager) { mExtensionManager.onAppClosed(); } closeView(); return mSysContext.getSecurityPolicy().checkAppClose(this); } public String getBaseUrl() { return mRunningMode.getAppUrl(this); } /** * 加载插件 * * @param context * @return */ public ConcurrentHashMap<String, XPluginBase> loadPlugin( XISystemContext context, XExtensionContext extContext) { return mPluginLoader.loadPlugins(context, extContext); } /** * 卸载插件 */ public void unloadPlugins() { mPluginLoader.unloadPlugins(); } /** * 得到加载器 * * @return */ public ConcurrentHashMap<String, XPluginBase> getPlugins() { return mPluginLoader.getPlugins(); } @Override public XApplication getApplication() { return this; } /** * 尝试显示app视图 */ public void tryShowView() { mSysContext.waitingDialogForAppStartFinished(); if (!mSysContext.isSplashShowing()) { showView(); } } /** * 启动监视器 */ public void startIdleWatcher(long interval, Runnable task) { if (null != mWatcher) { stopIdleWatcher(); } mWatcher = new XIdleWatcher(); mWatcher.start(interval, task); } /** * 停止监视 */ public void stopIdleWatcher() { if (mWatcher != null) { mWatcher.stop(); mWatcher = null; } } /** * 重置IdleWatcher */ public void resetIdleWatcher() { if (null != mWatcher) { mWatcher.notifyOperatered(); } } public void onUninstall() { mExtensionManager.onAppUninstalled(); } public XExtensionManager getExtensionManager() { return mExtensionManager; } /** * 关闭app view */ private void closeView() { mAppView.setVisibility(View.INVISIBLE); ViewGroup parent = (ViewGroup) mAppView.getParent(); parent.removeView(mAppView); mAppView.loadUrl("about:blank"); int childCount = parent.getChildCount(); if (0 != childCount) { View focusedView = parent.getChildAt(childCount - 1); focusedView.requestFocus(); } clearView(); } /** * 清空app View */ private void clearView() { if (null != mAppView) { this.mAppView.unRegisterAppEventListener(mEvtHandler); this.mAppView.setValid(false); } mAppView = null; } /** * 创建app view * * @return */ public void createView() { clearView(); mAppView = new XAppWebView(mSysContext, this); WebSettings settings = mAppView.getSettings(); mRunningMode.setAppCachedPolicy(settings); mAppView.exposeJsInterface(createExposedJsInterface()); mSysContext.addView(mAppView); mAppView.setVisibility(View.INVISIBLE); this.mAppView.registerAppEventListener(mEvtHandler); this.mAppView.setValid(true); } /** * 显示视图 */ private void showView() { mAppView.setVisibility(View.VISIBLE); boolean success = mAppView.requestFocus(); if (!success) { XLog.w(CLASS_NAME, "WebView request focus failed!"); } } /** * 获取系统上下文环境 * * @return */ public XISystemContext getSystemContext() { return mSysContext; } /** * 获取jsNative桥 * * @returnT */ public XJSNativeBridge getJSNativeBridge() { return mJsInterface; } @Override public void sendJavascript(String statement) { mJsMessageQueue.addJavaScript(statement); } @Override public void sendExtensionResult(XExtensionResult result, String callbackId) { mJsMessageQueue.addPluginResult(result, callbackId); } /** * 重置js消息队列 */ public void resetJsMessageQueue() { mJsMessageQueue.reset(); } /** * 加载js * * @param statement * js参数 */ public void loadJavascript(String statement) { StringBuffer sb = new StringBuffer(); sb.append("javascript:"); sb.append(statement); this.getView().loadUrl(sb.toString()); } /** * 检查引擎版本是否符合应用最低引擎版本要求 * *@param requiredVersion:应用需要的引擎版本 */ private void checkEngineVersionRequired(XApplication app) { String requiredVersion = app.getAppInfo().getEngineRequired(); String engineVersion = XConfiguration.getInstance().readEngineVersion(); if(XStringUtils.isEmptyString(requiredVersion)) { XLog.w(CLASS_NAME, "app required engine version is not configed"); return; } if(XStringUtils.isEmptyString(engineVersion)) { XLog.w(CLASS_NAME, "engine version is not configed"); return; } if(isVersionValid(requiredVersion) && isVersionValid(engineVersion) && requiredVersion.compareToIgnoreCase(engineVersion) > 0) { app.getSystemContext().toast(XStrings.getInstance().getString( XStrings.ENGINE_VERSION_NOT_MATCH_WARN)); } } /** * 检查配置的版本号是否符合规则: * 目前规则是: * 一位数字开始 * 至少2段,至多3段。 * 每段必须数字 * 3.1.2 3.0.0 * @param version * @return */ private boolean isVersionValid(String version) { if(XStringUtils.isEmptyString(version)) { return false; } boolean isValid = Pattern.matches("^\\d{1}([.]\\d{1,2}){1,2}$", version); if(!isValid) { XLog.w(CLASS_NAME, "version not invalid"); } return isValid; } /** * 设置安全策略 * @param policy */ public void setAppSecurityPolicy(XSecurityPolicy policy) { mSecurityPolicy = policy; } }