From d4e52285ed840d438ea59104f6ee606ed3b5d8db Mon Sep 17 00:00:00 2001 From: Felipe Leme Date: Mon, 18 Jun 2018 13:56:38 -0700 Subject: [PATCH] Make IAutofillManager fully oneway. The critical methods on this interface - like updateSession() - were already void, so all we had to do were to "onewaywize" the other methods. We could either refactor them to be truly async, or implement a blocking mechanism that let them still be sync *and* oneway - because these methods are not in the critical path, we opted for the latter, which is simpler and less risky. Fixes: 73536867 Test: mmma -j ./frameworks/base/apct-tests/perftests/autofill/ && \ adb install -r $OUT/data/app/AutofillPerfTests/AutofillPerfTests.apk && \ adb shell am instrument -w -e class android.view.autofill.LoginTest \ com.android.perftests.autofill/android.support.test.runner.AndroidJUnitRunner Test: CtsAutoFillServiceTestCases Change-Id: I380430aa2a7805aed6f629afb360566fc5402abb --- .../view/autofill/AutofillManager.java | 157 ++++++++++++++++-- .../view/autofill/IAutoFillManager.aidl | 50 +++--- .../autofill/AutofillManagerService.java | 131 ++++++++++----- 3 files changed, 254 insertions(+), 84 deletions(-) diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index 8f28102016d77..0504d1ede6035 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -57,6 +57,7 @@ import android.view.accessibility.AccessibilityWindowInfo; import com.android.internal.annotations.GuardedBy; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.internal.os.IResultReceiver; import com.android.internal.util.ArrayUtils; import com.android.internal.util.Preconditions; @@ -72,6 +73,8 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; //TODO: use java.lang.ref.Cleaner once Android supports Java 9 import sun.misc.Cleaner; @@ -572,10 +575,11 @@ public final class AutofillManager { final AutofillClient client = getClient(); if (client != null) { + final SyncResultReceiver receiver = new SyncResultReceiver(); try { - final boolean sessionWasRestored = mService.restoreSession(mSessionId, - client.autofillClientGetActivityToken(), - mServiceClient.asBinder()); + mService.restoreSession(mSessionId, client.autofillClientGetActivityToken(), + mServiceClient.asBinder(), receiver); + final boolean sessionWasRestored = receiver.getIntResult() == 1; if (!sessionWasRestored) { Log.w(TAG, "Session " + mSessionId + " could not be restored"); @@ -691,7 +695,9 @@ public final class AutofillManager { */ @Nullable public FillEventHistory getFillEventHistory() { try { - return mService.getFillEventHistory(); + final SyncResultReceiver receiver = new SyncResultReceiver(); + mService.getFillEventHistory(receiver); + return receiver.getObjectResult(SyncResultReceiver.TYPE_PARCELABLE); } catch (RemoteException e) { e.rethrowFromSystemServer(); return null; @@ -1242,8 +1248,10 @@ public final class AutofillManager { public boolean hasEnabledAutofillServices() { if (mService == null) return false; + final SyncResultReceiver receiver = new SyncResultReceiver(); try { - return mService.isServiceEnabled(mContext.getUserId(), mContext.getPackageName()); + mService.isServiceEnabled(mContext.getUserId(), mContext.getPackageName(), receiver); + return receiver.getIntResult() == 1; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1257,8 +1265,10 @@ public final class AutofillManager { public ComponentName getAutofillServiceComponentName() { if (mService == null) return null; + final SyncResultReceiver receiver = new SyncResultReceiver(); try { - return mService.getAutofillServiceComponentName(); + mService.getAutofillServiceComponentName(receiver); + return receiver.getObjectResult(SyncResultReceiver.TYPE_PARCELABLE); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1281,7 +1291,9 @@ public final class AutofillManager { */ @Nullable public String getUserDataId() { try { - return mService.getUserDataId(); + final SyncResultReceiver receiver = new SyncResultReceiver(); + mService.getUserDataId(receiver); + return receiver.getObjectResult(SyncResultReceiver.TYPE_STRING); } catch (RemoteException e) { e.rethrowFromSystemServer(); return null; @@ -1301,7 +1313,9 @@ public final class AutofillManager { */ @Nullable public UserData getUserData() { try { - return mService.getUserData(); + final SyncResultReceiver receiver = new SyncResultReceiver(); + mService.getUserData(receiver); + return receiver.getObjectResult(SyncResultReceiver.TYPE_PARCELABLE); } catch (RemoteException e) { e.rethrowFromSystemServer(); return null; @@ -1337,8 +1351,10 @@ public final class AutofillManager { * the user. */ public boolean isFieldClassificationEnabled() { + final SyncResultReceiver receiver = new SyncResultReceiver(); try { - return mService.isFieldClassificationEnabled(); + mService.isFieldClassificationEnabled(receiver); + return receiver.getIntResult() == 1; } catch (RemoteException e) { e.rethrowFromSystemServer(); return false; @@ -1358,8 +1374,10 @@ public final class AutofillManager { */ @Nullable public String getDefaultFieldClassificationAlgorithm() { + final SyncResultReceiver receiver = new SyncResultReceiver(); try { - return mService.getDefaultFieldClassificationAlgorithm(); + mService.getDefaultFieldClassificationAlgorithm(receiver); + return receiver.getObjectResult(SyncResultReceiver.TYPE_STRING); } catch (RemoteException e) { e.rethrowFromSystemServer(); return null; @@ -1376,9 +1394,10 @@ public final class AutofillManager { */ @NonNull public List getAvailableFieldClassificationAlgorithms() { - final String[] algorithms; + final SyncResultReceiver receiver = new SyncResultReceiver(); try { - algorithms = mService.getAvailableFieldClassificationAlgorithms(); + mService.getAvailableFieldClassificationAlgorithms(receiver); + final String[] algorithms = receiver.getObjectResult(SyncResultReceiver.TYPE_STRING); return algorithms != null ? Arrays.asList(algorithms) : Collections.emptyList(); } catch (RemoteException e) { e.rethrowFromSystemServer(); @@ -1399,8 +1418,10 @@ public final class AutofillManager { public boolean isAutofillSupported() { if (mService == null) return false; + final SyncResultReceiver receiver = new SyncResultReceiver(); try { - return mService.isServiceSupported(mContext.getUserId()); + mService.isServiceSupported(mContext.getUserId(), receiver); + return receiver.getIntResult() == 1; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1521,10 +1542,12 @@ public final class AutofillManager { final AutofillClient client = getClient(); if (client == null) return; // NOTE: getClient() already logged it.. - mSessionId = mService.startSession(client.autofillClientGetActivityToken(), + final SyncResultReceiver receiver = new SyncResultReceiver(); + mService.startSession(client.autofillClientGetActivityToken(), mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(), mCallback != null, flags, client.autofillClientGetComponentName(), - isCompatibilityModeEnabledLocked()); + isCompatibilityModeEnabledLocked(), receiver); + mSessionId = receiver.getIntResult(); if (mSessionId != NO_SESSION) { mState = STATE_ACTIVE; } @@ -1602,7 +1625,9 @@ public final class AutofillManager { mServiceClient = new AutofillManagerClient(this); try { final int userId = mContext.getUserId(); - final int flags = mService.addClient(mServiceClient, userId); + final SyncResultReceiver receiver = new SyncResultReceiver(); + mService.addClient(mServiceClient, userId, receiver); + final int flags = receiver.getIntResult(); mEnabled = (flags & FLAG_ADD_CLIENT_ENABLED) != 0; sDebug = (flags & FLAG_ADD_CLIENT_DEBUG) != 0; sVerbose = (flags & FLAG_ADD_CLIENT_VERBOSE) != 0; @@ -2818,4 +2843,104 @@ public final class AutofillManager { } } } + + /** + * @hide + */ + public static final class SyncResultReceiver extends IResultReceiver.Stub { + + private static final String EXTRA = "EXTRA"; + + /** + * How long to block waiting for {@link IResultReceiver} callbacks when calling server. + */ + private static final long BINDER_TIMEOUT_MS = 5000; + + private static final int TYPE_STRING = 0; + private static final int TYPE_STRING_ARRAY = 1; + private static final int TYPE_PARCELABLE = 2; + + private final CountDownLatch mLatch = new CountDownLatch(1); + private int mResult; + private Bundle mBundle; + + private void waitResult() { + try { + if (!mLatch.await(BINDER_TIMEOUT_MS, TimeUnit.MILLISECONDS)) { + throw new IllegalStateException("Not called in " + BINDER_TIMEOUT_MS + "ms"); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + + /** + * Gets the result from an operation that returns an {@code int}. + */ + int getIntResult() { + waitResult(); + return mResult; + } + + /** + * Gets the result from an operation that returns an {@code Object}. + * + * @param type type of expected object. + */ + @Nullable + @SuppressWarnings("unchecked") + T getObjectResult(int type) { + waitResult(); + if (mBundle == null) { + return null; + } + switch (type) { + case TYPE_STRING: + return (T) mBundle.getString(EXTRA); + case TYPE_STRING_ARRAY: + return (T) mBundle.getString(EXTRA); + case TYPE_PARCELABLE: + return (T) mBundle.getParcelable(EXTRA); + default: + throw new IllegalArgumentException("unsupported type: " + type); + } + } + + @Override + public void send(int resultCode, Bundle resultData) { + mResult = resultCode; + mBundle = resultData; + mLatch.countDown(); + } + + /** + * Creates a bundle for a {@code String} value. + */ + @NonNull + public static Bundle bundleFor(@Nullable String value) { + final Bundle bundle = new Bundle(); + bundle.putString(EXTRA, value); + return bundle; + } + + /** + * Creates a bundle for a {@code String[]} value. + */ + @NonNull + public static Bundle bundleFor(@Nullable String[] value) { + final Bundle bundle = new Bundle(); + bundle.putStringArray(EXTRA, value); + return bundle; + } + + /** + * Creates a bundle for a {@code Parcelable} value. + */ + @NonNull + public static Bundle bundleFor(@Nullable Parcelable value) { + final Bundle bundle = new Bundle(); + bundle.putParcelable(EXTRA, value); + return bundle; + } + } } diff --git a/core/java/android/view/autofill/IAutoFillManager.aidl b/core/java/android/view/autofill/IAutoFillManager.aidl index 6b26f233f58c5..26aeba5cfdfa1 100644 --- a/core/java/android/view/autofill/IAutoFillManager.aidl +++ b/core/java/android/view/autofill/IAutoFillManager.aidl @@ -28,41 +28,39 @@ import android.service.autofill.UserData; import android.view.autofill.AutofillId; import android.view.autofill.AutofillValue; import android.view.autofill.IAutoFillManagerClient; +import com.android.internal.os.IResultReceiver; /** * Mediator between apps being auto-filled and auto-fill service implementations. * * {@hide} */ - // TODO(b/73536867) STOPSHIP : this whole interface should be either oneway or not, and we're - // gradually converting the methods (as some of them return a value form the server and must be - // refactored). -interface IAutoFillManager { +oneway interface IAutoFillManager { // Returns flags: FLAG_ADD_CLIENT_ENABLED | FLAG_ADD_CLIENT_DEBUG | FLAG_ADD_CLIENT_VERBOSE - int addClient(in IAutoFillManagerClient client, int userId); + void addClient(in IAutoFillManagerClient client, int userId, in IResultReceiver result); void removeClient(in IAutoFillManagerClient client, int userId); - int startSession(IBinder activityToken, in IBinder appCallback, in AutofillId autoFillId, - in Rect bounds, in AutofillValue value, int userId, boolean hasCallback, int flags, - in ComponentName componentName, boolean compatMode); - FillEventHistory getFillEventHistory(); - boolean restoreSession(int sessionId, in IBinder activityToken, in IBinder appCallback); - oneway void updateSession(int sessionId, in AutofillId id, in Rect bounds, - in AutofillValue value, int action, int flags, int userId); - oneway void setAutofillFailure(int sessionId, in List ids, int userId); - oneway void finishSession(int sessionId, int userId); - oneway void cancelSession(int sessionId, int userId); - oneway void setAuthenticationResult(in Bundle data, int sessionId, int authenticationId, - int userId); - oneway void setHasCallback(int sessionId, int userId, boolean hasIt); + void startSession(IBinder activityToken, in IBinder appCallback, in AutofillId autoFillId, + in Rect bounds, in AutofillValue value, int userId, boolean hasCallback, int flags, + in ComponentName componentName, boolean compatMode, in IResultReceiver result); + void getFillEventHistory(in IResultReceiver result); + void restoreSession(int sessionId, in IBinder activityToken, in IBinder appCallback, + in IResultReceiver result); + void updateSession(int sessionId, in AutofillId id, in Rect bounds, + in AutofillValue value, int action, int flags, int userId); + void setAutofillFailure(int sessionId, in List ids, int userId); + void finishSession(int sessionId, int userId); + void cancelSession(int sessionId, int userId); + void setAuthenticationResult(in Bundle data, int sessionId, int authenticationId, int userId); + void setHasCallback(int sessionId, int userId, boolean hasIt); void disableOwnedAutofillServices(int userId); - boolean isServiceSupported(int userId); - boolean isServiceEnabled(int userId, String packageName); + void isServiceSupported(int userId, in IResultReceiver result); + void isServiceEnabled(int userId, String packageName, in IResultReceiver result); void onPendingSaveUi(int operation, IBinder token); - UserData getUserData(); - String getUserDataId(); + void getUserData(in IResultReceiver result); + void getUserDataId(in IResultReceiver result); void setUserData(in UserData userData); - boolean isFieldClassificationEnabled(); - ComponentName getAutofillServiceComponentName(); - String[] getAvailableFieldClassificationAlgorithms(); - String getDefaultFieldClassificationAlgorithm(); + void isFieldClassificationEnabled(in IResultReceiver result); + void getAutofillServiceComponentName(in IResultReceiver result); + void getAvailableFieldClassificationAlgorithms(in IResultReceiver result); + void getDefaultFieldClassificationAlgorithm(in IResultReceiver result); } diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java index 41e9d2bf49dec..021fdcd84971d 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java @@ -46,6 +46,7 @@ import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; +import android.os.Parcelable; import android.os.RemoteCallback; import android.os.RemoteException; import android.os.ResultReceiver; @@ -622,6 +623,38 @@ public final class AutofillManagerService extends SystemService { return getWhitelistedCompatModePackages(getWhitelistedCompatModePackagesFromSettings()); } + private void send(@NonNull IResultReceiver receiver, int value) { + try { + receiver.send(value, null); + } catch (RemoteException e) { + Slog.w(TAG, "Error async reporting result to client: " + e); + } + } + + private void send(@NonNull IResultReceiver receiver, @NonNull Bundle value) { + try { + receiver.send(0, value); + } catch (RemoteException e) { + Slog.w(TAG, "Error async reporting result to client: " + e); + } + } + + private void send(@NonNull IResultReceiver receiver, @Nullable String value) { + send(receiver, AutofillManager.SyncResultReceiver.bundleFor(value)); + } + + private void send(@NonNull IResultReceiver receiver, @Nullable String[] value) { + send(receiver, AutofillManager.SyncResultReceiver.bundleFor(value)); + } + + private void send(@NonNull IResultReceiver receiver, @Nullable Parcelable value) { + send(receiver, AutofillManager.SyncResultReceiver.bundleFor(value)); + } + + private void send(@NonNull IResultReceiver receiver, boolean value) { + send(receiver, value ? 1 : 0); + } + @Nullable @VisibleForTesting static Map getWhitelistedCompatModePackages(String setting) { @@ -827,9 +860,10 @@ public final class AutofillManagerService extends SystemService { final class AutoFillManagerServiceStub extends IAutoFillManager.Stub { @Override - public int addClient(IAutoFillManagerClient client, int userId) { + public void addClient(IAutoFillManagerClient client, int userId, + @NonNull IResultReceiver receiver) { + int flags = 0; synchronized (mLock) { - int flags = 0; if (getServiceForUserLocked(userId).addClientLocked(client)) { flags |= AutofillManager.FLAG_ADD_CLIENT_ENABLED; } @@ -839,8 +873,8 @@ public final class AutofillManagerService extends SystemService { if (sVerbose) { flags |= AutofillManager.FLAG_ADD_CLIENT_VERBOSE; } - return flags; } + send(receiver, flags); } @Override @@ -874,9 +908,9 @@ public final class AutofillManagerService extends SystemService { } @Override - public int startSession(IBinder activityToken, IBinder appCallback, AutofillId autofillId, + public void startSession(IBinder activityToken, IBinder appCallback, AutofillId autofillId, Rect bounds, AutofillValue value, int userId, boolean hasCallback, int flags, - ComponentName componentName, boolean compatMode) { + ComponentName componentName, boolean compatMode, IResultReceiver receiver) { activityToken = Preconditions.checkNotNull(activityToken, "activityToken"); appCallback = Preconditions.checkNotNull(appCallback, "appCallback"); @@ -892,61 +926,63 @@ public final class AutofillManagerService extends SystemService { throw new IllegalArgumentException(packageName + " is not a valid package", e); } + final int sessionId; synchronized (mLock) { final AutofillManagerServiceImpl service = getServiceForUserLocked(userId); - return service.startSessionLocked(activityToken, getCallingUid(), appCallback, + sessionId = service.startSessionLocked(activityToken, getCallingUid(), appCallback, autofillId, bounds, value, hasCallback, componentName, compatMode, mAllowInstantService, flags); } + send(receiver, sessionId); } @Override - public FillEventHistory getFillEventHistory() throws RemoteException { + public void getFillEventHistory(@NonNull IResultReceiver receiver) throws RemoteException { final int userId = UserHandle.getCallingUserId(); + FillEventHistory fillEventHistory = null; synchronized (mLock) { final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId); if (service != null) { - return service.getFillEventHistory(getCallingUid()); + fillEventHistory = service.getFillEventHistory(getCallingUid()); } else if (sVerbose) { Slog.v(TAG, "getFillEventHistory(): no service for " + userId); } } - - return null; + send(receiver, fillEventHistory); } @Override - public UserData getUserData() throws RemoteException { + public void getUserData(@NonNull IResultReceiver receiver) throws RemoteException { final int userId = UserHandle.getCallingUserId(); + UserData userData = null; synchronized (mLock) { final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId); if (service != null) { - return service.getUserData(getCallingUid()); + userData = service.getUserData(getCallingUid()); } else if (sVerbose) { Slog.v(TAG, "getUserData(): no service for " + userId); } } - - return null; + send(receiver, userData); } @Override - public String getUserDataId() throws RemoteException { + public void getUserDataId(@NonNull IResultReceiver receiver) throws RemoteException { final int userId = UserHandle.getCallingUserId(); + UserData userData = null; synchronized (mLock) { final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId); if (service != null) { - final UserData userData = service.getUserData(getCallingUid()); - return userData == null ? null : userData.getId(); + userData = service.getUserData(getCallingUid()); } else if (sVerbose) { Slog.v(TAG, "getUserDataId(): no service for " + userId); } } - - return null; + final String userDataId = userData == null ? null : userData.getId(); + send(receiver, userDataId); } @Override @@ -964,89 +1000,96 @@ public final class AutofillManagerService extends SystemService { } @Override - public boolean isFieldClassificationEnabled() throws RemoteException { + public void isFieldClassificationEnabled(@NonNull IResultReceiver receiver) + throws RemoteException { final int userId = UserHandle.getCallingUserId(); + boolean enabled = false; synchronized (mLock) { final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId); if (service != null) { - return service.isFieldClassificationEnabled(getCallingUid()); + enabled = service.isFieldClassificationEnabled(getCallingUid()); } else if (sVerbose) { Slog.v(TAG, "isFieldClassificationEnabled(): no service for " + userId); } } - - return false; + send(receiver, enabled); } @Override - public String getDefaultFieldClassificationAlgorithm() throws RemoteException { + public void getDefaultFieldClassificationAlgorithm(@NonNull IResultReceiver receiver) + throws RemoteException { final int userId = UserHandle.getCallingUserId(); + String algorithm = null; synchronized (mLock) { final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId); if (service != null) { - return service.getDefaultFieldClassificationAlgorithm(getCallingUid()); + algorithm = service.getDefaultFieldClassificationAlgorithm(getCallingUid()); } else { if (sVerbose) { Slog.v(TAG, "getDefaultFcAlgorithm(): no service for " + userId); } - return null; } } + send(receiver, algorithm); } @Override - public String[] getAvailableFieldClassificationAlgorithms() throws RemoteException { + public void getAvailableFieldClassificationAlgorithms(@NonNull IResultReceiver receiver) + throws RemoteException { final int userId = UserHandle.getCallingUserId(); + String[] algorithms = null; synchronized (mLock) { final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId); if (service != null) { - return service.getAvailableFieldClassificationAlgorithms(getCallingUid()); + algorithms = service.getAvailableFieldClassificationAlgorithms(getCallingUid()); } else { if (sVerbose) { Slog.v(TAG, "getAvailableFcAlgorithms(): no service for " + userId); } - return null; } } + send(receiver, algorithms); } @Override - public ComponentName getAutofillServiceComponentName() throws RemoteException { + public void getAutofillServiceComponentName(@NonNull IResultReceiver receiver) + throws RemoteException { final int userId = UserHandle.getCallingUserId(); + ComponentName componentName = null; synchronized (mLock) { final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId); if (service != null) { - return service.getServiceComponentName(); + componentName = service.getServiceComponentName(); } else if (sVerbose) { Slog.v(TAG, "getAutofillServiceComponentName(): no service for " + userId); } } - - return null; + send(receiver, componentName); } @Override - public boolean restoreSession(int sessionId, IBinder activityToken, IBinder appCallback) + public void restoreSession(int sessionId, @NonNull IBinder activityToken, + @NonNull IBinder appCallback, @NonNull IResultReceiver receiver) throws RemoteException { final int userId = UserHandle.getCallingUserId(); activityToken = Preconditions.checkNotNull(activityToken, "activityToken"); appCallback = Preconditions.checkNotNull(appCallback, "appCallback"); + boolean restored = false; synchronized (mLock) { final AutofillManagerServiceImpl service = mServicesCache.get(userId); if (service != null) { - return service.restoreSession(sessionId, getCallingUid(), activityToken, + restored = service.restoreSession(sessionId, getCallingUid(), activityToken, appCallback); } else if (sVerbose) { Slog.v(TAG, "restoreSession(): no service for " + userId); } } - - return false; + send(receiver, restored); } @Override @@ -1112,23 +1155,27 @@ public final class AutofillManagerService extends SystemService { } @Override - public boolean isServiceSupported(int userId) { + public void isServiceSupported(int userId, @NonNull IResultReceiver receiver) { + boolean supported = false; synchronized (mLock) { - return !mDisabledUsers.get(userId); + supported = !mDisabledUsers.get(userId); } + send(receiver, supported); } @Override - public boolean isServiceEnabled(int userId, String packageName) { + public void isServiceEnabled(int userId, @NonNull String packageName, + @NonNull IResultReceiver receiver) { + boolean enabled = false; synchronized (mLock) { final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId); if (service != null) { - return Objects.equals(packageName, service.getServicePackageName()); + enabled = Objects.equals(packageName, service.getServicePackageName()); } else if (sVerbose) { Slog.v(TAG, "isServiceEnabled(): no service for " + userId); } - return false; } + send(receiver, enabled); } @Override