1 // Copyright (c) 2013 Intel Corporation. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 package org.xwalk.app.runtime;
7 import android.app.Activity;
8 import android.content.Context;
9 import android.content.Intent;
10 import android.util.AttributeSet;
11 import android.view.KeyEvent;
12 import android.view.View;
13 import android.widget.FrameLayout;
15 import java.lang.reflect.Method;
16 import java.util.StringTokenizer;
17 import java.util.regex.Matcher;
18 import java.util.regex.Pattern;
21 * This class is to encapsulate the reflection detail of
22 * invoking XWalkRuntimeView class in library APK.
24 * A web application APK should use this class in its Activity.
26 public class XWalkRuntimeClient extends CrossPackageWrapper {
27 private final static String RUNTIME_VIEW_CLASS_NAME = "org.xwalk.runtime.XWalkRuntimeView";
28 private boolean mRuntimeLoaded = false;
29 private Object mInstance;
30 private Method mLoadAppFromUrl;
31 private Method mLoadAppFromManifest;
32 private Method mOnCreate;
33 private Method mOnResume;
34 private Method mOnPause;
35 private Method mOnDestroy;
36 private Method mOnActivityResult;
37 private Method mOnNewIntent;
38 private Method mEnableRemoteDebugging;
39 private Method mDisableRemoteDebugging;
40 private Method mOnKeyUp;
42 // For instrumentation test.
43 private Method mGetTitleForTest;
44 private Method mSetCallbackForTest;
45 private Method mLoadDataForTest;
47 public XWalkRuntimeClient(Activity activity, AttributeSet attrs, CrossPackageWrapperExceptionHandler exceptionHandler) {
48 super(activity, RUNTIME_VIEW_CLASS_NAME, exceptionHandler, Activity.class, Context.class, AttributeSet.class);
49 Context libCtx = getLibraryContext();
50 mInstance = this.createInstance(activity, libCtx, attrs);
51 Method getVersion = lookupMethod("getVersion");
52 String libVersion = (String) invokeMethod(getVersion, mInstance);
53 if (libVersion == null || !compareVersion(libVersion, getVersion())) {
54 handleException(new XWalkRuntimeLibraryException(
55 XWalkRuntimeLibraryException.XWALK_RUNTIME_LIBRARY_NOT_UP_TO_DATE_CRITICAL));
59 mRuntimeLoaded = true;
60 mLoadAppFromUrl = lookupMethod("loadAppFromUrl", String.class);
61 mLoadAppFromManifest = lookupMethod("loadAppFromManifest", String.class);
62 mOnCreate = lookupMethod("onCreate");
63 mOnResume = lookupMethod("onResume");
64 mOnPause = lookupMethod("onPause");
65 mOnDestroy = lookupMethod("onDestroy");
66 mOnActivityResult = lookupMethod("onActivityResult", int.class, int.class, Intent.class);
67 mOnNewIntent = lookupMethod("onNewIntent", Intent.class);
68 mEnableRemoteDebugging = lookupMethod("enableRemoteDebugging", String.class, String.class);
69 mDisableRemoteDebugging = lookupMethod("disableRemoteDebugging");
70 mOnKeyUp = lookupMethod("onKeyUp", int.class, KeyEvent.class);
74 * Compare the given versions.
75 * @param libVersion version of library apk
76 * @param clientVersion version of client
77 * @return true if library is not older than client, false otherwise or either of the version string
78 * is invalid. Valid string should be \d+[\.\d+]*
80 private static boolean compareVersion(String libVersion, String clientVersion) {
81 if (libVersion.equals(clientVersion)) {
84 Pattern version = Pattern.compile("\\d+(\\.\\d+)*");
85 Matcher lib = version.matcher(libVersion);
86 Matcher client = version.matcher(clientVersion);
87 if (lib.matches() && client.matches()) {
88 StringTokenizer libTokens = new StringTokenizer(libVersion, ".");
89 StringTokenizer clientTokens = new StringTokenizer(clientVersion, ".");
90 int libTokenCount = libTokens.countTokens();
91 int clientTokenCount = clientTokens.countTokens();
92 if (libTokenCount == clientTokenCount) {
93 while (libTokens.hasMoreTokens()) {
97 libValue = Integer.parseInt(libTokens.nextToken());
98 clientValue = Integer.parseInt(clientTokens.nextToken());
99 } catch (NumberFormatException e) {
102 if (libValue == clientValue) continue;
103 return libValue > clientValue;
107 return libTokenCount > clientTokenCount;
114 public void handleException(Exception e) {
115 // Here is for handling runtime library not found,
116 // Should never happen if runtime is embedded.
117 if (libraryIsEmbedded()) throw new RuntimeException(e);
119 // XWalkView will handle UnsatisfiedLinkError which indicates mismatch of CPU architecture.
120 // So exception here should be either library not installed for shared mode or invoke error.
121 Exception toHandle = e;
122 if (mRuntimeLoaded) {
123 toHandle = new XWalkRuntimeLibraryException(
124 XWalkRuntimeLibraryException.XWALK_RUNTIME_LIBRARY_INVOKE_FAILED, e);
126 if (!(e instanceof XWalkRuntimeLibraryException)) {
127 toHandle = new XWalkRuntimeLibraryException(
128 XWalkRuntimeLibraryException.XWALK_RUNTIME_LIBRARY_NOT_INSTALLED, e);
131 super.handleException(toHandle);
134 public FrameLayout get() {
135 return (FrameLayout) mInstance;
139 * Get the version information of current runtime client.
141 * @return the string containing the version information.
143 public static String getVersion() {
144 return XWalkRuntimeClientVersion.XWALK_RUNTIME_CLIENT_VERSION;
148 * Load a web application through the entry url. It may be
149 * a file from assets or a url from network.
151 * @param url the url of loaded html resource.
153 public void loadAppFromUrl(String url) {
154 invokeMethod(mLoadAppFromUrl, mInstance, url);
158 * Load a web application through the url of the manifest file.
159 * The manifest file typically is placed in android assets. Now it is
160 * compliant to W3C SysApps spec.
162 * @param manifestUrl the url of the manifest file
164 public void loadAppFromManifest(String manifestUrl) {
165 invokeMethod(mLoadAppFromManifest, mInstance, manifestUrl);
169 * Tell runtime that the application is on creating. This can make runtime
170 * be aware of application life cycle.
172 public void onCreate() {
173 invokeMethod(mOnCreate, mInstance);
177 * Tell runtime that the application is on resuming. This can make runtime
178 * be aware of application life cycle.
180 public void onResume() {
181 invokeMethod(mOnResume, mInstance);
185 * Tell runtime that the application is on pausing. This can make runtime
186 * be aware of application life cycle.
188 public void onPause() {
189 invokeMethod(mOnPause, mInstance);
193 * Tell runtime that the application is on destroying. This can make runtime
194 * be aware of application life cycle.
196 public void onDestroy() {
197 invokeMethod(mOnDestroy, mInstance);
201 * Tell runtime that one activity exists so that it can know the result code
204 * @param requestCode the request code to identify where the result is from
205 * @param resultCode the result code of the activity
206 * @param data the data to contain the result data
208 public void onActivityResult(int requestCode, int resultCode, Intent data) {
209 invokeMethod(mOnActivityResult, mInstance, requestCode, resultCode, data);
213 * Tell runtime that the activity receive a new Intent. The Intent may contain
214 * data that runtime wants to deal with.
215 * @param intent the new coming Intent.
216 * @return boolean whether runtime consumed it.
218 public boolean onNewIntent(Intent intent) {
219 Boolean handled = (Boolean) invokeMethod(mOnNewIntent, mInstance, intent);
220 if (handled != null) return handled;
225 * Enable remote debugging for the loaded web application. The caller
226 * can set the url of debugging url. Besides, the socket name for remote
227 * debugging has to be unique so typically the string can be appended
228 * with the package name of the application.
230 * @param frontEndUrl the url of debugging url. If it's empty, then a
231 * default url will be used.
232 * @param socketName the unique socket name for setting up socket for
234 * @return the url of web socket for remote debugging.
236 public void enableRemoteDebugging(String frontEndUrl, String socketName) {
237 invokeMethod(mEnableRemoteDebugging, mInstance, frontEndUrl, socketName);
241 * Disable remote debugging so runtime can close related stuff for
244 public void disableRemoteDebugging() {
245 invokeMethod(mDisableRemoteDebugging, mInstance);
249 * Passdown key-up event to the runtime view.
250 * Usually meet the case of clicking on the back key.
252 public boolean onKeyUp(int keyCode, KeyEvent event) {
253 // Normally, java can handle the convertion of boolean and Boolean well,
254 // But here invokeMethod is possible to return null if runtime lib not found,
255 // Convert null to boolean will cause NullPointerException.
256 Boolean handled = (Boolean) invokeMethod(mOnKeyUp, mInstance, keyCode, event);
257 if (handled != null) return handled;
261 // The following functions just for instrumentation test.
262 public View getViewForTest() {
263 return (View)mInstance;
266 public String getTitleForTest() {
267 if (mGetTitleForTest == null) {
268 mGetTitleForTest = lookupMethod("getTitleForTest");
271 return (String) invokeMethod(mGetTitleForTest, mInstance);
274 public void setCallbackForTest(Object callback) {
275 if (mSetCallbackForTest == null) {
276 mSetCallbackForTest = lookupMethod("setCallbackForTest", Object.class);
279 invokeMethod(mSetCallbackForTest, mInstance, callback);
282 public void loadDataForTest(String data, String mimeType, boolean isBase64Encoded) {
283 if (mLoadDataForTest == null) {
284 mLoadDataForTest = lookupMethod("loadDataForTest", String.class, String.class, boolean.class);
287 invokeMethod(mLoadDataForTest, mInstance, data, mimeType, isBase64Encoded);