f5d686188176b63e3414fba27f095b90edf4f063
[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.KeyEvent;
12 import android.view.View;
13 import android.widget.FrameLayout;
14
15 import java.lang.reflect.Method;
16 import java.util.StringTokenizer;
17 import java.util.regex.Matcher;
18 import java.util.regex.Pattern;
19
20 /**
21  * This class is to encapsulate the reflection detail of
22  * invoking XWalkRuntimeView class in library APK.
23  *
24  * A web application APK should use this class in its Activity.
25  */
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;
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 || !compareVersion(libVersion, getVersion())) {
54             handleException(new XWalkRuntimeLibraryException(
55                     XWalkRuntimeLibraryException.XWALK_RUNTIME_LIBRARY_NOT_UP_TO_DATE_CRITICAL));
56             mInstance = null;
57             return;
58         }
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);
71     }
72
73     /**
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+]*
79      */
80     private static boolean compareVersion(String libVersion, String clientVersion) {
81         if (libVersion.equals(clientVersion)) {
82             return true;
83         }
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()) {
94                     int libValue = 0;
95                     int clientValue = 0;
96                     try {
97                         libValue = Integer.parseInt(libTokens.nextToken());
98                         clientValue = Integer.parseInt(clientTokens.nextToken());
99                     } catch (NumberFormatException e) {
100                         return false;
101                     }
102                     if (libValue == clientValue) continue;
103                     return libValue > clientValue;
104                 }
105                 return true;
106             } else {
107                 return libTokenCount > clientTokenCount;
108             }
109         }
110         return false;
111     }
112
113     @Override
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);
118
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);
125         } else {
126             if (!(e instanceof XWalkRuntimeLibraryException)) {
127                 toHandle = new XWalkRuntimeLibraryException(
128                         XWalkRuntimeLibraryException.XWALK_RUNTIME_LIBRARY_NOT_INSTALLED, e);
129             }
130         }
131         super.handleException(toHandle);
132     }
133
134     public FrameLayout get() {
135         return (FrameLayout) mInstance;
136     }
137
138     /**
139      * Get the version information of current runtime client.
140      *
141      * @return the string containing the version information.
142      */
143     public static String getVersion() {
144         return XWalkRuntimeClientVersion.XWALK_RUNTIME_CLIENT_VERSION;
145     }
146
147     /**
148      * Load a web application through the entry url. It may be
149      * a file from assets or a url from network.
150      *
151      * @param url the url of loaded html resource.
152      */
153     public void loadAppFromUrl(String url) {
154         invokeMethod(mLoadAppFromUrl, mInstance, url);
155     }
156
157     /**
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.
161      *
162      * @param manifestUrl the url of the manifest file
163      */
164     public void loadAppFromManifest(String manifestUrl) {
165         invokeMethod(mLoadAppFromManifest, mInstance, manifestUrl);
166     }
167
168     /**
169      * Tell runtime that the application is on creating. This can make runtime
170      * be aware of application life cycle.
171      */
172     public void onCreate() {
173         invokeMethod(mOnCreate, mInstance);
174     }
175
176     /**
177      * Tell runtime that the application is on resuming. This can make runtime
178      * be aware of application life cycle.
179      */
180     public void onResume() {
181         invokeMethod(mOnResume, mInstance);
182     }
183
184     /**
185      * Tell runtime that the application is on pausing. This can make runtime
186      * be aware of application life cycle.
187      */
188     public void onPause() {
189         invokeMethod(mOnPause, mInstance);
190     }
191
192     /**
193      * Tell runtime that the application is on destroying. This can make runtime
194      * be aware of application life cycle.
195      */
196     public void onDestroy() {
197         invokeMethod(mOnDestroy, mInstance);
198     }
199
200     /**
201      * Tell runtime that one activity exists so that it can know the result code
202      * of the exit code.
203      *
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
207      */
208     public void onActivityResult(int requestCode, int resultCode, Intent data) {
209         invokeMethod(mOnActivityResult, mInstance, requestCode, resultCode, data);
210     }
211
212     /**
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. 
217      */
218     public boolean onNewIntent(Intent intent) {
219         Boolean handled = (Boolean) invokeMethod(mOnNewIntent, mInstance, intent);
220         if (handled != null) return handled;
221         return false;
222     }
223
224     /**
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.
229      *
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
233      *                   remote debugging.
234      * @return the url of web socket for remote debugging.
235      */
236     public void enableRemoteDebugging(String frontEndUrl, String socketName) {
237         invokeMethod(mEnableRemoteDebugging, mInstance, frontEndUrl, socketName);
238     }
239
240     /**
241      * Disable remote debugging so runtime can close related stuff for
242      * this feature.
243      */
244     public void disableRemoteDebugging() {
245         invokeMethod(mDisableRemoteDebugging, mInstance);
246     }
247
248     /**
249      * Passdown key-up event to the runtime view.
250      * Usually meet the case of clicking on the back key.
251      */
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;
258         return false;
259     }
260
261     // The following functions just for instrumentation test.
262     public View getViewForTest() {
263         return (View)mInstance;
264     }
265
266     public String getTitleForTest() {
267         if (mGetTitleForTest == null) {
268             mGetTitleForTest = lookupMethod("getTitleForTest");
269         }
270
271         return (String) invokeMethod(mGetTitleForTest, mInstance);
272     }
273
274     public void setCallbackForTest(Object callback) {
275         if (mSetCallbackForTest == null) {
276             mSetCallbackForTest = lookupMethod("setCallbackForTest", Object.class);
277         }
278
279         invokeMethod(mSetCallbackForTest, mInstance, callback);
280     }
281
282     public void loadDataForTest(String data, String mimeType, boolean isBase64Encoded) {
283         if (mLoadDataForTest == null) {
284             mLoadDataForTest = lookupMethod("loadDataForTest", String.class, String.class, boolean.class);
285         }
286
287         invokeMethod(mLoadDataForTest, mInstance, data, mimeType, isBase64Encoded);
288     }
289 }