Add skeleton implementation for Attention API
This service will let the system know whether the user is paying attention to
the device. This may be useful in certain scenarios - like keeping the screen
on, even when there has been no touch related user activity.
New permission BIND_ATTENTION_SERVICE is added to ensure that only the system
can bind to the attention service.
Test: manually tested the lifecycle is as expected
Bug: 111939367
Change-Id: I2dab9c69f3d0c6efb0db572f797f517dc6efcc72
diff --git a/Android.bp b/Android.bp
index c202408..38d8649 100644
--- a/Android.bp
+++ b/Android.bp
@@ -357,6 +357,8 @@
"core/java/android/service/textclassifier/ITextLanguageCallback.aidl",
"core/java/android/service/textclassifier/ITextLinksCallback.aidl",
"core/java/android/service/textclassifier/ITextSelectionCallback.aidl",
+ "core/java/android/service/attention/IAttentionService.aidl",
+ "core/java/android/service/attention/IAttentionCallback.aidl",
"core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl",
"core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl",
"core/java/android/view/accessibility/IAccessibilityManager.aidl",
diff --git a/api/system-current.txt b/api/system-current.txt
index 276be9d..bbc4dfa 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -18,6 +18,7 @@
field public static final java.lang.String ALLOW_ANY_CODEC_FOR_PLAYBACK = "android.permission.ALLOW_ANY_CODEC_FOR_PLAYBACK";
field public static final java.lang.String AMBIENT_WALLPAPER = "android.permission.AMBIENT_WALLPAPER";
field public static final java.lang.String BACKUP = "android.permission.BACKUP";
+ field public static final java.lang.String BIND_ATTENTION_SERVICE = "android.permission.BIND_ATTENTION_SERVICE";
field public static final java.lang.String BIND_AUGMENTED_AUTOFILL_SERVICE = "android.permission.BIND_AUGMENTED_AUTOFILL_SERVICE";
field public static final deprecated java.lang.String BIND_CONNECTION_SERVICE = "android.permission.BIND_CONNECTION_SERVICE";
field public static final java.lang.String BIND_CONTENT_CAPTURE_SERVICE = "android.permission.BIND_CONTENT_CAPTURE_SERVICE";
@@ -5401,6 +5402,28 @@
}
+package android.service.attention {
+
+ public abstract class AttentionService extends android.app.Service {
+ ctor public AttentionService();
+ method public final android.os.IBinder onBind(android.content.Intent);
+ method public abstract void onCancelAttentionCheck(int);
+ method public abstract void onCheckAttention(int, android.service.attention.AttentionService.AttentionCallback);
+ field public static final int ATTENTION_FAILURE_PREEMPTED = 2; // 0x2
+ field public static final int ATTENTION_FAILURE_TIMED_OUT = 3; // 0x3
+ field public static final int ATTENTION_FAILURE_UNKNOWN = 4; // 0x4
+ field public static final int ATTENTION_SUCCESS_ABSENT = 0; // 0x0
+ field public static final int ATTENTION_SUCCESS_PRESENT = 1; // 0x1
+ field public static final java.lang.String SERVICE_INTERFACE = "android.service.attention.AttentionService";
+ }
+
+ public static final class AttentionService.AttentionCallback {
+ method public void onFailure(int, int);
+ method public void onSuccess(int, int, long);
+ }
+
+}
+
package android.service.autofill {
public abstract class AutofillFieldClassificationService extends android.app.Service {
diff --git a/core/java/android/attention/AttentionManagerInternal.java b/core/java/android/attention/AttentionManagerInternal.java
new file mode 100644
index 0000000..6b7f10e
--- /dev/null
+++ b/core/java/android/attention/AttentionManagerInternal.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2019 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.attention;
+
+/**
+ * Attention manager local system server interface.
+ *
+ * @hide Only for use within the system server.
+ */
+public abstract class AttentionManagerInternal {
+ /**
+ * Returns {@code true} if attention service is supported on this device.
+ */
+ public abstract boolean isAttentionServiceSupported();
+
+ /**
+ * Checks whether user attention is at the screen and calls in the provided callback.
+ *
+ * @param requestCode a code associated with the attention check request; this code would be
+ * used to call back in {@link AttentionCallbackInternal#onSuccess} and
+ * {@link AttentionCallbackInternal#onFailure}
+ * @param timeoutMillis a budget for the attention check; if it takes longer - {@link
+ * AttentionCallbackInternal#onFailure} would be called with the {@link
+ * android.service.attention.AttentionService#ATTENTION_FAILURE_TIMED_OUT}
+ * code
+ * @param callback a callback for when the attention check has completed
+ * @return {@code true} if the attention check should succeed; {@false} otherwise.
+ */
+ public abstract boolean checkAttention(int requestCode,
+ long timeoutMillis, AttentionCallbackInternal callback);
+
+ /**
+ * Cancels the specified attention check in case it's no longer needed.
+ *
+ * @param requestCode a code provided during {@link #checkAttention}
+ */
+ public abstract void cancelAttentionCheck(int requestCode);
+
+ /** Internal interface for attention callback. */
+ public abstract static class AttentionCallbackInternal {
+ /**
+ * Provides the result of the attention check, if the check was successful.
+ *
+ * @param requestCode a code provided in {@link #checkAttention}
+ * @param result an int with the result of the check
+ * @param timestamp a {@code SystemClock.uptimeMillis()} timestamp associated with the
+ * attention check
+ */
+ public abstract void onSuccess(int requestCode, int result, long timestamp);
+
+ /**
+ * Provides the explanation for why the attention check had failed.
+ *
+ * @param requestCode a code provided in {@link #checkAttention}
+ * @param error an int with the reason for failure
+ */
+ public abstract void onFailure(int requestCode, int error);
+ }
+}
diff --git a/core/java/android/service/attention/AttentionService.java b/core/java/android/service/attention/AttentionService.java
new file mode 100644
index 0000000..f6e448dc
--- /dev/null
+++ b/core/java/android/service/attention/AttentionService.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2019 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.attention;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+
+/**
+ * Abstract base class for Attention service.
+ *
+ * <p> An attention service provides attention estimation related features to the system.
+ * The system's default AttentionService implementation is configured in
+ * {@code config_AttentionComponent}. If this config has no value, a stub is returned.
+ *
+ * See: {@link AttentionManagerService}.
+ *
+ * <pre>
+ * {@literal
+ * <service android:name=".YourAttentionService"
+ * android:permission="android.permission.BIND_ATTENTION_SERVICE">
+ * </service>}
+ * </pre>
+ *
+ * @hide
+ */
+@SystemApi
+public abstract class AttentionService extends Service {
+ /**
+ * 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}
+ * permission so that other applications can not abuse it.
+ */
+ public static final String SERVICE_INTERFACE =
+ "android.service.attention.AttentionService";
+
+ /** Attention is absent. */
+ public static final int ATTENTION_SUCCESS_ABSENT = 0;
+
+ /** Attention is present. */
+ public static final int ATTENTION_SUCCESS_PRESENT = 1;
+
+ /** Preempted by other camera user. */
+ public static final int ATTENTION_FAILURE_PREEMPTED = 2;
+
+ /** Preempted by other camera user. */
+ public static final int ATTENTION_FAILURE_TIMED_OUT = 3;
+
+ /** Unknown reasons for failing to determine the attention. */
+ public static final int ATTENTION_FAILURE_UNKNOWN = 4;
+
+ /**
+ * Result codes for when attention check was successful.
+ *
+ * @hide
+ */
+ @IntDef(prefix = {"ATTENTION_SUCCESS_"}, value = {ATTENTION_SUCCESS_ABSENT,
+ ATTENTION_SUCCESS_PRESENT})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface AttentionSuccessCodes {
+ }
+
+ /**
+ * Result codes explaining why attention check was not successful.
+ *
+ * @hide
+ */
+ @IntDef(prefix = {"ATTENTION_FAILURE_"}, value = {ATTENTION_FAILURE_PREEMPTED,
+ ATTENTION_FAILURE_TIMED_OUT, ATTENTION_FAILURE_UNKNOWN})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface AttentionFailureCodes {
+ }
+
+ private final IAttentionService.Stub mBinder = new IAttentionService.Stub() {
+
+ /** {@inheritDoc} */
+ @Override
+ public void checkAttention(int requestCode, IAttentionCallback callback) {
+ Preconditions.checkNotNull(callback);
+ AttentionService.this.onCheckAttention(requestCode, new AttentionCallback(callback));
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void cancelAttentionCheck(int requestCode) {
+ AttentionService.this.onCancelAttentionCheck(requestCode);
+ }
+ };
+
+ @Override
+ public final IBinder onBind(Intent intent) {
+ if (SERVICE_INTERFACE.equals(intent.getAction())) {
+ return mBinder;
+ }
+ return null;
+ }
+
+ /**
+ * Checks the user attention and calls into the provided callback.
+ *
+ * @param requestCode an identifier that could be used to cancel the request
+ * @param callback the callback to return the result to
+ */
+ public abstract void onCheckAttention(int requestCode, @NonNull AttentionCallback callback);
+
+ /** Cancels the attention check for a given request code. */
+ public abstract void onCancelAttentionCheck(int requestCode);
+
+
+ /** Callbacks for AttentionService results. */
+ public static final class AttentionCallback {
+ private final IAttentionCallback mCallback;
+
+ private AttentionCallback(IAttentionCallback callback) {
+ mCallback = callback;
+ }
+
+ /** Returns the result. */
+ public void onSuccess(int requestCode, @AttentionSuccessCodes int result, long timestamp) {
+ try {
+ mCallback.onSuccess(requestCode, result, timestamp);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /** Signals a failure. */
+ public void onFailure(int requestCode, @AttentionFailureCodes int error) {
+ try {
+ mCallback.onFailure(requestCode, error);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+ }
+}
diff --git a/core/java/android/service/attention/IAttentionCallback.aidl b/core/java/android/service/attention/IAttentionCallback.aidl
new file mode 100644
index 0000000..0e8a1e7
--- /dev/null
+++ b/core/java/android/service/attention/IAttentionCallback.aidl
@@ -0,0 +1,27 @@
+ /*
+ * Copyright (C) 2019 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.attention;
+
+/**
+ * Callback for onCheckAttention request.
+ *
+ * @hide
+ */
+oneway interface IAttentionCallback {
+ void onSuccess(int requestCode, int result, long timestamp);
+ void onFailure(int requestCode, int error);
+}
diff --git a/core/java/android/service/attention/IAttentionService.aidl b/core/java/android/service/attention/IAttentionService.aidl
new file mode 100644
index 0000000..c3b6f48
--- /dev/null
+++ b/core/java/android/service/attention/IAttentionService.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2019 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.attention;
+
+import android.service.attention.IAttentionCallback;
+
+/**
+ * Interface for a concrete implementation to provide to the AttentionManagerService.
+ *
+ * @hide
+ */
+oneway interface IAttentionService {
+ void checkAttention(int requestCode, IAttentionCallback callback);
+ void cancelAttentionCheck(int requestCode);
+}
\ No newline at end of file
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 2f3a491..11fc9a4 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3045,6 +3045,15 @@
<permission android:name="android.permission.BIND_TEXT_SERVICE"
android:protectionLevel="signature" />
+ <!-- @SystemApi Must be required by a AttentionService
+ to ensure that only the system can bind to it.
+ <p>Protection level: signature
+ @hide
+ -->
+ <permission android:name="android.permission.BIND_ATTENTION_SERVICE"
+ android:protectionLevel="signature" />
+ <uses-permission android:name="android.permission.BIND_ATTENTION_SERVICE" />
+
<!-- Must be required by a {@link android.net.VpnService},
to ensure that only the system can bind to it.
<p>Protection level: signature
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 2e3bd7c..70caaa8 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3434,7 +3434,7 @@
See android.view.textclassifier.TextClassificationManager.
-->
<string name="config_defaultTextClassifierPackage" translatable="false"></string>
-
+
<!-- The package name for the default wellbeing app.
This package must be trusted, as it has the permissions to control other applications
on the device.
@@ -3442,6 +3442,12 @@
-->
<string name="config_defaultWellbeingPackage" translatable="false"></string>
+ <!-- The component name for the default system attention service.
+ This service must be trusted, as it can be activated without explicit consent of the user.
+ See android.attention.AttentionManagerService.
+ -->
+ <string name="config_defaultAttentionService" translatable="false"></string>
+
<!-- The package name for the system's content capture service.
This service must be trusted, as it can be activated without explicit consent of the user.
If no service with the specified name exists on the device, content capture will be
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index daf8b44..ea37a53 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3284,6 +3284,7 @@
<java-symbol type="string" name="config_defaultAugmentedAutofillService" />
<java-symbol type="string" name="config_defaultAppPredictionService" />
<java-symbol type="string" name="config_defaultContentSuggestionsService" />
+ <java-symbol type="string" name="config_defaultAttentionService" />
<java-symbol type="string" name="notification_channel_foreground_service" />
<java-symbol type="string" name="foreground_service_app_in_background" />
diff --git a/services/core/java/com/android/server/attention/AttentionManagerService.java b/services/core/java/com/android/server/attention/AttentionManagerService.java
new file mode 100644
index 0000000..f60d6b0
--- /dev/null
+++ b/services/core/java/com/android/server/attention/AttentionManagerService.java
@@ -0,0 +1,548 @@
+/*
+ * Copyright (C) 2019 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 com.android.server.attention;
+
+import android.Manifest;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.attention.AttentionManagerInternal;
+import android.attention.AttentionManagerInternal.AttentionCallbackInternal;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.service.attention.AttentionService;
+import android.service.attention.AttentionService.AttentionFailureCodes;
+import android.service.attention.IAttentionCallback;
+import android.service.attention.IAttentionService;
+import android.text.TextUtils;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.DumpUtils;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.Preconditions;
+import com.android.server.SystemService;
+
+import java.io.PrintWriter;
+
+/**
+ * An attention service implementation that runs in System Server process.
+ * This service publishes a LocalService and reroutes calls to a {@link AttentionService} that it
+ * manages.
+ */
+public class AttentionManagerService extends SystemService {
+ private static final String LOG_TAG = "AttentionManagerService";
+
+ /** Service will unbind if connection is not used for that amount of time. */
+ private static final long CONNECTION_TTL_MILLIS = 60_000;
+
+ /** If the check attention called within that period - cached value will be returned. */
+ private static final long STALE_AFTER_MILLIS = 5_000;
+
+ private final Context mContext;
+ private final PowerManager mPowerManager;
+ private final ActivityManager mActivityManager;
+ private final Object mLock;
+ @GuardedBy("mLock")
+ private final SparseArray<UserState> mUserStates = new SparseArray<>();
+ private final AttentionHandler mAttentionHandler;
+
+ private ComponentName mComponentName;
+
+ public AttentionManagerService(Context context) {
+ super(context);
+ mContext = Preconditions.checkNotNull(context);
+ mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ mActivityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+ mLock = new Object();
+ mAttentionHandler = new AttentionHandler();
+ }
+
+ @Override
+ public void onStart() {
+ publishLocalService(AttentionManagerInternal.class, new LocalService());
+ }
+
+ @Override
+ public void onStopUser(int userId) {
+ cancelAndUnbindLocked(peekUserStateLocked(userId),
+ AttentionService.ATTENTION_FAILURE_UNKNOWN);
+ }
+
+ @Override
+ public void onBootPhase(int phase) {
+ super.onBootPhase(phase);
+ if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
+ mComponentName = resolveAttentionService(mContext);
+ if (mComponentName != null) {
+ // If the service is supported we want to keep receiving the screen off events.
+ mContext.registerReceiver(new ScreenStateReceiver(),
+ new IntentFilter(Intent.ACTION_SCREEN_OFF));
+ }
+ }
+ }
+
+ /**
+ * Returns {@code true} if attention service is supported on this device.
+ */
+ public boolean isAttentionServiceSupported() {
+ return mComponentName != null;
+ }
+
+ /**
+ * Checks whether user attention is at the screen and calls in the provided callback.
+ *
+ * @return {@code true} if the framework was able to send the provided callback to the service
+ */
+ public boolean checkAttention(int requestCode, long timeout,
+ AttentionCallbackInternal callback) {
+ Preconditions.checkNotNull(callback);
+
+ if (!isAttentionServiceSupported()) {
+ Slog.w(LOG_TAG, "Trying to call checkAttention() on an unsupported device.");
+ return false;
+ }
+
+ // don't allow attention check in screen off state
+ if (!mPowerManager.isInteractive()) {
+ return false;
+ }
+
+ synchronized (mLock) {
+ unbindAfterTimeoutLocked();
+
+ final UserState userState = getOrCreateCurrentUserStateLocked();
+ // lazily start the service, which should be very lightweight to start
+ if (!userState.bindLocked()) {
+ return false;
+ }
+
+ if (userState.mService == null) {
+ // make sure every callback is called back
+ if (userState.mPendingAttentionCheck != null) {
+ userState.mPendingAttentionCheck.cancel(
+ AttentionService.ATTENTION_FAILURE_UNKNOWN);
+ }
+ userState.mPendingAttentionCheck = new PendingAttentionCheck(requestCode,
+ callback, () -> checkAttention(requestCode, timeout, callback));
+ } else {
+ try {
+ // throttle frequent requests
+ final AttentionCheckCache attentionCheckCache = userState.mAttentionCheckCache;
+ if (attentionCheckCache != null && SystemClock.uptimeMillis()
+ < attentionCheckCache.mLastComputed + STALE_AFTER_MILLIS) {
+ callback.onSuccess(requestCode, attentionCheckCache.mResult,
+ attentionCheckCache.mTimestamp);
+ return true;
+ }
+
+ cancelAfterTimeoutLocked(timeout);
+
+ userState.mCurrentAttentionCheckRequestCode = requestCode;
+ userState.mService.checkAttention(requestCode, new IAttentionCallback.Stub() {
+ @Override
+ public void onSuccess(int requestCode, int result, long timestamp) {
+ callback.onSuccess(requestCode, result, timestamp);
+ userState.mAttentionCheckCache = new AttentionCheckCache(
+ SystemClock.uptimeMillis(), result,
+ timestamp);
+ }
+
+ @Override
+ public void onFailure(int requestCode, int error) {
+ callback.onFailure(requestCode, error);
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return null;
+ }
+ });
+ } catch (RemoteException e) {
+ Slog.e(LOG_TAG, "Cannot call into the AttentionService");
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ /** Cancels the specified attention check. */
+ public void cancelAttentionCheck(int requestCode) {
+ final UserState userState = getOrCreateCurrentUserStateLocked();
+ try {
+ userState.mService.cancelAttentionCheck(requestCode);
+ } catch (RemoteException e) {
+ Slog.e(LOG_TAG, "Cannot call into the AttentionService");
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void unbindAfterTimeoutLocked() {
+ mAttentionHandler.sendEmptyMessageDelayed(AttentionHandler.CONNECTION_EXPIRED,
+ CONNECTION_TTL_MILLIS);
+ }
+
+ @GuardedBy("mLock")
+ private void cancelAfterTimeoutLocked(long timeout) {
+ mAttentionHandler.sendEmptyMessageDelayed(AttentionHandler.ATTENTION_CHECK_TIMEOUT,
+ timeout);
+ }
+
+
+ @GuardedBy("mLock")
+ private UserState getOrCreateCurrentUserStateLocked() {
+ return getOrCreateUserStateLocked(mActivityManager.getCurrentUser());
+ }
+
+ @GuardedBy("mLock")
+ private UserState getOrCreateUserStateLocked(int userId) {
+ UserState result = mUserStates.get(userId);
+ if (result == null) {
+ result = new UserState(userId, mContext, mLock);
+ mUserStates.put(userId, result);
+ }
+ return result;
+ }
+
+ @GuardedBy("mLock")
+ UserState peekCurrentUserStateLocked() {
+ return peekUserStateLocked(mActivityManager.getCurrentUser());
+ }
+
+ @GuardedBy("mLock")
+ UserState peekUserStateLocked(int userId) {
+ return mUserStates.get(userId);
+ }
+
+ /**
+ * Provides attention service component name at runtime, making sure it's provided by the
+ * system.
+ */
+ private static ComponentName resolveAttentionService(Context context) {
+ // TODO(b/111939367): add a flag to turn on/off.
+ final String componentNameString = context.getString(
+ R.string.config_defaultAttentionService);
+
+ if (TextUtils.isEmpty(componentNameString)) {
+ return null;
+ }
+
+ final ComponentName componentName = ComponentName.unflattenFromString(componentNameString);
+ if (componentName == null) {
+ return null;
+ }
+
+ final Intent intent = new Intent(AttentionService.SERVICE_INTERFACE).setPackage(
+ componentName.getPackageName());
+
+ // Make sure that only system apps can declare the AttentionService.
+ final ResolveInfo resolveInfo = context.getPackageManager().resolveService(intent,
+ PackageManager.MATCH_SYSTEM_ONLY);
+ if (resolveInfo == null || resolveInfo.serviceInfo == null) {
+ Slog.wtf(LOG_TAG, String.format("Service %s not found in package %s",
+ AttentionService.SERVICE_INTERFACE, componentName
+ ));
+ return null;
+ }
+
+ final ServiceInfo serviceInfo = resolveInfo.serviceInfo;
+ final String permission = serviceInfo.permission;
+ if (Manifest.permission.BIND_ATTENTION_SERVICE.equals(permission)) {
+ return serviceInfo.getComponentName();
+ }
+ Slog.e(LOG_TAG, String.format(
+ "Service %s should require %s permission. Found %s permission",
+ serviceInfo.getComponentName(),
+ Manifest.permission.BIND_ATTENTION_SERVICE,
+ serviceInfo.permission));
+ return null;
+ }
+
+ private void dumpInternal(PrintWriter pw) {
+ if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return;
+ IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
+ ipw.println("Attention Manager Service (dumpsys attention)\n");
+
+ ipw.printPair("context", mContext);
+ pw.println();
+ synchronized (mLock) {
+ int size = mUserStates.size();
+ ipw.print("Number user states: ");
+ pw.println(size);
+ if (size > 0) {
+ ipw.increaseIndent();
+ for (int i = 0; i < size; i++) {
+ UserState userState = mUserStates.valueAt(i);
+ ipw.print(i);
+ ipw.print(":");
+ userState.dump(ipw);
+ ipw.println();
+ }
+ ipw.decreaseIndent();
+ }
+ }
+ }
+
+ private final class LocalService extends AttentionManagerInternal {
+ @Override
+ public boolean isAttentionServiceSupported() {
+ return AttentionManagerService.this.isAttentionServiceSupported();
+ }
+
+ @Override
+ public boolean checkAttention(int requestCode, long timeout,
+ AttentionCallbackInternal callback) {
+ return AttentionManagerService.this.checkAttention(requestCode, timeout, callback);
+ }
+
+ @Override
+ public void cancelAttentionCheck(int requestCode) {
+ AttentionManagerService.this.cancelAttentionCheck(requestCode);
+ }
+ }
+
+ private static final class AttentionCheckCache {
+ private final long mLastComputed;
+ private final int mResult;
+ private final long mTimestamp;
+
+ AttentionCheckCache(long lastComputed, @AttentionService.AttentionSuccessCodes int result,
+ long timestamp) {
+ mLastComputed = lastComputed;
+ mResult = result;
+ mTimestamp = timestamp;
+ }
+ }
+
+ private static final class PendingAttentionCheck {
+ private final int mRequestCode;
+ private final AttentionCallbackInternal mCallback;
+ private final Runnable mRunnable;
+
+ PendingAttentionCheck(int requestCode, AttentionCallbackInternal callback,
+ Runnable runnable) {
+ mRequestCode = requestCode;
+ mCallback = callback;
+ mRunnable = runnable;
+ }
+
+ void cancel(@AttentionFailureCodes int failureCode) {
+ mCallback.onFailure(mRequestCode, failureCode);
+ }
+
+ void run() {
+ mRunnable.run();
+ }
+ }
+
+ private static final class UserState {
+ final AttentionServiceConnection mConnection = new AttentionServiceConnection();
+
+ @GuardedBy("mLock")
+ IAttentionService mService;
+ @GuardedBy("mLock")
+ boolean mBinding;
+ @GuardedBy("mLock")
+ int mCurrentAttentionCheckRequestCode;
+ @GuardedBy("mLock")
+ PendingAttentionCheck mPendingAttentionCheck;
+
+ @GuardedBy("mLock")
+ AttentionCheckCache mAttentionCheckCache;
+
+ @UserIdInt
+ final int mUserId;
+ final Context mContext;
+ final Object mLock;
+
+ private UserState(int userId, Context context, Object lock) {
+ mUserId = userId;
+ mContext = Preconditions.checkNotNull(context);
+ mLock = Preconditions.checkNotNull(lock);
+ }
+
+
+ @GuardedBy("mLock")
+ private void handlePendingCallbackLocked() {
+ if (mService != null && mPendingAttentionCheck != null) {
+ mPendingAttentionCheck.run();
+ mPendingAttentionCheck = null;
+ }
+ }
+
+ /** Binds to the system's AttentionService which provides an actual implementation. */
+ @GuardedBy("mLock")
+ private boolean bindLocked() {
+ // No need to bind if service is binding or has already been bound.
+ if (mBinding || mService != null) {
+ return true;
+ }
+
+ final boolean willBind;
+ final long identity = Binder.clearCallingIdentity();
+
+ try {
+ final ComponentName componentName =
+ resolveAttentionService(mContext);
+ if (componentName == null) {
+ // Might happen if the storage is encrypted and the user is not unlocked
+ return false;
+ }
+ final Intent mServiceIntent = new Intent(
+ AttentionService.SERVICE_INTERFACE).setComponent(
+ componentName);
+ willBind = mContext.bindServiceAsUser(mServiceIntent, mConnection,
+ Context.BIND_AUTO_CREATE, UserHandle.CURRENT);
+ mBinding = willBind;
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ return willBind;
+ }
+
+ private void dump(IndentingPrintWriter pw) {
+ pw.printPair("context", mContext);
+ pw.printPair("userId", mUserId);
+ synchronized (mLock) {
+ pw.printPair("binding", mBinding);
+ pw.printPair("isAttentionCheckPending", mPendingAttentionCheck != null);
+ }
+ }
+
+ private final class AttentionServiceConnection implements ServiceConnection {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ init(IAttentionService.Stub.asInterface(service));
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ cleanupService();
+ }
+
+ @Override
+ public void onBindingDied(ComponentName name) {
+ cleanupService();
+ }
+
+ @Override
+ public void onNullBinding(ComponentName name) {
+ cleanupService();
+ }
+
+ void cleanupService() {
+ init(null);
+ }
+
+ private void init(@Nullable IAttentionService service) {
+ synchronized (mLock) {
+ mService = service;
+ mBinding = false;
+ handlePendingCallbackLocked();
+ }
+ }
+ }
+ }
+
+ private class AttentionHandler extends Handler {
+ private static final int CONNECTION_EXPIRED = 1;
+ private static final int ATTENTION_CHECK_TIMEOUT = 2;
+
+ AttentionHandler() {
+ super(Looper.myLooper());
+ }
+
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ // Do not occupy resources when not in use - unbind proactively.
+ case CONNECTION_EXPIRED: {
+ for (int i = 0; i < mUserStates.size(); i++) {
+ cancelAndUnbindLocked(mUserStates.valueAt(i),
+ AttentionService.ATTENTION_FAILURE_UNKNOWN);
+ }
+
+ }
+ break;
+
+ // Callee is no longer interested in the attention check result - cancel.
+ case ATTENTION_CHECK_TIMEOUT: {
+ cancelAndUnbindLocked(peekCurrentUserStateLocked(),
+ AttentionService.ATTENTION_FAILURE_TIMED_OUT);
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void cancelAndUnbindLocked(UserState userState,
+ @AttentionFailureCodes int failureCode) {
+ synchronized (mLock) {
+ if (userState != null && userState.mService != null) {
+ try {
+ userState.mService.cancelAttentionCheck(
+ userState.mCurrentAttentionCheckRequestCode);
+ } catch (RemoteException e) {
+ Slog.e(LOG_TAG, "Unable to cancel attention check");
+ }
+
+ if (userState.mPendingAttentionCheck != null) {
+ userState.mPendingAttentionCheck.cancel(failureCode);
+ }
+ mContext.unbindService(userState.mConnection);
+ userState.mConnection.cleanupService();
+ mUserStates.remove(userState.mUserId);
+ }
+ }
+ }
+
+ /**
+ * Unbinds and stops the service when the screen off intent is received.
+ * Attention service only makes sense when screen is ON; disconnect and stop service otherwise.
+ */
+ private final class ScreenStateReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) {
+ cancelAndUnbindLocked(peekCurrentUserStateLocked(),
+ AttentionService.ATTENTION_FAILURE_UNKNOWN);
+ }
+ }
+ }
+}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index fef2db9..cef47ca 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -74,6 +74,7 @@
import com.android.internal.widget.ILockSettings;
import com.android.server.am.ActivityManagerService;
import com.android.server.appbinding.AppBindingService;
+import com.android.server.attention.AttentionManagerService;
import com.android.server.audio.AudioService;
import com.android.server.biometrics.BiometricService;
import com.android.server.biometrics.face.FaceService;
@@ -148,10 +149,11 @@
import com.android.server.wm.ActivityTaskManagerService;
import com.android.server.wm.WindowManagerGlobalLock;
import com.android.server.wm.WindowManagerService;
-import com.google.android.startop.iorap.IorapForwardingService;
import dalvik.system.VMRuntime;
+import com.google.android.startop.iorap.IorapForwardingService;
+
import java.io.File;
import java.io.IOException;
import java.util.Locale;
@@ -1230,6 +1232,10 @@
traceEnd();
}
+ traceBeginAndSlog("StartAttentionManagerService");
+ mSystemServiceManager.startService(AttentionManagerService.class);
+ traceEnd();
+
traceBeginAndSlog("StartNetworkScoreService");
mSystemServiceManager.startService(NetworkScoreService.Lifecycle.class);
traceEnd();