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.View;
12 import android.widget.FrameLayout;
14 import java.lang.reflect.Method;
15 import java.util.StringTokenizer;
16 import java.util.regex.Matcher;
17 import java.util.regex.Pattern;
20 * This class is to encapsulate the reflection detail of
21 * invoking XWalkRuntimeView class in library APK.
23 * A web application APK should use this class in its Activity.
25 public class XWalkRuntimeClient extends CrossPackageWrapper {
26 private final static String RUNTIME_VIEW_CLASS_NAME = "org.xwalk.runtime.XWalkRuntimeView";
27 private boolean mRuntimeLoaded = false;
28 private Object mInstance;
29 private Method mLoadAppFromUrl;
30 private Method mLoadAppFromManifest;
31 private Method mOnCreate;
32 private Method mOnStart;
33 private Method mOnResume;
34 private Method mOnPause;
35 private Method mOnStop;
36 private Method mOnDestroy;
37 private Method mOnActivityResult;
38 private Method mOnNewIntent;
39 private Method mEnableRemoteDebugging;
40 private Method mDisableRemoteDebugging;
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) {
54 // If the code executes to here and libVersion got is null, it means
55 // the library package is available but native library is not.
56 // It probably meets CPU arch mismatch, stop execution here to avoid crash.
57 // A dialog should be prompt to user for this information.
60 if (!compareVersion(libVersion, getVersion())) {
61 handleException(new XWalkRuntimeLibraryException(
62 XWalkRuntimeLibraryException.XWALK_RUNTIME_LIBRARY_NOT_UP_TO_DATE_CRITICAL));
66 mRuntimeLoaded = true;
67 mLoadAppFromUrl = lookupMethod("loadAppFromUrl", String.class);
68 mLoadAppFromManifest = lookupMethod("loadAppFromManifest", String.class);
69 mOnCreate = lookupMethod("onCreate");
70 mOnStart = lookupMethod("onStart");
71 mOnResume = lookupMethod("onResume");
72 mOnPause = lookupMethod("onPause");
73 mOnStop = lookupMethod("onStop");
74 mOnDestroy = lookupMethod("onDestroy");
75 mOnActivityResult = lookupMethod("onActivityResult", int.class, int.class, Intent.class);
76 mOnNewIntent = lookupMethod("onNewIntent", Intent.class);
77 mEnableRemoteDebugging = lookupMethod("enableRemoteDebugging", String.class, String.class);
78 mDisableRemoteDebugging = lookupMethod("disableRemoteDebugging");
82 * Compare the given versions.
83 * @param libVersion version of library apk
84 * @param clientVersion version of client
85 * @return true if library is not older than client, false otherwise or either of the version string
86 * is invalid. Valid string should be \d+[\.\d+]*
88 private static boolean compareVersion(String libVersion, String clientVersion) {
89 if (libVersion.equals(clientVersion)) {
92 Pattern version = Pattern.compile("\\d+(\\.\\d+)*");
93 Matcher lib = version.matcher(libVersion);
94 Matcher client = version.matcher(clientVersion);
95 if (lib.matches() && client.matches()) {
96 StringTokenizer libTokens = new StringTokenizer(libVersion, ".");
97 StringTokenizer clientTokens = new StringTokenizer(clientVersion, ".");
98 int libTokenCount = libTokens.countTokens();
99 int clientTokenCount = clientTokens.countTokens();
100 if (libTokenCount == clientTokenCount) {
101 while (libTokens.hasMoreTokens()) {
105 libValue = Integer.parseInt(libTokens.nextToken());
106 clientValue = Integer.parseInt(clientTokens.nextToken());
107 } catch (NumberFormatException e) {
110 if (libValue == clientValue) continue;
111 return libValue > clientValue;
115 return libTokenCount > clientTokenCount;
122 public void handleException(Exception e) {
123 // Here is for handling runtime library not found,
124 // Should never happen if runtime is embedded.
125 if (libraryIsEmbedded()) throw new RuntimeException(e);
127 // XWalkView will handle UnsatisfiedLinkError which indicates mismatch of CPU architecture.
128 // So exception here should be either library not installed for shared mode or invoke error.
129 Exception toHandle = e;
130 if (mRuntimeLoaded) {
131 toHandle = new XWalkRuntimeLibraryException(
132 XWalkRuntimeLibraryException.XWALK_RUNTIME_LIBRARY_INVOKE_FAILED, e);
134 if (!(e instanceof XWalkRuntimeLibraryException)) {
135 toHandle = new XWalkRuntimeLibraryException(
136 XWalkRuntimeLibraryException.XWALK_RUNTIME_LIBRARY_NOT_INSTALLED, e);
139 super.handleException(toHandle);
142 public FrameLayout get() {
143 return (FrameLayout) mInstance;
147 * Get the version information of current runtime client.
149 * @return the string containing the version information.
151 public static String getVersion() {
152 return XWalkRuntimeClientVersion.XWALK_RUNTIME_CLIENT_VERSION;
156 * Load a web application through the entry url. It may be
157 * a file from assets or a url from network.
159 * @param url the url of loaded html resource.
161 public void loadAppFromUrl(String url) {
162 invokeMethod(mLoadAppFromUrl, mInstance, url);
166 * Load a web application through the url of the manifest file.
167 * The manifest file typically is placed in android assets. Now it is
168 * compliant to W3C SysApps spec.
170 * @param manifestUrl the url of the manifest file
172 public void loadAppFromManifest(String manifestUrl) {
173 invokeMethod(mLoadAppFromManifest, mInstance, manifestUrl);
177 * Tell runtime that the application is on creating. This can make runtime
178 * be aware of application life cycle.
180 public void onCreate() {
181 invokeMethod(mOnCreate, mInstance);
185 * Tell runtime that the application is on starting. This can make runtime
186 * be aware of application life cycle.
188 public void onStart() {
189 invokeMethod(mOnStart, mInstance);
193 * Tell runtime that the application is on resuming. This can make runtime
194 * be aware of application life cycle.
196 public void onResume() {
197 invokeMethod(mOnResume, mInstance);
201 * Tell runtime that the application is on pausing. This can make runtime
202 * be aware of application life cycle.
204 public void onPause() {
205 invokeMethod(mOnPause, mInstance);
209 * Tell runtime that the application is on stopping. This can make runtime
210 * be aware of application life cycle.
212 public void onStop() {
213 invokeMethod(mOnStop, mInstance);
217 * Tell runtime that the application is on destroying. This can make runtime
218 * be aware of application life cycle.
220 public void onDestroy() {
221 invokeMethod(mOnDestroy, mInstance);
225 * Tell runtime that one activity exists so that it can know the result code
228 * @param requestCode the request code to identify where the result is from
229 * @param resultCode the result code of the activity
230 * @param data the data to contain the result data
232 public void onActivityResult(int requestCode, int resultCode, Intent data) {
233 invokeMethod(mOnActivityResult, mInstance, requestCode, resultCode, data);
237 * Tell runtime that the activity receive a new Intent. The Intent may contain
238 * data that runtime wants to deal with.
239 * @param intent the new coming Intent.
240 * @return boolean whether runtime consumed it.
242 public boolean onNewIntent(Intent intent) {
243 Boolean handled = (Boolean) invokeMethod(mOnNewIntent, mInstance, intent);
244 if (handled != null) return handled;
249 * Enable remote debugging for the loaded web application. The caller
250 * can set the url of debugging url. Besides, the socket name for remote
251 * debugging has to be unique so typically the string can be appended
252 * with the package name of the application.
254 * @param frontEndUrl the url of debugging url. If it's empty, then a
255 * default url will be used.
256 * @param socketName the unique socket name for setting up socket for
258 * @return the url of web socket for remote debugging.
260 public void enableRemoteDebugging(String frontEndUrl, String socketName) {
261 invokeMethod(mEnableRemoteDebugging, mInstance, frontEndUrl, socketName);
265 * Disable remote debugging so runtime can close related stuff for
268 public void disableRemoteDebugging() {
269 invokeMethod(mDisableRemoteDebugging, mInstance);
272 // The following functions just for instrumentation test.
273 public View getViewForTest() {
274 return (View)mInstance;
277 public String getTitleForTest() {
278 if (mGetTitleForTest == null) {
279 mGetTitleForTest = lookupMethod("getTitleForTest");
282 return (String) invokeMethod(mGetTitleForTest, mInstance);
285 public void setCallbackForTest(Object callback) {
286 if (mSetCallbackForTest == null) {
287 mSetCallbackForTest = lookupMethod("setCallbackForTest", Object.class);
290 invokeMethod(mSetCallbackForTest, mInstance, callback);
293 public void loadDataForTest(String data, String mimeType, boolean isBase64Encoded) {
294 if (mLoadDataForTest == null) {
295 mLoadDataForTest = lookupMethod("loadDataForTest", String.class, String.class, boolean.class);
298 invokeMethod(mLoadDataForTest, mInstance, data, mimeType, isBase64Encoded);