From 7ad11281378a1248c08c703b14cfc681e37cac1b Mon Sep 17 00:00:00 2001 From: Svet Ganov Date: Tue, 28 Feb 2017 13:08:56 -0800 Subject: [PATCH] Allow auto-fill services to disable themselves 1. Added a new API for a connected auto-fill service to disable itself 2. Added a new shell command to destroy all pending sessions which is used in CTS tests 3. Fixed a bug where the unbind timeout was in minutes instead of seconds Test: wrote CTS tests, all auto-fill tests pass bug:35848030 Change-Id: I681605aa0b8c004a0f14e30b57117c291d89a894 --- Android.mk | 1 + api/current.txt | 1 + api/system-current.txt | 1 + api/test-current.txt | 1 + .../service/autofill/AutoFillService.java | 44 +++++++++++++---- .../service/autofill/IAutoFillService.aidl | 4 +- .../autofill/IAutoFillServiceConnection.aidl | 26 ++++++++++ .../autofill/AutoFillManagerService.java | 23 +++++++++ .../autofill/AutoFillManagerServiceImpl.java | 28 +++++++++++ .../AutoFillManagerServiceShellCommand.java | 49 +++++++++++++++---- .../server/autofill/RemoteFillService.java | 22 +++++++-- 11 files changed, 175 insertions(+), 25 deletions(-) create mode 100644 core/java/android/service/autofill/IAutoFillServiceConnection.aidl diff --git a/Android.mk b/Android.mk index f544620b993f2..f318a5a754c1e 100644 --- a/Android.mk +++ b/Android.mk @@ -265,6 +265,7 @@ LOCAL_SRC_FILES += \ core/java/android/security/IKeystoreService.aidl \ core/java/android/security/keymaster/IKeyAttestationApplicationIdProvider.aidl \ core/java/android/service/autofill/IAutoFillService.aidl \ + core/java/android/service/autofill/IAutoFillServiceConnection.aidl \ core/java/android/service/autofill/IFillCallback.aidl \ core/java/android/service/autofill/ISaveCallback.aidl \ core/java/android/service/carrier/ICarrierService.aidl \ diff --git a/api/current.txt b/api/current.txt index 9c443f832ae66..5099c2b62a984 100644 --- a/api/current.txt +++ b/api/current.txt @@ -36472,6 +36472,7 @@ package android.service.autofill { public abstract class AutoFillService extends android.app.Service { ctor public AutoFillService(); + method public final void disableSelf(); method public final android.os.IBinder onBind(android.content.Intent); method public void onConnected(); method public void onDisconnected(); diff --git a/api/system-current.txt b/api/system-current.txt index eb237fae6a508..41b52ec999d58 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -39395,6 +39395,7 @@ package android.service.autofill { public abstract class AutoFillService extends android.app.Service { ctor public AutoFillService(); + method public final void disableSelf(); method public final android.os.IBinder onBind(android.content.Intent); method public void onConnected(); method public void onDisconnected(); diff --git a/api/test-current.txt b/api/test-current.txt index ccf688c67367b..5183e27a9e071 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -36611,6 +36611,7 @@ package android.service.autofill { public abstract class AutoFillService extends android.app.Service { ctor public AutoFillService(); + method public final void disableSelf(); method public final android.os.IBinder onBind(android.content.Intent); method public void onConnected(); method public void onDisconnected(); diff --git a/core/java/android/service/autofill/AutoFillService.java b/core/java/android/service/autofill/AutoFillService.java index 4099f59ec9ef6..3c211eb972e7d 100644 --- a/core/java/android/service/autofill/AutoFillService.java +++ b/core/java/android/service/autofill/AutoFillService.java @@ -15,9 +15,12 @@ */ package android.service.autofill; +import android.accessibilityservice.IAccessibilityServiceConnection; import android.annotation.NonNull; import android.annotation.Nullable; +import android.os.Message; import android.os.RemoteException; +import android.view.accessibility.AccessibilityInteractionClient; import com.android.internal.os.HandlerCaller; import android.annotation.SdkConstant; import android.app.Activity; @@ -78,6 +81,15 @@ public abstract class AutoFillService extends Service { private static final int MSG_ON_SAVE_REQUEST = 4; private final IAutoFillService mInterface = new IAutoFillService.Stub() { + @Override + public void onInit(IAutoFillServiceConnection connection) { + if (connection != null) { + mHandlerCaller.obtainMessageO(MSG_CONNECT, connection).sendToTarget(); + } else { + mHandlerCaller.obtainMessage(MSG_DISCONNECT).sendToTarget(); + } + } + @Override public void onFillRequest(AssistStructure structure, Bundle extras, IFillCallback callback) { @@ -98,21 +110,12 @@ public abstract class AutoFillService extends Service { mHandlerCaller.obtainMessageOOO(MSG_ON_SAVE_REQUEST, structure, extras, callback).sendToTarget(); } - - @Override - public void onConnected() { - mHandlerCaller.sendMessage(mHandlerCaller.obtainMessage(MSG_CONNECT)); - } - - @Override - public void onDisconnected() { - mHandlerCaller.sendMessage(mHandlerCaller.obtainMessage(MSG_DISCONNECT)); - } }; private final HandlerCaller.Callback mHandlerCallback = (msg) -> { switch (msg.what) { case MSG_CONNECT: { + mConnection = (IAutoFillServiceConnection) msg.obj; onConnected(); break; } case MSG_ON_FILL_REQUEST: { @@ -136,6 +139,7 @@ public abstract class AutoFillService extends Service { break; } case MSG_DISCONNECT: { onDisconnected(); + mConnection = null; break; } default: { Log.w(TAG, "MyCallbacks received invalid message type: " + msg); @@ -145,6 +149,8 @@ public abstract class AutoFillService extends Service { private HandlerCaller mHandlerCaller; + private IAutoFillServiceConnection mConnection; + /** * {@inheritDoc} * @@ -223,4 +229,22 @@ public abstract class AutoFillService extends Service { public void onDisconnected() { //TODO(b/33197203): is not called anymore, fix it! } + + /** + * Disables the service. After calling this method, the service will + * be disabled and settings will show that it is turned off. + * + *

You should call this method only after a call to {@link #onConnected()} + * and before the corresponding call to {@link #onDisconnected()}. In other words + * you can disable your service only while the system is connected to it.

+ */ + public final void disableSelf() { + if (mConnection != null) { + try { + mConnection.disableSelf(); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + } } diff --git a/core/java/android/service/autofill/IAutoFillService.aidl b/core/java/android/service/autofill/IAutoFillService.aidl index fa1ea65e30b05..80685d87a7883 100644 --- a/core/java/android/service/autofill/IAutoFillService.aidl +++ b/core/java/android/service/autofill/IAutoFillService.aidl @@ -18,6 +18,7 @@ package android.service.autofill; import android.app.assist.AssistStructure; import android.os.Bundle; +import android.service.autofill.IAutoFillServiceConnection; import android.service.autofill.IFillCallback; import android.service.autofill.ISaveCallback; import com.android.internal.os.IResultReceiver; @@ -28,10 +29,9 @@ import com.android.internal.os.IResultReceiver; * @hide */ oneway interface IAutoFillService { + void onInit(in IAutoFillServiceConnection connection); void onFillRequest(in AssistStructure structure, in Bundle extras, in IFillCallback callback); void onSaveRequest(in AssistStructure structure, in Bundle extras, in ISaveCallback callback); - void onConnected(); - void onDisconnected(); } diff --git a/core/java/android/service/autofill/IAutoFillServiceConnection.aidl b/core/java/android/service/autofill/IAutoFillServiceConnection.aidl new file mode 100644 index 0000000000000..cf73a10ca58ca --- /dev/null +++ b/core/java/android/service/autofill/IAutoFillServiceConnection.aidl @@ -0,0 +1,26 @@ +/* + * 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.service.autofill; + +/** + * Interface from an auto fill service to the system. + * + * @hide + */ +interface IAutoFillServiceConnection { + void disableSelf(); +} diff --git a/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java b/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java index 7a85d4e9b62e6..8947b7d99eef0 100644 --- a/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java +++ b/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java @@ -185,6 +185,29 @@ public final class AutoFillManagerService extends SystemService { } } + // Called by Shell command. + void destroySessions(int userId, IResultReceiver receiver) { + Slog.i(TAG, "destroySessions() for userId " + userId); + mContext.enforceCallingPermission(MANAGE_AUTO_FILL, TAG); + + synchronized (mLock) { + if (userId != UserHandle.USER_ALL) { + mServicesCache.get(userId).destroySessionsLocked(); + } else { + final int size = mServicesCache.size(); + for (int i = 0; i < size; i++) { + mServicesCache.valueAt(i).destroySessionsLocked(); + } + } + } + + try { + receiver.send(0, new Bundle()); + } catch (RemoteException e) { + // Just ignore it... + } + } + // Called by Shell command. void listSessions(int userId, IResultReceiver receiver) { Slog.i(TAG, "listSessions() for userId " + userId); diff --git a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java index bfc6e836c3b0d..ad4dac070d389 100644 --- a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java +++ b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java @@ -405,6 +405,12 @@ final class AutoFillManagerServiceImpl { } } + void destroySessionsLocked() { + for (Session session : mSessions.values()) { + session.removeSelf(); + } + } + void listSessionsLocked(ArrayList output) { for (IBinder activityToken : mSessions.keySet()) { output.add((mInfo != null ? mInfo.getServiceInfo().getComponentName() @@ -664,6 +670,28 @@ final class AutoFillManagerServiceImpl { }); } + // FillServiceCallbacks + @Override + public void onDisableSelf() { + final long identity = Binder.clearCallingIdentity(); + try { + final String autoFillService = Settings.Secure.getStringForUser( + mContext.getContentResolver(), + Settings.Secure.AUTO_FILL_SERVICE, mUserId); + if (mInfo.getServiceInfo().getComponentName().equals( + ComponentName.unflattenFromString(autoFillService))) { + Settings.Secure.putStringForUser(mContext.getContentResolver(), + Settings.Secure.AUTO_FILL_SERVICE, null, mUserId); + } + } finally { + Binder.restoreCallingIdentity(identity); + } + synchronized (mLock) { + destroyLocked(); + mSessions.remove(this); + } + } + // FillServiceCallbacks @Override public void onServiceDied(RemoteFillService service) { diff --git a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceShellCommand.java b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceShellCommand.java index cfa4a1d0a1079..76eed2ac2639d 100644 --- a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceShellCommand.java +++ b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceShellCommand.java @@ -20,7 +20,6 @@ import static com.android.server.autofill.AutoFillManagerService.RECEIVER_BUNDLE import android.app.ActivityManager; import android.os.Bundle; -import android.os.RemoteException; import android.os.ShellCommand; import android.os.UserHandle; @@ -50,6 +49,8 @@ public final class AutoFillManagerServiceShellCommand extends ShellCommand { return requestSave(); case "list": return requestList(pw); + case "destroy": + return requestDestroy(pw); case "reset": return requestReset(); default: @@ -67,6 +68,9 @@ public final class AutoFillManagerServiceShellCommand extends ShellCommand { pw.println(" list sessions [--user USER_ID]"); pw.println(" List all pending sessions."); pw.println(""); + pw.println(" destroy sessions [--user USER_ID]"); + pw.println(" Destroy all pending sessions."); + pw.println(""); pw.println(" save [--user USER_ID]"); pw.println(" Request provider to save contents of the top activity. "); pw.println(""); @@ -82,30 +86,55 @@ public final class AutoFillManagerServiceShellCommand extends ShellCommand { return 0; } - private int requestList(PrintWriter pw) { - final String type = getNextArgRequired(); - if (!type.equals("sessions")) { - pw.println("Error: invalid list type"); + private int requestDestroy(PrintWriter pw) { + if (!isNextArgSessions(pw)) { return -1; - } + final int userId = getUserIdFromArgsOrAllUsers(); final CountDownLatch latch = new CountDownLatch(1); final IResultReceiver receiver = new IResultReceiver.Stub() { - @Override - public void send(int resultCode, Bundle resultData) throws RemoteException { + public void send(int resultCode, Bundle resultData) { + latch.countDown(); + } + }; + return requestSessionCommon(pw, latch, () -> mService.destroySessions(userId, receiver)); + } + + private int requestList(PrintWriter pw) { + if (!isNextArgSessions(pw)) { + return -1; + } + + final int userId = getUserIdFromArgsOrAllUsers(); + final CountDownLatch latch = new CountDownLatch(1); + final IResultReceiver receiver = new IResultReceiver.Stub() { + @Override + public void send(int resultCode, Bundle resultData) { final ArrayList sessions = resultData .getStringArrayList(RECEIVER_BUNDLE_EXTRA_SESSIONS); - for (String session : sessions) { pw.println(session); } latch.countDown(); } }; + return requestSessionCommon(pw, latch, () -> mService.listSessions(userId, receiver)); + } - mService.listSessions(userId, receiver); + private boolean isNextArgSessions(PrintWriter pw) { + final String type = getNextArgRequired(); + if (!type.equals("sessions")) { + pw.println("Error: invalid list type"); + return false; + } + return true; + } + + private int requestSessionCommon(PrintWriter pw, CountDownLatch latch, + Runnable command) { + command.run(); try { final boolean received = latch.await(5, TimeUnit.SECONDS); diff --git a/services/autofill/java/com/android/server/autofill/RemoteFillService.java b/services/autofill/java/com/android/server/autofill/RemoteFillService.java index c469718780c0c..04eeb324ddf63 100644 --- a/services/autofill/java/com/android/server/autofill/RemoteFillService.java +++ b/services/autofill/java/com/android/server/autofill/RemoteFillService.java @@ -33,6 +33,7 @@ import android.os.UserHandle; import android.service.autofill.AutoFillService; import android.service.autofill.FillResponse; import android.service.autofill.IAutoFillService; +import android.service.autofill.IAutoFillServiceConnection; import android.service.autofill.IFillCallback; import android.service.autofill.ISaveCallback; import android.text.format.DateUtils; @@ -57,7 +58,7 @@ final class RemoteFillService implements DeathRecipient { private static final boolean DEBUG = Helper.DEBUG; // How long after the last interaction with the service we would unbind - private static final long TIMEOUT_IDLE_BIND_MILLIS = 5 * DateUtils.MINUTE_IN_MILLIS; + private static final long TIMEOUT_IDLE_BIND_MILLIS = 5 * DateUtils.SECOND_IN_MILLIS; private final Context mContext; @@ -91,6 +92,7 @@ final class RemoteFillService implements DeathRecipient { void onSaveRequestSuccess(); void onSaveRequestFailure(CharSequence message); void onServiceDied(RemoteFillService service); + void onDisableSelf(); } public RemoteFillService(Context context, ComponentName componentName, @@ -197,6 +199,10 @@ final class RemoteFillService implements DeathRecipient { } } + private void handleDisableSelf() { + mCallbacks.onDisableSelf(); + } + private boolean isBound() { return mAutoFillService != null; } @@ -239,7 +245,7 @@ final class RemoteFillService implements DeathRecipient { // Need to double check if it's null, since it could be set on onServiceDisconnected() if (mAutoFillService != null) { try { - mAutoFillService.onDisconnected(); + mAutoFillService.onInit(null); } catch (Exception e) { Slog.w(LOG_TAG, "Exception calling onDisconnected(): " + e); } @@ -321,7 +327,12 @@ final class RemoteFillService implements DeathRecipient { // Need to double check if it's null, since it could be set on // onServiceDisconnected() if (mAutoFillService != null) { - mAutoFillService.onConnected(); + mAutoFillService.onInit(new IAutoFillServiceConnection.Stub() { + @Override + public void disableSelf() { + mHandler.obtainMessage(MyHandler.MSG_ON_DISABLE_SELF).sendToTarget(); + } + }); } } catch (RemoteException e) { Slog.w(LOG_TAG, "Exception calling onConnected(): " + e); @@ -347,6 +358,7 @@ final class RemoteFillService implements DeathRecipient { public static final int MSG_BINDER_DIED = 2; public static final int MSG_UNBIND = 3; public static final int MSG_ON_PENDING_REQUEST = 4; + public static final int MSG_ON_DISABLE_SELF = 5; public MyHandler(Context context) { // Cannot use lambda - doesn't compile @@ -374,6 +386,10 @@ final class RemoteFillService implements DeathRecipient { case MSG_ON_PENDING_REQUEST: { handlePendingRequest((PendingRequest) message.obj); } break; + + case MSG_ON_DISABLE_SELF: { + handleDisableSelf(); + } break; } } }, false);