1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
2  * vim: ts=4 sw=4 expandtab:
3  * This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 package org.mozilla.geckoview;
8 
9 import java.net.URLConnection;
10 import java.util.ArrayList;
11 import java.util.Arrays;
12 import java.util.UUID;
13 
14 import org.mozilla.gecko.annotation.WrapForJNI;
15 import org.mozilla.gecko.EventDispatcher;
16 import org.mozilla.gecko.gfx.LayerSession;
17 import org.mozilla.gecko.GeckoAppShell;
18 import org.mozilla.gecko.GeckoEditableChild;
19 import org.mozilla.gecko.GeckoThread;
20 import org.mozilla.gecko.IGeckoEditableParent;
21 import org.mozilla.gecko.mozglue.JNIObject;
22 import org.mozilla.gecko.NativeQueue;
23 import org.mozilla.gecko.util.BundleEventListener;
24 import org.mozilla.gecko.util.EventCallback;
25 import org.mozilla.gecko.util.GeckoBundle;
26 import org.mozilla.gecko.util.ThreadUtils;
27 
28 import android.content.ContentResolver;
29 import android.content.Context;
30 import android.content.res.Resources;
31 import android.database.Cursor;
32 import android.net.Uri;
33 import android.os.Binder;
34 import android.os.Bundle;
35 import android.os.IBinder;
36 import android.os.IInterface;
37 import android.os.Parcel;
38 import android.os.Parcelable;
39 import android.os.SystemClock;
40 import android.support.annotation.Nullable;
41 import android.support.annotation.NonNull;
42 import android.util.Log;
43 
44 public class GeckoSession extends LayerSession
45                           implements Parcelable {
46     private static final String LOGTAG = "GeckoSession";
47     private static final boolean DEBUG = false;
48 
49     // Type of changes given to onWindowChanged.
50     // Window has been cleared due to the session being closed.
51     private static final int WINDOW_CLOSE = 0;
52     // Window has been set due to the session being opened.
53     private static final int WINDOW_OPEN = 1; // Window has been opened.
54     // Window has been cleared due to the session being transferred to another session.
55     private static final int WINDOW_TRANSFER_OUT = 2; // Window has been transfer.
56     // Window has been set due to another session being transferred to this one.
57     private static final int WINDOW_TRANSFER_IN = 3;
58 
59     private enum State implements NativeQueue.State {
60         INITIAL(0),
61         READY(1);
62 
63         private final int mRank;
64 
State(int rank)65         private State(int rank) {
66             mRank = rank;
67         }
68 
69         @Override
is(final NativeQueue.State other)70         public boolean is(final NativeQueue.State other) {
71             return this == other;
72         }
73 
74         @Override
isAtLeast(final NativeQueue.State other)75         public boolean isAtLeast(final NativeQueue.State other) {
76             return (other instanceof State) &&
77                    mRank >= ((State) other).mRank;
78         }
79     }
80 
81     private final NativeQueue mNativeQueue =
82         new NativeQueue(State.INITIAL, State.READY);
83 
84     private final EventDispatcher mEventDispatcher =
85         new EventDispatcher(mNativeQueue);
86 
87     private final TextInputController mTextInput = new TextInputController(this, mNativeQueue);
88 
89     private String mId = UUID.randomUUID().toString().replace("-", "");
getId()90     /* package */ String getId() { return mId; }
91 
92     private final GeckoSessionHandler<ContentDelegate> mContentHandler =
93         new GeckoSessionHandler<ContentDelegate>(
94             "GeckoViewContent", this,
95             new String[]{
96                 "GeckoView:ContextMenu",
97                 "GeckoView:DOMTitleChanged",
98                 "GeckoView:DOMWindowFocus",
99                 "GeckoView:DOMWindowClose",
100                 "GeckoView:FullScreenEnter",
101                 "GeckoView:FullScreenExit"
102             }
103         ) {
104             @Override
105             public void handleMessage(final ContentDelegate delegate,
106                                       final String event,
107                                       final GeckoBundle message,
108                                       final EventCallback callback) {
109 
110                 if ("GeckoView:ContextMenu".equals(event)) {
111                     delegate.onContextMenu(GeckoSession.this,
112                                            message.getInt("screenX"),
113                                            message.getInt("screenY"),
114                                            message.getString("uri"),
115                                            message.getString("elementSrc"));
116                 } else if ("GeckoView:DOMTitleChanged".equals(event)) {
117                     delegate.onTitleChange(GeckoSession.this,
118                                            message.getString("title"));
119                 } else if ("GeckoView:DOMWindowFocus".equals(event)) {
120                     delegate.onFocusRequest(GeckoSession.this);
121                 } else if ("GeckoView:DOMWindowClose".equals(event)) {
122                     delegate.onCloseRequest(GeckoSession.this);
123                 } else if ("GeckoView:FullScreenEnter".equals(event)) {
124                     delegate.onFullScreen(GeckoSession.this, true);
125                 } else if ("GeckoView:FullScreenExit".equals(event)) {
126                     delegate.onFullScreen(GeckoSession.this, false);
127                 }
128             }
129         };
130 
131     private final GeckoSessionHandler<NavigationDelegate> mNavigationHandler =
132         new GeckoSessionHandler<NavigationDelegate>(
133             "GeckoViewNavigation", this,
134             new String[]{
135                 "GeckoView:LocationChange",
136                 "GeckoView:OnLoadRequest",
137                 "GeckoView:OnNewSession"
138             }
139         ) {
140             // This needs to match nsIBrowserDOMWindow.idl
141             private int convertGeckoTarget(int geckoTarget) {
142                 switch (geckoTarget) {
143                     case 0: // OPEN_DEFAULTWINDOW
144                     case 1: // OPEN_CURRENTWINDOW
145                         return NavigationDelegate.TARGET_WINDOW_CURRENT;
146                     default: // OPEN_NEWWINDOW, OPEN_NEWTAB, OPEN_SWITCHTAB
147                         return NavigationDelegate.TARGET_WINDOW_NEW;
148                 }
149             }
150 
151             @Override
152             public void handleMessage(final NavigationDelegate delegate,
153                                       final String event,
154                                       final GeckoBundle message,
155                                       final EventCallback callback) {
156                 if ("GeckoView:LocationChange".equals(event)) {
157                     delegate.onLocationChange(GeckoSession.this,
158                                               message.getString("uri"));
159                     delegate.onCanGoBack(GeckoSession.this,
160                                          message.getBoolean("canGoBack"));
161                     delegate.onCanGoForward(GeckoSession.this,
162                                             message.getBoolean("canGoForward"));
163                 } else if ("GeckoView:OnLoadRequest".equals(event)) {
164                     final String uri = message.getString("uri");
165                     final int where = convertGeckoTarget(message.getInt("where"));
166                     final boolean result =
167                         delegate.onLoadRequest(GeckoSession.this, uri, where);
168                     callback.sendSuccess(result);
169                 } else if ("GeckoView:OnNewSession".equals(event)) {
170                     final String uri = message.getString("uri");
171                     delegate.onNewSession(GeckoSession.this, uri,
172                         new Response<GeckoSession>() {
173                             @Override
174                             public void respond(GeckoSession session) {
175                                 if (session == null) {
176                                     callback.sendSuccess(null);
177                                     return;
178                                 }
179 
180                                 if (session.isOpen()) {
181                                     throw new IllegalArgumentException("Must use an unopened GeckoSession instance");
182                                 }
183 
184                                 session.open(null);
185                                 callback.sendSuccess(session.getId());
186                             }
187                         });
188                 }
189             }
190         };
191 
192     private final GeckoSessionHandler<ProgressDelegate> mProgressHandler =
193         new GeckoSessionHandler<ProgressDelegate>(
194             "GeckoViewProgress", this,
195             new String[]{
196                 "GeckoView:PageStart",
197                 "GeckoView:PageStop",
198                 "GeckoView:SecurityChanged"
199             }
200         ) {
201             @Override
202             public void handleMessage(final ProgressDelegate delegate,
203                                       final String event,
204                                       final GeckoBundle message,
205                                       final EventCallback callback) {
206                 if ("GeckoView:PageStart".equals(event)) {
207                     delegate.onPageStart(GeckoSession.this,
208                                          message.getString("uri"));
209                 } else if ("GeckoView:PageStop".equals(event)) {
210                     delegate.onPageStop(GeckoSession.this,
211                                         message.getBoolean("success"));
212                 } else if ("GeckoView:SecurityChanged".equals(event)) {
213                     final GeckoBundle identity = message.getBundle("identity");
214                     delegate.onSecurityChange(GeckoSession.this, new ProgressDelegate.SecurityInformation(identity));
215                 }
216             }
217         };
218 
219     private final GeckoSessionHandler<ScrollDelegate> mScrollHandler =
220         new GeckoSessionHandler<ScrollDelegate>(
221             "GeckoViewScroll", this,
222             new String[]{ "GeckoView:ScrollChanged" }
223         ) {
224             @Override
225             public void handleMessage(final ScrollDelegate delegate,
226                                       final String event,
227                                       final GeckoBundle message,
228                                       final EventCallback callback) {
229 
230                 if ("GeckoView:ScrollChanged".equals(event)) {
231                     delegate.onScrollChanged(GeckoSession.this,
232                                              message.getInt("scrollX"),
233                                              message.getInt("scrollY"));
234                 }
235             }
236         };
237 
238     private final GeckoSessionHandler<TrackingProtectionDelegate> mTrackingProtectionHandler =
239         new GeckoSessionHandler<TrackingProtectionDelegate>(
240             "GeckoViewTrackingProtection", this,
241             new String[]{ "GeckoView:TrackingProtectionBlocked" }
242         ) {
243             @Override
244             public void handleMessage(final TrackingProtectionDelegate delegate,
245                                       final String event,
246                                       final GeckoBundle message,
247                                       final EventCallback callback) {
248 
249                 if ("GeckoView:TrackingProtectionBlocked".equals(event)) {
250                     final String uri = message.getString("src");
251                     final String matchedList = message.getString("matchedList");
252                     delegate.onTrackerBlocked(GeckoSession.this, uri,
253                         TrackingProtection.listToCategory(matchedList));
254                 }
255             }
256         };
257 
258     private final GeckoSessionHandler<PermissionDelegate> mPermissionHandler =
259         new GeckoSessionHandler<PermissionDelegate>(
260             "GeckoViewPermission", this,
261             new String[] {
262                 "GeckoView:AndroidPermission",
263                 "GeckoView:ContentPermission",
264                 "GeckoView:MediaPermission"
265             }, /* alwaysListen */ true
266         ) {
267             @Override
268             public void handleMessage(final PermissionDelegate delegate,
269                                       final String event,
270                                       final GeckoBundle message,
271                                       final EventCallback callback) {
272 
273                 if (delegate == null) {
274                     callback.sendSuccess(/* granted */ false);
275                     return;
276                 }
277                 if ("GeckoView:AndroidPermission".equals(event)) {
278                     delegate.onAndroidPermissionsRequest(
279                             GeckoSession.this, message.getStringArray("perms"),
280                             new PermissionCallback("android", callback));
281                 } else if ("GeckoView:ContentPermission".equals(event)) {
282                     final String typeString = message.getString("perm");
283                     final int type;
284                     if ("geolocation".equals(typeString)) {
285                         type = PermissionDelegate.PERMISSION_GEOLOCATION;
286                     } else if ("desktop_notification".equals(typeString)) {
287                         type = PermissionDelegate.PERMISSION_DESKTOP_NOTIFICATION;
288                     } else {
289                         throw new IllegalArgumentException("Unknown permission request: " + typeString);
290                     }
291                     delegate.onContentPermissionRequest(
292                             GeckoSession.this, message.getString("uri"),
293                             type, message.getString("access"),
294                             new PermissionCallback(typeString, callback));
295                 } else if ("GeckoView:MediaPermission".equals(event)) {
296                     GeckoBundle[] videoBundles = message.getBundleArray("video");
297                     GeckoBundle[] audioBundles = message.getBundleArray("audio");
298                     PermissionDelegate.MediaSource[] videos = null;
299                     PermissionDelegate.MediaSource[] audios = null;
300 
301                     if (videoBundles != null) {
302                         videos = new PermissionDelegate.MediaSource[videoBundles.length];
303                         for (int i = 0; i < videoBundles.length; i++) {
304                             videos[i] = new PermissionDelegate.MediaSource(videoBundles[i]);
305                         }
306                     }
307 
308                     if (audioBundles != null) {
309                         audios = new PermissionDelegate.MediaSource[audioBundles.length];
310                         for (int i = 0; i < audioBundles.length; i++) {
311                             audios[i] = new PermissionDelegate.MediaSource(audioBundles[i]);
312                         }
313                     }
314 
315                     delegate.onMediaPermissionRequest(
316                             GeckoSession.this, message.getString("uri"),
317                             videos, audios, new PermissionCallback("media", callback));
318                 }
319             }
320         };
321 
322     /* package */ int handlersCount;
323 
324     private final GeckoSessionHandler<?>[] mSessionHandlers = new GeckoSessionHandler<?>[] {
325         mContentHandler, mNavigationHandler, mProgressHandler, mScrollHandler,
326         mTrackingProtectionHandler, mPermissionHandler
327     };
328 
329     private static class PermissionCallback implements
330         PermissionDelegate.Callback, PermissionDelegate.MediaCallback {
331 
332         private final String mType;
333         private EventCallback mCallback;
334 
PermissionCallback(final String type, final EventCallback callback)335         public PermissionCallback(final String type, final EventCallback callback) {
336             mType = type;
337             mCallback = callback;
338         }
339 
submit(final Object response)340         private void submit(final Object response) {
341             if (mCallback != null) {
342                 mCallback.sendSuccess(response);
343                 mCallback = null;
344             }
345         }
346 
347         @Override // PermissionDelegate.Callback
grant()348         public void grant() {
349             if ("media".equals(mType)) {
350                 throw new UnsupportedOperationException();
351             }
352             submit(/* response */ true);
353         }
354 
355         @Override // PermissionDelegate.Callback, PermissionDelegate.MediaCallback
reject()356         public void reject() {
357             submit(/* response */ false);
358         }
359 
360         @Override // PermissionDelegate.MediaCallback
grant(final String video, final String audio)361         public void grant(final String video, final String audio) {
362             if (!"media".equals(mType)) {
363                 throw new UnsupportedOperationException();
364             }
365             final GeckoBundle response = new GeckoBundle(2);
366             response.putString("video", video);
367             response.putString("audio", audio);
368             submit(response);
369         }
370 
371         @Override // PermissionDelegate.MediaCallback
grant(final PermissionDelegate.MediaSource video, final PermissionDelegate.MediaSource audio)372         public void grant(final PermissionDelegate.MediaSource video, final PermissionDelegate.MediaSource audio) {
373             grant(video != null ? video.id : null,
374                   audio != null ? audio.id : null);
375         }
376     }
377 
378     /**
379      * Get the current prompt delegate for this GeckoSession.
380      * @return PromptDelegate instance or null if using default delegate.
381      */
getPermissionDelegate()382     public PermissionDelegate getPermissionDelegate() {
383         return mPermissionHandler.getDelegate();
384     }
385 
386     /**
387      * Set the current permission delegate for this GeckoSession.
388      * @param delegate PermissionDelegate instance or null to use the default delegate.
389      */
setPermissionDelegate(final PermissionDelegate delegate)390     public void setPermissionDelegate(final PermissionDelegate delegate) {
391         mPermissionHandler.setDelegate(delegate, this);
392     }
393 
394     private PromptDelegate mPromptDelegate;
395 
396     private final Listener mListener = new Listener();
397 
398     /* package */ static final class Window extends JNIObject implements IInterface {
399         private NativeQueue mNativeQueue;
400         private Binder mBinder;
401 
Window(final NativeQueue nativeQueue)402         public Window(final NativeQueue nativeQueue) {
403             mNativeQueue = nativeQueue;
404         }
405 
406         @Override // IInterface
asBinder()407         public Binder asBinder() {
408             if (mBinder == null) {
409                 mBinder = new Binder();
410                 mBinder.attachInterface(this, Window.class.getName());
411             }
412             return mBinder;
413         }
414 
415         // Create a new Gecko window and assign an initial set of Java session objects to it.
416         @WrapForJNI(dispatchTo = "proxy")
open(Window instance, NativeQueue queue, Compositor compositor, EventDispatcher dispatcher, GeckoBundle settings, String id, String chromeUri, int screenId, boolean privateMode)417         public static native void open(Window instance, NativeQueue queue,
418                                        Compositor compositor, EventDispatcher dispatcher,
419                                        GeckoBundle settings, String id, String chromeUri,
420                                        int screenId, boolean privateMode);
421 
422         @Override // JNIObject
disposeNative()423         public void disposeNative() {
424             if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
425                 nativeDisposeNative();
426             } else {
427                 GeckoThread.queueNativeCallUntil(GeckoThread.State.PROFILE_READY,
428                         this, "nativeDisposeNative");
429             }
430         }
431 
432         @WrapForJNI(dispatchTo = "proxy", stubName = "DisposeNative")
nativeDisposeNative()433         private native void nativeDisposeNative();
434 
435         // Force the underlying Gecko window to close and release assigned Java objects.
close()436         public void close() {
437             // Reset our queue, so we don't end up with queued calls on a disposed object.
438             synchronized (this) {
439                 if (mNativeQueue == null) {
440                     // Already closed elsewhere.
441                     return;
442                 }
443                 mNativeQueue.reset(State.INITIAL);
444                 mNativeQueue = null;
445             }
446 
447             // Detach ourselves from the binder as well, to prevent this window from being
448             // read from any parcels.
449             asBinder().attachInterface(null, Window.class.getName());
450 
451             if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
452                 nativeClose();
453             } else {
454                 GeckoThread.queueNativeCallUntil(GeckoThread.State.PROFILE_READY,
455                         this, "nativeClose");
456             }
457         }
458 
459         @WrapForJNI(dispatchTo = "proxy", stubName = "Close")
nativeClose()460         private native void nativeClose();
461 
462         // Assign a new set of Java session objects to the underlying Gecko window.
463         // This replaces previously assigned objects from open() or transfer() calls.
transfer(final NativeQueue queue, final Compositor compositor, final EventDispatcher dispatcher, final GeckoBundle settings)464         public synchronized void transfer(final NativeQueue queue,
465                                           final Compositor compositor,
466                                           final EventDispatcher dispatcher,
467                                           final GeckoBundle settings) {
468             if (mNativeQueue == null) {
469                 // Already closed.
470                 return;
471             }
472 
473             if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
474                 nativeTransfer(queue, compositor, dispatcher, settings);
475             } else {
476                 GeckoThread.queueNativeCallUntil(GeckoThread.State.PROFILE_READY,
477                         this, "nativeTransfer",
478                         NativeQueue.class, queue,
479                         Compositor.class, compositor,
480                         EventDispatcher.class, dispatcher,
481                         GeckoBundle.class, settings);
482             }
483 
484             if (mNativeQueue != queue) {
485                 // Reset the old queue to prevent old events from affecting this window.
486                 // Gecko will call onReady later with the new queue if needed.
487                 mNativeQueue.reset(State.INITIAL);
488                 mNativeQueue = queue;
489             }
490         }
491 
492         @WrapForJNI(dispatchTo = "proxy", stubName = "Transfer")
nativeTransfer(NativeQueue queue, Compositor compositor, EventDispatcher dispatcher, GeckoBundle settings)493         private native void nativeTransfer(NativeQueue queue, Compositor compositor,
494                                            EventDispatcher dispatcher, GeckoBundle settings);
495 
496         @WrapForJNI(dispatchTo = "proxy")
attachEditable(IGeckoEditableParent parent, GeckoEditableChild child)497         public native void attachEditable(IGeckoEditableParent parent,
498                                           GeckoEditableChild child);
499 
500         @WrapForJNI(calledFrom = "gecko")
onReady(final @Nullable NativeQueue queue)501         private synchronized void onReady(final @Nullable NativeQueue queue) {
502             // onReady is called the first time the Gecko window is ready, with a null queue
503             // argument. In this case, we simply set the current queue to ready state.
504             //
505             // After the initial call, onReady is called again every time Window.transfer()
506             // is called, with a non-null queue argument. In this case, we only set the
507             // current queue to ready state _if_ the current queue matches the given queue,
508             // because if the queues don't match, we know there is another onReady call coming.
509 
510             if ((queue == null && mNativeQueue == null) ||
511                 (queue != null && mNativeQueue != queue)) {
512                 return;
513             }
514 
515             if (mNativeQueue.checkAndSetState(State.INITIAL, State.READY) &&
516                     queue == null) {
517                 Log.i(LOGTAG, "zerdatime " + SystemClock.elapsedRealtime() +
518                       " - chrome startup finished");
519             }
520         }
521     }
522 
523     private class Listener implements BundleEventListener {
registerListeners()524         /* package */ void registerListeners() {
525             getEventDispatcher().registerUiThreadListener(this,
526                 "GeckoView:Prompt",
527                 null);
528         }
529 
530         @Override
handleMessage(final String event, final GeckoBundle message, final EventCallback callback)531         public void handleMessage(final String event, final GeckoBundle message,
532                                   final EventCallback callback) {
533             if (DEBUG) {
534                 Log.d(LOGTAG, "handleMessage: event = " + event);
535             }
536 
537             if ("GeckoView:Prompt".equals(event)) {
538                 handlePromptEvent(GeckoSession.this, message, callback);
539             }
540         }
541     }
542 
543     protected Window mWindow;
544     private GeckoSessionSettings mSettings;
545 
GeckoSession()546     public GeckoSession() {
547         this(null);
548     }
549 
GeckoSession(final GeckoSessionSettings settings)550     public GeckoSession(final GeckoSessionSettings settings) {
551         mSettings = new GeckoSessionSettings(settings, this);
552         mListener.registerListeners();
553 
554         if (BuildConfig.DEBUG && handlersCount != mSessionHandlers.length) {
555             throw new AssertionError("Add new handler to handlers list");
556         }
557     }
558 
transferFrom(final Window window, final GeckoSessionSettings settings, final String id)559     private void transferFrom(final Window window, final GeckoSessionSettings settings,
560                               final String id) {
561         if (isOpen()) {
562             throw new IllegalStateException("Session is open");
563         }
564 
565         if (window != null) {
566             onWindowChanged(WINDOW_TRANSFER_IN, /* inProgress */ true);
567         }
568 
569         mWindow = window;
570         mSettings = new GeckoSessionSettings(settings, this);
571         mId = id;
572 
573         if (mWindow != null) {
574             mWindow.transfer(mNativeQueue, mCompositor,
575                              mEventDispatcher, mSettings.asBundle());
576 
577             onWindowChanged(WINDOW_TRANSFER_IN, /* inProgress */ false);
578         }
579     }
580 
transferFrom(final GeckoSession session)581     /* package */ void transferFrom(final GeckoSession session) {
582         final boolean changing = (session.mWindow != null);
583         if (changing) {
584             session.onWindowChanged(WINDOW_TRANSFER_OUT, /* inProgress */ true);
585         }
586 
587         transferFrom(session.mWindow, session.mSettings, session.mId);
588         session.mWindow = null;
589 
590         if (changing) {
591             session.onWindowChanged(WINDOW_TRANSFER_OUT, /* inProgress */ false);
592         }
593     }
594 
595     @Override // Parcelable
describeContents()596     public int describeContents() {
597         return 0;
598     }
599 
600     @Override // Parcelable
writeToParcel(Parcel out, int flags)601     public void writeToParcel(Parcel out, int flags) {
602         out.writeStrongInterface(mWindow);
603         out.writeParcelable(mSettings, flags);
604         out.writeString(mId);
605     }
606 
607     // AIDL code may call readFromParcel even though it's not part of Parcelable.
readFromParcel(final Parcel source)608     public void readFromParcel(final Parcel source) {
609         final IBinder binder = source.readStrongBinder();
610         final IInterface ifce = (binder != null) ?
611                 binder.queryLocalInterface(Window.class.getName()) : null;
612         final Window window = (ifce instanceof Window) ? (Window) ifce : null;
613         final GeckoSessionSettings settings =
614                 source.readParcelable(getClass().getClassLoader());
615         final String id = source.readString();
616         transferFrom(window, settings, id);
617     }
618 
619     public static final Creator<GeckoSession> CREATOR = new Creator<GeckoSession>() {
620         @Override
621         public GeckoSession createFromParcel(final Parcel in) {
622             final GeckoSession session = new GeckoSession();
623             session.readFromParcel(in);
624             return session;
625         }
626 
627         @Override
628         public GeckoSession[] newArray(final int size) {
629             return new GeckoSession[size];
630         }
631     };
632 
633     /**
634      * Preload GeckoSession by starting Gecko in the background, if Gecko is not already running.
635      *
636      * @param context Activity or Application Context for starting GeckoSession.
637      */
preload(final @NonNull Context context)638     public static void preload(final @NonNull Context context) {
639         preload(context, /* geckoArgs */ null,
640                 /* extras */ null, /* multiprocess */ false);
641     }
642 
643     /**
644      * Preload GeckoSession by starting Gecko with the specified arguments in the background,
645      * if Gecko is not already running.
646      *
647      * @param context Activity or Application Context for starting GeckoSession.
648      * @param geckoArgs Arguments to be passed to Gecko, if Gecko is not already running.
649      * @param multiprocess True if child process in multiprocess mode should be preloaded.
650      */
preload(final @NonNull Context context, final @Nullable String[] geckoArgs, final @Nullable Bundle extras, final boolean multiprocess)651     public static void preload(final @NonNull Context context,
652                                final @Nullable String[] geckoArgs,
653                                final @Nullable Bundle extras,
654                                final boolean multiprocess) {
655         final Context appContext = context.getApplicationContext();
656         if (!appContext.equals(GeckoAppShell.getApplicationContext())) {
657             GeckoAppShell.setApplicationContext(appContext);
658         }
659 
660         if (GeckoThread.isLaunched()) {
661             return;
662         }
663 
664         final int flags = multiprocess ? GeckoThread.FLAG_PRELOAD_CHILD : 0;
665         if (GeckoThread.initMainProcess(/* profile */ null, geckoArgs, extras, flags)) {
666             GeckoThread.launch();
667         }
668     }
669 
isOpen()670     public boolean isOpen() {
671         return mWindow != null;
672     }
673 
isReady()674     /* package */ boolean isReady() {
675         return mNativeQueue.isReady();
676     }
677 
678     /**
679      * Opens the session.
680      *
681      * The session is in a 'closed' state when first created. Opening it creates
682      * the underlying Gecko objects necessary to load a page, etc. Most GeckoSession
683      * methods only take affect on an open session, and are queued until the session
684      * is opened here. Opening a session is an asynchronous operation. You can check
685      * the current state via isOpen().
686      *
687      * Call this when you are ready to use a GeckoSession instance.
688      *
689      * @param appContext An application context
690      */
open(final @Nullable Context appContext)691     public void open(final @Nullable Context appContext) {
692         ThreadUtils.assertOnUiThread();
693 
694         if (isOpen()) {
695             throw new IllegalStateException("Session is open");
696         }
697 
698         if (appContext != null) {
699             final boolean multiprocess =
700                     mSettings.getBoolean(GeckoSessionSettings.USE_MULTIPROCESS);
701             preload(appContext, /* geckoArgs */ null, /* extras */ null, multiprocess);
702         }
703 
704         openWindow();
705     }
706 
openWindow()707     private void openWindow() {
708         final String chromeUri = mSettings.getString(GeckoSessionSettings.CHROME_URI);
709         final int screenId = mSettings.getInt(GeckoSessionSettings.SCREEN_ID);
710         final boolean isPrivate = mSettings.getBoolean(GeckoSessionSettings.USE_PRIVATE_MODE);
711 
712         mWindow = new Window(mNativeQueue);
713 
714         onWindowChanged(WINDOW_OPEN, /* inProgress */ true);
715 
716         if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
717             Window.open(mWindow, mNativeQueue, mCompositor, mEventDispatcher,
718                         mSettings.asBundle(), mId, chromeUri, screenId, isPrivate);
719         } else {
720             GeckoThread.queueNativeCallUntil(
721                 GeckoThread.State.PROFILE_READY,
722                 Window.class, "open",
723                 Window.class, mWindow,
724                 NativeQueue.class, mNativeQueue,
725                 Compositor.class, mCompositor,
726                 EventDispatcher.class, mEventDispatcher,
727                 GeckoBundle.class, mSettings.asBundle(),
728                 String.class, mId,
729                 String.class, chromeUri,
730                 screenId, isPrivate);
731         }
732 
733         onWindowChanged(WINDOW_OPEN, /* inProgress */ false);
734     }
735 
736     /**
737      * Closes the session.
738      *
739      * This frees the underlying Gecko objects and unloads the current page. The session may be
740      * reopened later, but page state is not restored. Call this when you are finished using
741      * a GeckoSession instance.
742      */
close()743     public void close() {
744         ThreadUtils.assertOnUiThread();
745 
746         if (!isOpen()) {
747             Log.w(LOGTAG, "Attempted to close a GeckoSession that was already closed.");
748             return;
749         }
750 
751         onWindowChanged(WINDOW_CLOSE, /* inProgress */ true);
752 
753         mWindow.close();
754         mWindow.disposeNative();
755         mWindow = null;
756 
757         onWindowChanged(WINDOW_CLOSE, /* inProgress */ false);
758     }
759 
onWindowChanged(int change, boolean inProgress)760     private void onWindowChanged(int change, boolean inProgress) {
761         if ((change == WINDOW_OPEN || change == WINDOW_TRANSFER_IN) && !inProgress) {
762             mTextInput.onWindowChanged(mWindow);
763         }
764 
765         if (change == WINDOW_CLOSE) {
766             // Detach when window is closing, and reattach immediately after window is closed.
767             // We reattach immediate after closing because we want any actions performed while the
768             // session is closed to be properly queued, until the session is open again.
769             for (final GeckoSessionHandler<?> handler : mSessionHandlers) {
770                 handler.setSessionIsReady(getEventDispatcher(), !inProgress);
771             }
772         }
773     }
774 
775     /**
776      * Get the TextInputController instance for this session.
777      *
778      * @return TextInputController instance.
779      */
getTextInputController()780     public @NonNull TextInputController getTextInputController() {
781         // May be called on any thread.
782         return mTextInput;
783     }
784 
785     /**
786     * Load the given URI.
787     * @param uri The URI of the resource to load.
788     */
loadUri(String uri)789     public void loadUri(String uri) {
790         final GeckoBundle msg = new GeckoBundle();
791         msg.putString("uri", uri);
792         mEventDispatcher.dispatch("GeckoView:LoadUri", msg);
793     }
794 
795     /**
796     * Load the given URI.
797     * @param uri The URI of the resource to load.
798     */
loadUri(Uri uri)799     public void loadUri(Uri uri) {
800         loadUri(uri.toString());
801     }
802 
803     /**
804     * Reload the current URI.
805     */
reload()806     public void reload() {
807         mEventDispatcher.dispatch("GeckoView:Reload", null);
808     }
809 
810     /**
811     * Stop loading.
812     */
stop()813     public void stop() {
814         mEventDispatcher.dispatch("GeckoView:Stop", null);
815     }
816 
817     /**
818     * Go back in history.
819     */
goBack()820     public void goBack() {
821         mEventDispatcher.dispatch("GeckoView:GoBack", null);
822     }
823 
824     /**
825     * Go forward in history.
826     */
goForward()827     public void goForward() {
828         mEventDispatcher.dispatch("GeckoView:GoForward", null);
829     }
830 
831     /**
832     * Set this GeckoSession as active or inactive. Setting a GeckoSession to inactive will
833     * significantly reduce its memory footprint, but should only be done if the
834     * GeckoSession is not currently visible.
835     * @param active A boolean determining whether the GeckoSession is active
836     */
setActive(boolean active)837     public void setActive(boolean active) {
838         final GeckoBundle msg = new GeckoBundle();
839         msg.putBoolean("active", active);
840         mEventDispatcher.dispatch("GeckoView:SetActive", msg);
841     }
842 
getSettings()843     public GeckoSessionSettings getSettings() {
844         return mSettings;
845     }
846 
importScript(final String url)847     public void importScript(final String url) {
848         if (url.startsWith("resource://android/assets/")) {
849             final GeckoBundle data = new GeckoBundle(1);
850             data.putString("scriptURL", url);
851             getEventDispatcher().dispatch("GeckoView:ImportScript", data);
852             return;
853         }
854 
855         throw new IllegalArgumentException("Must import script from 'resources://android/assets/' location.");
856     }
857 
858     /**
859     * Exits fullscreen mode
860     */
exitFullScreen()861     public void exitFullScreen() {
862         mEventDispatcher.dispatch("GeckoViewContent:ExitFullScreen", null);
863     }
864 
865     /**
866     * Set the content callback handler.
867     * This will replace the current handler.
868     * @param delegate An implementation of ContentDelegate.
869     */
setContentDelegate(ContentDelegate delegate)870     public void setContentDelegate(ContentDelegate delegate) {
871         mContentHandler.setDelegate(delegate, this);
872     }
873 
874     /**
875     * Get the content callback handler.
876     * @return The current content callback handler.
877     */
getContentDelegate()878     public ContentDelegate getContentDelegate() {
879         return mContentHandler.getDelegate();
880     }
881 
882     /**
883     * Set the progress callback handler.
884     * This will replace the current handler.
885     * @param delegate An implementation of ProgressDelegate.
886     */
setProgressDelegate(ProgressDelegate delegate)887     public void setProgressDelegate(ProgressDelegate delegate) {
888         mProgressHandler.setDelegate(delegate, this);
889     }
890 
891     /**
892     * Get the progress callback handler.
893     * @return The current progress callback handler.
894     */
getProgressDelegate()895     public ProgressDelegate getProgressDelegate() {
896         return mProgressHandler.getDelegate();
897     }
898 
899     /**
900     * Set the navigation callback handler.
901     * This will replace the current handler.
902     * @param delegate An implementation of NavigationDelegate.
903     */
setNavigationDelegate(NavigationDelegate delegate)904     public void setNavigationDelegate(NavigationDelegate delegate) {
905         mNavigationHandler.setDelegate(delegate, this);
906     }
907 
908     /**
909     * Get the navigation callback handler.
910     * @return The current navigation callback handler.
911     */
getNavigationDelegate()912     public NavigationDelegate getNavigationDelegate() {
913         return mNavigationHandler.getDelegate();
914     }
915 
916     /**
917     * Set the content scroll callback handler.
918     * This will replace the current handler.
919     * @param delegate An implementation of ScrollDelegate.
920     */
setScrollDelegate(ScrollDelegate delegate)921     public void setScrollDelegate(ScrollDelegate delegate) {
922         mScrollHandler.setDelegate(delegate, this);
923     }
924 
getScrollDelegate()925     public ScrollDelegate getScrollDelegate() {
926         return mScrollHandler.getDelegate();
927     }
928 
929     /**
930     * Set the tracking protection callback handler.
931     * This will replace the current handler.
932     * @param delegate An implementation of TrackingProtectionDelegate.
933     */
setTrackingProtectionDelegate(TrackingProtectionDelegate delegate)934     public void setTrackingProtectionDelegate(TrackingProtectionDelegate delegate) {
935         mTrackingProtectionHandler.setDelegate(delegate, this);
936     }
937 
938     /**
939     * Get the tracking protection callback handler.
940     * @return The current tracking protection callback handler.
941     */
getTrackingProtectionDelegate()942     public TrackingProtectionDelegate getTrackingProtectionDelegate() {
943         return mTrackingProtectionHandler.getDelegate();
944     }
945 
946     /**
947      * Set the current prompt delegate for this GeckoSession.
948      * @param delegate PromptDelegate instance or null to use the built-in delegate.
949      */
setPromptDelegate(PromptDelegate delegate)950     public void setPromptDelegate(PromptDelegate delegate) {
951         mPromptDelegate = delegate;
952     }
953 
954     /**
955      * Get the current prompt delegate for this GeckoSession.
956      * @return PromptDelegate instance or null if using built-in delegate.
957      */
getPromptDelegate()958     public PromptDelegate getPromptDelegate() {
959         return mPromptDelegate;
960     }
961 
962     private static class PromptCallback implements
963         PromptDelegate.AlertCallback, PromptDelegate.ButtonCallback,
964         PromptDelegate.TextCallback, PromptDelegate.AuthCallback,
965         PromptDelegate.ChoiceCallback, PromptDelegate.FileCallback {
966 
967         private final String mType;
968         private final String mMode;
969         private final boolean mHasCheckbox;
970         private final String mCheckboxMessage;
971 
972         private EventCallback mCallback;
973         private boolean mCheckboxValue;
974         private GeckoBundle mResult;
975 
PromptCallback(final String type, final String mode, final GeckoBundle message, final EventCallback callback)976         public PromptCallback(final String type, final String mode,
977                               final GeckoBundle message, final EventCallback callback) {
978             mType = type;
979             mMode = mode;
980             mCallback = callback;
981             mHasCheckbox = message.getBoolean("hasCheck");
982             mCheckboxMessage = message.getString("checkMsg");
983             mCheckboxValue = message.getBoolean("checkValue");
984         }
985 
ensureResult()986         private GeckoBundle ensureResult() {
987             if (mResult == null) {
988                 // Usually result object contains two items.
989                 mResult = new GeckoBundle(2);
990             }
991             return mResult;
992         }
993 
submit()994         private void submit() {
995             if (mHasCheckbox) {
996                 ensureResult().putBoolean("checkValue", mCheckboxValue);
997             }
998             if (mCallback != null) {
999                 mCallback.sendSuccess(mResult);
1000                 mCallback = null;
1001             }
1002         }
1003 
1004         @Override // AlertCallbcak
dismiss()1005         public void dismiss() {
1006             // Send a null result.
1007             mResult = null;
1008             submit();
1009         }
1010 
1011         @Override // AlertCallbcak
hasCheckbox()1012         public boolean hasCheckbox() {
1013             return mHasCheckbox;
1014         }
1015 
1016         @Override // AlertCallbcak
getCheckboxMessage()1017         public String getCheckboxMessage() {
1018             return mCheckboxMessage;
1019         }
1020 
1021         @Override // AlertCallbcak
getCheckboxValue()1022         public boolean getCheckboxValue() {
1023             return mCheckboxValue;
1024         }
1025 
1026         @Override // AlertCallbcak
setCheckboxValue(final boolean value)1027         public void setCheckboxValue(final boolean value) {
1028             mCheckboxValue = value;
1029         }
1030 
1031         @Override // ButtonCallback
confirm(final int value)1032         public void confirm(final int value) {
1033             if ("button".equals(mType)) {
1034                 ensureResult().putInt("button", value);
1035             } else {
1036                 throw new UnsupportedOperationException();
1037             }
1038             submit();
1039         }
1040 
1041         @Override // TextCallback, AuthCallback, ChoiceCallback, FileCallback
confirm(final String value)1042         public void confirm(final String value) {
1043             if ("text".equals(mType) || "color".equals(mType) || "datetime".equals(mType)) {
1044                 ensureResult().putString(mType, value);
1045             } else if ("auth".equals(mType)) {
1046                 if (!"password".equals(mMode)) {
1047                     throw new IllegalArgumentException();
1048                 }
1049                 ensureResult().putString("password", value);
1050             } else if ("choice".equals(mType)) {
1051                 confirm(new String[] { value });
1052                 return;
1053             } else {
1054                 throw new UnsupportedOperationException();
1055             }
1056             submit();
1057         }
1058 
1059         @Override // AuthCallback
confirm(final String username, final String password)1060         public void confirm(final String username, final String password) {
1061             if ("auth".equals(mType)) {
1062                 if (!"auth".equals(mMode)) {
1063                     throw new IllegalArgumentException();
1064                 }
1065                 ensureResult().putString("username", username);
1066                 ensureResult().putString("password", password);
1067             } else {
1068                 throw new UnsupportedOperationException();
1069             }
1070             submit();
1071         }
1072 
1073         @Override // ChoiceCallback, FileCallback
confirm(final String[] values)1074         public void confirm(final String[] values) {
1075             if (("menu".equals(mMode) || "single".equals(mMode)) &&
1076                 (values == null || values.length != 1)) {
1077                 throw new IllegalArgumentException();
1078             }
1079             if ("choice".equals(mType)) {
1080                 ensureResult().putStringArray("choices", values);
1081             } else {
1082                 throw new UnsupportedOperationException();
1083             }
1084             submit();
1085         }
1086 
1087         @Override // ChoiceCallback
confirm(PromptDelegate.Choice item)1088         public void confirm(PromptDelegate.Choice item) {
1089             if ("choice".equals(mType)) {
1090                 confirm(item == null ? null : item.id);
1091                 return;
1092             } else {
1093                 throw new UnsupportedOperationException();
1094             }
1095         }
1096 
1097         @Override // ChoiceCallback
confirm(PromptDelegate.Choice[] items)1098         public void confirm(PromptDelegate.Choice[] items) {
1099             if (("menu".equals(mMode) || "single".equals(mMode)) &&
1100                 (items == null || items.length != 1)) {
1101                 throw new IllegalArgumentException();
1102             }
1103             if ("choice".equals(mType)) {
1104                 if (items == null) {
1105                     confirm((String[]) null);
1106                     return;
1107                 }
1108                 final String[] ids = new String[items.length];
1109                 for (int i = 0; i < ids.length; i++) {
1110                     ids[i] = (items[i] == null) ? null : items[i].id;
1111                 }
1112                 confirm(ids);
1113                 return;
1114             } else {
1115                 throw new UnsupportedOperationException();
1116             }
1117         }
1118 
1119         @Override // FileCallback
confirm(final Context context, final Uri uri)1120         public void confirm(final Context context, final Uri uri) {
1121             if ("file".equals(mType)) {
1122                 confirm(context, uri == null ? null : new Uri[] { uri });
1123                 return;
1124             } else {
1125                 throw new UnsupportedOperationException();
1126             }
1127         }
1128 
getFile(final Context context, final Uri uri)1129         private static String getFile(final Context context, final Uri uri) {
1130             if (uri == null) {
1131                 return null;
1132             }
1133             if ("file".equals(uri.getScheme())) {
1134                 return uri.getPath();
1135             }
1136             final ContentResolver cr = context.getContentResolver();
1137             final Cursor cur = cr.query(uri, new String[] { "_data" }, /* selection */ null,
1138                                         /* args */ null, /* sort */ null);
1139             if (cur == null) {
1140                 return null;
1141             }
1142             try {
1143                 final int idx = cur.getColumnIndex("_data");
1144                 if (idx < 0 || !cur.moveToFirst()) {
1145                     return null;
1146                 }
1147                 do {
1148                     try {
1149                         final String path = cur.getString(idx);
1150                         if (path != null && !path.isEmpty()) {
1151                             return path;
1152                         }
1153                     } catch (final Exception e) {
1154                     }
1155                 } while (cur.moveToNext());
1156             } finally {
1157                 cur.close();
1158             }
1159             return null;
1160         }
1161 
1162         @Override // FileCallback
confirm(final Context context, final Uri[] uris)1163         public void confirm(final Context context, final Uri[] uris) {
1164             if ("single".equals(mMode) && (uris == null || uris.length != 1)) {
1165                 throw new IllegalArgumentException();
1166             }
1167             if ("file".equals(mType)) {
1168                 final String[] paths = new String[uris != null ? uris.length : 0];
1169                 for (int i = 0; i < paths.length; i++) {
1170                     paths[i] = getFile(context, uris[i]);
1171                     if (paths[i] == null) {
1172                         Log.e(LOGTAG, "Only file URI is supported: " + uris[i]);
1173                     }
1174                 }
1175                 ensureResult().putStringArray("files", paths);
1176             } else {
1177                 throw new UnsupportedOperationException();
1178             }
1179             submit();
1180         }
1181     }
1182 
handlePromptEvent(final GeckoSession session, final GeckoBundle message, final EventCallback callback)1183     /* package */ static void handlePromptEvent(final GeckoSession session,
1184                                                 final GeckoBundle message,
1185                                                 final EventCallback callback) {
1186         final PromptDelegate delegate = session.getPromptDelegate();
1187         if (delegate == null) {
1188             // Default behavior is same as calling dismiss() on callback.
1189             callback.sendSuccess(null);
1190             return;
1191         }
1192 
1193         final String type = message.getString("type");
1194         final String mode = message.getString("mode");
1195         final PromptCallback cb = new PromptCallback(type, mode, message, callback);
1196         final String title = message.getString("title");
1197         final String msg = message.getString("msg");
1198         switch (type) {
1199             case "alert": {
1200                 delegate.onAlert(session, title, msg, cb);
1201                 break;
1202             }
1203             case "button": {
1204                 final String[] btnTitle = message.getStringArray("btnTitle");
1205                 final String[] btnCustomTitle = message.getStringArray("btnCustomTitle");
1206                 for (int i = 0; i < btnCustomTitle.length; i++) {
1207                     final int resId;
1208                     if ("ok".equals(btnTitle[i])) {
1209                         resId = android.R.string.ok;
1210                     } else if ("cancel".equals(btnTitle[i])) {
1211                         resId = android.R.string.cancel;
1212                     } else if ("yes".equals(btnTitle[i])) {
1213                         resId = android.R.string.yes;
1214                     } else if ("no".equals(btnTitle[i])) {
1215                         resId = android.R.string.no;
1216                     } else {
1217                         continue;
1218                     }
1219                     btnCustomTitle[i] = Resources.getSystem().getString(resId);
1220                 }
1221                 delegate.onButtonPrompt(session, title, msg, btnCustomTitle, cb);
1222                 break;
1223             }
1224             case "text": {
1225                 delegate.onTextPrompt(session, title, msg, message.getString("value"), cb);
1226                 break;
1227             }
1228             case "auth": {
1229                 delegate.onAuthPrompt(session, title, msg, new PromptDelegate.AuthOptions(message.getBundle("options")), cb);
1230                 break;
1231             }
1232             case "choice": {
1233                 final int intMode;
1234                 if ("menu".equals(mode)) {
1235                     intMode = PromptDelegate.Choice.CHOICE_TYPE_MENU;
1236                 } else if ("single".equals(mode)) {
1237                     intMode = PromptDelegate.Choice.CHOICE_TYPE_SINGLE;
1238                 } else if ("multiple".equals(mode)) {
1239                     intMode = PromptDelegate.Choice.CHOICE_TYPE_MULTIPLE;
1240                 } else {
1241                     callback.sendError("Invalid mode");
1242                     return;
1243                 }
1244 
1245                 GeckoBundle[] choiceBundles = message.getBundleArray("choices");
1246                 PromptDelegate.Choice choices[];
1247                 if (choiceBundles == null || choiceBundles.length == 0) {
1248                     choices = null;
1249                 } else {
1250                     choices = new PromptDelegate.Choice[choiceBundles.length];
1251                     for (int i = 0; i < choiceBundles.length; i++) {
1252                         choices[i] = new PromptDelegate.Choice(choiceBundles[i]);
1253                     }
1254                 }
1255                 delegate.onChoicePrompt(session, title, msg, intMode,
1256                                          choices, cb);
1257                 break;
1258             }
1259             case "color": {
1260                 delegate.onColorPrompt(session, title, message.getString("value"), cb);
1261                 break;
1262             }
1263             case "datetime": {
1264                 final int intMode;
1265                 if ("date".equals(mode)) {
1266                     intMode = PromptDelegate.DATETIME_TYPE_DATE;
1267                 } else if ("month".equals(mode)) {
1268                     intMode = PromptDelegate.DATETIME_TYPE_MONTH;
1269                 } else if ("week".equals(mode)) {
1270                     intMode = PromptDelegate.DATETIME_TYPE_WEEK;
1271                 } else if ("time".equals(mode)) {
1272                     intMode = PromptDelegate.DATETIME_TYPE_TIME;
1273                 } else if ("datetime-local".equals(mode)) {
1274                     intMode = PromptDelegate.DATETIME_TYPE_DATETIME_LOCAL;
1275                 } else {
1276                     callback.sendError("Invalid mode");
1277                     return;
1278                 }
1279                 delegate.onDateTimePrompt(session, title, intMode,
1280                                            message.getString("value"),
1281                                            message.getString("min"),
1282                                            message.getString("max"), cb);
1283                 break;
1284             }
1285             case "file": {
1286                 final int intMode;
1287                 if ("single".equals(mode)) {
1288                     intMode = PromptDelegate.FILE_TYPE_SINGLE;
1289                 } else if ("multiple".equals(mode)) {
1290                     intMode = PromptDelegate.FILE_TYPE_MULTIPLE;
1291                 } else {
1292                     callback.sendError("Invalid mode");
1293                     return;
1294                 }
1295                 String[] mimeTypes = message.getStringArray("mimeTypes");
1296                 final String[] extensions = message.getStringArray("extension");
1297                 if (extensions != null) {
1298                     final ArrayList<String> combined =
1299                             new ArrayList<>(mimeTypes.length + extensions.length);
1300                     combined.addAll(Arrays.asList(mimeTypes));
1301                     for (final String extension : extensions) {
1302                         final String mimeType =
1303                                 URLConnection.guessContentTypeFromName(extension);
1304                         if (mimeType != null) {
1305                             combined.add(mimeType);
1306                         }
1307                     }
1308                     mimeTypes = combined.toArray(new String[combined.size()]);
1309                 }
1310                 delegate.onFilePrompt(session, title, intMode, mimeTypes, cb);
1311                 break;
1312             }
1313             default: {
1314                 callback.sendError("Invalid type");
1315                 break;
1316             }
1317         }
1318     }
1319 
getEventDispatcher()1320     public EventDispatcher getEventDispatcher() {
1321         return mEventDispatcher;
1322     }
1323 
1324     public interface ProgressDelegate {
1325         /**
1326          * Class representing security information for a site.
1327          */
1328         public class SecurityInformation {
1329             public static final int SECURITY_MODE_UNKNOWN = 0;
1330             public static final int SECURITY_MODE_IDENTIFIED = 1;
1331             public static final int SECURITY_MODE_VERIFIED = 2;
1332 
1333             public static final int CONTENT_UNKNOWN = 0;
1334             public static final int CONTENT_BLOCKED = 1;
1335             public static final int CONTENT_LOADED = 2;
1336             /**
1337              * Indicates whether or not the site is secure.
1338              */
1339             public final boolean isSecure;
1340             /**
1341              * Indicates whether or not the site is a security exception.
1342              */
1343             public final boolean isException;
1344             /**
1345              * Contains the origin of the certificate.
1346              */
1347             public final String origin;
1348             /**
1349              * Contains the host associated with the certificate.
1350              */
1351             public final String host;
1352             /**
1353              * Contains the human-readable name of the certificate subject.
1354              */
1355             public final String organization;
1356             /**
1357              * Contains the full name of the certificate subject, including location.
1358              */
1359             public final String subjectName;
1360             /**
1361              * Contains the common name of the issuing authority.
1362              */
1363             public final String issuerCommonName;
1364             /**
1365              * Contains the full/proper name of the issuing authority.
1366              */
1367             public final String issuerOrganization;
1368             /**
1369              * Indicates the security level of the site; possible values are SECURITY_MODE_UNKNOWN,
1370              * SECURITY_MODE_IDENTIFIED, and SECURITY_MODE_VERIFIED. SECURITY_MODE_IDENTIFIED
1371              * indicates domain validation only, while SECURITY_MODE_VERIFIED indicates extended validation.
1372              */
1373             public final int securityMode;
1374             /**
1375              * Indicates the presence of passive mixed content; possible values are
1376              * CONTENT_UNKNOWN, CONTENT_BLOCKED, and CONTENT_LOADED.
1377              */
1378             public final int mixedModePassive;
1379             /**
1380              * Indicates the presence of active mixed content; possible values are
1381              * CONTENT_UNKNOWN, CONTENT_BLOCKED, and CONTENT_LOADED.
1382              */
1383             public final int mixedModeActive;
1384             /**
1385              * Indicates the status of tracking protection; possible values are
1386              * CONTENT_UNKNOWN, CONTENT_BLOCKED, and CONTENT_LOADED.
1387              */
1388             public final int trackingMode;
1389 
SecurityInformation(GeckoBundle identityData)1390             /* package */ SecurityInformation(GeckoBundle identityData) {
1391                 final GeckoBundle mode = identityData.getBundle("mode");
1392 
1393                 mixedModePassive = mode.getInt("mixed_display");
1394                 mixedModeActive = mode.getInt("mixed_active");
1395                 trackingMode = mode.getInt("tracking");
1396 
1397                 securityMode = mode.getInt("identity");
1398 
1399                 isSecure = identityData.getBoolean("secure");
1400                 isException = identityData.getBoolean("securityException");
1401                 origin = identityData.getString("origin");
1402                 host = identityData.getString("host");
1403                 organization = identityData.getString("organization");
1404                 subjectName = identityData.getString("subjectName");
1405                 issuerCommonName = identityData.getString("issuerCommonName");
1406                 issuerOrganization = identityData.getString("issuerOrganization");
1407             }
1408         }
1409 
1410         /**
1411         * A View has started loading content from the network.
1412         * @param session GeckoSession that initiated the callback.
1413         * @param url The resource being loaded.
1414         */
onPageStart(GeckoSession session, String url)1415         void onPageStart(GeckoSession session, String url);
1416 
1417         /**
1418         * A View has finished loading content from the network.
1419         * @param session GeckoSession that initiated the callback.
1420         * @param success Whether the page loaded successfully or an error occurred.
1421         */
onPageStop(GeckoSession session, boolean success)1422         void onPageStop(GeckoSession session, boolean success);
1423 
1424         /**
1425         * The security status has been updated.
1426         * @param session GeckoSession that initiated the callback.
1427         * @param securityInfo The new security information.
1428         */
onSecurityChange(GeckoSession session, SecurityInformation securityInfo)1429         void onSecurityChange(GeckoSession session, SecurityInformation securityInfo);
1430     }
1431 
1432     public interface ContentDelegate {
1433         /**
1434         * A page title was discovered in the content or updated after the content
1435         * loaded.
1436         * @param session The GeckoSession that initiated the callback.
1437         * @param title The title sent from the content.
1438         */
onTitleChange(GeckoSession session, String title)1439         void onTitleChange(GeckoSession session, String title);
1440 
1441         /**
1442         * A page has requested focus. Note that window.focus() in content will not result
1443         * in this being called.
1444         * @param session The GeckoSession that initiated the callback.
1445         */
onFocusRequest(GeckoSession session)1446         void onFocusRequest(GeckoSession session);
1447 
1448         /**
1449         * A page has requested to close
1450         * @param session The GeckoSession that initiated the callback.
1451         */
onCloseRequest(GeckoSession session)1452         void onCloseRequest(GeckoSession session);
1453 
1454         /**
1455          * A page has entered or exited full screen mode. Typically, the implementation
1456          * would set the Activity containing the GeckoSession to full screen when the page is
1457          * in full screen mode.
1458          *
1459          * @param session The GeckoSession that initiated the callback.
1460          * @param fullScreen True if the page is in full screen mode.
1461          */
onFullScreen(GeckoSession session, boolean fullScreen)1462         void onFullScreen(GeckoSession session, boolean fullScreen);
1463 
1464 
1465         /**
1466          * A user has initiated the context menu via long-press.
1467          * This event is fired on links, (nested) images and (nested) media
1468          * elements.
1469          *
1470          * @param session The GeckoSession that initiated the callback.
1471          * @param screenX The screen coordinates of the press.
1472          * @param screenY The screen coordinates of the press.
1473          * @param uri The URI of the pressed link, set for links and
1474          *            image-links.
1475          * @param elementSrc The source URI of the pressed element, set for
1476          *                   (nested) images and media elements.
1477          */
onContextMenu(GeckoSession session, int screenX, int screenY, String uri, String elementSrc)1478         void onContextMenu(GeckoSession session, int screenX, int screenY,
1479                            String uri, String elementSrc);
1480     }
1481 
1482     /**
1483      * This is used to send responses in delegate methods that have asynchronous responses.
1484      */
1485     public interface Response<T> {
1486         /**
1487          * @param val The value contained in the response
1488          */
respond(T val)1489         void respond(T val);
1490     }
1491 
1492     public interface NavigationDelegate {
1493         /**
1494         * A view has started loading content from the network.
1495         * @param session The GeckoSession that initiated the callback.
1496         * @param url The resource being loaded.
1497         */
onLocationChange(GeckoSession session, String url)1498         void onLocationChange(GeckoSession session, String url);
1499 
1500         /**
1501         * The view's ability to go back has changed.
1502         * @param session The GeckoSession that initiated the callback.
1503         * @param canGoBack The new value for the ability.
1504         */
onCanGoBack(GeckoSession session, boolean canGoBack)1505         void onCanGoBack(GeckoSession session, boolean canGoBack);
1506 
1507         /**
1508         * The view's ability to go forward has changed.
1509         * @param session The GeckoSession that initiated the callback.
1510         * @param canGoForward The new value for the ability.
1511         */
onCanGoForward(GeckoSession session, boolean canGoForward)1512         void onCanGoForward(GeckoSession session, boolean canGoForward);
1513 
1514         public static final int TARGET_WINDOW_NONE = 0;
1515         public static final int TARGET_WINDOW_CURRENT = 1;
1516         public static final int TARGET_WINDOW_NEW = 2;
1517 
1518         /**
1519          * A request to open an URI.
1520          * @param session The GeckoSession that initiated the callback.
1521          * @param uri The URI to be loaded.
1522          * @param target The target where the window has requested to open. One of
1523          *               TARGET_WINDOW_*.
1524          *
1525          * @return Whether or not the load was handled. Returning false will allow Gecko
1526          *         to continue the load as normal.
1527          */
onLoadRequest(GeckoSession session, String uri, int target)1528         boolean onLoadRequest(GeckoSession session, String uri, int target);
1529 
1530         /**
1531         * A request has been made to open a new session. The URI is provided only for
1532         * informational purposes. Do not call GeckoSession.loadUri() here. Additionally, the
1533         * returned GeckoSession must be a newly-created one.
1534         *
1535         * @param session The GeckoSession that initiated the callback.
1536         * @param uri The URI to be loaded.
1537         *
1538         * @param response A Response which will hold the returned GeckoSession
1539         */
onNewSession(GeckoSession session, String uri, Response<GeckoSession> response)1540         void onNewSession(GeckoSession session, String uri, Response<GeckoSession> response);
1541     }
1542 
1543     /**
1544      * GeckoSession applications implement this interface to handle prompts triggered by
1545      * content in the GeckoSession, such as alerts, authentication dialogs, and select list
1546      * pickers.
1547      **/
1548     public interface PromptDelegate {
1549         /**
1550          * Callback interface for notifying the result of a prompt, and for accessing the
1551          * optional features for prompts (e.g. optional checkbox).
1552          */
1553         interface AlertCallback {
1554             /**
1555              * Called by the prompt implementation when the prompt is dismissed without a
1556              * result, for example if the user presses the "Back" button. All prompts
1557              * must call dismiss() or confirm(), if available, when the prompt is dismissed.
1558              */
dismiss()1559             void dismiss();
1560 
1561             /**
1562              * Return whether the prompt shown should include a checkbox. For example, if
1563              * a page shows multiple prompts within a short period of time, the next
1564              * prompt will include a checkbox to let the user disable future prompts.
1565              * Although the API allows checkboxes for all prompts, in practice, only
1566              * alert/button/text/auth prompts will possibly have a checkbox.
1567              *
1568              * @return True if prompt includes a checkbox.
1569              */
hasCheckbox()1570             boolean hasCheckbox();
1571 
1572             /**
1573              * Return the message label for the optional checkbox.
1574              *
1575              * @return Checkbox message or null if none.
1576              */
getCheckboxMessage()1577             String getCheckboxMessage();
1578 
1579             /**
1580              * Return the initial value for the optional checkbox.
1581              *
1582              * @return Initial checkbox value.
1583              */
getCheckboxValue()1584             boolean getCheckboxValue();
1585 
1586             /**
1587              * Set the current value for the optional checkbox.
1588              *
1589              * @param value New checkbox value.
1590              */
setCheckboxValue(boolean value)1591             void setCheckboxValue(boolean value);
1592         }
1593 
1594         /**
1595          * Display a simple message prompt.
1596          *
1597          * @param session GeckoSession that triggered the prompt
1598          * @param title Title for the prompt dialog.
1599          * @param msg Message for the prompt dialog.
1600          * @param callback Callback interface.
1601          */
onAlert(GeckoSession session, String title, String msg, AlertCallback callback)1602         void onAlert(GeckoSession session, String title, String msg, AlertCallback callback);
1603 
1604         /**
1605          * Callback interface for notifying the result of a button prompt.
1606          */
1607         interface ButtonCallback extends AlertCallback {
1608             /**
1609              * Called by the prompt implementation when the button prompt is dismissed by
1610              * the user pressing one of the buttons.
1611              *
1612              * @param button Button result; one of BUTTON_TYPE_* constants.
1613              */
confirm(int button)1614             void confirm(int button);
1615         }
1616 
1617         static final int BUTTON_TYPE_POSITIVE = 0;
1618         static final int BUTTON_TYPE_NEUTRAL = 1;
1619         static final int BUTTON_TYPE_NEGATIVE = 2;
1620 
1621         /**
1622          * Display a prompt with up to three buttons.
1623          *
1624          * @param session GeckoSession that triggered the prompt
1625          * @param title Title for the prompt dialog.
1626          * @param msg Message for the prompt dialog.
1627          * @param btnMsg Array of 3 elements indicating labels for the individual buttons.
1628          *               btnMsg[BUTTON_TYPE_POSITIVE] is the label for the "positive" button.
1629          *               btnMsg[BUTTON_TYPE_NEUTRAL] is the label for the "neutral" button.
1630          *               btnMsg[BUTTON_TYPE_NEGATIVE] is the label for the "negative" button.
1631          *               The button is hidden if the corresponding label is null.
1632          * @param callback Callback interface.
1633          */
onButtonPrompt(GeckoSession session, String title, String msg, String[] btnMsg, ButtonCallback callback)1634         void onButtonPrompt(GeckoSession session, String title, String msg,
1635                              String[] btnMsg, ButtonCallback callback);
1636 
1637         /**
1638          * Callback interface for notifying the result of prompts that have text results,
1639          * including color and date/time pickers.
1640          */
1641         interface TextCallback extends AlertCallback {
1642             /**
1643              * Called by the prompt implementation when the text prompt is confirmed by
1644              * the user, for example by pressing the "OK" button.
1645              *
1646              * @param text Text result.
1647              */
confirm(String text)1648             void confirm(String text);
1649         }
1650 
1651         /**
1652          * Display a prompt for inputting text.
1653          *
1654          * @param session GeckoSession that triggered the prompt
1655          * @param title Title for the prompt dialog.
1656          * @param msg Message for the prompt dialog.
1657          * @param value Default input text for the prompt.
1658          * @param callback Callback interface.
1659          */
onTextPrompt(GeckoSession session, String title, String msg, String value, TextCallback callback)1660         void onTextPrompt(GeckoSession session, String title, String msg,
1661                            String value, TextCallback callback);
1662 
1663         /**
1664          * Callback interface for notifying the result of authentication prompts.
1665          */
1666         interface AuthCallback extends AlertCallback {
1667             /**
1668              * Called by the prompt implementation when a password-only prompt is
1669              * confirmed by the user.
1670              *
1671              * @param password Entered password.
1672              */
confirm(String password)1673             void confirm(String password);
1674 
1675             /**
1676              * Called by the prompt implementation when a username/password prompt is
1677              * confirmed by the user.
1678              *
1679              * @param username Entered username.
1680              * @param password Entered password.
1681              */
confirm(String username, String password)1682             void confirm(String username, String password);
1683         }
1684 
1685         class AuthOptions {
1686             /**
1687              * The auth prompt is for a network host.
1688              */
1689             public static final int AUTH_FLAG_HOST = 1;
1690             /**
1691              * The auth prompt is for a proxy.
1692              */
1693             public static final int AUTH_FLAG_PROXY = 2;
1694             /**
1695              * The auth prompt should only request a password.
1696              */
1697             public static final int AUTH_FLAG_ONLY_PASSWORD = 8;
1698             /**
1699              * The auth prompt is the result of a previous failed login.
1700              */
1701             public static final int AUTH_FLAG_PREVIOUS_FAILED = 16;
1702             /**
1703              * The auth prompt is for a cross-origin sub-resource.
1704              */
1705             public static final int AUTH_FLAG_CROSS_ORIGIN_SUB_RESOURCE = 32;
1706 
1707             /**
1708              * The auth request is unencrypted or the encryption status is unknown.
1709              */
1710             public static final int AUTH_LEVEL_NONE = 0;
1711             /**
1712              * The auth request only encrypts password but not data.
1713              */
1714             public static final int AUTH_LEVEL_PW_ENCRYPTED = 1;
1715             /**
1716              * The auth request encrypts both password and data.
1717              */
1718             public static final int AUTH_LEVEL_SECURE = 2;
1719 
1720             /**
1721              * An int bit-field of AUTH_FLAG_* flags.
1722              */
1723             public int flags;
1724 
1725             /**
1726              * A string containing the URI for the auth request or null if unknown.
1727              */
1728             public String uri;
1729 
1730             /**
1731              * An int, one of AUTH_LEVEL_*, indicating level of encryption.
1732              */
1733             public int level;
1734 
1735             /**
1736              * A string containing the initial username or null if password-only.
1737              */
1738             public String username;
1739 
1740             /**
1741              * A string containing the initial password.
1742              */
1743             public String password;
1744 
AuthOptions(GeckoBundle options)1745             /* package */ AuthOptions(GeckoBundle options) {
1746                 flags = options.getInt("flags");
1747                 uri = options.getString("uri");
1748                 level = options.getInt("level");
1749                 username = options.getString("username");
1750                 password = options.getString("password");
1751             }
1752         }
1753 
1754         /**
1755          * Display a prompt for authentication credentials.
1756          *
1757          * @param session GeckoSession that triggered the prompt
1758          * @param title Title for the prompt dialog.
1759          * @param msg Message for the prompt dialog.
1760          * @param options AuthOptions containing options for the prompt
1761          * @param callback Callback interface.
1762          */
onAuthPrompt(GeckoSession session, String title, String msg, AuthOptions options, AuthCallback callback)1763         void onAuthPrompt(GeckoSession session, String title, String msg,
1764                            AuthOptions options, AuthCallback callback);
1765 
1766         class Choice {
1767             /**
1768              * Display choices in a menu that dismisses as soon as an item is chosen.
1769              */
1770             public static final int CHOICE_TYPE_MENU = 1;
1771 
1772             /**
1773              * Display choices in a list that allows a single selection.
1774              */
1775             public static final int CHOICE_TYPE_SINGLE = 2;
1776 
1777             /**
1778              * Display choices in a list that allows multiple selections.
1779              */
1780             public static final int CHOICE_TYPE_MULTIPLE = 3;
1781 
1782             /**
1783              * A boolean indicating if the item is disabled. Item should not be
1784              * selectable if this is true.
1785              */
1786             public final boolean disabled;
1787 
1788             /**
1789              * A String giving the URI of the item icon, or null if none exists
1790              * (only valid for menus)
1791              */
1792             public final String icon;
1793 
1794             /**
1795              * A String giving the ID of the item or group
1796              */
1797             public final String id;
1798 
1799             /**
1800              * A Choice array of sub-items in a group, or null if not a group
1801              */
1802             public final Choice[] items;
1803 
1804             /**
1805              * A string giving the label for displaying the item or group
1806              */
1807             public final String label;
1808 
1809             /**
1810              * A boolean indicating if the item should be pre-selected
1811              * (pre-checked for menu items)
1812              */
1813             public final boolean selected;
1814 
1815             /**
1816              * A boolean indicating if the item should be a menu separator
1817              * (only valid for menus)
1818              */
1819             public final boolean separator;
1820 
Choice(GeckoBundle choice)1821             /* package */ Choice(GeckoBundle choice) {
1822                 disabled = choice.getBoolean("disabled");
1823                 icon = choice.getString("icon");
1824                 id = choice.getString("id");
1825                 label = choice.getString("label");
1826                 selected = choice.getBoolean("selected");
1827                 separator = choice.getBoolean("separator");
1828 
1829                 GeckoBundle[] choices = choice.getBundleArray("items");
1830                 if (choices == null) {
1831                     items = null;
1832                 } else {
1833                     items = new Choice[choices.length];
1834                     for (int i = 0; i < choices.length; i++) {
1835                         items[i] = new Choice(choices[i]);
1836                     }
1837                 }
1838             }
1839         }
1840 
1841         /**
1842          * Callback interface for notifying the result of menu or list choice.
1843          */
1844         interface ChoiceCallback extends AlertCallback {
1845             /**
1846              * Called by the prompt implementation when the menu or single-choice list is
1847              * dismissed by the user.
1848              *
1849              * @param id ID of the selected item.
1850              */
confirm(String id)1851             void confirm(String id);
1852 
1853             /**
1854              * Called by the prompt implementation when the multiple-choice list is
1855              * dismissed by the user.
1856              *
1857              * @param ids IDs of the selected items.
1858              */
confirm(String[] ids)1859             void confirm(String[] ids);
1860 
1861             /**
1862              * Called by the prompt implementation when the menu or single-choice list is
1863              * dismissed by the user.
1864              *
1865              * @param item Choice representing the selected item; must be an original
1866              *             Choice object that was passed to the implementation.
1867              */
confirm(Choice item)1868             void confirm(Choice item);
1869 
1870             /**
1871              * Called by the prompt implementation when the multiple-choice list is
1872              * dismissed by the user.
1873              *
1874              * @param items Choice array representing the selected items; must be original
1875              *              Choice objects that were passed to the implementation.
1876              */
confirm(Choice[] items)1877             void confirm(Choice[] items);
1878         }
1879 
1880 
1881         /**
1882          * Display a menu prompt or list prompt.
1883          *
1884          * @param session GeckoSession that triggered the prompt
1885          * @param title Title for the prompt dialog, or null for no title.
1886          * @param msg Message for the prompt dialog, or null for no message.
1887          * @param type One of CHOICE_TYPE_* indicating the type of prompt.
1888          * @param choices Array of Choices each representing an item or group.
1889          * @param callback Callback interface.
1890          */
onChoicePrompt(GeckoSession session, String title, String msg, int type, Choice[] choices, ChoiceCallback callback)1891         void onChoicePrompt(GeckoSession session, String title, String msg, int type,
1892                              Choice[] choices, ChoiceCallback callback);
1893 
1894         /**
1895          * Display a color prompt.
1896          *
1897          * @param session GeckoSession that triggered the prompt
1898          * @param title Title for the prompt dialog.
1899          * @param value Initial color value in HTML color format.
1900          * @param callback Callback interface; the result passed to confirm() must be in
1901          *                 HTML color format.
1902          */
onColorPrompt(GeckoSession session, String title, String value, TextCallback callback)1903         void onColorPrompt(GeckoSession session, String title, String value,
1904                             TextCallback callback);
1905 
1906         /**
1907          * Prompt for year, month, and day.
1908          */
1909         static final int DATETIME_TYPE_DATE = 1;
1910 
1911         /**
1912          * Prompt for year and month.
1913          */
1914         static final int DATETIME_TYPE_MONTH = 2;
1915 
1916         /**
1917          * Prompt for year and week.
1918          */
1919         static final int DATETIME_TYPE_WEEK = 3;
1920 
1921         /**
1922          * Prompt for hour and minute.
1923          */
1924         static final int DATETIME_TYPE_TIME = 4;
1925 
1926         /**
1927          * Prompt for year, month, day, hour, and minute, without timezone.
1928          */
1929         static final int DATETIME_TYPE_DATETIME_LOCAL = 5;
1930 
1931         /**
1932          * Display a date/time prompt.
1933          *
1934          * @param session GeckoSession that triggered the prompt
1935          * @param title Title for the prompt dialog; currently always null.
1936          * @param type One of DATETIME_TYPE_* indicating the type of prompt.
1937          * @param value Initial date/time value in HTML date/time format.
1938          * @param min Minimum date/time value in HTML date/time format.
1939          * @param max Maximum date/time value in HTML date/time format.
1940          * @param callback Callback interface; the result passed to confirm() must be in
1941          *                 HTML date/time format.
1942          */
onDateTimePrompt(GeckoSession session, String title, int type, String value, String min, String max, TextCallback callback)1943         void onDateTimePrompt(GeckoSession session, String title, int type,
1944                                String value, String min, String max, TextCallback callback);
1945 
1946         /**
1947          * Callback interface for notifying the result of file prompts.
1948          */
1949         interface FileCallback extends AlertCallback {
1950             /**
1951              * Called by the prompt implementation when the user makes a file selection in
1952              * single-selection mode.
1953              *
1954              * @param context An application Context for parsing URIs.
1955              * @param uri The URI of the selected file.
1956              */
confirm(Context context, Uri uri)1957             void confirm(Context context, Uri uri);
1958 
1959             /**
1960              * Called by the prompt implementation when the user makes file selections in
1961              * multiple-selection mode.
1962              *
1963              * @param context An application Context for parsing URIs.
1964              * @param uris Array of URI objects for the selected files.
1965              */
confirm(Context context, Uri[] uris)1966             void confirm(Context context, Uri[] uris);
1967         }
1968 
1969         static final int FILE_TYPE_SINGLE = 1;
1970         static final int FILE_TYPE_MULTIPLE = 2;
1971 
1972         /**
1973          * Display a file prompt.
1974          *
1975          * @param session GeckoSession that triggered the prompt
1976          * @param title Title for the prompt dialog.
1977          * @param type One of FILE_TYPE_* indicating the prompt type.
1978          * @param mimeTypes Array of permissible MIME types for the selected files, in
1979          *                  the form "type/subtype", where "type" and/or "subtype" can be
1980          *                  "*" to indicate any value.
1981          * @param callback Callback interface.
1982          */
onFilePrompt(GeckoSession session, String title, int type, String[] mimeTypes, FileCallback callback)1983         void onFilePrompt(GeckoSession session, String title, int type,
1984                            String[] mimeTypes, FileCallback callback);
1985     }
1986 
1987     /**
1988      * GeckoSession applications implement this interface to handle content scroll
1989      * events.
1990      **/
1991     public interface ScrollDelegate {
1992         /**
1993          * The scroll position of the content has changed.
1994          *
1995         * @param session GeckoSession that initiated the callback.
1996         * @param scrollX The new horizontal scroll position in pixels.
1997         * @param scrollY The new vertical scroll position in pixels.
1998         */
onScrollChanged(GeckoSession session, int scrollX, int scrollY)1999         public void onScrollChanged(GeckoSession session, int scrollX, int scrollY);
2000     }
2001 
2002     private final TrackingProtection mTrackingProtection = new TrackingProtection(this);
2003 
2004     /**
2005      * GeckoSession applications implement this interface to handle tracking
2006      * protection events.
2007      **/
2008     public interface TrackingProtectionDelegate {
2009         static final int CATEGORY_AD = 1 << 0;
2010         static final int CATEGORY_ANALYTIC = 1 << 1;
2011         static final int CATEGORY_SOCIAL = 1 << 2;
2012         static final int CATEGORY_CONTENT = 1 << 3;
2013 
2014         /**
2015          * A tracking element has been blocked from loading.
2016          *
2017         * @param session The GeckoSession that initiated the callback.
2018         * @param uri The URI of the blocked element.
2019         * @param categories The tracker categories of the blocked element.
2020         *                   One or more of the {@link TrackingProtectionDelegate#CATEGORY_AD}
2021         *                   flags.
2022         */
onTrackerBlocked(GeckoSession session, String uri, int categories)2023         void onTrackerBlocked(GeckoSession session, String uri, int categories);
2024     }
2025 
2026     /**
2027      * Enable tracking protection.
2028      * @param categories The categories of trackers that should be blocked.
2029      *                   Use one or more of the {@link TrackingProtectionDelegate#CATEGORY_AD}
2030      *                   flags.
2031      **/
enableTrackingProtection(int categories)2032     public void enableTrackingProtection(int categories) {
2033         mTrackingProtection.enable(categories);
2034     }
2035 
2036     /**
2037      * Disable tracking protection.
2038      **/
disableTrackingProtection()2039     public void disableTrackingProtection() {
2040         mTrackingProtection.disable();
2041     }
2042 
2043     /**
2044      * GeckoSession applications implement this interface to handle requests for permissions
2045      * from content, such as geolocation and notifications. For each permission, usually
2046      * two requests are generated: one request for the Android app permission through
2047      * requestAppPermissions, which is typically handled by a system permission dialog;
2048      * and another request for the content permission (e.g. through
2049      * requestContentPermission), which is typically handled by an app-specific
2050      * permission dialog.
2051      **/
2052     public interface PermissionDelegate {
2053         /**
2054          * Permission for using the geolocation API.
2055          * See: https://developer.mozilla.org/en-US/docs/Web/API/Geolocation
2056          */
2057         public static final int PERMISSION_GEOLOCATION = 0;
2058 
2059         /**
2060          * Permission for using the notifications API.
2061          * See: https://developer.mozilla.org/en-US/docs/Web/API/notification
2062          */
2063         public static final int PERMISSION_DESKTOP_NOTIFICATION = 1;
2064 
2065         /**
2066          * Callback interface for notifying the result of a permission request.
2067          */
2068         interface Callback {
2069             /**
2070              * Called by the implementation after permissions are granted; the
2071              * implementation must call either grant() or reject() for every request.
2072              */
grant()2073             void grant();
2074 
2075             /**
2076              * Called by the implementation when permissions are not granted; the
2077              * implementation must call either grant() or reject() for every request.
2078              */
reject()2079             void reject();
2080         }
2081 
2082         /**
2083          * Request Android app permissions.
2084          *
2085          * @param session GeckoSession instance requesting the permissions.
2086          * @param permissions List of permissions to request; possible values are,
2087          *                    android.Manifest.permission.ACCESS_COARSE_LOCATION
2088          *                    android.Manifest.permission.ACCESS_FINE_LOCATION
2089          *                    android.Manifest.permission.CAMERA
2090          *                    android.Manifest.permission.RECORD_AUDIO
2091          * @param callback Callback interface.
2092          */
onAndroidPermissionsRequest(GeckoSession session, String[] permissions, Callback callback)2093         void onAndroidPermissionsRequest(GeckoSession session, String[] permissions,
2094                                        Callback callback);
2095 
2096         /**
2097          * Request content permission.
2098          *
2099          * @param session GeckoSession instance requesting the permission.
2100          * @param uri The URI of the content requesting the permission.
2101          * @param type The type of the requested permission; possible values are,
2102          *             PERMISSION_GEOLOCATION
2103          *             PERMISSION_DESKTOP_NOTIFICATION
2104          * @param access Not used.
2105          * @param callback Callback interface.
2106          */
onContentPermissionRequest(GeckoSession session, String uri, int type, String access, Callback callback)2107         void onContentPermissionRequest(GeckoSession session, String uri, int type,
2108                                       String access, Callback callback);
2109 
2110         class MediaSource {
2111             /**
2112              * The media source is a camera.
2113              */
2114             public static final int SOURCE_CAMERA = 0;
2115 
2116             /**
2117              * The media source is the screen.
2118              */
2119             public static final int SOURCE_SCREEN  = 1;
2120 
2121             /**
2122              * The media source is an application.
2123              */
2124             public static final int SOURCE_APPLICATION = 2;
2125 
2126             /**
2127              * The media source is a window.
2128              */
2129             public static final int SOURCE_WINDOW = 3;
2130 
2131             /**
2132              * The media source is the browser.
2133              */
2134             public static final int SOURCE_BROWSER = 4;
2135 
2136             /**
2137              * The media source is a microphone.
2138              */
2139             public static final int SOURCE_MICROPHONE = 5;
2140 
2141             /**
2142              * The media source is audio capture.
2143              */
2144             public static final int SOURCE_AUDIOCAPTURE = 6;
2145 
2146             /**
2147              * The media source does not fall into any of the other categories.
2148              */
2149             public static final int SOURCE_OTHER = 7;
2150 
2151             /**
2152              * The media type is video.
2153              */
2154             public static final int TYPE_VIDEO = 0;
2155 
2156             /**
2157              * The media type is audio.
2158              */
2159             public static final int TYPE_AUDIO = 1;
2160 
2161             /**
2162              * A string giving the origin-specific source identifier.
2163              */
2164             public final String id;
2165 
2166             /**
2167              * A string giving the non-origin-specific source identifier.
2168              */
2169             public final String rawId;
2170 
2171             /**
2172              * A string giving the name of the video source from the system
2173              * (for example, "Camera 0, Facing back, Orientation 90").
2174              * May be empty.
2175              */
2176             public final String name;
2177 
2178             /**
2179              * An int giving the media source type.
2180              * Possible values for a video source are:
2181              * SOURCE_CAMERA, SOURCE_SCREEN, SOURCE_APPLICATION, SOURCE_WINDOW, SOURCE_BROWSER, and SOURCE_OTHER.
2182              * Possible values for an audio source are:
2183              * SOURCE_MICROPHONE, SOURCE_AUDIOCAPTURE, and SOURCE_OTHER.
2184              */
2185             public final int source;
2186 
2187             /**
2188              * An int giving the type of media, must be either TYPE_VIDEO or TYPE_AUDIO.
2189              */
2190             public final int type;
2191 
getSourceFromString(String src)2192             private static int getSourceFromString(String src) {
2193                 // The strings here should match those in MediaSourceEnum in MediaStreamTrack.webidl
2194                 if ("camera".equals(src)) {
2195                     return SOURCE_CAMERA;
2196                 } else if ("screen".equals(src)) {
2197                     return SOURCE_SCREEN;
2198                 } else if ("application".equals(src)) {
2199                     return SOURCE_APPLICATION;
2200                 } else if ("window".equals(src)) {
2201                     return SOURCE_WINDOW;
2202                 } else if ("browser".equals(src)) {
2203                     return SOURCE_BROWSER;
2204                 } else if ("microphone".equals(src)) {
2205                     return SOURCE_MICROPHONE;
2206                 } else if ("audioCapture".equals(src)) {
2207                     return SOURCE_AUDIOCAPTURE;
2208                 } else if ("other".equals(src)) {
2209                     return SOURCE_OTHER;
2210                 } else {
2211                     throw new IllegalArgumentException("String: " + src + " is not a valid media source string");
2212                 }
2213             }
2214 
getTypeFromString(String type)2215             private static int getTypeFromString(String type) {
2216                 // The strings here should match the possible types in MediaDevice::MediaDevice in MediaManager.cpp
2217                 if ("video".equals(type)) {
2218                     return TYPE_VIDEO;
2219                 } else if ("audio".equals(type)) {
2220                     return TYPE_AUDIO;
2221                 } else {
2222                     throw new IllegalArgumentException("String: " + type + " is not a valid media type string");
2223                 }
2224             }
2225 
MediaSource(GeckoBundle media)2226             /* package */ MediaSource(GeckoBundle media) {
2227                 id = media.getString("id");
2228                 rawId = media.getString("rawId");
2229                 name = media.getString("name");
2230                 source = getSourceFromString(media.getString("source"));
2231                 type = getTypeFromString(media.getString("type"));
2232             }
2233         }
2234 
2235         /**
2236          * Callback interface for notifying the result of a media permission request,
2237          * including which media source(s) to use.
2238          */
2239         interface MediaCallback {
2240             /**
2241              * Called by the implementation after permissions are granted; the
2242              * implementation must call one of grant() or reject() for every request.
2243              *
2244              * @param video "id" value from the bundle for the video source to use,
2245              *              or null when video is not requested.
2246              * @param audio "id" value from the bundle for the audio source to use,
2247              *              or null when audio is not requested.
2248              */
grant(final String video, final String audio)2249             void grant(final String video, final String audio);
2250 
2251             /**
2252              * Called by the implementation after permissions are granted; the
2253              * implementation must call one of grant() or reject() for every request.
2254              *
2255              * @param video MediaSource for the video source to use (must be an original
2256              *              MediaSource object that was passed to the implementation);
2257              *              or null when video is not requested.
2258              * @param audio MediaSource for the audio source to use (must be an original
2259              *              MediaSource object that was passed to the implementation);
2260              *              or null when audio is not requested.
2261              */
grant(final MediaSource video, final MediaSource audio)2262             void grant(final MediaSource video, final MediaSource audio);
2263 
2264             /**
2265              * Called by the implementation when permissions are not granted; the
2266              * implementation must call one of grant() or reject() for every request.
2267              */
reject()2268             void reject();
2269         }
2270 
2271         /**
2272          * Request content media permissions, including request for which video and/or
2273          * audio source to use.
2274          *
2275          * @param session GeckoSession instance requesting the permission.
2276          * @param uri The URI of the content requesting the permission.
2277          * @param video List of video sources, or null if not requesting video.
2278          * @param audio List of audio sources, or null if not requesting audio.
2279          * @param callback Callback interface.
2280          */
onMediaPermissionRequest(GeckoSession session, String uri, MediaSource[] video, MediaSource[] audio, MediaCallback callback)2281         void onMediaPermissionRequest(GeckoSession session, String uri, MediaSource[] video,
2282                                     MediaSource[] audio, MediaCallback callback);
2283     }
2284 }
2285