diff options
7 files changed, 492 insertions, 8 deletions
diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 66943031aeb5..d231a5a3f175 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -10417,6 +10417,8 @@ package android.service.attention { method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent); method public abstract void onCancelAttentionCheck(@NonNull android.service.attention.AttentionService.AttentionCallback); method public abstract void onCheckAttention(@NonNull android.service.attention.AttentionService.AttentionCallback); + method public void onStartProximityUpdates(@NonNull android.service.attention.AttentionService.ProximityCallback); + method public void onStopProximityUpdates(); field public static final int ATTENTION_FAILURE_CAMERA_PERMISSION_ABSENT = 6; // 0x6 field public static final int ATTENTION_FAILURE_CANCELLED = 3; // 0x3 field public static final int ATTENTION_FAILURE_PREEMPTED = 4; // 0x4 @@ -10424,6 +10426,7 @@ package android.service.attention { field public static final int ATTENTION_FAILURE_UNKNOWN = 2; // 0x2 field public static final int ATTENTION_SUCCESS_ABSENT = 0; // 0x0 field public static final int ATTENTION_SUCCESS_PRESENT = 1; // 0x1 + field public static final double PROXIMITY_UNKNOWN = -1.0; field public static final String SERVICE_INTERFACE = "android.service.attention.AttentionService"; } @@ -10432,6 +10435,10 @@ package android.service.attention { method public void onSuccess(int, long); } + public static final class AttentionService.ProximityCallback { + method public void onProximityUpdate(double); + } + } package android.service.autofill { diff --git a/core/java/android/attention/AttentionManagerInternal.java b/core/java/android/attention/AttentionManagerInternal.java index 941e9e2ecce5..4e00da1b8c10 100644 --- a/core/java/android/attention/AttentionManagerInternal.java +++ b/core/java/android/attention/AttentionManagerInternal.java @@ -46,6 +46,25 @@ public abstract class AttentionManagerInternal { */ public abstract void cancelAttentionCheck(AttentionCallbackInternal callback); + /** + * Requests the continuous updates of proximity signal via the provided callback, + * until the given callback is unregistered. Currently, AttentionManagerService only + * anticipates one client and updates one client at a time. If a new client wants to + * onboard to receiving Proximity updates, please make a feature request to make proximity + * feature multi-client before depending on this feature. + * + * @param callback a callback that receives the proximity updates + * @return {@code true} if the registration should succeed. + */ + public abstract boolean onStartProximityUpdates(ProximityCallbackInternal callback); + + /** + * Requests to stop providing continuous updates until the callback is registered. + * + * @param callback a callback that was used in {@link #onStartProximityUpdates} + */ + public abstract void onStopProximityUpdates(ProximityCallbackInternal callback); + /** Internal interface for attention callback. */ public abstract static class AttentionCallbackInternal { /** @@ -64,4 +83,13 @@ public abstract class AttentionManagerInternal { */ public abstract void onFailure(int error); } + + /** Internal interface for proximity callback. */ + public abstract static class ProximityCallbackInternal { + /** + * @param distance the estimated distance of the user (in meter) + * The distance will be PROXIMITY_UNKNOWN if the proximity sensing was inconclusive. + */ + public abstract void onProximityUpdate(double distance); + } } diff --git a/core/java/android/service/attention/AttentionService.java b/core/java/android/service/attention/AttentionService.java index 49ab5db74b87..f5c59b597c3a 100644 --- a/core/java/android/service/attention/AttentionService.java +++ b/core/java/android/service/attention/AttentionService.java @@ -24,11 +24,14 @@ import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.os.RemoteException; +import android.util.Slog; import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.lang.ref.WeakReference; +import java.util.Objects; /** @@ -51,6 +54,7 @@ import java.lang.annotation.RetentionPolicy; */ @SystemApi public abstract class AttentionService extends Service { + private static final String LOG_TAG = "AttentionService"; /** * The {@link Intent} that must be declared as handled by the service. To be supported, the * service must also require the {@link android.Manifest.permission#BIND_ATTENTION_SERVICE} @@ -80,6 +84,9 @@ public abstract class AttentionService extends Service { /** Camera permission is not granted. */ public static final int ATTENTION_FAILURE_CAMERA_PERMISSION_ABSENT = 6; + /** Users’ proximity is unknown (proximity sensing was inconclusive and is unsupported). */ + public static final double PROXIMITY_UNKNOWN = -1; + /** * Result codes for when attention check was successful. * @@ -118,6 +125,20 @@ public abstract class AttentionService extends Service { Preconditions.checkNotNull(callback); AttentionService.this.onCancelAttentionCheck(new AttentionCallback(callback)); } + + /** {@inheritDoc} */ + @Override + public void onStartProximityUpdates(IProximityCallback callback) { + Objects.requireNonNull(callback); + AttentionService.this.onStartProximityUpdates(new ProximityCallback(callback)); + + } + + /** {@inheritDoc} */ + @Override + public void onStopProximityUpdates() { + AttentionService.this.onStopProximityUpdates(); + } }; @Nullable @@ -143,6 +164,23 @@ public abstract class AttentionService extends Service { */ public abstract void onCancelAttentionCheck(@NonNull AttentionCallback callback); + /** + * Requests the continuous updates of proximity signal via the provided callback, + * until the given callback is unregistered. + * + * @param callback the callback to return the result to + */ + public void onStartProximityUpdates(@NonNull ProximityCallback callback) { + Slog.w(LOG_TAG, "Override this method."); + } + + /** + * Requests to stop providing continuous updates until the callback is registered. + */ + public void onStopProximityUpdates() { + Slog.w(LOG_TAG, "Override this method."); + } + /** Callbacks for AttentionService results. */ public static final class AttentionCallback { @NonNull private final IAttentionCallback mCallback; @@ -174,4 +212,26 @@ public abstract class AttentionService extends Service { } } } + + /** Callbacks for ProximityCallback results. */ + public static final class ProximityCallback { + @NonNull private final WeakReference<IProximityCallback> mCallback; + + private ProximityCallback(@NonNull IProximityCallback callback) { + mCallback = new WeakReference<>(callback); + } + + /** + * @param distance the estimated distance of the user (in meter) + * The distance will be PROXIMITY_UNKNOWN if the proximity sensing was inconclusive. + * + */ + public void onProximityUpdate(double distance) { + try { + mCallback.get().onProximityUpdate(distance); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + } } diff --git a/core/java/android/service/attention/IAttentionService.aidl b/core/java/android/service/attention/IAttentionService.aidl index 99e79973cfd2..8bb881ba1708 100644 --- a/core/java/android/service/attention/IAttentionService.aidl +++ b/core/java/android/service/attention/IAttentionService.aidl @@ -17,6 +17,7 @@ package android.service.attention; import android.service.attention.IAttentionCallback; +import android.service.attention.IProximityCallback; /** * Interface for a concrete implementation to provide to the AttentionManagerService. @@ -26,4 +27,6 @@ import android.service.attention.IAttentionCallback; oneway interface IAttentionService { void checkAttention(IAttentionCallback callback); void cancelAttentionCheck(IAttentionCallback callback); + void onStartProximityUpdates(IProximityCallback callback); + void onStopProximityUpdates(); }
\ No newline at end of file diff --git a/core/java/android/service/attention/IProximityCallback.aidl b/core/java/android/service/attention/IProximityCallback.aidl new file mode 100644 index 000000000000..9ecf9bc28e84 --- /dev/null +++ b/core/java/android/service/attention/IProximityCallback.aidl @@ -0,0 +1,10 @@ +package android.service.attention; + +/** + * Callback for onStartProximityUpdates request. + * + * @hide + */ +oneway interface IProximityCallback { + void onProximityUpdate(double distance); +} diff --git a/services/core/java/com/android/server/attention/AttentionManagerService.java b/services/core/java/com/android/server/attention/AttentionManagerService.java index a1395890fd3c..d2fa386ad6ba 100644 --- a/services/core/java/com/android/server/attention/AttentionManagerService.java +++ b/services/core/java/com/android/server/attention/AttentionManagerService.java @@ -22,6 +22,7 @@ import static android.content.Context.BIND_INCLUDE_CAPABILITIES; import static android.provider.DeviceConfig.NAMESPACE_ATTENTION_MANAGER_SERVICE; import static android.service.attention.AttentionService.ATTENTION_FAILURE_CANCELLED; import static android.service.attention.AttentionService.ATTENTION_FAILURE_UNKNOWN; +import static android.service.attention.AttentionService.PROXIMITY_UNKNOWN; import android.Manifest; import android.annotation.NonNull; @@ -29,6 +30,7 @@ import android.annotation.Nullable; import android.app.ActivityThread; import android.attention.AttentionManagerInternal; import android.attention.AttentionManagerInternal.AttentionCallbackInternal; +import android.attention.AttentionManagerInternal.ProximityCallbackInternal; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; @@ -57,6 +59,7 @@ import android.service.attention.AttentionService.AttentionFailureCodes; import android.service.attention.AttentionService.AttentionSuccessCodes; import android.service.attention.IAttentionCallback; import android.service.attention.IAttentionService; +import android.service.attention.IProximityCallback; import android.text.TextUtils; import android.util.Slog; @@ -134,6 +137,15 @@ public class AttentionManagerService extends SystemService { @GuardedBy("mLock") AttentionCheck mCurrentAttentionCheck; + /** + * A proxy for relaying proximity information between the Attention Service and the client. + * The proxy will be initialized when the client calls onStartProximityUpdates and will be + * disabled only when the client calls onStopProximityUpdates. + */ + @VisibleForTesting + @GuardedBy("mLock") + ProximityUpdate mCurrentProximityUpdate; + public AttentionManagerService(Context context) { this(context, (PowerManager) context.getSystemService(Context.POWER_SERVICE), new Object(), null); @@ -315,6 +327,77 @@ public class AttentionManagerService extends SystemService { } } + /** + * Requests the continuous updates of proximity signal via the provided callback, + * until the given callback is stopped. + * + * Calling this multiple times for duplicate requests will be no-ops, returning true. + * + * @return {@code true} if the framework was able to dispatch the request + */ + @VisibleForTesting + boolean onStartProximityUpdates(ProximityCallbackInternal callbackInternal) { + Objects.requireNonNull(callbackInternal); + if (!mIsServiceEnabled) { + Slog.w(LOG_TAG, "Trying to call onProximityUpdate() on an unsupported device."); + return false; + } + + if (!isServiceAvailable()) { + Slog.w(LOG_TAG, "Service is not available at this moment."); + return false; + } + + // don't allow proximity request in screen off state. + // This behavior might change in the future. + if (!mPowerManager.isInteractive()) { + Slog.w(LOG_TAG, "Proximity Service is unavailable during screen off at this moment."); + return false; + } + + synchronized (mLock) { + // schedule shutting down the connection if no one resets this timer + freeIfInactiveLocked(); + + // lazily start the service, which should be very lightweight to start + bindLocked(); + + /* + Prevent spamming with multiple requests, only one at a time is allowed. + If there are use-cases for keeping track of multiple requests, we + can refactor ProximityUpdate object to keep track of multiple internal callbacks. + */ + if (mCurrentProximityUpdate != null && mCurrentProximityUpdate.mStartedUpdates) { + if (mCurrentProximityUpdate.mCallbackInternal == callbackInternal) { + Slog.w(LOG_TAG, "Provided callback is already registered. Skipping."); + return true; + } else { + // reject the new request since the old request is still alive. + Slog.w(LOG_TAG, "New proximity update cannot be processed because there is " + + "already an ongoing update"); + return false; + } + } + mCurrentProximityUpdate = new ProximityUpdate(callbackInternal); + return mCurrentProximityUpdate.startUpdates(); + } + } + + /** Cancels the specified proximity registration. */ + @VisibleForTesting + void onStopProximityUpdates(ProximityCallbackInternal callbackInternal) { + synchronized (mLock) { + if (mCurrentProximityUpdate == null + || !mCurrentProximityUpdate.mCallbackInternal.equals(callbackInternal) + || !mCurrentProximityUpdate.mStartedUpdates) { + Slog.w(LOG_TAG, "Cannot stop a non-current callback"); + return; + } + mCurrentProximityUpdate.cancelUpdates(); + mCurrentProximityUpdate = null; + } + } + @GuardedBy("mLock") @VisibleForTesting protected void freeIfInactiveLocked() { @@ -390,15 +473,18 @@ public class AttentionManagerService extends SystemService { ipw.println("Class=" + mComponentName.getClassName()); ipw.decreaseIndent(); } - ipw.println("binding=" + mBinding); - ipw.println("current attention check:"); synchronized (mLock) { + ipw.println("binding=" + mBinding); + ipw.println("current attention check:"); if (mCurrentAttentionCheck != null) { mCurrentAttentionCheck.dump(ipw); } if (mAttentionCheckCacheBuffer != null) { mAttentionCheckCacheBuffer.dump(ipw); } + if (mCurrentProximityUpdate != null) { + mCurrentProximityUpdate.dump(ipw); + } } } @@ -417,6 +503,17 @@ public class AttentionManagerService extends SystemService { public void cancelAttentionCheck(AttentionCallbackInternal callbackInternal) { AttentionManagerService.this.cancelAttentionCheck(callbackInternal); } + + @Override + public boolean onStartProximityUpdates( + ProximityCallbackInternal callback) { + return AttentionManagerService.this.onStartProximityUpdates(callback); + } + + @Override + public void onStopProximityUpdates(ProximityCallbackInternal callback) { + AttentionManagerService.this.onStopProximityUpdates(callback); + } } @VisibleForTesting @@ -536,6 +633,71 @@ public class AttentionManagerService extends SystemService { } } + @VisibleForTesting + final class ProximityUpdate { + private final ProximityCallbackInternal mCallbackInternal; + private final IProximityCallback mIProximityCallback; + private boolean mStartedUpdates; + + ProximityUpdate(ProximityCallbackInternal callbackInternal) { + mCallbackInternal = callbackInternal; + mIProximityCallback = new IProximityCallback.Stub() { + @Override + public void onProximityUpdate(double distance) { + synchronized (mLock) { + mCallbackInternal.onProximityUpdate(distance); + freeIfInactiveLocked(); + } + } + }; + } + + boolean startUpdates() { + synchronized (mLock) { + if (mStartedUpdates) { + Slog.w(LOG_TAG, "Already registered to a proximity service."); + return false; + } + if (mService == null) { + Slog.w(LOG_TAG, + "There is no service bound. Proximity update request rejected."); + return false; + } + try { + mService.onStartProximityUpdates(mIProximityCallback); + mStartedUpdates = true; + } catch (RemoteException e) { + Slog.e(LOG_TAG, "Cannot call into the AttentionService", e); + return false; + } + } + return true; + } + + void cancelUpdates() { + synchronized (mLock) { + if (mStartedUpdates) { + if (mService == null) { + mStartedUpdates = false; + return; + } + try { + mService.onStopProximityUpdates(); + mStartedUpdates = false; + } catch (RemoteException e) { + Slog.e(LOG_TAG, "Cannot call into the AttentionService", e); + } + } + } + } + + void dump(IndentingPrintWriter ipw) { + ipw.increaseIndent(); + ipw.println("is StartedUpdates=" + mStartedUpdates); + ipw.decreaseIndent(); + } + } + private void appendResultToAttentionCacheBuffer(AttentionCheckCache cache) { synchronized (mLock) { if (mAttentionCheckCacheBuffer == null) { @@ -593,6 +755,18 @@ public class AttentionManagerService extends SystemService { mCurrentAttentionCheck.mCallbackInternal.onFailure(ATTENTION_FAILURE_UNKNOWN); } } + if (mCurrentProximityUpdate != null && mCurrentProximityUpdate.mStartedUpdates) { + if (mService != null) { + try { + mService.onStartProximityUpdates(mCurrentProximityUpdate.mIProximityCallback); + } catch (RemoteException e) { + Slog.e(LOG_TAG, "Cannot call into the AttentionService", e); + } + } else { + mCurrentProximityUpdate.cancelUpdates(); + mCurrentProximityUpdate = null; + } + } } @VisibleForTesting @@ -609,7 +783,9 @@ public class AttentionManagerService extends SystemService { switch (msg.what) { // Do not occupy resources when not in use - unbind proactively. case CHECK_CONNECTION_EXPIRATION: { - cancelAndUnbindLocked(); + synchronized (mLock) { + cancelAndUnbindLocked(); + } } break; @@ -653,10 +829,15 @@ public class AttentionManagerService extends SystemService { @GuardedBy("mLock") private void cancelAndUnbindLocked() { synchronized (mLock) { - if (mCurrentAttentionCheck == null) { + if (mCurrentAttentionCheck == null && mCurrentProximityUpdate == null) { return; } - cancel(); + if (mCurrentAttentionCheck != null) { + cancel(); + } + if (mCurrentProximityUpdate != null) { + mCurrentProximityUpdate.cancelUpdates(); + } if (mService == null) { return; } @@ -702,7 +883,9 @@ public class AttentionManagerService extends SystemService { @Override public void onReceive(Context context, Intent intent) { if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) { - cancelAndUnbindLocked(); + synchronized (mLock) { + cancelAndUnbindLocked(); + } } } } @@ -730,8 +913,27 @@ public class AttentionManagerService extends SystemService { } } + class TestableProximityCallbackInternal extends ProximityCallbackInternal { + private double mLastCallbackCode = PROXIMITY_UNKNOWN; + + @Override + public void onProximityUpdate(double distance) { + mLastCallbackCode = distance; + } + + public void reset() { + mLastCallbackCode = PROXIMITY_UNKNOWN; + } + + public double getLastCallbackCode() { + return mLastCallbackCode; + } + } + final TestableAttentionCallbackInternal mTestableAttentionCallback = new TestableAttentionCallbackInternal(); + final TestableProximityCallbackInternal mTestableProximityCallback = + new TestableProximityCallbackInternal(); @Override public int onCommand(@Nullable final String cmd) { @@ -749,6 +951,10 @@ public class AttentionManagerService extends SystemService { return cmdCallCheckAttention(); case "cancelCheckAttention": return cmdCallCancelAttention(); + case "onStartProximityUpdates": + return cmdCallOnStartProximityUpdates(); + case "onStopProximityUpdates": + return cmdCallOnStopProximityUpdates(); default: throw new IllegalArgumentException("Invalid argument"); } @@ -758,6 +964,8 @@ public class AttentionManagerService extends SystemService { return cmdClearTestableAttentionService(); case "getLastTestCallbackCode": return cmdGetLastTestCallbackCode(); + case "getLastTestProximityCallbackCode": + return cmdGetLastTestProximityCallbackCode(); default: return handleDefaultCommands(cmd); } @@ -782,6 +990,7 @@ public class AttentionManagerService extends SystemService { private int cmdClearTestableAttentionService() { sTestAttentionServicePackage = ""; mTestableAttentionCallback.reset(); + mTestableProximityCallback.reset(); resetStates(); return 0; } @@ -800,6 +1009,20 @@ public class AttentionManagerService extends SystemService { return 0; } + private int cmdCallOnStartProximityUpdates() { + final PrintWriter out = getOutPrintWriter(); + boolean calledSuccessfully = onStartProximityUpdates(mTestableProximityCallback); + out.println(calledSuccessfully ? "true" : "false"); + return 0; + } + + private int cmdCallOnStopProximityUpdates() { + final PrintWriter out = getOutPrintWriter(); + onStopProximityUpdates(mTestableProximityCallback); + out.println("true"); + return 0; + } + private int cmdResolveAttentionServiceComponent() { final PrintWriter out = getOutPrintWriter(); ComponentName resolvedComponent = resolveAttentionService(mContext); @@ -813,7 +1036,16 @@ public class AttentionManagerService extends SystemService { return 0; } + private int cmdGetLastTestProximityCallbackCode() { + final PrintWriter out = getOutPrintWriter(); + out.println(mTestableProximityCallback.getLastCallbackCode()); + return 0; + } + private void resetStates() { + synchronized (mLock) { + mCurrentProximityUpdate = null; + } mComponentName = resolveAttentionService(mContext); } @@ -844,11 +1076,24 @@ public class AttentionManagerService extends SystemService { + " (to see the result, call getLastTestCallbackCode)"); out.println(" := false, otherwise"); out.println(" call cancelCheckAttention: Cancels check attention"); + out.println(" call onStartProximityUpdates: Calls onStartProximityUpdates"); + out.println(" ---returns:"); + out.println( + " := true, if the request was successfully dispatched to the service " + + "implementation." + + " (to see the result, call getLastTestProximityCallbackCode)"); + out.println(" := false, otherwise"); + out.println(" call onStopProximityUpdates: Cancels proximity updates"); out.println(" getLastTestCallbackCode"); out.println(" ---returns:"); out.println( " := An integer, representing the last callback code received from the " + "bounded implementation. If none, it will return -1"); + out.println(" getLastTestProximityCallbackCode"); + out.println(" ---returns:"); + out.println( + " := A double, representing the last proximity value received from the " + + "bounded implementation. If none, it will return -1.0"); } } diff --git a/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java index 9ee1205b4325..3890d4d006e2 100644 --- a/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java @@ -24,14 +24,20 @@ import static com.android.server.attention.AttentionManagerService.KEY_STALE_AFT import static com.google.common.truth.Truth.assertThat; +import static junit.framework.Assert.fail; + import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; import android.attention.AttentionManagerInternal.AttentionCallbackInternal; +import android.attention.AttentionManagerInternal.ProximityCallbackInternal; import android.content.ComponentName; import android.content.Context; import android.os.IBinder; @@ -42,6 +48,7 @@ import android.os.RemoteException; import android.provider.DeviceConfig; import android.service.attention.IAttentionCallback; import android.service.attention.IAttentionService; +import android.service.attention.IProximityCallback; import androidx.test.filters.SmallTest; @@ -49,6 +56,7 @@ import com.android.server.attention.AttentionManagerService.AttentionCheck; import com.android.server.attention.AttentionManagerService.AttentionCheckCache; import com.android.server.attention.AttentionManagerService.AttentionCheckCacheBuffer; import com.android.server.attention.AttentionManagerService.AttentionHandler; +import com.android.server.attention.AttentionManagerService.ProximityUpdate; import org.junit.Before; import org.junit.Test; @@ -59,10 +67,13 @@ import org.mockito.MockitoAnnotations; /** * Tests for {@link com.android.server.attention.AttentionManagerService} */ +@SuppressWarnings("GuardedBy") @SmallTest public class AttentionManagerServiceTest { + private static final double PROXIMITY_SUCCESS_STATE = 1.0; private AttentionManagerService mSpyAttentionManager; private final int mTimeout = 1000; + private final Object mLock = new Object(); @Mock private AttentionCallbackInternal mMockAttentionCallbackInternal; @Mock @@ -73,6 +84,8 @@ public class AttentionManagerServiceTest { private IThermalService mMockIThermalService; @Mock Context mContext; + @Mock + private ProximityCallbackInternal mMockProximityCallbackInternal; @Before public void setUp() throws RemoteException { @@ -84,7 +97,6 @@ public class AttentionManagerServiceTest { doReturn(true).when(mMockIPowerManager).isInteractive(); mPowerManager = new PowerManager(mContext, mMockIPowerManager, mMockIThermalService, null); - Object mLock = new Object(); // setup a spy on attention manager AttentionManagerService attentionManager = new AttentionManagerService( mContext, @@ -100,6 +112,119 @@ public class AttentionManagerServiceTest { mSpyAttentionManager); mSpyAttentionManager.mCurrentAttentionCheck = attentionCheck; mSpyAttentionManager.mService = new MockIAttentionService(); + doNothing().when(mSpyAttentionManager).freeIfInactiveLocked(); + } + + @Test + public void testRegisterProximityUpdates_returnFalseWhenServiceDisabled() { + mSpyAttentionManager.mIsServiceEnabled = false; + + assertThat(mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal)) + .isFalse(); + } + + @Test + public void testRegisterProximityUpdates_returnFalseWhenServiceUnavailable() { + mSpyAttentionManager.mIsServiceEnabled = true; + doReturn(false).when(mSpyAttentionManager).isServiceAvailable(); + + assertThat(mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal)) + .isFalse(); + } + + @Test + public void testRegisterProximityUpdates_returnFalseWhenPowerManagerNotInteract() + throws RemoteException { + mSpyAttentionManager.mIsServiceEnabled = true; + doReturn(true).when(mSpyAttentionManager).isServiceAvailable(); + doReturn(false).when(mMockIPowerManager).isInteractive(); + + assertThat(mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal)) + .isFalse(); + } + + @Test + public void testRegisterProximityUpdates_callOnSuccess() throws RemoteException { + mSpyAttentionManager.mIsServiceEnabled = true; + doReturn(true).when(mSpyAttentionManager).isServiceAvailable(); + doReturn(true).when(mMockIPowerManager).isInteractive(); + + assertThat(mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal)) + .isTrue(); + verify(mMockProximityCallbackInternal, times(1)) + .onProximityUpdate(PROXIMITY_SUCCESS_STATE); + } + + @Test + public void testRegisterProximityUpdates_callOnSuccessTwiceInARow() throws RemoteException { + mSpyAttentionManager.mIsServiceEnabled = true; + doReturn(true).when(mSpyAttentionManager).isServiceAvailable(); + doReturn(true).when(mMockIPowerManager).isInteractive(); + + assertThat(mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal)) + .isTrue(); + + ProximityUpdate prevProximityUpdate = mSpyAttentionManager.mCurrentProximityUpdate; + assertThat(mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal)) + .isTrue(); + assertThat(mSpyAttentionManager.mCurrentProximityUpdate).isEqualTo(prevProximityUpdate); + verify(mMockProximityCallbackInternal, times(1)) + .onProximityUpdate(PROXIMITY_SUCCESS_STATE); + } + + @Test + public void testUnregisterProximityUpdates_noCrashWhenNoCallbackIsRegistered() { + mSpyAttentionManager.onStopProximityUpdates(mMockProximityCallbackInternal); + verifyZeroInteractions(mMockProximityCallbackInternal); + } + + @Test + public void testUnregisterProximityUpdates_noCrashWhenCallbackMismatched() + throws RemoteException { + mSpyAttentionManager.mIsServiceEnabled = true; + doReturn(true).when(mSpyAttentionManager).isServiceAvailable(); + doReturn(true).when(mMockIPowerManager).isInteractive(); + mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal); + verify(mMockProximityCallbackInternal, times(1)) + .onProximityUpdate(PROXIMITY_SUCCESS_STATE); + + ProximityCallbackInternal mismatchedCallback = new ProximityCallbackInternal() { + @Override + public void onProximityUpdate(double distance) { + fail("Callback shouldn't have responded."); + } + }; + mSpyAttentionManager.onStopProximityUpdates(mismatchedCallback); + + verifyNoMoreInteractions(mMockProximityCallbackInternal); + } + + @Test + public void testUnregisterProximityUpdates_cancelRegistrationWhenMatched() + throws RemoteException { + mSpyAttentionManager.mIsServiceEnabled = true; + doReturn(true).when(mSpyAttentionManager).isServiceAvailable(); + doReturn(true).when(mMockIPowerManager).isInteractive(); + mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal); + mSpyAttentionManager.onStopProximityUpdates(mMockProximityCallbackInternal); + + assertThat(mSpyAttentionManager.mCurrentProximityUpdate).isNull(); + } + + @Test + public void testUnregisterProximityUpdates_noCrashWhenTwiceInARow() throws RemoteException { + // Attention Service registers proximity updates. + mSpyAttentionManager.mIsServiceEnabled = true; + doReturn(true).when(mSpyAttentionManager).isServiceAvailable(); + doReturn(true).when(mMockIPowerManager).isInteractive(); + mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal); + verify(mMockProximityCallbackInternal, times(1)) + .onProximityUpdate(PROXIMITY_SUCCESS_STATE); + + // Attention Service unregisters the proximity update twice in a row. + mSpyAttentionManager.onStopProximityUpdates(mMockProximityCallbackInternal); + mSpyAttentionManager.onStopProximityUpdates(mMockProximityCallbackInternal); + verifyNoMoreInteractions(mMockProximityCallbackInternal); } @Test @@ -127,7 +252,6 @@ public class AttentionManagerServiceTest { mSpyAttentionManager.mIsServiceEnabled = true; doReturn(true).when(mSpyAttentionManager).isServiceAvailable(); doReturn(true).when(mMockIPowerManager).isInteractive(); - doNothing().when(mSpyAttentionManager).freeIfInactiveLocked(); mSpyAttentionManager.mCurrentAttentionCheck = null; AttentionCallbackInternal callback = Mockito.mock(AttentionCallbackInternal.class); @@ -213,6 +337,13 @@ public class AttentionManagerServiceTest { public void cancelAttentionCheck(IAttentionCallback callback) { } + public void onStartProximityUpdates(IProximityCallback callback) throws RemoteException { + callback.onProximityUpdate(PROXIMITY_SUCCESS_STATE); + } + + public void onStopProximityUpdates() throws RemoteException { + } + public IBinder asBinder() { return null; } |