f7317aa7b6339be618497ea29e2aada8c3aa95a1
[platform/framework/web/crosswalk.git] / src / xwalk / app / android / runtime_client / src / org / xwalk / app / runtime / XWalkRuntimeClient.java
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.
4
5 package org.xwalk.app.runtime;
6
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;
13
14 import java.lang.reflect.Method;
15 import java.util.StringTokenizer;
16 import java.util.regex.Matcher;
17 import java.util.regex.Pattern;
18
19 /**
20  * This class is to encapsulate the reflection detail of
21  * invoking XWalkRuntimeView class in library APK.
22  *
23  * A web application APK should use this class in its Activity.
24  */
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;
41
42     // For instrumentation test.
43     private Method mGetTitleForTest;
44     private Method mSetCallbackForTest;
45     private Method mLoadDataForTest;
46
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.
58             return;
59         }
60         if (!compareVersion(libVersion, getVersion())) {
61             handleException(new XWalkRuntimeLibraryException(
62                     XWalkRuntimeLibraryException.XWALK_RUNTIME_LIBRARY_NOT_UP_TO_DATE_CRITICAL));
63             mInstance = null;
64             return;
65         }
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");
79     }
80
81     /**
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+]*
87      */
88     private static boolean compareVersion(String libVersion, String clientVersion) {
89         if (libVersion.equals(clientVersion)) {
90             return true;
91         }
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()) {
102                     int libValue = 0;
103                     int clientValue = 0;
104                     try {
105                         libValue = Integer.parseInt(libTokens.nextToken());
106                         clientValue = Integer.parseInt(clientTokens.nextToken());
107                     } catch (NumberFormatException e) {
108                         return false;
109                     }
110                     if (libValue == clientValue) continue;
111                     return libValue > clientValue;
112                 }
113                 return true;
114             } else {
115                 return libTokenCount > clientTokenCount;
116             }
117         }
118         return false;
119     }
120
121     @Override
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);
126
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);
133         } else {
134             if (!(e instanceof XWalkRuntimeLibraryException)) {
135                 toHandle = new XWalkRuntimeLibraryException(
136                         XWalkRuntimeLibraryException.XWALK_RUNTIME_LIBRARY_NOT_INSTALLED, e);
137             }
138         }
139         super.handleException(toHandle);
140     }
141
142     public FrameLayout get() {
143         return (FrameLayout) mInstance;
144     }
145
146     /**
147      * Get the version information of current runtime client.
148      *
149      * @return the string containing the version information.
150      */
151     public static String getVersion() {
152         return XWalkRuntimeClientVersion.XWALK_RUNTIME_CLIENT_VERSION;
153     }
154
155     /**
156      * Load a web application through the entry url. It may be
157      * a file from assets or a url from network.
158      *
159      * @param url the url of loaded html resource.
160      */
161     public void loadAppFromUrl(String url) {
162         invokeMethod(mLoadAppFromUrl, mInstance, url);
163     }
164
165     /**
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.
169      *
170      * @param manifestUrl the url of the manifest file
171      */
172     public void loadAppFromManifest(String manifestUrl) {
173         invokeMethod(mLoadAppFromManifest, mInstance, manifestUrl);
174     }
175
176     /**
177      * Tell runtime that the application is on creating. This can make runtime
178      * be aware of application life cycle.
179      */
180     public void onCreate() {
181         invokeMethod(mOnCreate, mInstance);
182     }
183
184     /**
185      * Tell runtime that the application is on starting. This can make runtime
186      * be aware of application life cycle.
187      */
188     public void onStart() {
189         invokeMethod(mOnStart, mInstance);
190     }
191
192     /**
193      * Tell runtime that the application is on resuming. This can make runtime
194      * be aware of application life cycle.
195      */
196     public void onResume() {
197         invokeMethod(mOnResume, mInstance);
198     }
199
200     /**
201      * Tell runtime that the application is on pausing. This can make runtime
202      * be aware of application life cycle.
203      */
204     public void onPause() {
205         invokeMethod(mOnPause, mInstance);
206     }
207
208     /**
209      * Tell runtime that the application is on stopping. This can make runtime
210      * be aware of application life cycle.
211      */
212     public void onStop() {
213         invokeMethod(mOnStop, mInstance);
214     }
215
216     /**
217      * Tell runtime that the application is on destroying. This can make runtime
218      * be aware of application life cycle.
219      */
220     public void onDestroy() {
221         invokeMethod(mOnDestroy, mInstance);
222     }
223
224     /**
225      * Tell runtime that one activity exists so that it can know the result code
226      * of the exit code.
227      *
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
231      */
232     public void onActivityResult(int requestCode, int resultCode, Intent data) {
233         invokeMethod(mOnActivityResult, mInstance, requestCode, resultCode, data);
234     }
235
236     /**
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. 
241      */
242     public boolean onNewIntent(Intent intent) {
243         Boolean handled = (Boolean) invokeMethod(mOnNewIntent, mInstance, intent);
244         if (handled != null) return handled;
245         return false;
246     }
247
248     /**
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.
253      *
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
257      *                   remote debugging.
258      * @return the url of web socket for remote debugging.
259      */
260     public void enableRemoteDebugging(String frontEndUrl, String socketName) {
261         invokeMethod(mEnableRemoteDebugging, mInstance, frontEndUrl, socketName);
262     }
263
264     /**
265      * Disable remote debugging so runtime can close related stuff for
266      * this feature.
267      */
268     public void disableRemoteDebugging() {
269         invokeMethod(mDisableRemoteDebugging, mInstance);
270     }
271
272     // The following functions just for instrumentation test.
273     public View getViewForTest() {
274         return (View)mInstance;
275     }
276
277     public String getTitleForTest() {
278         if (mGetTitleForTest == null) {
279             mGetTitleForTest = lookupMethod("getTitleForTest");
280         }
281
282         return (String) invokeMethod(mGetTitleForTest, mInstance);
283     }
284
285     public void setCallbackForTest(Object callback) {
286         if (mSetCallbackForTest == null) {
287             mSetCallbackForTest = lookupMethod("setCallbackForTest", Object.class);
288         }
289
290         invokeMethod(mSetCallbackForTest, mInstance, callback);
291     }
292
293     public void loadDataForTest(String data, String mimeType, boolean isBase64Encoded) {
294         if (mLoadDataForTest == null) {
295             mLoadDataForTest = lookupMethod("loadDataForTest", String.class, String.class, boolean.class);
296         }
297
298         invokeMethod(mLoadDataForTest, mInstance, data, mimeType, isBase64Encoded);
299     }
300 }