Upstream version 5.34.92.0
[platform/framework/web/crosswalk.git] / src / xwalk / runtime / android / java / src / org / xwalk / runtime / extension / api / presentation / PresentationExtension.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.runtime.extension.api.presentation;
6
7 import android.content.Context;
8 import android.os.Build;
9 import android.os.Bundle;
10 import android.util.JsonReader;
11 import android.util.JsonWriter;
12 import android.util.Log;
13 import android.view.Display;
14 import android.view.ViewGroup;
15
16 import java.io.IOException;
17 import java.io.StringReader;
18 import java.io.StringWriter;
19 import java.net.URI;
20 import java.net.URISyntaxException;
21
22 import org.chromium.base.ThreadUtils;
23
24 import org.xwalk.runtime.extension.api.XWalkDisplayManager;
25 import org.xwalk.runtime.extension.XWalkExtension;
26 import org.xwalk.runtime.extension.XWalkExtensionContext;
27
28 /**
29  * A XWalk extension for Presentation API implementation on Android.
30  */
31 public class PresentationExtension extends XWalkExtension {
32     public final static String TAG = "PresentationExtension";
33     public final static String JS_API_PATH = "jsapi/presentation_api.js";
34     public final static String NAME = "navigator.presentation";
35
36     // Tags:
37     private final static String TAG_BASE_URL = "baseUrl";
38     private final static String TAG_CMD = "cmd";
39     private final static String TAG_DATA = "data";
40     private final static String TAG_REQUEST_ID = "requestId";
41     private final static String TAG_URL = "url";
42
43     // Command messages:
44     private final static String CMD_DISPLAY_AVAILABLE_CHANGE = "DisplayAvailableChange";
45     private final static String CMD_QUERY_DISPLAY_AVAILABILITY = "QueryDisplayAvailability";
46     private final static String CMD_REQUEST_SHOW = "RequestShow";
47     private final static String CMD_SHOW_SUCCEEDED = "ShowSucceeded";
48     private final static String CMD_SHOW_FAILED = "ShowFailed";
49
50     // Error messages:
51     private final static String ERROR_INVALID_ACCESS = "InvalidAccessError";
52     private final static String ERROR_INVALID_PARAMETER = "InvalidParameterError";
53     private final static String ERROR_INVALID_STATE = "InvalidStateError";
54     private final static String ERROR_NOT_FOUND = "NotFoundError";
55     private final static String ERROR_NOT_SUPPORTED = "NotSupportedError";
56
57     private XWalkDisplayManager mDisplayManager;
58
59     // The number of available presentation displays on the system.
60     private int mAvailableDisplayCount = 0;
61
62     // The presentation content and view to be showed on the secondary display.
63     // Currently, only one presentation is allowed to show at the same time.
64     private XWalkPresentationContent mPresentationContent;
65     private XWalkPresentationContent.PresentationDelegate mPresentationDelegate;
66     private PresentationView mPresentationView;
67
68     /**
69      * Listens for the secondary display arrival and removal.
70      *
71      * We rely on onDisplayAdded/onDisplayRemoved callback to trigger the display
72      * availability change event. The presentation display becomes available if
73      * the first secondary display is arrived, and becomes unavailable if one
74      * of the last secondary display is removed.
75      *
76      * Note the display id is a system-wide unique number for each physical connection.
77      * It means that for the same display device, the display id assigned by the system
78      * would be different if it is re-connected again.
79      */
80     private final XWalkDisplayManager.DisplayListener mDisplayListener =
81             new XWalkDisplayManager.DisplayListener() {
82         @Override
83         public void onDisplayAdded(int displayId) {
84             ++mAvailableDisplayCount;
85
86             // Notify that the secondary display for presentation show becomes
87             // available now if the first one is added.
88             if (mAvailableDisplayCount == 1) notifyAvailabilityChanged(true);
89         }
90
91         @Override
92         public void onDisplayRemoved(int displayId) {
93             --mAvailableDisplayCount;
94
95             // Notify that the secondary display for presentation show becomes
96             // unavailable now if the last one is removed already.
97             if (mAvailableDisplayCount == 0) {
98                 notifyAvailabilityChanged(false);
99                 // Destroy the presentation content if there is no available secondary display
100                 // any more.
101                 if (mPresentationContent != null) {
102                     mPresentationContent.close();
103                     mPresentationContent = null;
104                 }
105             }
106         }
107
108         @Override
109         public void onDisplayChanged(int displayId) {
110             // TODO(hmin): Figure out the behaviour when the display is changed.
111         }
112     };
113
114     public PresentationExtension(String name, String jsApi, XWalkExtensionContext context) {
115         super(name, jsApi, context);
116
117         mDisplayManager = XWalkDisplayManager.getInstance(context.getContext());
118         Display[] displays = mDisplayManager.getPresentationDisplays();
119         mAvailableDisplayCount = displays.length;
120     }
121
122     private Display getPreferredDisplay() {
123         Display[] displays = mDisplayManager.getPresentationDisplays();
124         if (displays.length > 0) return displays[0];
125         else return null;
126     }
127
128     private void notifyAvailabilityChanged(boolean isAvailable) {
129         StringWriter contents = new StringWriter();
130         JsonWriter writer = new JsonWriter(contents);
131
132         try {
133             writer.beginObject();
134             writer.name(TAG_CMD).value(CMD_DISPLAY_AVAILABLE_CHANGE);
135             writer.name(TAG_DATA).value(isAvailable);
136             writer.endObject();
137             writer.close();
138
139             broadcastMessage(contents.toString());
140         } catch (IOException e) {
141             Log.e(TAG, "Error: " + e.toString());
142         }
143     }
144
145     private void notifyRequestShowSucceed(int instanceId, int requestId, int presentationId) {
146         StringWriter contents = new StringWriter();
147         JsonWriter writer = new JsonWriter(contents);
148
149         try {
150             writer.beginObject();
151             writer.name(TAG_CMD).value(CMD_SHOW_SUCCEEDED);
152             writer.name(TAG_REQUEST_ID).value(requestId);
153             writer.name(TAG_DATA).value(presentationId);
154             writer.endObject();
155             writer.close();
156
157             postMessage(instanceId, contents.toString());
158         } catch (IOException e) {
159             Log.e(TAG, "Error: " + e.toString());
160         }
161     }
162
163     private void notifyRequestShowFail(int instanceId, int requestId, String errorMessage) {
164         StringWriter contents = new StringWriter();
165         JsonWriter writer = new JsonWriter(contents);
166
167         try {
168             writer.beginObject();
169             writer.name(TAG_CMD).value(CMD_SHOW_FAILED);
170             writer.name(TAG_REQUEST_ID).value(requestId);
171             writer.name(TAG_DATA).value(errorMessage);
172             writer.endObject();
173             writer.close();
174
175             postMessage(instanceId, contents.toString());
176         } catch (IOException e) {
177             Log.e(TAG, "Error: " + e.toString());
178         }
179     }
180
181     @Override
182     public void onMessage(int instanceId, String message) {
183         StringReader contents = new StringReader(message);
184         JsonReader reader = new JsonReader(contents);
185
186         int requestId = -1;
187         String cmd = null;
188         String url = null;
189         String baseUrl = null;
190         try {
191             reader.beginObject();
192             while (reader.hasNext()) {
193                 String name = reader.nextName();
194                 if (name.equals(TAG_CMD)) {
195                     cmd = reader.nextString();
196                 } else if (name.equals(TAG_REQUEST_ID)) {
197                     requestId = reader.nextInt();
198                 } else if (name.equals(TAG_URL)) {
199                     url = reader.nextString();
200                 } else if (name.equals(TAG_BASE_URL)) {
201                     baseUrl = reader.nextString();
202                 } else {
203                     reader.skipValue();
204                 }
205             }
206             reader.endObject();
207             reader.close();
208
209             if (cmd != null && cmd.equals(CMD_REQUEST_SHOW) && requestId >= 0) {
210                 handleRequestShow(instanceId, requestId, url, baseUrl);
211             }
212         } catch (IOException e) {
213             Log.d(TAG, "Error: " + e);
214         }
215     }
216
217     private void handleRequestShow(final int instanceId, final int requestId,
218                                    final String url, final String baseUrl) {
219         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
220             notifyRequestShowFail(instanceId, requestId, ERROR_NOT_SUPPORTED);
221             return;
222         }
223
224         if (mAvailableDisplayCount == 0) {
225             Log.d(TAG, "No available presentation display is found.");
226             notifyRequestShowFail(instanceId, requestId, ERROR_NOT_FOUND);
227             return;
228         }
229
230         // We have to post the runnable task for presentation view creation to
231         // UI thread since extension API is not running on UI thread.
232         ThreadUtils.runOnUiThread(new Runnable() {
233             @Override
234             public void run() {
235                 Display preferredDisplay = getPreferredDisplay();
236
237                 // No availble display is found.
238                 if (preferredDisplay == null) {
239                     notifyRequestShowFail(instanceId, requestId, ERROR_NOT_FOUND);
240                     return;
241                 }
242
243                 // Only one presentation is allowed to show on the presentation display. Notify
244                 // the JS side that an error occurs if there is already one presentation showed.
245                 if (mPresentationContent != null) {
246                     notifyRequestShowFail(instanceId, requestId, ERROR_INVALID_ACCESS);
247                     return;
248                 }
249
250                 // Check the url passed to requestShow.
251                 // If it's relative, combine it with baseUrl to make it abslute.
252                 // If the url is invalid, notify the JS side ERROR_INVALID_PARAMETER exception.
253                 String targetUrl = url;
254                 URI targetUri = null;
255                 try {
256                     targetUri = new URI(url);
257                     if (!targetUri.isAbsolute()) {
258                         URI baseUri = new URI(baseUrl);
259                         targetUrl = baseUri.resolve(targetUri).toString();
260                     }
261                 } catch (URISyntaxException e) {
262                     Log.e(TAG, "Invalid url passed to requestShow");
263                     notifyRequestShowFail(instanceId, requestId, ERROR_INVALID_PARAMETER);
264                     return;
265                 }
266
267                 mPresentationContent = new XWalkPresentationContent(
268                         mExtensionContext.getContext(),
269                         mExtensionContext.getActivity(),
270                         new XWalkPresentationContent.PresentationDelegate() {
271                     @Override
272                     public void onContentLoaded(XWalkPresentationContent content) {
273                         notifyRequestShowSucceed(instanceId, requestId, content.getPresentationId());
274                     }
275
276                     @Override
277                     public void onContentClosed(XWalkPresentationContent content) {
278                         if (content == mPresentationContent) {
279                             mPresentationContent.close();
280                             mPresentationContent = null;
281                             if (mPresentationView != null) mPresentationView.cancel();
282                         }
283                     }
284                 });
285
286                 // Start to load the content from the target url.
287                 mPresentationContent.load(targetUrl);
288
289                 // Update the presentation view in order that the content could be presented
290                 // on the preferred display.
291                 updatePresentationView(preferredDisplay);
292             }
293         });
294     }
295
296     @Override
297     public String onSyncMessage(int instanceId, String message) {
298         if (message.equals(CMD_QUERY_DISPLAY_AVAILABILITY)) {
299             return mAvailableDisplayCount != 0 ? "true" : "false";
300         } else {
301             Log.e(TAG, "Unexpected sync message received: " + message);
302             return "";
303         }
304     }
305
306     @Override
307     public void onResume() {
308         Display[] displays = mDisplayManager.getPresentationDisplays();
309
310         // If there was available displays but right now no one is available for presentation,
311         // we need to notify the display availability changes and reset the display count.
312         if (displays.length == 0 && mAvailableDisplayCount > 0) {
313             notifyAvailabilityChanged(false);
314             mAvailableDisplayCount = 0;
315             if (mPresentationContent != null) {
316                 mPresentationContent.close();
317                 mPresentationContent = null;
318             }
319         }
320
321         // If there was no available display but right now there is at least one available
322         // display, we need to notify the display availability changes and update the display
323         // count.
324         if (displays.length > 0 && mAvailableDisplayCount == 0) {
325             notifyAvailabilityChanged(true);
326             mAvailableDisplayCount = displays.length;
327         }
328
329         // If there was available displays and right now there is also at least one
330         // available display, we only need to update the display count.
331         if (displays.length > 0 && mAvailableDisplayCount > 0) {
332             mAvailableDisplayCount = displays.length;
333         }
334
335         if (mPresentationContent != null) {
336             mPresentationContent.onResume();
337         }
338
339         updatePresentationView(getPreferredDisplay());
340
341         // Register the listener to display manager.
342         mDisplayManager.registerDisplayListener(mDisplayListener);
343     }
344
345     private void updatePresentationView(Display preferredDisplay) {
346         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1 ||
347                 preferredDisplay == null) {
348             return;
349         }
350
351         // No presentation content is ready for use.
352         if (mPresentationView == null && mPresentationContent == null) {
353             return;
354         }
355
356         // If the presentation view is showed on another display, we need to dismiss it
357         // and re-create a new one.
358         if (mPresentationView != null && mPresentationView.getDisplay() != preferredDisplay) {
359             mPresentationView.dismiss();
360             mPresentationView = null;
361         }
362
363         // If the presentation view is not NULL and its associated display is not changed,
364         // the displaying system will automatically restore the content on the view once
365         // the Activity gets resumed.
366         if (mPresentationView == null && mPresentationContent != null) {
367             // Remove the content view from its previous view hierarchy if have.
368             ViewGroup parent = (ViewGroup)mPresentationContent.getContentView().getParent();
369             if (parent != null) parent.removeView(mPresentationContent.getContentView());
370
371             mPresentationView = PresentationView.createInstance(mExtensionContext.getContext(), preferredDisplay);
372             mPresentationView.setContentView(mPresentationContent.getContentView());
373             mPresentationView.setPresentationListener(new PresentationView.PresentationListener() {
374                 @Override
375                 public void onDismiss(PresentationView view) {
376                     // We need to pause the content if the view is dismissed from the screen
377                     // to avoid unnecessary overhead to update the content, e.g. stop animation
378                     // and JS execution.
379                     if (view == mPresentationView) {
380                         if (mPresentationContent != null) mPresentationContent.onPause();
381                         mPresentationView = null;
382                     }
383                 }
384
385                 @Override
386                 public void onShow(PresentationView view) {
387                     // The presentation content may be paused due to the presentation view was
388                     // dismissed, we need to resume it when the new view is showed.
389                     if (view == mPresentationView && mPresentationContent != null) {
390                         mPresentationContent.onResume();
391                     }
392                 }
393             });
394         }
395
396         mPresentationView.show();
397     }
398
399     @Override
400     public void onPause() {
401         if (mPresentationView != null) {
402             mPresentationView.dismiss();
403             mPresentationView = null;
404         }
405
406         if (mPresentationContent != null) mPresentationContent.onPause();
407
408         // No need to listen display changes when the activity is paused.
409         mDisplayManager.unregisterDisplayListener(mDisplayListener);
410     }
411
412     @Override
413     public void onDestroy() {
414     }
415 }