зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1577003 - Move autofill stuff from TextInputDelegate into AutofillDelegate r=geckoview-reviewers,agi,esawin
This also moves `autofill()` and `provideAutofillVirtualStructure()` into `GeckoSession`. Differential Revision: https://phabricator.services.mozilla.com/D47481 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
8d375d387b
Коммит
05c1575597
|
@ -254,11 +254,11 @@ class ContentDelegateTest : BaseSessionTest() {
|
|||
// SessionAccessibility for a11y auto-fill support.
|
||||
mainSession.loadTestPath(FORMS_HTML_PATH)
|
||||
// Wait for the auto-fill nodes to populate.
|
||||
sessionRule.waitUntilCalled(object : Callbacks.TextInputDelegate {
|
||||
sessionRule.waitUntilCalled(object : Callbacks.AutofillDelegate {
|
||||
// For the root document and the iframe document, each has a form group and
|
||||
// a group for inputs outside of forms, so the total count is 4.
|
||||
@AssertCalled(count = 4)
|
||||
override fun notifyAutoFill(session: GeckoSession, notification: Int, virtualId: Int) {
|
||||
override fun onAutofill(session: GeckoSession, notification: Int, virtualId: Int) {
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -361,10 +361,10 @@ class ContentDelegateTest : BaseSessionTest() {
|
|||
|
||||
val rootStructure = MockViewNode()
|
||||
|
||||
mainSession.textInput.onProvideAutofillVirtualStructure(rootStructure, 0)
|
||||
mainSession.provideAutofillVirtualStructure(null, rootStructure, 0)
|
||||
checkAutoFillChild(rootStructure)
|
||||
|
||||
mainSession.textInput.autofill(autoFillValues)
|
||||
mainSession.autofill(autoFillValues)
|
||||
|
||||
// Wait on the promises and check for correct values.
|
||||
for ((key, actual, expected, eventInterface) in promises.map { it.value.asJSList<String>() }) {
|
||||
|
@ -390,7 +390,7 @@ class ContentDelegateTest : BaseSessionTest() {
|
|||
{ it.className == "android.widget.EditText" },
|
||||
root: MockViewNode? = null): Int {
|
||||
val node = if (root !== null) root else MockViewNode().also {
|
||||
mainSession.textInput.onProvideAutofillVirtualStructure(it, 0)
|
||||
mainSession.provideAutofillVirtualStructure(null, it, 0)
|
||||
}
|
||||
|
||||
return (if (cond(node)) 1 else 0) +
|
||||
|
@ -400,12 +400,12 @@ class ContentDelegateTest : BaseSessionTest() {
|
|||
|
||||
// Wait for the accessibility nodes to populate.
|
||||
mainSession.loadTestPath(FORMS_HTML_PATH)
|
||||
sessionRule.waitUntilCalled(object : Callbacks.TextInputDelegate {
|
||||
sessionRule.waitUntilCalled(object : Callbacks.AutofillDelegate {
|
||||
@AssertCalled(count = 4)
|
||||
override fun notifyAutoFill(session: GeckoSession, notification: Int, virtualId: Int) {
|
||||
override fun onAutofill(session: GeckoSession, notification: Int, virtualId: Int) {
|
||||
assertThat("Should be starting auto-fill", notification, equalTo(forEachCall(
|
||||
GeckoSession.TextInputDelegate.AUTO_FILL_NOTIFY_STARTED,
|
||||
GeckoSession.TextInputDelegate.AUTO_FILL_NOTIFY_VIEW_ADDED)))
|
||||
GeckoSession.AutofillDelegate.AUTO_FILL_NOTIFY_STARTED,
|
||||
GeckoSession.AutofillDelegate.AUTO_FILL_NOTIFY_VIEW_ADDED)))
|
||||
assertThat("ID should be valid", virtualId, not(equalTo(View.NO_ID)))
|
||||
}
|
||||
})
|
||||
|
@ -414,12 +414,12 @@ class ContentDelegateTest : BaseSessionTest() {
|
|||
|
||||
// Now wait for the nodes to clear.
|
||||
mainSession.loadTestPath(HELLO_HTML_PATH)
|
||||
sessionRule.waitUntilCalled(object : Callbacks.TextInputDelegate {
|
||||
sessionRule.waitUntilCalled(object : Callbacks.AutofillDelegate {
|
||||
@AssertCalled(count = 1)
|
||||
override fun notifyAutoFill(session: GeckoSession, notification: Int, virtualId: Int) {
|
||||
override fun onAutofill(session: GeckoSession, notification: Int, virtualId: Int) {
|
||||
assertThat("Should be canceling auto-fill",
|
||||
notification,
|
||||
equalTo(GeckoSession.TextInputDelegate.AUTO_FILL_NOTIFY_CANCELED))
|
||||
equalTo(GeckoSession.AutofillDelegate.AUTO_FILL_NOTIFY_CANCELED))
|
||||
assertThat("ID should be valid", virtualId, equalTo(View.NO_ID))
|
||||
}
|
||||
})
|
||||
|
@ -429,12 +429,12 @@ class ContentDelegateTest : BaseSessionTest() {
|
|||
// Now wait for the nodes to reappear.
|
||||
mainSession.waitForPageStop()
|
||||
mainSession.goBack()
|
||||
sessionRule.waitUntilCalled(object : Callbacks.TextInputDelegate {
|
||||
sessionRule.waitUntilCalled(object : Callbacks.AutofillDelegate {
|
||||
@AssertCalled(count = 4)
|
||||
override fun notifyAutoFill(session: GeckoSession, notification: Int, virtualId: Int) {
|
||||
override fun onAutofill(session: GeckoSession, notification: Int, virtualId: Int) {
|
||||
assertThat("Should be starting auto-fill", notification, equalTo(forEachCall(
|
||||
GeckoSession.TextInputDelegate.AUTO_FILL_NOTIFY_STARTED,
|
||||
GeckoSession.TextInputDelegate.AUTO_FILL_NOTIFY_VIEW_ADDED)))
|
||||
GeckoSession.AutofillDelegate.AUTO_FILL_NOTIFY_STARTED,
|
||||
GeckoSession.AutofillDelegate.AUTO_FILL_NOTIFY_VIEW_ADDED)))
|
||||
assertThat("ID should be valid", virtualId, not(equalTo(View.NO_ID)))
|
||||
}
|
||||
})
|
||||
|
@ -444,12 +444,12 @@ class ContentDelegateTest : BaseSessionTest() {
|
|||
countAutoFillNodes({ it.isFocused }), equalTo(0))
|
||||
|
||||
mainSession.evaluateJS("document.querySelector('#pass2').focus()")
|
||||
sessionRule.waitUntilCalled(object : Callbacks.TextInputDelegate {
|
||||
sessionRule.waitUntilCalled(object : Callbacks.AutofillDelegate {
|
||||
@AssertCalled(count = 1)
|
||||
override fun notifyAutoFill(session: GeckoSession, notification: Int, virtualId: Int) {
|
||||
override fun onAutofill(session: GeckoSession, notification: Int, virtualId: Int) {
|
||||
assertThat("Should be entering auto-fill view",
|
||||
notification,
|
||||
equalTo(GeckoSession.TextInputDelegate.AUTO_FILL_NOTIFY_VIEW_ENTERED))
|
||||
equalTo(GeckoSession.AutofillDelegate.AUTO_FILL_NOTIFY_VIEW_ENTERED))
|
||||
assertThat("ID should be valid", virtualId, not(equalTo(View.NO_ID)))
|
||||
}
|
||||
})
|
||||
|
@ -461,12 +461,12 @@ class ContentDelegateTest : BaseSessionTest() {
|
|||
equalTo(7))
|
||||
|
||||
mainSession.evaluateJS("document.querySelector('#pass2').blur()")
|
||||
sessionRule.waitUntilCalled(object : Callbacks.TextInputDelegate {
|
||||
sessionRule.waitUntilCalled(object : Callbacks.AutofillDelegate {
|
||||
@AssertCalled(count = 1)
|
||||
override fun notifyAutoFill(session: GeckoSession, notification: Int, virtualId: Int) {
|
||||
override fun onAutofill(session: GeckoSession, notification: Int, virtualId: Int) {
|
||||
assertThat("Should be exiting auto-fill view",
|
||||
notification,
|
||||
equalTo(GeckoSession.TextInputDelegate.AUTO_FILL_NOTIFY_VIEW_EXITED))
|
||||
equalTo(GeckoSession.AutofillDelegate.AUTO_FILL_NOTIFY_VIEW_EXITED))
|
||||
assertThat("ID should be valid", virtualId, not(equalTo(View.NO_ID)))
|
||||
}
|
||||
})
|
||||
|
@ -482,9 +482,9 @@ class ContentDelegateTest : BaseSessionTest() {
|
|||
|
||||
mainSession.loadTestPath(FORMS2_HTML_PATH)
|
||||
// Wait for the auto-fill nodes to populate.
|
||||
sessionRule.waitUntilCalled(object : Callbacks.TextInputDelegate {
|
||||
sessionRule.waitUntilCalled(object : Callbacks.AutofillDelegate {
|
||||
@AssertCalled(count = 1)
|
||||
override fun notifyAutoFill(session: GeckoSession, notification: Int, virtualId: Int) {
|
||||
override fun onAutofill(session: GeckoSession, notification: Int, virtualId: Int) {
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -524,7 +524,7 @@ class ContentDelegateTest : BaseSessionTest() {
|
|||
|
||||
val rootStructure = MockViewNode()
|
||||
|
||||
mainSession.textInput.onProvideAutofillVirtualStructure(rootStructure, 0)
|
||||
mainSession.provideAutofillVirtualStructure(null, rootStructure, 0)
|
||||
// form and iframe have each 2 hints.
|
||||
assertThat("autofill hint count",
|
||||
checkAutoFillChild(rootStructure), equalTo(4))
|
||||
|
|
|
@ -23,12 +23,13 @@ import org.json.JSONObject
|
|||
class Callbacks private constructor() {
|
||||
object Default : All
|
||||
|
||||
interface All : ContentBlockingDelegate, ContentDelegate,
|
||||
interface All : AutofillDelegate, ContentBlockingDelegate, ContentDelegate,
|
||||
HistoryDelegate, MediaDelegate,
|
||||
NavigationDelegate, PermissionDelegate, ProgressDelegate,
|
||||
PromptDelegate, ScrollDelegate, SelectionActionDelegate,
|
||||
TextInputDelegate
|
||||
|
||||
interface AutofillDelegate : GeckoSession.AutofillDelegate {}
|
||||
interface ContentDelegate : GeckoSession.ContentDelegate {}
|
||||
interface NavigationDelegate : GeckoSession.NavigationDelegate {}
|
||||
interface PermissionDelegate : GeckoSession.PermissionDelegate {}
|
||||
|
@ -58,8 +59,5 @@ class Callbacks private constructor() {
|
|||
|
||||
override fun updateCursorAnchorInfo(session: GeckoSession, info: CursorAnchorInfo) {
|
||||
}
|
||||
|
||||
override fun notifyAutoFill(session: GeckoSession, notification: Int, virtualId: Int) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,383 @@
|
|||
package org.mozilla.geckoview;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.AnyThread;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.text.InputType;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
import android.view.View;
|
||||
import android.view.ViewStructure;
|
||||
|
||||
import org.mozilla.gecko.util.BundleEventListener;
|
||||
import org.mozilla.gecko.util.EventCallback;
|
||||
import org.mozilla.gecko.util.GeckoBundle;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/* package */ class AutofillSupport {
|
||||
private static final String LOGTAG = "AutofillSupport";
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
private final GeckoSession mSession;
|
||||
private GeckoSession.AutofillDelegate mDelegate;
|
||||
private SparseArray<GeckoBundle> mAutoFillNodes;
|
||||
private SparseArray<EventCallback> mAutoFillRoots;
|
||||
private int mAutoFillFocusedId = View.NO_ID;
|
||||
private int mAutoFillFocusedRoot = View.NO_ID;
|
||||
|
||||
public AutofillSupport(final GeckoSession session) {
|
||||
mSession = session;
|
||||
|
||||
session.getEventDispatcher().registerUiThreadListener(
|
||||
new BundleEventListener() {
|
||||
@Override
|
||||
public void handleMessage(final String event, final GeckoBundle message,
|
||||
final EventCallback callback) {
|
||||
if ("GeckoView:AddAutoFill".equals(event)) {
|
||||
addAutoFill(message, callback);
|
||||
} else if ("GeckoView:ClearAutoFill".equals(event)) {
|
||||
clearAutoFill();
|
||||
} else if ("GeckoView:OnAutoFillFocus".equals(event)) {
|
||||
onAutoFillFocus(message);
|
||||
}
|
||||
}
|
||||
},
|
||||
"GeckoView:AddAutoFill",
|
||||
"GeckoView:ClearAutoFill",
|
||||
"GeckoView:OnAutoFillFocus",
|
||||
null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform auto-fill using the specified values.
|
||||
*
|
||||
* @param values Map of auto-fill IDs to values.
|
||||
*/
|
||||
public void autofill(final SparseArray<CharSequence> values) {
|
||||
if (mAutoFillRoots == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
GeckoBundle response = null;
|
||||
EventCallback callback = null;
|
||||
|
||||
for (int i = 0; i < values.size(); i++) {
|
||||
final int id = values.keyAt(i);
|
||||
final CharSequence value = values.valueAt(i);
|
||||
|
||||
if (DEBUG) {
|
||||
Log.d(LOGTAG, "performAutoFill(" + id + ')');
|
||||
}
|
||||
int rootId = id;
|
||||
for (int currentId = id; currentId != View.NO_ID; ) {
|
||||
final GeckoBundle bundle = mAutoFillNodes.get(currentId);
|
||||
if (bundle == null) {
|
||||
return;
|
||||
}
|
||||
rootId = currentId;
|
||||
currentId = bundle.getInt("parent", View.NO_ID);
|
||||
}
|
||||
|
||||
final EventCallback newCallback = mAutoFillRoots.get(rootId);
|
||||
if (callback == null || newCallback != callback) {
|
||||
if (callback != null) {
|
||||
callback.sendSuccess(response);
|
||||
}
|
||||
response = new GeckoBundle(values.size() - i);
|
||||
callback = newCallback;
|
||||
}
|
||||
response.putString(String.valueOf(id), String.valueOf(value));
|
||||
}
|
||||
|
||||
if (callback != null) {
|
||||
callback.sendSuccess(response);
|
||||
}
|
||||
}
|
||||
|
||||
public void setDelegate(final GeckoSession.AutofillDelegate delegate) {
|
||||
mDelegate = delegate;
|
||||
}
|
||||
|
||||
public GeckoSession.AutofillDelegate getDelegate() {
|
||||
return mDelegate;
|
||||
}
|
||||
|
||||
|
||||
/* package */ void addAutoFill(@NonNull final GeckoBundle message,
|
||||
@NonNull final EventCallback callback) {
|
||||
final boolean initializing;
|
||||
if (mAutoFillRoots == null) {
|
||||
mAutoFillRoots = new SparseArray<>();
|
||||
mAutoFillNodes = new SparseArray<>();
|
||||
initializing = true;
|
||||
} else {
|
||||
initializing = false;
|
||||
}
|
||||
|
||||
final int id = message.getInt("id");
|
||||
if (DEBUG) {
|
||||
Log.d(LOGTAG, "addAutoFill(" + id + ')');
|
||||
}
|
||||
mAutoFillRoots.append(id, callback);
|
||||
populateAutofillNodes(message);
|
||||
|
||||
if (mDelegate == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (initializing) {
|
||||
mDelegate.onAutofill(
|
||||
mSession, GeckoSession.AutofillDelegate.AUTO_FILL_NOTIFY_STARTED, id);
|
||||
} else {
|
||||
mDelegate.onAutofill(
|
||||
mSession, GeckoSession.AutofillDelegate.AUTO_FILL_NOTIFY_VIEW_ADDED, id);
|
||||
}
|
||||
}
|
||||
|
||||
private void populateAutofillNodes(final GeckoBundle bundle) {
|
||||
final int id = bundle.getInt("id");
|
||||
|
||||
mAutoFillNodes.append(id, bundle);
|
||||
|
||||
final GeckoBundle[] children = bundle.getBundleArray("children");
|
||||
if (children != null) {
|
||||
for (GeckoBundle child : children) {
|
||||
populateAutofillNodes(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* package */ void clearAutoFill() {
|
||||
if (mAutoFillRoots == null) {
|
||||
return;
|
||||
}
|
||||
if (DEBUG) {
|
||||
Log.d(LOGTAG, "clearAutoFill()");
|
||||
}
|
||||
mAutoFillRoots = null;
|
||||
mAutoFillNodes = null;
|
||||
mAutoFillFocusedId = View.NO_ID;
|
||||
mAutoFillFocusedRoot = View.NO_ID;
|
||||
|
||||
if (mDelegate != null) {
|
||||
mDelegate.onAutofill(
|
||||
mSession, GeckoSession.AutofillDelegate.AUTO_FILL_NOTIFY_CANCELED, View.NO_ID);
|
||||
}
|
||||
}
|
||||
|
||||
/* package */ void onAutoFillFocus(@Nullable final GeckoBundle message) {
|
||||
if (mAutoFillRoots == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final int id;
|
||||
final int root;
|
||||
if (message != null) {
|
||||
id = message.getInt("id");
|
||||
root = message.getInt("root");
|
||||
} else {
|
||||
id = root = View.NO_ID;
|
||||
}
|
||||
|
||||
if (DEBUG) {
|
||||
Log.d(LOGTAG, "onAutoFillFocus(" + id + ')');
|
||||
}
|
||||
if (mAutoFillFocusedId == id) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mDelegate != null && mAutoFillFocusedId != View.NO_ID) {
|
||||
mDelegate.onAutofill(
|
||||
mSession, GeckoSession.AutofillDelegate.AUTO_FILL_NOTIFY_VIEW_EXITED,
|
||||
mAutoFillFocusedId);
|
||||
}
|
||||
|
||||
mAutoFillFocusedId = id;
|
||||
mAutoFillFocusedRoot = root;
|
||||
|
||||
if (mDelegate != null && mAutoFillFocusedId != View.NO_ID) {
|
||||
mDelegate.onAutofill(
|
||||
mSession, GeckoSession.AutofillDelegate.AUTO_FILL_NOTIFY_VIEW_ENTERED,
|
||||
mAutoFillFocusedId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Fill the specified {@link ViewStructure} with auto-fill fields from the current page.
|
||||
*
|
||||
* @param structure Structure to be filled.
|
||||
* @param flags Zero or a combination of {@link View#AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
|
||||
* AUTOFILL_FLAG_*} constants.
|
||||
*/
|
||||
@TargetApi(23)
|
||||
@UiThread
|
||||
public void provideAutofillVirtualStructure(@Nullable final View view,
|
||||
@NonNull final ViewStructure structure,
|
||||
final int flags) {
|
||||
if (view != null) {
|
||||
structure.setClassName(view.getClass().getName());
|
||||
}
|
||||
structure.setEnabled(true);
|
||||
structure.setVisibility(View.VISIBLE);
|
||||
|
||||
final Rect rect = getDummyAutoFillRect(mSession, false, null);
|
||||
structure.setDimens(rect.left, rect.top, 0, 0, rect.width(), rect.height());
|
||||
|
||||
if (mAutoFillRoots == null) {
|
||||
structure.setChildCount(0);
|
||||
return;
|
||||
}
|
||||
|
||||
final int size = mAutoFillRoots.size();
|
||||
structure.setChildCount(size);
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
final int id = mAutoFillRoots.keyAt(i);
|
||||
final GeckoBundle root = mAutoFillNodes.get(id);
|
||||
fillAutoFillStructure(view, id, root, structure.newChild(i), rect);
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(23)
|
||||
private void fillAutoFillStructure(@Nullable final View view, final int id,
|
||||
@NonNull final GeckoBundle bundle,
|
||||
@NonNull final ViewStructure structure,
|
||||
@NonNull final Rect rect) {
|
||||
if (mAutoFillRoots == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (DEBUG) {
|
||||
Log.d(LOGTAG, "fillAutoFillStructure(" + id + ')');
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
if (view != null) {
|
||||
structure.setAutofillId(view.getAutofillId(), id);
|
||||
}
|
||||
structure.setWebDomain(bundle.getString("origin"));
|
||||
}
|
||||
structure.setId(id, null, null, null);
|
||||
|
||||
if (mAutoFillFocusedRoot != View.NO_ID &&
|
||||
mAutoFillFocusedRoot == bundle.getInt("root", View.NO_ID)) {
|
||||
structure.setDimens(0, 0, 0, 0, rect.width(), rect.height());
|
||||
}
|
||||
|
||||
final GeckoBundle[] children = bundle.getBundleArray("children");
|
||||
if (children != null) {
|
||||
structure.setChildCount(children.length);
|
||||
for (int i = 0; i < children.length; i++) {
|
||||
final GeckoBundle childBundle = children[i];
|
||||
final int childId = childBundle.getInt("id");
|
||||
final ViewStructure childStructure = structure.newChild(i);
|
||||
fillAutoFillStructure(view, childId, childBundle, childStructure, rect);
|
||||
mAutoFillNodes.append(childId, childBundle);
|
||||
}
|
||||
}
|
||||
|
||||
String tag = bundle.getString("tag", "");
|
||||
final String type = bundle.getString("type", "text");
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
final GeckoBundle attrs = bundle.getBundle("attributes");
|
||||
final ViewStructure.HtmlInfo.Builder builder =
|
||||
structure.newHtmlInfoBuilder(tag.toLowerCase(Locale.US));
|
||||
for (final String key : attrs.keys()) {
|
||||
builder.addAttribute(key, String.valueOf(attrs.get(key)));
|
||||
}
|
||||
structure.setHtmlInfo(builder.build());
|
||||
}
|
||||
|
||||
if ("INPUT".equals(tag) && !bundle.getBoolean("editable", false)) {
|
||||
tag = ""; // Don't process non-editable inputs (e.g. type="button").
|
||||
}
|
||||
switch (tag) {
|
||||
case "INPUT":
|
||||
case "TEXTAREA": {
|
||||
final boolean disabled = bundle.getBoolean("disabled");
|
||||
structure.setClassName("android.widget.EditText");
|
||||
structure.setEnabled(!disabled);
|
||||
structure.setFocusable(!disabled);
|
||||
structure.setFocused(id == mAutoFillFocusedId);
|
||||
structure.setVisibility(View.VISIBLE);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
structure.setAutofillType(View.AUTOFILL_TYPE_TEXT);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
if (children != null) {
|
||||
structure.setClassName("android.view.ViewGroup");
|
||||
} else {
|
||||
structure.setClassName("android.view.View");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 26 && "INPUT".equals(tag)) {
|
||||
// LastPass will fill password to the feild that setAutofillHints is unset and setInputType is set.
|
||||
switch (type) {
|
||||
case "email":
|
||||
structure.setAutofillHints(new String[] { View.AUTOFILL_HINT_EMAIL_ADDRESS });
|
||||
structure.setInputType(InputType.TYPE_CLASS_TEXT |
|
||||
InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS);
|
||||
break;
|
||||
case "number":
|
||||
structure.setInputType(InputType.TYPE_CLASS_NUMBER);
|
||||
break;
|
||||
case "password":
|
||||
structure.setAutofillHints(new String[] { View.AUTOFILL_HINT_PASSWORD });
|
||||
structure.setInputType(InputType.TYPE_CLASS_TEXT |
|
||||
InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD);
|
||||
break;
|
||||
case "tel":
|
||||
structure.setAutofillHints(new String[] { View.AUTOFILL_HINT_PHONE });
|
||||
structure.setInputType(InputType.TYPE_CLASS_PHONE);
|
||||
break;
|
||||
case "url":
|
||||
structure.setInputType(InputType.TYPE_CLASS_TEXT |
|
||||
InputType.TYPE_TEXT_VARIATION_URI);
|
||||
break;
|
||||
case "text":
|
||||
final String autofillhint = bundle.getString("autofillhint", "");
|
||||
if (autofillhint.equals("username")) {
|
||||
structure.setAutofillHints(new String[] { View.AUTOFILL_HINT_USERNAME });
|
||||
structure.setInputType(InputType.TYPE_CLASS_TEXT |
|
||||
InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* package */ static Rect getDummyAutoFillRect(@NonNull final GeckoSession session,
|
||||
final boolean screen,
|
||||
@Nullable final View view) {
|
||||
final Rect rect = new Rect();
|
||||
session.getSurfaceBounds(rect);
|
||||
if (screen) {
|
||||
if (view == null) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
final int[] offset = new int[2];
|
||||
view.getLocationOnScreen(offset);
|
||||
rect.offset(offset[0], offset[1]);
|
||||
}
|
||||
return rect;
|
||||
}
|
||||
|
||||
public void onScreenMetricsUpdated() {
|
||||
if (mDelegate != null && mAutoFillFocusedId != View.NO_ID) {
|
||||
getDelegate().onAutofill(
|
||||
mSession, GeckoSession.AutofillDelegate.AUTO_FILL_NOTIFY_VIEW_ENTERED, mAutoFillFocusedId);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -34,6 +34,7 @@ import org.mozilla.gecko.util.GeckoBundle;
|
|||
import org.mozilla.gecko.util.IntentUtils;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
|
@ -58,10 +59,14 @@ import android.support.annotation.UiThread;
|
|||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
import android.util.LongSparseArray;
|
||||
import android.util.SparseArray;
|
||||
import android.view.Surface;
|
||||
import android.view.inputmethod.CursorAnchorInfo;
|
||||
import android.view.inputmethod.ExtractedText;
|
||||
import android.view.inputmethod.ExtractedTextRequest;
|
||||
import android.view.View;
|
||||
import android.view.ViewStructure;
|
||||
import android.view.autofill.AutofillManager;
|
||||
|
||||
public class GeckoSession implements Parcelable {
|
||||
private static final String LOGTAG = "GeckoSession";
|
||||
|
@ -121,6 +126,7 @@ public class GeckoSession implements Parcelable {
|
|||
private OverscrollEdgeEffect mOverscroll;
|
||||
private DynamicToolbarAnimator mToolbar;
|
||||
private CompositorController mController;
|
||||
private AutofillSupport mAutofillSupport;
|
||||
|
||||
private boolean mAttachedCompositor;
|
||||
private boolean mCompositorReady;
|
||||
|
@ -1523,7 +1529,7 @@ public class GeckoSession implements Parcelable {
|
|||
mTextInput.onWindowChanged(mWindow);
|
||||
}
|
||||
if ((change == WINDOW_CLOSE || change == WINDOW_TRANSFER_OUT) && !inProgress) {
|
||||
mTextInput.clearAutoFill();
|
||||
getAutofillSupport().clearAutoFill();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5103,41 +5109,6 @@ public class GeckoSession implements Parcelable {
|
|||
@UiThread
|
||||
default void updateCursorAnchorInfo(@NonNull GeckoSession session,
|
||||
@NonNull CursorAnchorInfo info) {}
|
||||
|
||||
/** An auto-fill session has started, usually as a result of loading a page. */
|
||||
int AUTO_FILL_NOTIFY_STARTED = 0;
|
||||
/** An auto-fill session has been committed, usually as a result of submitting a form. */
|
||||
int AUTO_FILL_NOTIFY_COMMITTED = 1;
|
||||
/** An auto-fill session has been canceled, usually as a result of unloading a page. */
|
||||
int AUTO_FILL_NOTIFY_CANCELED = 2;
|
||||
/** A view within the auto-fill session has been added. */
|
||||
int AUTO_FILL_NOTIFY_VIEW_ADDED = 3;
|
||||
/** A view within the auto-fill session has been removed. */
|
||||
int AUTO_FILL_NOTIFY_VIEW_REMOVED = 4;
|
||||
/** A view within the auto-fill session has been updated (e.g. change in state). */
|
||||
int AUTO_FILL_NOTIFY_VIEW_UPDATED = 5;
|
||||
/** A view within the auto-fill session has gained focus. */
|
||||
int AUTO_FILL_NOTIFY_VIEW_ENTERED = 6;
|
||||
/** A view within the auto-fill session has lost focus. */
|
||||
int AUTO_FILL_NOTIFY_VIEW_EXITED = 7;
|
||||
|
||||
/**
|
||||
* Notify that an auto-fill event has occurred. The default implementation forwards the
|
||||
* notification to the system {@link android.view.autofill.AutofillManager}. This method is
|
||||
* only called on Android 6.0 and above, and it is called in viewless mode as well.
|
||||
*
|
||||
* @param session Session instance.
|
||||
* @param notification Notification type as one of the {@link #AUTO_FILL_NOTIFY_STARTED
|
||||
* AUTO_FILL_NOTIFY_*} constants.
|
||||
* @param virtualId Virtual ID of the target, or {@link android.view.View#NO_ID} if not
|
||||
* applicable. The ID matches one of the virtual IDs provided by {@link
|
||||
* SessionTextInput#onProvideAutofillVirtualStructure} and can be used
|
||||
* with {@link SessionTextInput#autofill}.
|
||||
*/
|
||||
@UiThread
|
||||
default void notifyAutoFill(@NonNull GeckoSession session,
|
||||
@AutoFillNotification int notification,
|
||||
int virtualId) {}
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
|
@ -5145,18 +5116,6 @@ public class GeckoSession implements Parcelable {
|
|||
TextInputDelegate.RESTART_REASON_CONTENT_CHANGE})
|
||||
/* package */ @interface RestartReason {}
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({
|
||||
TextInputDelegate.AUTO_FILL_NOTIFY_STARTED,
|
||||
TextInputDelegate.AUTO_FILL_NOTIFY_COMMITTED,
|
||||
TextInputDelegate.AUTO_FILL_NOTIFY_CANCELED,
|
||||
TextInputDelegate.AUTO_FILL_NOTIFY_VIEW_ADDED,
|
||||
TextInputDelegate.AUTO_FILL_NOTIFY_VIEW_REMOVED,
|
||||
TextInputDelegate.AUTO_FILL_NOTIFY_VIEW_UPDATED,
|
||||
TextInputDelegate.AUTO_FILL_NOTIFY_VIEW_ENTERED,
|
||||
TextInputDelegate.AUTO_FILL_NOTIFY_VIEW_EXITED})
|
||||
/* package */ @interface AutoFillNotification {}
|
||||
|
||||
/* package */ void onSurfaceChanged(final Surface surface, final int x, final int y, final int width,
|
||||
final int height) {
|
||||
ThreadUtils.assertOnUiThread();
|
||||
|
@ -5375,7 +5334,7 @@ public class GeckoSession implements Parcelable {
|
|||
mViewportTop = scrollY;
|
||||
mViewportZoom = zoom;
|
||||
|
||||
mTextInput.onScreenMetricsUpdated();
|
||||
getAutofillSupport().onScreenMetricsUpdated();
|
||||
}
|
||||
|
||||
/* protected */ void onWindowBoundsChanged() {
|
||||
|
@ -5628,4 +5587,98 @@ public class GeckoSession implements Parcelable {
|
|||
HistoryDelegate.VISIT_UNRECOVERABLE_ERROR
|
||||
})
|
||||
/* package */ @interface VisitFlags {}
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({
|
||||
AutofillDelegate.AUTO_FILL_NOTIFY_STARTED,
|
||||
AutofillDelegate.AUTO_FILL_NOTIFY_COMMITTED,
|
||||
AutofillDelegate.AUTO_FILL_NOTIFY_CANCELED,
|
||||
AutofillDelegate.AUTO_FILL_NOTIFY_VIEW_ADDED,
|
||||
AutofillDelegate.AUTO_FILL_NOTIFY_VIEW_REMOVED,
|
||||
AutofillDelegate.AUTO_FILL_NOTIFY_VIEW_UPDATED,
|
||||
AutofillDelegate.AUTO_FILL_NOTIFY_VIEW_ENTERED,
|
||||
AutofillDelegate.AUTO_FILL_NOTIFY_VIEW_EXITED})
|
||||
/* package */ @interface AutofillNotification {}
|
||||
|
||||
public interface AutofillDelegate {
|
||||
|
||||
/** An auto-fill session has started, usually as a result of loading a page. */
|
||||
int AUTO_FILL_NOTIFY_STARTED = 0;
|
||||
/** An auto-fill session has been committed, usually as a result of submitting a form. */
|
||||
int AUTO_FILL_NOTIFY_COMMITTED = 1;
|
||||
/** An auto-fill session has been canceled, usually as a result of unloading a page. */
|
||||
int AUTO_FILL_NOTIFY_CANCELED = 2;
|
||||
/** A view within the auto-fill session has been added. */
|
||||
int AUTO_FILL_NOTIFY_VIEW_ADDED = 3;
|
||||
/** A view within the auto-fill session has been removed. */
|
||||
int AUTO_FILL_NOTIFY_VIEW_REMOVED = 4;
|
||||
/** A view within the auto-fill session has been updated (e.g. change in state). */
|
||||
int AUTO_FILL_NOTIFY_VIEW_UPDATED = 5;
|
||||
/** A view within the auto-fill session has gained focus. */
|
||||
int AUTO_FILL_NOTIFY_VIEW_ENTERED = 6;
|
||||
/** A view within the auto-fill session has lost focus. */
|
||||
int AUTO_FILL_NOTIFY_VIEW_EXITED = 7;
|
||||
|
||||
/**
|
||||
* Notify that an auto-fill event has occurred. The default implementation forwards the
|
||||
* notification to the system {@link AutofillManager}. This method is
|
||||
* only called on Android 6.0 and above, and it is called in viewless mode as well.
|
||||
*
|
||||
* @param session Session instance.
|
||||
* @param notification Notification type as one of the {@link #AUTO_FILL_NOTIFY_STARTED
|
||||
* AUTO_FILL_NOTIFY_*} constants.
|
||||
* @param virtualId Virtual ID of the target, or {@link View#NO_ID} if not
|
||||
* applicable. The ID matches one of the virtual IDs provided by {@link
|
||||
* GeckoSession#getAutofillElements()} and can be used
|
||||
* with {@link GeckoSession#autofill}.
|
||||
*/
|
||||
@UiThread
|
||||
default void onAutofill(@NonNull GeckoSession session,
|
||||
@GeckoSession.AutofillNotification int notification,
|
||||
int virtualId) {}
|
||||
}
|
||||
|
||||
private AutofillSupport getAutofillSupport() {
|
||||
if (mAutofillSupport == null) {
|
||||
mAutofillSupport = new AutofillSupport(this);
|
||||
}
|
||||
|
||||
return mAutofillSupport;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the autofill delegate for this session.
|
||||
*
|
||||
* @param delegate An instance of {@link AutofillDelegate}.
|
||||
*/
|
||||
@UiThread
|
||||
public void setAutofillDelegate(final @Nullable AutofillDelegate delegate) {
|
||||
getAutofillSupport().setDelegate(delegate);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The current {@link AutofillDelegate} for this session, if any.
|
||||
*/
|
||||
@UiThread
|
||||
public @Nullable AutofillDelegate getAutofillDelegate() {
|
||||
return getAutofillSupport().getDelegate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform auto-fill using the specified values.
|
||||
*
|
||||
* @param values Map of auto-fill IDs to values.
|
||||
*/
|
||||
@UiThread
|
||||
public void autofill(final @NonNull SparseArray<CharSequence> values) {
|
||||
getAutofillSupport().autofill(values);
|
||||
}
|
||||
|
||||
@TargetApi(23)
|
||||
@UiThread
|
||||
public void provideAutofillVirtualStructure(@Nullable final View view,
|
||||
@NonNull final ViewStructure structure,
|
||||
final int flags) {
|
||||
getAutofillSupport().provideAutofillVirtualStructure(view, structure, flags);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -714,7 +714,7 @@ public class GeckoView extends FrameLayout {
|
|||
super.onProvideAutofillVirtualStructure(structure, flags);
|
||||
|
||||
if (mSession != null) {
|
||||
mSession.getTextInput().onProvideAutofillVirtualStructure(structure, flags);
|
||||
mSession.provideAutofillVirtualStructure(this, structure, flags);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -734,7 +734,7 @@ public class GeckoView extends FrameLayout {
|
|||
strValues.put(values.keyAt(i), value.getTextValue());
|
||||
}
|
||||
}
|
||||
mSession.getTextInput().autofill(strValues);
|
||||
mSession.autofill(strValues);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -5,37 +5,19 @@
|
|||
|
||||
package org.mozilla.geckoview;
|
||||
|
||||
import org.mozilla.gecko.GeckoAppShell;
|
||||
import org.mozilla.gecko.InputMethods;
|
||||
import org.mozilla.gecko.annotation.WrapForJNI;
|
||||
import org.mozilla.gecko.IGeckoEditableParent;
|
||||
import org.mozilla.gecko.NativeQueue;
|
||||
import org.mozilla.gecko.util.ActivityUtils;
|
||||
import org.mozilla.gecko.util.BundleEventListener;
|
||||
import org.mozilla.gecko.util.EventCallback;
|
||||
import org.mozilla.gecko.util.GeckoBundle;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.support.annotation.AnyThread;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.text.Editable;
|
||||
import android.text.InputType;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewStructure;
|
||||
import android.view.autofill.AutofillManager;
|
||||
import android.view.inputmethod.CursorAnchorInfo;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.ExtractedText;
|
||||
|
@ -43,7 +25,12 @@ import android.view.inputmethod.ExtractedTextRequest;
|
|||
import android.view.inputmethod.InputConnection;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
|
||||
import java.util.Locale;
|
||||
import org.mozilla.gecko.IGeckoEditableParent;
|
||||
import org.mozilla.gecko.InputMethods;
|
||||
import org.mozilla.gecko.NativeQueue;
|
||||
import org.mozilla.gecko.annotation.WrapForJNI;
|
||||
import org.mozilla.gecko.util.ActivityUtils;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
|
||||
/**
|
||||
* {@code SessionTextInput} handles text input for {@code GeckoSession} through key events or input
|
||||
|
@ -238,77 +225,6 @@ public final class SessionTextInput {
|
|||
imm.updateCursorAnchorInfo(view, info);
|
||||
}
|
||||
}
|
||||
|
||||
private Rect displayRectForId(@NonNull final GeckoSession session,
|
||||
@NonNull final int virtualId,
|
||||
@Nullable final View view) {
|
||||
final SessionTextInput textInput = session.getTextInput();
|
||||
Rect contentRect;
|
||||
if (textInput.mAutoFillNodes.indexOfKey(virtualId) >= 0) {
|
||||
GeckoBundle element = textInput.mAutoFillNodes
|
||||
.get(virtualId);
|
||||
GeckoBundle bounds = element.getBundle("bounds");
|
||||
contentRect = new Rect(bounds.getInt("left"),
|
||||
bounds.getInt("top"),
|
||||
bounds.getInt("right"),
|
||||
bounds.getInt("bottom"));
|
||||
|
||||
final Matrix matrix = new Matrix();
|
||||
final RectF rectF = new RectF(contentRect);
|
||||
if (GeckoAppShell.isFennec()) {
|
||||
session.getClientToScreenMatrix(matrix);
|
||||
} else {
|
||||
session.getPageToScreenMatrix(matrix);
|
||||
}
|
||||
matrix.mapRect(rectF);
|
||||
rectF.roundOut(contentRect);
|
||||
if (DEBUG) {
|
||||
Log.d(LOGTAG, "Displaying autofill rect at (" + contentRect.left + ", " +
|
||||
contentRect.top + "), (" + contentRect.right + ", " +
|
||||
contentRect.bottom + ")");
|
||||
}
|
||||
} else {
|
||||
contentRect = getDummyAutoFillRect(session, true, view);
|
||||
}
|
||||
|
||||
return contentRect;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyAutoFill(@NonNull final GeckoSession session,
|
||||
@GeckoSession.AutoFillNotification final int notification,
|
||||
final int virtualId) {
|
||||
ThreadUtils.assertOnUiThread();
|
||||
final View view = session.getTextInput().getView();
|
||||
if (Build.VERSION.SDK_INT < 26 || view == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final AutofillManager manager =
|
||||
view.getContext().getSystemService(AutofillManager.class);
|
||||
if (manager == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (notification) {
|
||||
case AUTO_FILL_NOTIFY_STARTED:
|
||||
// This line seems necessary for auto-fill to work on the initial page.
|
||||
manager.cancel();
|
||||
break;
|
||||
case AUTO_FILL_NOTIFY_COMMITTED:
|
||||
manager.commit();
|
||||
break;
|
||||
case AUTO_FILL_NOTIFY_CANCELED:
|
||||
manager.cancel();
|
||||
break;
|
||||
case AUTO_FILL_NOTIFY_VIEW_ENTERED:
|
||||
manager.notifyViewEntered(view, virtualId, displayRectForId(session, virtualId, view));
|
||||
break;
|
||||
case AUTO_FILL_NOTIFY_VIEW_EXITED:
|
||||
manager.notifyViewExited(view, virtualId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final GeckoSession mSession;
|
||||
|
@ -316,36 +232,12 @@ public final class SessionTextInput {
|
|||
private final GeckoEditable mEditable;
|
||||
private InputConnectionClient mInputConnection;
|
||||
private GeckoSession.TextInputDelegate mDelegate;
|
||||
// Auto-fill nodes.
|
||||
private SparseArray<GeckoBundle> mAutoFillNodes;
|
||||
private SparseArray<EventCallback> mAutoFillRoots;
|
||||
private int mAutoFillFocusedId = View.NO_ID;
|
||||
private int mAutoFillFocusedRoot = View.NO_ID;
|
||||
|
||||
/* package */ SessionTextInput(final @NonNull GeckoSession session,
|
||||
final @NonNull NativeQueue queue) {
|
||||
mSession = session;
|
||||
mQueue = queue;
|
||||
mEditable = new GeckoEditable(session);
|
||||
|
||||
session.getEventDispatcher().registerUiThreadListener(
|
||||
new BundleEventListener() {
|
||||
@Override
|
||||
public void handleMessage(final String event, final GeckoBundle message,
|
||||
final EventCallback callback) {
|
||||
if ("GeckoView:AddAutoFill".equals(event)) {
|
||||
addAutoFill(message, callback);
|
||||
} else if ("GeckoView:ClearAutoFill".equals(event)) {
|
||||
clearAutoFill();
|
||||
} else if ("GeckoView:OnAutoFillFocus".equals(event)) {
|
||||
onAutoFillFocus(message);
|
||||
}
|
||||
}
|
||||
},
|
||||
"GeckoView:AddAutoFill",
|
||||
"GeckoView:ClearAutoFill",
|
||||
"GeckoView:OnAutoFillFocus",
|
||||
null);
|
||||
}
|
||||
|
||||
/* package */ void onWindowChanged(final GeckoSession.Window window) {
|
||||
|
@ -529,314 +421,4 @@ public final class SessionTextInput {
|
|||
}
|
||||
return mDelegate;
|
||||
}
|
||||
|
||||
/*package*/ void onScreenMetricsUpdated() {
|
||||
if (mAutoFillFocusedId != View.NO_ID) {
|
||||
getDelegate().notifyAutoFill(
|
||||
mSession, GeckoSession.TextInputDelegate.AUTO_FILL_NOTIFY_VIEW_ENTERED, mAutoFillFocusedId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill the specified {@link ViewStructure} with auto-fill fields from the current page.
|
||||
*
|
||||
* @param structure Structure to be filled.
|
||||
* @param flags Zero or a combination of {@link View#AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
|
||||
* AUTOFILL_FLAG_*} constants.
|
||||
*/
|
||||
@TargetApi(23)
|
||||
@UiThread
|
||||
public void onProvideAutofillVirtualStructure(@NonNull final ViewStructure structure,
|
||||
final int flags) {
|
||||
final View view = getView();
|
||||
if (view != null) {
|
||||
structure.setClassName(view.getClass().getName());
|
||||
}
|
||||
structure.setEnabled(true);
|
||||
structure.setVisibility(View.VISIBLE);
|
||||
|
||||
final Rect rect = getDummyAutoFillRect(mSession, false, null);
|
||||
structure.setDimens(rect.left, rect.top, 0, 0, rect.width(), rect.height());
|
||||
|
||||
if (mAutoFillRoots == null) {
|
||||
structure.setChildCount(0);
|
||||
return;
|
||||
}
|
||||
|
||||
final int size = mAutoFillRoots.size();
|
||||
structure.setChildCount(size);
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
final int id = mAutoFillRoots.keyAt(i);
|
||||
final GeckoBundle root = mAutoFillNodes.get(id);
|
||||
fillAutoFillStructure(view, id, root, structure.newChild(i), rect);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform auto-fill using the specified values.
|
||||
*
|
||||
* @param values Map of auto-fill IDs to values.
|
||||
*/
|
||||
@UiThread
|
||||
public void autofill(final @NonNull SparseArray<CharSequence> values) {
|
||||
if (mAutoFillRoots == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
GeckoBundle response = null;
|
||||
EventCallback callback = null;
|
||||
|
||||
for (int i = 0; i < values.size(); i++) {
|
||||
final int id = values.keyAt(i);
|
||||
final CharSequence value = values.valueAt(i);
|
||||
|
||||
if (DEBUG) {
|
||||
Log.d(LOGTAG, "performAutoFill(" + id + ')');
|
||||
}
|
||||
int rootId = id;
|
||||
for (int currentId = id; currentId != View.NO_ID; ) {
|
||||
final GeckoBundle bundle = mAutoFillNodes.get(currentId);
|
||||
if (bundle == null) {
|
||||
return;
|
||||
}
|
||||
rootId = currentId;
|
||||
currentId = bundle.getInt("parent", View.NO_ID);
|
||||
}
|
||||
|
||||
final EventCallback newCallback = mAutoFillRoots.get(rootId);
|
||||
if (callback == null || newCallback != callback) {
|
||||
if (callback != null) {
|
||||
callback.sendSuccess(response);
|
||||
}
|
||||
response = new GeckoBundle(values.size() - i);
|
||||
callback = newCallback;
|
||||
}
|
||||
response.putString(String.valueOf(id), String.valueOf(value));
|
||||
}
|
||||
|
||||
if (callback != null) {
|
||||
callback.sendSuccess(response);
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(23)
|
||||
private void fillAutoFillStructure(@Nullable final View view, final int id,
|
||||
@NonNull final GeckoBundle bundle,
|
||||
@NonNull final ViewStructure structure,
|
||||
@NonNull final Rect rect) {
|
||||
if (mAutoFillRoots == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (DEBUG) {
|
||||
Log.d(LOGTAG, "fillAutoFillStructure(" + id + ')');
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
if (view != null) {
|
||||
structure.setAutofillId(view.getAutofillId(), id);
|
||||
}
|
||||
structure.setWebDomain(bundle.getString("origin"));
|
||||
}
|
||||
structure.setId(id, null, null, null);
|
||||
|
||||
if (mAutoFillFocusedRoot != View.NO_ID &&
|
||||
mAutoFillFocusedRoot == bundle.getInt("root", View.NO_ID)) {
|
||||
structure.setDimens(0, 0, 0, 0, rect.width(), rect.height());
|
||||
}
|
||||
|
||||
final GeckoBundle[] children = bundle.getBundleArray("children");
|
||||
if (children != null) {
|
||||
structure.setChildCount(children.length);
|
||||
for (int i = 0; i < children.length; i++) {
|
||||
final GeckoBundle childBundle = children[i];
|
||||
final int childId = childBundle.getInt("id");
|
||||
final ViewStructure childStructure = structure.newChild(i);
|
||||
fillAutoFillStructure(view, childId, childBundle, childStructure, rect);
|
||||
mAutoFillNodes.append(childId, childBundle);
|
||||
}
|
||||
}
|
||||
|
||||
String tag = bundle.getString("tag", "");
|
||||
final String type = bundle.getString("type", "text");
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
final GeckoBundle attrs = bundle.getBundle("attributes");
|
||||
final ViewStructure.HtmlInfo.Builder builder =
|
||||
structure.newHtmlInfoBuilder(tag.toLowerCase(Locale.US));
|
||||
for (final String key : attrs.keys()) {
|
||||
builder.addAttribute(key, String.valueOf(attrs.get(key)));
|
||||
}
|
||||
structure.setHtmlInfo(builder.build());
|
||||
}
|
||||
|
||||
if ("INPUT".equals(tag) && !bundle.getBoolean("editable", false)) {
|
||||
tag = ""; // Don't process non-editable inputs (e.g. type="button").
|
||||
}
|
||||
switch (tag) {
|
||||
case "INPUT":
|
||||
case "TEXTAREA": {
|
||||
final boolean disabled = bundle.getBoolean("disabled");
|
||||
structure.setClassName("android.widget.EditText");
|
||||
structure.setEnabled(!disabled);
|
||||
structure.setFocusable(!disabled);
|
||||
structure.setFocused(id == mAutoFillFocusedId);
|
||||
structure.setVisibility(View.VISIBLE);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
structure.setAutofillType(View.AUTOFILL_TYPE_TEXT);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
if (children != null) {
|
||||
structure.setClassName("android.view.ViewGroup");
|
||||
} else {
|
||||
structure.setClassName("android.view.View");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 26 && "INPUT".equals(tag)) {
|
||||
// LastPass will fill password to the feild that setAutofillHints is unset and setInputType is set.
|
||||
switch (type) {
|
||||
case "email":
|
||||
structure.setAutofillHints(new String[] { View.AUTOFILL_HINT_EMAIL_ADDRESS });
|
||||
structure.setInputType(InputType.TYPE_CLASS_TEXT |
|
||||
InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS);
|
||||
break;
|
||||
case "number":
|
||||
structure.setInputType(InputType.TYPE_CLASS_NUMBER);
|
||||
break;
|
||||
case "password":
|
||||
structure.setAutofillHints(new String[] { View.AUTOFILL_HINT_PASSWORD });
|
||||
structure.setInputType(InputType.TYPE_CLASS_TEXT |
|
||||
InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD);
|
||||
break;
|
||||
case "tel":
|
||||
structure.setAutofillHints(new String[] { View.AUTOFILL_HINT_PHONE });
|
||||
structure.setInputType(InputType.TYPE_CLASS_PHONE);
|
||||
break;
|
||||
case "url":
|
||||
structure.setInputType(InputType.TYPE_CLASS_TEXT |
|
||||
InputType.TYPE_TEXT_VARIATION_URI);
|
||||
break;
|
||||
case "text":
|
||||
final String autofillhint = bundle.getString("autofillhint", "");
|
||||
if (autofillhint.equals("username")) {
|
||||
structure.setAutofillHints(new String[] { View.AUTOFILL_HINT_USERNAME });
|
||||
structure.setInputType(InputType.TYPE_CLASS_TEXT |
|
||||
InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* package */ void addAutoFill(@NonNull final GeckoBundle message,
|
||||
@NonNull final EventCallback callback) {
|
||||
if (Build.VERSION.SDK_INT < 23) {
|
||||
return;
|
||||
}
|
||||
|
||||
final boolean initializing;
|
||||
if (mAutoFillRoots == null) {
|
||||
mAutoFillRoots = new SparseArray<>();
|
||||
mAutoFillNodes = new SparseArray<>();
|
||||
initializing = true;
|
||||
} else {
|
||||
initializing = false;
|
||||
}
|
||||
|
||||
final int id = message.getInt("id");
|
||||
if (DEBUG) {
|
||||
Log.d(LOGTAG, "addAutoFill(" + id + ')');
|
||||
}
|
||||
mAutoFillRoots.append(id, callback);
|
||||
populateAutofillNodes(message);
|
||||
|
||||
if (initializing) {
|
||||
getDelegate().notifyAutoFill(
|
||||
mSession, GeckoSession.TextInputDelegate.AUTO_FILL_NOTIFY_STARTED, id);
|
||||
} else {
|
||||
getDelegate().notifyAutoFill(
|
||||
mSession, GeckoSession.TextInputDelegate.AUTO_FILL_NOTIFY_VIEW_ADDED, id);
|
||||
}
|
||||
}
|
||||
|
||||
private void populateAutofillNodes(final GeckoBundle bundle) {
|
||||
final int id = bundle.getInt("id");
|
||||
|
||||
mAutoFillNodes.append(id, bundle);
|
||||
|
||||
final GeckoBundle[] children = bundle.getBundleArray("children");
|
||||
if (children != null) {
|
||||
for (GeckoBundle child : children) {
|
||||
populateAutofillNodes(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* package */ void clearAutoFill() {
|
||||
if (mAutoFillRoots == null) {
|
||||
return;
|
||||
}
|
||||
if (DEBUG) {
|
||||
Log.d(LOGTAG, "clearAutoFill()");
|
||||
}
|
||||
mAutoFillRoots = null;
|
||||
mAutoFillNodes = null;
|
||||
mAutoFillFocusedId = View.NO_ID;
|
||||
mAutoFillFocusedRoot = View.NO_ID;
|
||||
|
||||
getDelegate().notifyAutoFill(
|
||||
mSession, GeckoSession.TextInputDelegate.AUTO_FILL_NOTIFY_CANCELED, View.NO_ID);
|
||||
}
|
||||
|
||||
/* package */ void onAutoFillFocus(@Nullable final GeckoBundle message) {
|
||||
if (mAutoFillRoots == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final int id;
|
||||
final int root;
|
||||
if (message != null) {
|
||||
id = message.getInt("id");
|
||||
root = message.getInt("root");
|
||||
} else {
|
||||
id = root = View.NO_ID;
|
||||
}
|
||||
|
||||
if (DEBUG) {
|
||||
Log.d(LOGTAG, "onAutoFillFocus(" + id + ')');
|
||||
}
|
||||
if (mAutoFillFocusedId == id) {
|
||||
return;
|
||||
}
|
||||
if (mAutoFillFocusedId != View.NO_ID) {
|
||||
getDelegate().notifyAutoFill(
|
||||
mSession, GeckoSession.TextInputDelegate.AUTO_FILL_NOTIFY_VIEW_EXITED,
|
||||
mAutoFillFocusedId);
|
||||
}
|
||||
|
||||
mAutoFillFocusedId = id;
|
||||
mAutoFillFocusedRoot = root;
|
||||
}
|
||||
|
||||
/* package */ static Rect getDummyAutoFillRect(@NonNull final GeckoSession session,
|
||||
final boolean screen,
|
||||
@Nullable final View view) {
|
||||
final Rect rect = new Rect();
|
||||
session.getSurfaceBounds(rect);
|
||||
if (screen) {
|
||||
if (view == null) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
final int[] offset = new int[2];
|
||||
view.getLocationOnScreen(offset);
|
||||
rect.offset(offset[0], offset[1]);
|
||||
}
|
||||
return rect;
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче