Bug 1549418 - Add GeckoRuntime.ActivityDelegate r=geckoview-reviewers,agi,esawin

This allows GeckoView to ask the embedding application to launch
other Activities on our behalf and collect the result. We need
this to invoke the FIDO authentication tools for WebAuthn.

Differential Revision: https://phabricator.services.mozilla.com/D91445
This commit is contained in:
James Willcox 2020-10-02 15:43:39 +00:00
Родитель 479ef2392a
Коммит 4312bd0da2
4 изменённых файлов: 120 добавлений и 2 удалений

Просмотреть файл

@ -1,4 +1,5 @@
import android.app.Activity;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
@ -563,6 +564,7 @@ package org.mozilla.geckoview {
method @UiThread public void configurationChanged(@NonNull Configuration);
method @UiThread @NonNull public static GeckoRuntime create(@NonNull Context);
method @UiThread @NonNull public static GeckoRuntime create(@NonNull Context, @NonNull GeckoRuntimeSettings);
method @UiThread @Nullable public GeckoRuntime.ActivityDelegate getActivityDelegate();
method @UiThread @NonNull public ContentBlockingController getContentBlockingController();
method @UiThread @NonNull public static synchronized GeckoRuntime getDefault(@NonNull Context);
method @UiThread @Nullable public GeckoRuntime.Delegate getDelegate();
@ -577,6 +579,7 @@ package org.mozilla.geckoview {
method @UiThread public void orientationChanged();
method @UiThread public void orientationChanged(int);
method @AnyThread public void readFromParcel(@NonNull Parcel);
method @UiThread public void setActivityDelegate(@Nullable GeckoRuntime.ActivityDelegate);
method @UiThread public void setDelegate(@Nullable GeckoRuntime.Delegate);
method @UiThread public void setLoginStorageDelegate(@Nullable Autocomplete.LoginStorageDelegate);
method @UiThread public void setServiceWorkerDelegate(@Nullable GeckoRuntime.ServiceWorkerDelegate);
@ -589,6 +592,10 @@ package org.mozilla.geckoview {
field public static final String EXTRA_MINIDUMP_PATH = "minidumpPath";
}
public static interface GeckoRuntime.ActivityDelegate {
method @UiThread @Nullable public GeckoResult<Intent> onStartActivityForResult(@NonNull PendingIntent);
}
public static interface GeckoRuntime.Delegate {
method @UiThread public void onShutdown();
}

Просмотреть файл

@ -11,7 +11,9 @@ import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.OnLifecycleEvent;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@ -166,6 +168,7 @@ public final class GeckoRuntime implements Parcelable {
private Delegate mDelegate;
private ServiceWorkerDelegate mServiceWorkerDelegate;
private WebNotificationDelegate mNotificationDelegate;
private ActivityDelegate mActivityDelegate;
private StorageController mStorageController;
private final WebExtensionController mWebExtensionController;
private WebPushController mPushController;
@ -187,7 +190,7 @@ public final class GeckoRuntime implements Parcelable {
@WrapForJNI
@UiThread
private @Nullable static GeckoRuntime getInstance() {
/* package */ @Nullable static GeckoRuntime getInstance() {
return sRuntime;
}
@ -647,6 +650,86 @@ public final class GeckoRuntime implements Parcelable {
});
}
/**
* This is used to allow GeckoRuntime to start activities via the embedding
* application (and {@link android.app.Activity}). Currently this is used to invoke the
* Google Play FIDO Activity in order to integrate with the Web Authentication API.
*
* @see <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API">Web Authentication API</a>
*/
public interface ActivityDelegate {
/**
* Sometimes GeckoView needs the application to perform a
* {@link android.app.Activity#startActivityForResult(Intent, int)}
* on its behalf. Implementations of this method should call that based on the information
* in the passed {@link PendingIntent}, collect the result, and resolve the returned
* {@link GeckoResult} with that data. If the
* Activity does not return {@link android.app.Activity#RESULT_OK}, the {@link GeckoResult}
* must be completed with an exception of your choosing.
*
* @param intent The {@link PendingIntent} to launch
* @return A {@link GeckoResult} that is eventually resolved with the Activity result.
*/
@UiThread
@Nullable GeckoResult<Intent> onStartActivityForResult(@NonNull PendingIntent intent);
}
/**
* Set the {@link ActivityDelegate} instance on this runtime.
* This delegate is used to provide GeckoView support for launching external
* activities and receiving results from those activities.
*
* @param delegate The {@link ActivityDelegate} handling intent launching requests.
*/
@UiThread
public void setActivityDelegate(
final @Nullable ActivityDelegate delegate) {
ThreadUtils.assertOnUiThread();
mActivityDelegate = delegate;
}
/**
* Get the {@link ActivityDelegate} instance set on this runtime, if any,
*
* @return The {@link ActivityDelegate} set on this runtime.
*/
@UiThread
public @Nullable ActivityDelegate getActivityDelegate() {
ThreadUtils.assertOnUiThread();
return mActivityDelegate;
}
@AnyThread
/* package */ GeckoResult<Intent> startActivityForResult(final @NonNull PendingIntent intent) {
if (!ThreadUtils.isOnUiThread()) {
// Delegates expect to be called on the UI thread.
final GeckoResult<Intent> result = new GeckoResult<>();
ThreadUtils.runOnUiThread(() -> {
final GeckoResult<Intent> delegateResult = startActivityForResult(intent);
if (delegateResult != null) {
delegateResult.accept(val -> result.complete(val), e -> result.completeExceptionally(e));
} else {
result.completeExceptionally(new IllegalStateException("No result"));
}
});
return result;
}
if (mActivityDelegate == null) {
return GeckoResult.fromException(new IllegalStateException("No delegate attached"));
}
@SuppressLint("WrongThread")
GeckoResult<Intent> result = mActivityDelegate.onStartActivityForResult(intent);
if (result == null) {
result = GeckoResult.fromException(new IllegalStateException("No result"));
}
return result;
}
@AnyThread
@SuppressWarnings("checkstyle:javadocmethod")
public @NonNull GeckoRuntimeSettings getSettings() {

Просмотреть файл

@ -26,10 +26,14 @@ exclude: true
- Added [`ContentBlocking.SafeBrowsingProvider`][83.3] to configure Safe
Browsing providers.
([bug 1660241]({{bugzilla}}1660241))
- Added [`GeckoRuntime.ActivityDelegate`][83.4] which allows applications to handle
starting external Activities on behalf of GeckoView. Currently this is used to integrate
FIDO support for WebAuthn.
[83.1]: {{javadoc_uri}}/WebExtension.MetaData.html#temporary
[83.2]: {{javadoc_uri}}/MediaSession.Delegate.html#onMetadata-org.mozilla.geckoview.GeckoSession-org.mozilla.geckoview.MediaSession-org.mozilla.geckoview.MediaSession.Metadata-
[83.3]: {{javadoc_uri}}/ContentBlocking.SafeBrowsingProvider.html
[83.4]: {{javadoc_uri}}/GeckoRuntime.ActivityDelegate.html
## v82
- ⚠️ [`WebNotification.source`][79.2] is now `@Nullable` to account for
@ -819,4 +823,4 @@ to allow adding gecko profiler markers.
[65.24]: {{javadoc_uri}}/CrashReporter.html#sendCrashReport-android.content.Context-android.os.Bundle-java.lang.String-
[65.25]: {{javadoc_uri}}/GeckoResult.html
[api-version]: 2a562a8a5620ba5db90a9e7f59f6396eaeb4356f
[api-version]: d3186b70503f71a6476c765f76c6d49f9a1fb282

Просмотреть файл

@ -37,6 +37,7 @@ import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Intent;
import android.content.IntentSender;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
@ -418,6 +419,9 @@ public class GeckoViewActivity
private LinkedList<WebResponse> mPendingDownloads = new LinkedList<>();
private int mNextActivityResultCode = 10;
private HashMap<Integer, GeckoResult<Intent>> mPendingActivityResult = new HashMap<>();
private LocationView.CommitListener mCommitListener = new LocationView.CommitListener() {
@Override
public void onCommit(String text) {
@ -673,6 +677,18 @@ public class GeckoViewActivity
mKillProcessOnDestroy = true;
finish();
});
sGeckoRuntime.setActivityDelegate(pendingIntent -> {
final GeckoResult<Intent> result = new GeckoResult<>();
try {
final int code = mNextActivityResultCode++;
mPendingActivityResult.put(code, result);
GeckoViewActivity.this.startIntentSenderForResult(pendingIntent.getIntentSender(), code, null, 0, 0, 0);
} catch (IntentSender.SendIntentException e) {
result.completeExceptionally(e);
}
return result;
});
}
sExtensionManager.setExtensionDelegate(this);
@ -1170,6 +1186,14 @@ public class GeckoViewActivity
final BasicGeckoViewPrompt prompt = (BasicGeckoViewPrompt)
mTabSessionManager.getCurrentSession().getPromptDelegate();
prompt.onFileCallbackResult(resultCode, data);
} else if (mPendingActivityResult.containsKey(requestCode)) {
final GeckoResult<Intent> result = mPendingActivityResult.remove(requestCode);
if (resultCode == Activity.RESULT_OK) {
result.complete(data);
} else {
result.completeExceptionally(new RuntimeException("Unknown error"));
}
} else {
super.onActivityResult(requestCode, resultCode, data);
}