Merge "Initial IME integration."
This commit is contained in:
committed by
Android (Google) Code Review
commit
000f7cd646
@@ -46210,6 +46210,13 @@ package android.view.autofill {
|
||||
field public static final android.os.Parcelable.Creator<android.view.autofill.AutoFillId> CREATOR;
|
||||
}
|
||||
|
||||
public final class AutoFillManager {
|
||||
method public void updateAutoFillInput(android.view.View, int);
|
||||
method public void updateAutoFillInput(android.view.View, int, android.graphics.Rect, int);
|
||||
field public static final int FLAG_UPDATE_UI_HIDE = 2; // 0x2
|
||||
field public static final int FLAG_UPDATE_UI_SHOW = 1; // 0x1
|
||||
}
|
||||
|
||||
public final class AutoFillType implements android.os.Parcelable {
|
||||
method public int describeContents();
|
||||
method public static android.view.autofill.AutoFillType forList();
|
||||
@@ -46269,7 +46276,7 @@ package android.view.autofill {
|
||||
|
||||
public static abstract class VirtualViewDelegate.Callback {
|
||||
ctor public VirtualViewDelegate.Callback();
|
||||
method public void onFocusChanged(int, boolean);
|
||||
method public void onAutoFillInputUpdated(int, android.graphics.Rect, int);
|
||||
method public void onNodeRemoved(int...);
|
||||
method public void onValueChanged(int);
|
||||
}
|
||||
|
||||
@@ -49595,6 +49595,13 @@ package android.view.autofill {
|
||||
field public static final android.os.Parcelable.Creator<android.view.autofill.AutoFillId> CREATOR;
|
||||
}
|
||||
|
||||
public final class AutoFillManager {
|
||||
method public void updateAutoFillInput(android.view.View, int);
|
||||
method public void updateAutoFillInput(android.view.View, int, android.graphics.Rect, int);
|
||||
field public static final int FLAG_UPDATE_UI_HIDE = 2; // 0x2
|
||||
field public static final int FLAG_UPDATE_UI_SHOW = 1; // 0x1
|
||||
}
|
||||
|
||||
public final class AutoFillType implements android.os.Parcelable {
|
||||
method public int describeContents();
|
||||
method public static android.view.autofill.AutoFillType forList();
|
||||
@@ -49654,7 +49661,7 @@ package android.view.autofill {
|
||||
|
||||
public static abstract class VirtualViewDelegate.Callback {
|
||||
ctor public VirtualViewDelegate.Callback();
|
||||
method public void onFocusChanged(int, boolean);
|
||||
method public void onAutoFillInputUpdated(int, android.graphics.Rect, int);
|
||||
method public void onNodeRemoved(int...);
|
||||
method public void onValueChanged(int);
|
||||
}
|
||||
|
||||
@@ -46520,6 +46520,13 @@ package android.view.autofill {
|
||||
field public static final android.os.Parcelable.Creator<android.view.autofill.AutoFillId> CREATOR;
|
||||
}
|
||||
|
||||
public final class AutoFillManager {
|
||||
method public void updateAutoFillInput(android.view.View, int);
|
||||
method public void updateAutoFillInput(android.view.View, int, android.graphics.Rect, int);
|
||||
field public static final int FLAG_UPDATE_UI_HIDE = 2; // 0x2
|
||||
field public static final int FLAG_UPDATE_UI_SHOW = 1; // 0x1
|
||||
}
|
||||
|
||||
public final class AutoFillType implements android.os.Parcelable {
|
||||
method public int describeContents();
|
||||
method public static android.view.autofill.AutoFillType forList();
|
||||
@@ -46579,7 +46586,7 @@ package android.view.autofill {
|
||||
|
||||
public static abstract class VirtualViewDelegate.Callback {
|
||||
ctor public VirtualViewDelegate.Callback();
|
||||
method public void onFocusChanged(int, boolean);
|
||||
method public void onAutoFillInputUpdated(int, android.graphics.Rect, int);
|
||||
method public void onNodeRemoved(int...);
|
||||
method public void onValueChanged(int);
|
||||
}
|
||||
|
||||
@@ -112,6 +112,7 @@ import android.os.health.SystemHealthManager;
|
||||
import android.os.storage.StorageManager;
|
||||
import android.print.IPrintManager;
|
||||
import android.print.PrintManager;
|
||||
import android.service.autofill.IAutoFillManagerService;
|
||||
import android.service.persistentdata.IPersistentDataBlockService;
|
||||
import android.service.persistentdata.PersistentDataBlockManager;
|
||||
import android.telecom.TelecomManager;
|
||||
@@ -126,6 +127,7 @@ import android.view.WindowManager;
|
||||
import android.view.WindowManagerImpl;
|
||||
import android.view.accessibility.AccessibilityManager;
|
||||
import android.view.accessibility.CaptioningManager;
|
||||
import android.view.autofill.AutoFillManager;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.view.textclassifier.TextClassificationManager;
|
||||
import android.view.textservice.TextServicesManager;
|
||||
@@ -804,6 +806,14 @@ final class SystemServiceRegistry {
|
||||
IBinder b = ServiceManager.getServiceOrThrow(Context.FONT_SERVICE);
|
||||
return new FontManager(IFontManager.Stub.asInterface(b));
|
||||
}});
|
||||
registerService(Context.AUTO_FILL_MANAGER_SERVICE, AutoFillManager.class,
|
||||
new CachedServiceFetcher<AutoFillManager>() {
|
||||
@Override
|
||||
public AutoFillManager createService(ContextImpl ctx) throws ServiceNotFoundException {
|
||||
IBinder b = ServiceManager.getServiceOrThrow(Context.AUTO_FILL_MANAGER_SERVICE);
|
||||
IAutoFillManagerService service = IAutoFillManagerService.Stub.asInterface(b);
|
||||
return new AutoFillManager(ctx, service);
|
||||
}});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
*/
|
||||
package android.service.autofill;
|
||||
|
||||
import static android.service.voice.VoiceInteractionSession.KEY_FLAGS;
|
||||
import static android.service.voice.VoiceInteractionSession.KEY_STRUCTURE;
|
||||
import static android.view.View.AUTO_FILL_FLAG_TYPE_FILL;
|
||||
import static android.view.View.AUTO_FILL_FLAG_TYPE_SAVE;
|
||||
|
||||
@@ -30,13 +28,11 @@ import android.os.CancellationSignal;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
import android.view.autofill.AutoFillId;
|
||||
import android.view.autofill.FillResponse;
|
||||
|
||||
import com.android.internal.os.HandlerCaller;
|
||||
import com.android.internal.os.IResultReceiver;
|
||||
import com.android.internal.os.SomeArgs;
|
||||
|
||||
// TODO(b/33197203): improve javadoc (of both class and methods); in particular, make sure the
|
||||
@@ -49,8 +45,8 @@ import com.android.internal.os.SomeArgs;
|
||||
*/
|
||||
public abstract class AutoFillService extends Service {
|
||||
|
||||
static final String TAG = "AutoFillService";
|
||||
static final boolean DEBUG = true; // TODO: set to false once stable
|
||||
private static final String TAG = "AutoFillService";
|
||||
static final boolean DEBUG = true; // TODO(b/33197203): set to false once stable
|
||||
|
||||
/**
|
||||
* The {@link Intent} that must be declared as handled by the service.
|
||||
|
||||
@@ -21,7 +21,6 @@ import static android.service.autofill.AutoFillService.DEBUG;
|
||||
import android.annotation.Nullable;
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
import android.view.autofill.FillResponse;
|
||||
|
||||
@@ -16,7 +16,9 @@
|
||||
|
||||
package android.service.autofill;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.os.Bundle;
|
||||
import android.view.autofill.AutoFillId;
|
||||
|
||||
/**
|
||||
* Mediator between apps being auto-filled and auto-fill service implementations.
|
||||
@@ -24,5 +26,9 @@ import android.os.Bundle;
|
||||
* {@hide}
|
||||
*/
|
||||
oneway interface IAutoFillManagerService {
|
||||
|
||||
void showAutoFillInput(in AutoFillId id, in Rect boundaries);
|
||||
|
||||
// TODO(b/33197203): remove it and refactor onShellCommand
|
||||
void requestAutoFill(IBinder activityToken, int userId, in Bundle extras, int flags);
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ import static android.service.autofill.AutoFillService.DEBUG;
|
||||
import android.app.Activity;
|
||||
import android.app.assist.AssistStructure.ViewNode;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
import android.view.autofill.AutoFillId;
|
||||
|
||||
@@ -1765,6 +1765,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
*/
|
||||
int mAccessibilityViewId = NO_ID;
|
||||
|
||||
/**
|
||||
* The stable ID of this view for auto-fill purposes.
|
||||
*/
|
||||
private int mAutoFillId = NO_ID;
|
||||
|
||||
|
||||
private int mAccessibilityCursorPosition = ACCESSIBILITY_CURSOR_POSITION_UNDEFINED;
|
||||
|
||||
SendViewStateChangedAccessibilityEvent mSendViewStateChangedAccessibilityEvent;
|
||||
@@ -6932,8 +6938,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
if (forAutoFill) {
|
||||
// The auto-fill id needs to be unique, but its value doesn't matter, so it's better to
|
||||
// reuse the accessibility id to save space.
|
||||
structure.setAutoFillId(getAccessibilityViewId());
|
||||
|
||||
mAutoFillId = getAccessibilityViewId();
|
||||
structure.setAutoFillId(mAutoFillId);
|
||||
structure.setAutoFillType(getAutoFillType());
|
||||
}
|
||||
|
||||
@@ -7063,7 +7069,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes the auto-fill type that should be used on callas to
|
||||
* Describes the auto-fill type that should be used on calls to
|
||||
* {@link #autoFill(AutoFillValue)} and
|
||||
* {@link VirtualViewDelegate#autoFill(int, AutoFillValue)}.
|
||||
*
|
||||
@@ -7559,6 +7565,20 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
return mAccessibilityViewId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the unique identifier of this view for auto-fill purposes.
|
||||
*
|
||||
* <p>It's only set after {@link #onProvideAutoFillStructure(ViewStructure, int)} is called.
|
||||
*
|
||||
* @return The view autofill id or {@link #NO_ID} if
|
||||
* {@link #onProvideAutoFillStructure(ViewStructure, int)} was not called yet.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public int getAutoFillViewId() {
|
||||
return mAutoFillId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the unique identifier of the window in which this View reseides.
|
||||
*
|
||||
|
||||
@@ -42,6 +42,13 @@ public final class AutoFillId implements Parcelable {
|
||||
mVirtualId = virtualChildId;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public AutoFillId(int parentId, int virtualChildId) {
|
||||
mVirtual = true;
|
||||
mViewId = parentId;
|
||||
mVirtualId = virtualChildId;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public int getViewId() {
|
||||
return mViewId;
|
||||
|
||||
136
core/java/android/view/autofill/AutoFillManager.java
Normal file
136
core/java/android/view/autofill/AutoFillManager.java
Normal file
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.view.autofill;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
import android.os.RemoteException;
|
||||
import android.service.autofill.IAutoFillManagerService;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
|
||||
/**
|
||||
* App entry point to the AutoFill Framework.
|
||||
*/
|
||||
// TODO(b/33197203): improve this javadoc
|
||||
public final class AutoFillManager {
|
||||
|
||||
private static final String TAG = "AutoFillManager";
|
||||
private static final boolean DEBUG = true; // TODO(b/33197203): change to false once stable
|
||||
|
||||
/**
|
||||
* Flag used to show the auto-fill UI affordance for a view.
|
||||
*/
|
||||
public static final int FLAG_UPDATE_UI_SHOW = 1 << 0;
|
||||
|
||||
/**
|
||||
* Flag used to hide the auto-fill UI affordance for a view.
|
||||
*/
|
||||
public static final int FLAG_UPDATE_UI_HIDE = 1 << 1;
|
||||
|
||||
private final IAutoFillManagerService mService;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public AutoFillManager(@SuppressWarnings("unused") Context context,
|
||||
IAutoFillManagerService service) {
|
||||
mService = service;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the auto-fill bar for a given {@link View}.
|
||||
*
|
||||
* <b>Typically called twice, with different flags ({@link #FLAG_UPDATE_UI_SHOW} and
|
||||
* {@link #FLAG_UPDATE_UI_HIDE} respectively), as the user "entered" and "exited" a view.
|
||||
*
|
||||
* @param view view to be updated.
|
||||
* @param flags either {@link #FLAG_UPDATE_UI_SHOW} or
|
||||
* {@link #FLAG_UPDATE_UI_HIDE}.
|
||||
*/
|
||||
public void updateAutoFillInput(View view, int flags) {
|
||||
if (DEBUG) {
|
||||
Log.v(TAG, "updateAutoFillInput(" + view.getAutoFillViewId() + "): flags=" + flags);
|
||||
}
|
||||
|
||||
updateAutoFillInput(view, false, View.NO_ID, null, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the auto-fill bar for a virtual child of a given {@link View}.
|
||||
*
|
||||
* <b>Typically called twice, with different flags ({@link #FLAG_UPDATE_UI_SHOW} and
|
||||
* {@link #FLAG_UPDATE_UI_HIDE} respectively), as the user "entered" and "exited" a view.
|
||||
*
|
||||
* @param parent parent view.
|
||||
* @param childId id identifying the virtual child inside the parent view.
|
||||
* @param boundaries boundaries of the child (inside the parent; could be {@code null} when
|
||||
* flag is {@link #FLAG_UPDATE_UI_HIDE}.
|
||||
* @param flags either {@link #FLAG_UPDATE_UI_SHOW} or
|
||||
* {@link #FLAG_UPDATE_UI_HIDE}.
|
||||
*/
|
||||
public void updateAutoFillInput(View parent, int childId, @Nullable Rect boundaries,
|
||||
int flags) {
|
||||
if (DEBUG) {
|
||||
Log.v(TAG, "updateAutoFillInput(" + parent.getAutoFillViewId() + ", " + childId
|
||||
+ "): boundaries=" + boundaries + ", flags=" + flags);
|
||||
}
|
||||
updateAutoFillInput(parent, true, childId, boundaries, flags);
|
||||
}
|
||||
|
||||
private void updateAutoFillInput(View view, boolean virtual, int childId, Rect boundaries,
|
||||
int flags) {
|
||||
if ((flags & FLAG_UPDATE_UI_SHOW) != 0) {
|
||||
final int viewId = view.getAutoFillViewId();
|
||||
final AutoFillId id = virtual
|
||||
? new AutoFillId(viewId, childId)
|
||||
: new AutoFillId(viewId);
|
||||
showAutoFillInput(id, boundaries);
|
||||
return;
|
||||
}
|
||||
// TODO(b/33197203): handle FLAG_UPDATE_UI_HIDE
|
||||
}
|
||||
|
||||
private void showAutoFillInput(AutoFillId id, Rect boundaries) {
|
||||
final int autoFillViewId = id.getViewId();
|
||||
/*
|
||||
* TODO(b/33197203): currently SHOW_AUTO_FILL_BAR is only set once per activity (i.e, when
|
||||
* the view does not have an auto-fill id), but it should be called again for views that
|
||||
* were not part of the initial auto-fill dataset returned by the service. For example:
|
||||
*
|
||||
* 1.Activity has 4 fields, `first_name`, `last_name`, and `address`.
|
||||
* 2.User taps `first_name`.
|
||||
* 3.Service returns a dataset with ids for `first_name` and `last_name`.
|
||||
* 4.When user taps `first_name` (again) or `last_name`, flag should not have
|
||||
* SHOW_AUTO_FILL_BAR set, but when user taps `address`, it should (since that field was
|
||||
* not part of the initial dataset).
|
||||
*
|
||||
* Similarly, once the activity is auto-filled, the flag logic should be reset (so if the
|
||||
* user taps the view again, a new auto-fill request is made)
|
||||
*/
|
||||
if (autoFillViewId != View.NO_ID) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
mService.showAutoFillInput(id, boundaries);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package android.view.autofill;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.graphics.Rect;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewStructure;
|
||||
@@ -71,20 +73,18 @@ public abstract class VirtualViewDelegate {
|
||||
public abstract static class Callback {
|
||||
|
||||
/**
|
||||
* Sent when the focus inside the hierarchy changed.
|
||||
* Sent when the auto-fill bar for a child must be updated.
|
||||
*
|
||||
* <p>Typically callled twice - for the nodes that lost and gained focus.
|
||||
*
|
||||
* <p>This method should only be called when the change was not caused by the AutoFill
|
||||
* Framework itselft (i.e, through {@link VirtualViewDelegate#autoFill(int, AutoFillValue)},
|
||||
* but by external causes (for example, when the user changed the value through the view's
|
||||
* UI).
|
||||
*
|
||||
* @param virtualId id of the node whose focus changed.
|
||||
* @param hasFocus {@code true} when focus was gained, {@code false} when it was lost.
|
||||
* See {@link AutoFillManager#updateAutoFillInput(View, int, android.graphics.Rect, int)}
|
||||
* for more details.
|
||||
*/
|
||||
public void onFocusChanged(int virtualId, boolean hasFocus) {
|
||||
if (DEBUG) Log.d(TAG, "onFocusChanged() for " + virtualId + ": " + hasFocus);
|
||||
// TODO(b/33197203): do we really need it, or should the parent view just call
|
||||
// AutoFillManager.updateAutoFillInput() directly?
|
||||
public void onAutoFillInputUpdated(int virtualId, @Nullable Rect boundaries, int flags) {
|
||||
if (DEBUG) {
|
||||
Log.v(TAG, "onAutoFillInputUpdated(): virtualId=" + virtualId + ", boundaries="
|
||||
+ boundaries + ", flags=" + flags);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -169,8 +169,6 @@ public class EditText extends TextView {
|
||||
Log.w(VIEW_LOG_TAG, "EditText.autoFill(): no text on AutoFillValue");
|
||||
return;
|
||||
}
|
||||
// TODO(b/33197203): once auto-fill is triggered by the IME, we'll need a new setText()
|
||||
// or setAutoFillText() method on TextView to avoid re-triggering it.
|
||||
setText(text);
|
||||
}
|
||||
|
||||
|
||||
@@ -136,6 +136,7 @@ import android.view.accessibility.AccessibilityEvent;
|
||||
import android.view.accessibility.AccessibilityManager;
|
||||
import android.view.accessibility.AccessibilityNodeInfo;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.view.autofill.AutoFillManager;
|
||||
import android.view.inputmethod.BaseInputConnection;
|
||||
import android.view.inputmethod.CompletionInfo;
|
||||
import android.view.inputmethod.CorrectionInfo;
|
||||
@@ -264,6 +265,7 @@ import java.util.Locale;
|
||||
public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
|
||||
static final String LOG_TAG = "TextView";
|
||||
static final boolean DEBUG_EXTRACT = false;
|
||||
static final boolean DEBUG_AUTOFILL = false;
|
||||
|
||||
// Enum for the "typeface" XML parameter.
|
||||
// TODO: How can we get this from the XML instead of hardcoding it here?
|
||||
@@ -9024,6 +9026,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
||||
Spannable sp = (Spannable) mText;
|
||||
MetaKeyKeyListener.resetMetaState(sp);
|
||||
}
|
||||
} else {
|
||||
final AutoFillManager afm = mContext.getSystemService(AutoFillManager.class);
|
||||
if (afm != null) {
|
||||
if (DEBUG_AUTOFILL) {
|
||||
Log.v(LOG_TAG, "onFocusChanged(): id=" + getAutoFillViewId() + ", focused= "
|
||||
+ focused);
|
||||
}
|
||||
afm.updateAutoFillInput(this, AutoFillManager.FLAG_UPDATE_UI_HIDE);
|
||||
}
|
||||
}
|
||||
|
||||
startStopMarquee(focused);
|
||||
@@ -10602,6 +10613,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
||||
* @hide
|
||||
*/
|
||||
protected void viewClicked(InputMethodManager imm) {
|
||||
final AutoFillManager afm = mContext.getSystemService(AutoFillManager.class);
|
||||
if (afm != null) {
|
||||
if (DEBUG_AUTOFILL) Log.v(LOG_TAG, "viewClicked(): id=" + getAutoFillViewId());
|
||||
|
||||
// TODO(b/33197203): integrate with onFocus and/or move to view?
|
||||
afm.updateAutoFillInput(this, AutoFillManager.FLAG_UPDATE_UI_SHOW);
|
||||
}
|
||||
|
||||
if (imm != null) {
|
||||
imm.viewClicked(this);
|
||||
}
|
||||
|
||||
@@ -19,10 +19,6 @@ package com.android.server.autofill;
|
||||
import static android.Manifest.permission.MANAGE_AUTO_FILL;
|
||||
import static android.content.Context.AUTO_FILL_MANAGER_SERVICE;
|
||||
import static android.view.View.AUTO_FILL_FLAG_TYPE_FILL;
|
||||
import static android.view.View.AUTO_FILL_FLAG_TYPE_SAVE;
|
||||
|
||||
import static com.android.server.autofill.AutoFillUI.MSG_SHOW_ALL_NOTIFICATIONS;
|
||||
import static com.android.server.autofill.AutoFillUI.SHOW_ALL_NOTIFICATIONS_DELAY_MS;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.AppGlobals;
|
||||
@@ -32,11 +28,13 @@ import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ServiceInfo;
|
||||
import android.database.ContentObserver;
|
||||
import android.graphics.Rect;
|
||||
import android.net.Uri;
|
||||
import android.os.Binder;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ResultReceiver;
|
||||
@@ -51,9 +49,12 @@ import android.util.Log;
|
||||
import android.util.Slog;
|
||||
import android.util.SparseArray;
|
||||
import android.util.TimeUtils;
|
||||
import android.view.autofill.AutoFillId;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.os.BackgroundThread;
|
||||
import com.android.internal.os.HandlerCaller;
|
||||
import com.android.internal.os.SomeArgs;
|
||||
import com.android.server.FgThread;
|
||||
import com.android.server.SystemService;
|
||||
|
||||
@@ -70,13 +71,12 @@ import java.io.PrintWriter;
|
||||
public final class AutoFillManagerService extends SystemService {
|
||||
|
||||
private static final String TAG = "AutoFillManagerService";
|
||||
static final boolean DEBUG = true; // TODO: change to false once stable
|
||||
static final boolean DEBUG = true; // TODO(b/33197203): change to false once stable
|
||||
|
||||
private static final long SERVICE_BINDING_LIFETIME_MS = 5 * DateUtils.MINUTE_IN_MILLIS;
|
||||
|
||||
private static final int ARG_NOT_USED = 0;
|
||||
|
||||
protected static final int MSG_UNBIND = 1;
|
||||
protected static final int MSG_SHOW_AUTO_FILL = 2;
|
||||
|
||||
private final AutoFillManagerServiceStub mServiceStub;
|
||||
private final AutoFillUI mUi;
|
||||
@@ -85,23 +85,28 @@ public final class AutoFillManagerService extends SystemService {
|
||||
|
||||
private final Object mLock = new Object();
|
||||
|
||||
private final Handler mHandler = new Handler() {
|
||||
private final HandlerCaller.Callback mHandlerCallback = new HandlerCaller.Callback() {
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
public void executeMessage(Message msg) {
|
||||
switch (msg.what) {
|
||||
case MSG_UNBIND:
|
||||
case MSG_UNBIND: {
|
||||
removeStaleServiceForUser(msg.arg1);
|
||||
return;
|
||||
case MSG_SHOW_ALL_NOTIFICATIONS:
|
||||
mUi.showAllNotifications();
|
||||
} case MSG_SHOW_AUTO_FILL: {
|
||||
final SomeArgs args = (SomeArgs) msg.obj;
|
||||
showAutoFillInput(msg.arg1, (AutoFillId) args.arg1, (Rect) args.arg2);
|
||||
return;
|
||||
default:
|
||||
} default: {
|
||||
Slog.w(TAG, "Invalid message: " + msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
private HandlerCaller mHandlerCaller;
|
||||
|
||||
/**
|
||||
* Cache of {@link AutoFillManagerServiceImpl} per user id.
|
||||
* <p>
|
||||
@@ -122,6 +127,8 @@ public final class AutoFillManagerService extends SystemService {
|
||||
public AutoFillManagerService(Context context) {
|
||||
super(context);
|
||||
|
||||
mHandlerCaller = new HandlerCaller(null, Looper.getMainLooper(), mHandlerCallback, true);
|
||||
|
||||
mContext = context;
|
||||
mUi = new AutoFillUI(context, this, mLock);
|
||||
mResolver = context.getContentResolver();
|
||||
@@ -139,14 +146,6 @@ public final class AutoFillManagerService extends SystemService {
|
||||
if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
|
||||
new SettingsObserver(BackgroundThread.getHandler());
|
||||
}
|
||||
if (phase == PHASE_BOOT_COMPLETED) {
|
||||
// TODO: if sent right away, the notification is not displayed. Since the notification
|
||||
// mechanism is a temporary approach anyways, just delay it..
|
||||
if (DEBUG)
|
||||
Slog.d(TAG, "Showing notifications in " + SHOW_ALL_NOTIFICATIONS_DELAY_MS + "ms");
|
||||
mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SHOW_ALL_NOTIFICATIONS),
|
||||
SHOW_ALL_NOTIFICATIONS_DELAY_MS);
|
||||
}
|
||||
}
|
||||
|
||||
private AutoFillManagerServiceImpl newServiceForUser(int userId) {
|
||||
@@ -200,7 +199,7 @@ public final class AutoFillManagerService extends SystemService {
|
||||
}
|
||||
// Keep service connection alive for a while, in case user needs to interact with it
|
||||
// (for example, to save the data that was inputted in)
|
||||
mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_UNBIND, userId, ARG_NOT_USED),
|
||||
mHandlerCaller.sendMessageDelayed(mHandlerCaller.obtainMessageI(MSG_UNBIND, userId),
|
||||
SERVICE_BINDING_LIFETIME_MS);
|
||||
return service;
|
||||
}
|
||||
@@ -245,18 +244,41 @@ public final class AutoFillManagerService extends SystemService {
|
||||
|
||||
}
|
||||
|
||||
|
||||
private void requestAutoFillLocked(IBinder activityToken, int userId, Bundle extras,
|
||||
int flags) {
|
||||
final AutoFillManagerServiceImpl service = getServiceForUserLocked(userId);
|
||||
if (service != null) {
|
||||
service.requestAutoFill(activityToken, extras, flags);
|
||||
}
|
||||
}
|
||||
|
||||
private void showAutoFillInput(int userId, AutoFillId id, Rect rect) {
|
||||
if (DEBUG) Slog.d(TAG, "handler.showAutoFillInput(): id=" + id + ", rect=" + rect);
|
||||
|
||||
synchronized (mLock) {
|
||||
requestAutoFillLocked(null, userId, null, AUTO_FILL_FLAG_TYPE_FILL);
|
||||
}
|
||||
}
|
||||
|
||||
final class AutoFillManagerServiceStub extends IAutoFillManagerService.Stub {
|
||||
|
||||
@Override
|
||||
public void showAutoFillInput(AutoFillId id, Rect boundaries) {
|
||||
if (DEBUG) Slog.d(TAG, "showAutoFillInput(): id=" + id + ", boundaries=" + boundaries);
|
||||
|
||||
// TODO(b/33197203): fail if it's not called by same uid as the top activity
|
||||
mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIOO(MSG_SHOW_AUTO_FILL,
|
||||
UserHandle.getCallingUserId(), id, boundaries));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestAutoFill(IBinder activityToken, int userId, Bundle extras, int flags) {
|
||||
if (DEBUG) Slog.d(TAG, "requestAutoFill: flags=" + flags + ", userId=" + userId);
|
||||
mContext.enforceCallingPermission(MANAGE_AUTO_FILL, TAG);
|
||||
|
||||
synchronized (mLock) {
|
||||
final AutoFillManagerServiceImpl service = getServiceForUserLocked(userId);
|
||||
if (service != null) {
|
||||
service.requestAutoFill(activityToken, extras, flags);
|
||||
}
|
||||
requestAutoFillLocked(activityToken, userId, extras, flags);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -307,7 +329,6 @@ public final class AutoFillManagerService extends SystemService {
|
||||
if (DEBUG) Slog.d(TAG, "settings (" + uri + " changed for " + userId);
|
||||
synchronized (mLock) {
|
||||
removeCachedServiceForUserLocked(userId);
|
||||
mUi.updateNotification(userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,7 +163,6 @@ final class AutoFillManagerServiceImpl {
|
||||
}
|
||||
final AssistStructure structure = resultData
|
||||
.getParcelable(VoiceInteractionSession.KEY_STRUCTURE);
|
||||
final Bundle data = resultData.getBundle(VoiceInteractionSession.KEY_RECEIVER_EXTRAS);
|
||||
final int flags = resultData.getInt(VoiceInteractionSession.KEY_FLAGS, 0);
|
||||
|
||||
final ServerCallback serverCallback;
|
||||
|
||||
@@ -16,40 +16,29 @@
|
||||
package com.android.server.autofill;
|
||||
|
||||
import static android.view.View.AUTO_FILL_FLAG_TYPE_SAVE;
|
||||
import static android.view.View.AUTO_FILL_FLAG_TYPE_FILL;
|
||||
|
||||
import static com.android.server.autofill.AutoFillManagerService.DEBUG;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AppGlobals;
|
||||
import android.app.Notification;
|
||||
import android.app.Notification.Action;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ServiceInfo;
|
||||
import android.content.pm.UserInfo;
|
||||
import android.os.Binder;
|
||||
import android.os.Bundle;
|
||||
import android.os.RemoteException;
|
||||
import android.os.UserManager;
|
||||
import android.provider.Settings;
|
||||
import android.service.autofill.AutoFillService;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.util.Slog;
|
||||
import android.view.autofill.Dataset;
|
||||
import android.view.autofill.AutoFillId;
|
||||
import android.view.autofill.Dataset;
|
||||
import android.view.autofill.FillResponse;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.server.UiThread;
|
||||
|
||||
import java.util.Arrays;
|
||||
@@ -69,9 +58,10 @@ final class AutoFillUI {
|
||||
|
||||
AutoFillUI(Context context, AutoFillManagerService service, Object lock) {
|
||||
mContext = context;
|
||||
mResolver = context.getContentResolver();
|
||||
mService = service;
|
||||
mLock = lock;
|
||||
|
||||
setNotificationListener();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -121,56 +111,60 @@ final class AutoFillUI {
|
||||
private static final String EXTRA_FILL_RESPONSE = "fill_response";
|
||||
private static final String EXTRA_DATASET = "dataset";
|
||||
|
||||
private static final String TYPE_EMULATE = "emulate";
|
||||
private static final String TYPE_OPTIONS = "options";
|
||||
private static final String TYPE_DELETE_CALLBACK = "delete_callback";
|
||||
private static final String TYPE_PICK_DATASET = "pick_dataset";
|
||||
private static final String TYPE_SAVE = "save";
|
||||
|
||||
static final int MSG_SHOW_ALL_NOTIFICATIONS = 42;
|
||||
static final int SHOW_ALL_NOTIFICATIONS_DELAY_MS = 5000;
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private BroadcastReceiver mNotificationReceiver;
|
||||
private final ContentResolver mResolver;
|
||||
@GuardedBy("mLock")
|
||||
private final AutoFillManagerService mService;
|
||||
private final Object mLock;
|
||||
|
||||
// Hack used to generate unique pending intents
|
||||
static int sResultCode = 0;
|
||||
|
||||
private void setNotificationListener() {
|
||||
synchronized (mLock) {
|
||||
if (mNotificationReceiver == null) {
|
||||
mNotificationReceiver = new NotificationReceiver();
|
||||
mContext.registerReceiver(mNotificationReceiver,
|
||||
new IntentFilter(NOTIFICATION_AUTO_FILL_INTENT));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class NotificationReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
final int userId = intent.getIntExtra(EXTRA_USER_ID, -1);
|
||||
|
||||
final AutoFillManagerServiceImpl service = mService.getServiceForUserLocked(userId);
|
||||
if (service == null) {
|
||||
Slog.w(TAG, "no auto-fill service for user " + userId);
|
||||
return;
|
||||
}
|
||||
|
||||
final int callbackId = intent.getIntExtra(EXTRA_CALLBACK_ID, -1);
|
||||
final String type = intent.getStringExtra(EXTRA_NOTIFICATION_TYPE);
|
||||
if (type == null) {
|
||||
Slog.wtf(TAG, "No extra " + EXTRA_NOTIFICATION_TYPE + " on intent " + intent);
|
||||
return;
|
||||
}
|
||||
final FillResponse fillData = intent.getParcelableExtra(EXTRA_FILL_RESPONSE);
|
||||
final Dataset dataset = intent.getParcelableExtra(EXTRA_DATASET);
|
||||
final Bundle datasetArgs = dataset == null ? null : dataset.getExtras();
|
||||
final Bundle fillDataArgs = fillData == null ? null : fillData.getExtras();
|
||||
|
||||
// Bundle sent on AutoFillService methods - only set if service provided a bundle
|
||||
final Bundle extras = (datasetArgs == null && fillDataArgs == null)
|
||||
? null : new Bundle();
|
||||
|
||||
if (DEBUG) Slog.d(TAG, "Notification received: type=" + type + ", userId=" + userId
|
||||
+ ", callbackId=" + callbackId);
|
||||
synchronized (mLock) {
|
||||
final AutoFillManagerServiceImpl service = mService.getServiceForUserLocked(userId);
|
||||
if (service == null) {
|
||||
Slog.w(TAG, "no auto-fill service for user " + userId);
|
||||
return;
|
||||
}
|
||||
|
||||
final int callbackId = intent.getIntExtra(EXTRA_CALLBACK_ID, -1);
|
||||
final String type = intent.getStringExtra(EXTRA_NOTIFICATION_TYPE);
|
||||
if (type == null) {
|
||||
Slog.wtf(TAG, "No extra " + EXTRA_NOTIFICATION_TYPE + " on intent " + intent);
|
||||
return;
|
||||
}
|
||||
final FillResponse fillData = intent.getParcelableExtra(EXTRA_FILL_RESPONSE);
|
||||
final Dataset dataset = intent.getParcelableExtra(EXTRA_DATASET);
|
||||
final Bundle datasetArgs = dataset == null ? null : dataset.getExtras();
|
||||
final Bundle fillDataArgs = fillData == null ? null : fillData.getExtras();
|
||||
|
||||
// Bundle sent on AutoFillService methods - only set if service provided a bundle
|
||||
final Bundle extras = (datasetArgs == null && fillDataArgs == null)
|
||||
? null : new Bundle();
|
||||
|
||||
if (DEBUG) Slog.d(TAG, "Notification received: type=" + type + ", userId=" + userId
|
||||
+ ", callbackId=" + callbackId);
|
||||
switch (type) {
|
||||
case TYPE_EMULATE:
|
||||
service.requestAutoFill(null, extras, AUTO_FILL_FLAG_TYPE_FILL);
|
||||
break;
|
||||
case TYPE_SAVE:
|
||||
if (datasetArgs != null) {
|
||||
if (DEBUG) Log.d(TAG, "filldata args on save notificataion: " +
|
||||
@@ -210,54 +204,6 @@ final class AutoFillUI {
|
||||
}
|
||||
}
|
||||
|
||||
private ComponentName getProviderForUser(int userId) {
|
||||
ComponentName serviceComponent = null;
|
||||
ServiceInfo serviceInfo = null;
|
||||
final String componentName = Settings.Secure.getStringForUser(
|
||||
mResolver, Settings.Secure.AUTO_FILL_SERVICE, userId);
|
||||
if (!TextUtils.isEmpty(componentName)) {
|
||||
try {
|
||||
serviceComponent = ComponentName.unflattenFromString(componentName);
|
||||
serviceInfo =
|
||||
AppGlobals.getPackageManager().getServiceInfo(serviceComponent, 0, userId);
|
||||
} catch (RuntimeException | RemoteException e) {
|
||||
Slog.wtf(TAG, "Bad auto-fill service name " + componentName, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (DEBUG) Slog.d(TAG, "getServiceComponentForUser(" + userId + "): component="
|
||||
+ serviceComponent + ", info: " + serviceInfo);
|
||||
if (serviceInfo == null) {
|
||||
Slog.w(TAG, "no service info for " + serviceComponent);
|
||||
return null;
|
||||
}
|
||||
return serviceComponent;
|
||||
}
|
||||
|
||||
void showAllNotifications() {
|
||||
final UserManager userManager =
|
||||
(UserManager) mContext.getSystemService(Context.USER_SERVICE);
|
||||
|
||||
final List<UserInfo> allUsers = userManager.getUsers(true);
|
||||
|
||||
for (UserInfo user : allUsers) {
|
||||
final ComponentName serviceComponent = getProviderForUser(user.id);
|
||||
if (serviceComponent != null) {
|
||||
showMainNotification(serviceComponent, user.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void updateNotification(int userId) {
|
||||
final ComponentName serviceComponent = getProviderForUser(userId);
|
||||
if (serviceComponent == null) {
|
||||
cancelMainNotification(userId);
|
||||
} else {
|
||||
showMainNotification(serviceComponent, userId);
|
||||
}
|
||||
}
|
||||
|
||||
private static Intent newNotificationIntent(int userId, String type) {
|
||||
final Intent intent = new Intent(NOTIFICATION_AUTO_FILL_INTENT);
|
||||
intent.putExtra(EXTRA_USER_ID, userId);
|
||||
@@ -295,65 +241,6 @@ final class AutoFillUI {
|
||||
return builder.append(']').toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a permanent notification that triggers the auto-fill workflow for the given user.
|
||||
*
|
||||
* <p>It emulates calling the auto-fill service when the IME is shown.
|
||||
*/
|
||||
private void showMainNotification(ComponentName serviceComponent, int userId) {
|
||||
if (DEBUG) Log.d(TAG, "showNotification() for " + userId + ": " + serviceComponent);
|
||||
|
||||
synchronized (mLock) {
|
||||
if (mNotificationReceiver == null) {
|
||||
mNotificationReceiver = new NotificationReceiver();
|
||||
mContext.registerReceiver(mNotificationReceiver,
|
||||
new IntentFilter(NOTIFICATION_AUTO_FILL_INTENT));
|
||||
}
|
||||
}
|
||||
|
||||
final Intent fillIntent = newNotificationIntent(userId, TYPE_EMULATE);
|
||||
final PendingIntent fillPendingIntent = PendingIntent.getBroadcast(mContext,
|
||||
-1, fillIntent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
|
||||
final String packageName = serviceComponent.getPackageName();
|
||||
String providerName = null;
|
||||
final PackageManager pm = mContext.getPackageManager();
|
||||
try {
|
||||
final ApplicationInfo info = pm.getApplicationInfoAsUser(packageName, 0, userId);
|
||||
if (info != null) {
|
||||
providerName = pm.getApplicationLabel(info).toString();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
providerName = packageName;
|
||||
}
|
||||
final String title = "AutoFill IME Emulation";
|
||||
final String subTitle = "Tap notification to start auto-fill workflow (by '" + providerName
|
||||
+ "' on top activity on user " + userId + ".\n"
|
||||
+ "Once provider replies, a new notification will show your options.";
|
||||
|
||||
final Notification notification = new Notification.Builder(mContext)
|
||||
.setCategory(Notification.CATEGORY_SYSTEM)
|
||||
.setOngoing(true)
|
||||
.setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
|
||||
.setLocalOnly(true)
|
||||
.setColor(mContext.getColor(
|
||||
com.android.internal.R.color.system_notification_accent_color))
|
||||
.setContentTitle(title)
|
||||
.setStyle(new Notification.BigTextStyle().bigText(subTitle))
|
||||
.setContentIntent(fillPendingIntent)
|
||||
.build();
|
||||
NotificationManager.from(mContext).notify(TYPE_EMULATE, userId, notification);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels the permament notification created by
|
||||
* {@link #showMainNotification(ComponentName, int)}.
|
||||
*/
|
||||
private void cancelMainNotification(int userId) {
|
||||
if (DEBUG) Log.d(TAG, "cancelNotificationLocked(): " + userId);
|
||||
NotificationManager.from(mContext).cancel(TYPE_EMULATE, userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a notification with the results of an auto-fill request, using notications actions
|
||||
* to emulate the auto-fill bar buttons displaying the dataset names.
|
||||
|
||||
Reference in New Issue
Block a user