blob: 1b45eb4f00bb4d8830b073a216816e160efda52b [file] [log] [blame]
/*
* 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 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 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.ResultReceiver;
import android.os.ShellCallback;
import android.os.ShellCommand;
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.service.attention.AttentionService;
import android.service.attention.AttentionService.AttentionFailureCodes;
import android.service.attention.AttentionService.AttentionSuccessCodes;
import android.service.attention.IAttentionCallback;
import android.service.attention.IAttentionService;
import android.text.TextUtils;
import android.util.Slog;
import android.util.SparseArray;
import android.util.StatsLog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
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.FileDescriptor;
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";
private static final boolean DEBUG = false;
/** Default value in absence of {@link DeviceConfig} override. */
private static final boolean DEFAULT_SERVICE_ENABLED = true;
/** 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;
/** DeviceConfig flag name, if {@code true}, enables AttentionManagerService features. */
private static final String SERVICE_ENABLED = "service_enabled";
private static String sTestAttentionServicePackage;
private final Context mContext;
private final PowerManager mPowerManager;
private final Object mLock;
@GuardedBy("mLock")
private final SparseArray<UserState> mUserStates = new SparseArray<>();
private AttentionHandler mAttentionHandler;
@VisibleForTesting
ComponentName mComponentName;
public AttentionManagerService(Context context) {
this(context, (PowerManager) context.getSystemService(Context.POWER_SERVICE),
new Object(), null);
mAttentionHandler = new AttentionHandler();
}
@VisibleForTesting
AttentionManagerService(Context context, PowerManager powerManager, Object lock,
AttentionHandler handler) {
super(context);
mContext = Preconditions.checkNotNull(context);
mPowerManager = powerManager;
mLock = lock;
mAttentionHandler = handler;
}
@Override
public void onBootPhase(int phase) {
if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
mContext.registerReceiver(new ScreenStateReceiver(),
new IntentFilter(Intent.ACTION_SCREEN_OFF));
}
}
@Override
public void onStart() {
publishBinderService(Context.ATTENTION_SERVICE, new BinderService());
publishLocalService(AttentionManagerInternal.class, new LocalService());
}
@Override
public void onSwitchUser(int userId) {
cancelAndUnbindLocked(peekUserStateLocked(userId));
}
/** Returns {@code true} if attention service is configured on this device. */
public static boolean isServiceConfigured(Context context) {
return !TextUtils.isEmpty(getServiceConfigPackage(context));
}
/** Resolves and sets up the attention service if it had not been done yet. */
private boolean isServiceAvailable() {
if (mComponentName == null) {
mComponentName = resolveAttentionService(mContext);
}
return mComponentName != null;
}
/**
* Returns {@code true} if attention service is supported on this device.
*/
private boolean isAttentionServiceSupported() {
return isServiceEnabled() && isServiceAvailable();
}
@VisibleForTesting
protected boolean isServiceEnabled() {
return DeviceConfig.getBoolean(NAMESPACE_ATTENTION_MANAGER_SERVICE, SERVICE_ENABLED,
DEFAULT_SERVICE_ENABLED);
}
/**
* Checks whether user attention is at the screen and calls in the provided callback.
*
* Calling this multiple times quickly in a row will result in either a) returning a cached
* value, if present, or b) returning {@code false} because only one active request at a time is
* allowed.
*
* @return {@code true} if the framework was able to dispatch the request
*/
@VisibleForTesting
boolean checkAttention(long timeout, AttentionCallbackInternal callbackInternal) {
Preconditions.checkNotNull(callbackInternal);
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) {
final long now = SystemClock.uptimeMillis();
// schedule shutting down the connection if no one resets this timer
freeIfInactiveLocked();
final UserState userState = getOrCreateCurrentUserStateLocked();
// lazily start the service, which should be very lightweight to start
userState.bindLocked();
// throttle frequent requests
final AttentionCheckCache cache = userState.mAttentionCheckCache;
if (cache != null && now < cache.mLastComputed + STALE_AFTER_MILLIS) {
callbackInternal.onSuccess(cache.mResult, cache.mTimestamp);
return true;
}
// prevent spamming with multiple requests, only one at a time is allowed
if (userState.mCurrentAttentionCheck != null) {
if (!userState.mCurrentAttentionCheck.mIsDispatched
|| !userState.mCurrentAttentionCheck.mIsFulfilled) {
return false;
}
}
userState.mCurrentAttentionCheck = createAttentionCheck(callbackInternal, userState);
if (userState.mService != null) {
try {
// schedule request cancellation if not returned by that point yet
cancelAfterTimeoutLocked(timeout);
userState.mService.checkAttention(
userState.mCurrentAttentionCheck.mIAttentionCallback);
userState.mCurrentAttentionCheck.mIsDispatched = true;
} catch (RemoteException e) {
Slog.e(LOG_TAG, "Cannot call into the AttentionService");
return false;
}
}
return true;
}
}
private AttentionCheck createAttentionCheck(AttentionCallbackInternal callbackInternal,
UserState userState) {
final IAttentionCallback iAttentionCallback = new IAttentionCallback.Stub() {
@Override
public void onSuccess(@AttentionSuccessCodes int result, long timestamp) {
// the callback might have been cancelled already
if (!userState.mCurrentAttentionCheck.mIsFulfilled) {
callbackInternal.onSuccess(result, timestamp);
userState.mCurrentAttentionCheck.mIsFulfilled = true;
}
synchronized (mLock) {
userState.mAttentionCheckCache = new AttentionCheckCache(
SystemClock.uptimeMillis(), result,
timestamp);
}
StatsLog.write(
StatsLog.ATTENTION_MANAGER_SERVICE_RESULT_REPORTED,
result);
}
@Override
public void onFailure(@AttentionFailureCodes int error) {
// the callback might have been cancelled already
if (!userState.mCurrentAttentionCheck.mIsFulfilled) {
callbackInternal.onFailure(error);
userState.mCurrentAttentionCheck.mIsFulfilled = true;
}
StatsLog.write(
StatsLog.ATTENTION_MANAGER_SERVICE_RESULT_REPORTED,
error);
}
};
return new AttentionCheck(callbackInternal, iAttentionCallback);
}
/** Cancels the specified attention check. */
@VisibleForTesting
void cancelAttentionCheck(AttentionCallbackInternal callbackInternal) {
synchronized (mLock) {
final UserState userState = peekCurrentUserStateLocked();
if (userState == null) {
return;
}
if (!userState.mCurrentAttentionCheck.mCallbackInternal.equals(callbackInternal)) {
Slog.w(LOG_TAG, "Cannot cancel a non-current request");
return;
}
cancel(userState);
}
}
@GuardedBy("mLock")
@VisibleForTesting
protected void freeIfInactiveLocked() {
// If we are called here, it means someone used the API again - reset the timer then.
mAttentionHandler.removeMessages(AttentionHandler.CHECK_CONNECTION_EXPIRATION);
// Schedule resources cleanup if no one calls the API again.
mAttentionHandler.sendEmptyMessageDelayed(AttentionHandler.CHECK_CONNECTION_EXPIRATION,
CONNECTION_TTL_MILLIS);
}
@GuardedBy("mLock")
private void cancelAfterTimeoutLocked(long timeout) {
mAttentionHandler.sendEmptyMessageDelayed(AttentionHandler.ATTENTION_CHECK_TIMEOUT,
timeout);
}
@GuardedBy("mLock")
@VisibleForTesting
protected UserState getOrCreateCurrentUserStateLocked() {
return getOrCreateUserStateLocked(ActivityManager.getCurrentUser());
}
@GuardedBy("mLock")
@VisibleForTesting
protected UserState getOrCreateUserStateLocked(int userId) {
UserState result = mUserStates.get(userId);
if (result == null) {
result = new UserState(userId, mContext, mLock, mAttentionHandler, mComponentName);
mUserStates.put(userId, result);
}
return result;
}
@GuardedBy("mLock")
@Nullable
@VisibleForTesting
protected UserState peekCurrentUserStateLocked() {
return peekUserStateLocked(ActivityManager.getCurrentUser());
}
@GuardedBy("mLock")
@Nullable
private UserState peekUserStateLocked(int userId) {
return mUserStates.get(userId);
}
private static String getServiceConfigPackage(Context context) {
return context.getPackageManager().getAttentionServicePackageName();
}
/**
* Provides attention service component name at runtime, making sure it's provided by the
* system.
*/
private static ComponentName resolveAttentionService(Context context) {
final String serviceConfigPackage = getServiceConfigPackage(context);
String resolvedPackage;
int flags = PackageManager.MATCH_SYSTEM_ONLY;
if (!TextUtils.isEmpty(sTestAttentionServicePackage)) {
resolvedPackage = sTestAttentionServicePackage;
flags = PackageManager.GET_META_DATA;
} else if (!TextUtils.isEmpty(serviceConfigPackage)) {
resolvedPackage = serviceConfigPackage;
} else {
return null;
}
final Intent intent = new Intent(AttentionService.SERVICE_INTERFACE).setPackage(
resolvedPackage);
final ResolveInfo resolveInfo = context.getPackageManager().resolveService(intent, flags);
if (resolveInfo == null || resolveInfo.serviceInfo == null) {
Slog.wtf(LOG_TAG, String.format("Service %s not found in package %s",
AttentionService.SERVICE_INTERFACE, serviceConfigPackage
));
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(IndentingPrintWriter ipw) {
ipw.println("Attention Manager Service (dumpsys attention) state:\n");
ipw.println("AttentionServicePackageName=" + getServiceConfigPackage(mContext));
ipw.println("Resolved component:");
if (mComponentName != null) {
ipw.increaseIndent();
ipw.println("Component=" + mComponentName.getPackageName());
ipw.println("Class=" + mComponentName.getClassName());
ipw.decreaseIndent();
}
synchronized (mLock) {
int size = mUserStates.size();
ipw.print("Number user states: ");
ipw.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(long timeout, AttentionCallbackInternal callbackInternal) {
return AttentionManagerService.this.checkAttention(timeout, callbackInternal);
}
@Override
public void cancelAttentionCheck(AttentionCallbackInternal callbackInternal) {
AttentionManagerService.this.cancelAttentionCheck(callbackInternal);
}
}
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;
}
}
@VisibleForTesting
static final class AttentionCheck {
private final AttentionCallbackInternal mCallbackInternal;
private final IAttentionCallback mIAttentionCallback;
private boolean mIsDispatched;
private boolean mIsFulfilled;
AttentionCheck(AttentionCallbackInternal callbackInternal,
IAttentionCallback iAttentionCallback) {
mCallbackInternal = callbackInternal;
mIAttentionCallback = iAttentionCallback;
}
void cancelInternal() {
mIsFulfilled = true;
mCallbackInternal.onFailure(ATTENTION_FAILURE_CANCELLED);
}
}
@VisibleForTesting
protected static class UserState {
private final ComponentName mComponentName;
private final AttentionServiceConnection mConnection = new AttentionServiceConnection();
@GuardedBy("mLock")
IAttentionService mService;
@GuardedBy("mLock")
AttentionCheck mCurrentAttentionCheck;
@GuardedBy("mLock")
AttentionCheckCache mAttentionCheckCache;
@GuardedBy("mLock")
private boolean mBinding;
@UserIdInt
private final int mUserId;
private final Context mContext;
private final Object mLock;
private final Handler mAttentionHandler;
UserState(int userId, Context context, Object lock, Handler handler,
ComponentName componentName) {
mUserId = userId;
mContext = Preconditions.checkNotNull(context);
mLock = Preconditions.checkNotNull(lock);
mComponentName = Preconditions.checkNotNull(componentName);
mAttentionHandler = handler;
}
@GuardedBy("mLock")
private void handlePendingCallbackLocked() {
if (!mCurrentAttentionCheck.mIsDispatched) {
if (mService != null) {
try {
mService.checkAttention(mCurrentAttentionCheck.mIAttentionCallback);
mCurrentAttentionCheck.mIsDispatched = true;
} catch (RemoteException e) {
Slog.e(LOG_TAG, "Cannot call into the AttentionService");
}
} else {
mCurrentAttentionCheck.mCallbackInternal.onFailure(ATTENTION_FAILURE_UNKNOWN);
}
}
}
/** Binds to the system's AttentionService which provides an actual implementation. */
@GuardedBy("mLock")
private void bindLocked() {
// No need to bind if service is binding or has already been bound.
if (mBinding || mService != null) {
return;
}
mBinding = true;
// mContext.bindServiceAsUser() calls into ActivityManagerService which it may already
// hold the lock and had called into PowerManagerService, which holds a lock.
// That would create a deadlock. To solve that, putting it on a handler.
mAttentionHandler.post(() -> {
final Intent serviceIntent = new Intent(
AttentionService.SERVICE_INTERFACE).setComponent(
mComponentName);
// Note: no reason to clear the calling identity, we won't have one in a handler.
mContext.bindServiceAsUser(serviceIntent, mConnection,
Context.BIND_AUTO_CREATE, UserHandle.CURRENT);
});
}
private void dump(IndentingPrintWriter pw) {
pw.println("userId=" + mUserId);
synchronized (mLock) {
pw.println("binding=" + mBinding);
pw.println("current attention check:");
if (mCurrentAttentionCheck != null) {
pw.increaseIndent();
pw.println("is dispatched=" + mCurrentAttentionCheck.mIsDispatched);
pw.println("is fulfilled:=" + mCurrentAttentionCheck.mIsFulfilled);
pw.decreaseIndent();
}
pw.println("attention check cache:");
if (mAttentionCheckCache != null) {
pw.increaseIndent();
pw.println("last computed=" + mAttentionCheckCache.mLastComputed);
pw.println("timestamp=" + mAttentionCheckCache.mTimestamp);
pw.println("result=" + mAttentionCheckCache.mResult);
pw.decreaseIndent();
}
}
}
private 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();
}
}
}
}
@VisibleForTesting
protected class AttentionHandler extends Handler {
private static final int CHECK_CONNECTION_EXPIRATION = 1;
private static final int ATTENTION_CHECK_TIMEOUT = 2;
AttentionHandler() {
super(Looper.myLooper());
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
// Do not occupy resources when not in use - unbind proactively.
case CHECK_CONNECTION_EXPIRATION: {
for (int i = 0; i < mUserStates.size(); i++) {
cancelAndUnbindLocked(mUserStates.valueAt(i));
}
}
break;
// Callee is no longer interested in the attention check result - cancel.
case ATTENTION_CHECK_TIMEOUT: {
synchronized (mLock) {
cancel(peekCurrentUserStateLocked());
}
}
break;
default:
break;
}
}
}
@VisibleForTesting
void cancel(UserState userState) {
if (userState == null || userState.mCurrentAttentionCheck == null) {
return;
}
if (userState.mCurrentAttentionCheck.mIsFulfilled) {
if (DEBUG) {
Slog.d(LOG_TAG, "Trying to cancel the check that has been already fulfilled.");
}
return;
}
if (userState.mService == null) {
userState.mCurrentAttentionCheck.cancelInternal();
return;
}
try {
userState.mService.cancelAttentionCheck(
userState.mCurrentAttentionCheck.mIAttentionCallback);
} catch (RemoteException e) {
Slog.e(LOG_TAG, "Unable to cancel attention check");
userState.mCurrentAttentionCheck.cancelInternal();
}
}
@GuardedBy("mLock")
private void cancelAndUnbindLocked(UserState userState) {
synchronized (mLock) {
if (userState == null) {
return;
}
cancel(userState);
if (userState.mService == null) {
return;
}
mAttentionHandler.post(() -> mContext.unbindService(userState.mConnection));
// Note: this will set mBinding to false even though it could still be trying to bind
// (i.e. the runnable was posted in bindLocked but then cancelAndUnbindLocked was
// called before it's run yet). This is a safe state at the moment,
// since it will eventually, but feels like a source for confusion down the road and
// may cause some expensive and unnecessary work to be done.
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());
}
}
}
private final class AttentionManagerServiceShellCommand extends ShellCommand {
class TestableAttentionCallbackInternal extends AttentionCallbackInternal {
private int mLastCallbackCode = -1;
@Override
public void onSuccess(int result, long timestamp) {
mLastCallbackCode = result;
}
@Override
public void onFailure(int error) {
mLastCallbackCode = error;
}
public void reset() {
mLastCallbackCode = -1;
}
public int getLastCallbackCode() {
return mLastCallbackCode;
}
}
final TestableAttentionCallbackInternal mTestableAttentionCallback =
new TestableAttentionCallbackInternal();
@Override
public int onCommand(@Nullable final String cmd) {
if (cmd == null) {
return handleDefaultCommands(cmd);
}
final PrintWriter err = getErrPrintWriter();
try {
switch (cmd) {
case "getAttentionServiceComponent":
return cmdResolveAttentionServiceComponent();
case "call":
switch (getNextArgRequired()) {
case "checkAttention":
return cmdCallCheckAttention();
case "cancelCheckAttention":
return cmdCallCancelAttention();
default:
throw new IllegalArgumentException("Invalid argument");
}
case "setTestableAttentionService":
return cmdSetTestableAttentionService(getNextArgRequired());
case "clearTestableAttentionService":
return cmdClearTestableAttentionService();
case "getLastTestCallbackCode":
return cmdGetLastTestCallbackCode();
default:
return handleDefaultCommands(cmd);
}
} catch (IllegalArgumentException e) {
err.println("Error: " + e.getMessage());
}
return -1;
}
private int cmdSetTestableAttentionService(String testingServicePackage) {
final PrintWriter out = getOutPrintWriter();
if (TextUtils.isEmpty(testingServicePackage)) {
out.println("false");
} else {
sTestAttentionServicePackage = testingServicePackage;
resetStates();
out.println(mComponentName != null ? "true" : "false");
}
return 0;
}
private int cmdClearTestableAttentionService() {
sTestAttentionServicePackage = "";
mTestableAttentionCallback.reset();
resetStates();
return 0;
}
private int cmdCallCheckAttention() {
final PrintWriter out = getOutPrintWriter();
boolean calledSuccessfully = checkAttention(2000, mTestableAttentionCallback);
out.println(calledSuccessfully ? "true" : "false");
return 0;
}
private int cmdCallCancelAttention() {
final PrintWriter out = getOutPrintWriter();
cancelAttentionCheck(mTestableAttentionCallback);
out.println("true");
return 0;
}
private int cmdResolveAttentionServiceComponent() {
final PrintWriter out = getOutPrintWriter();
ComponentName resolvedComponent = resolveAttentionService(mContext);
out.println(resolvedComponent != null ? resolvedComponent.flattenToShortString() : "");
return 0;
}
private int cmdGetLastTestCallbackCode() {
final PrintWriter out = getOutPrintWriter();
out.println(mTestableAttentionCallback.getLastCallbackCode());
return 0;
}
private void resetStates() {
mComponentName = resolveAttentionService(mContext);
mUserStates.clear();
}
@Override
public void onHelp() {
final PrintWriter out = getOutPrintWriter();
out.println("Attention commands: ");
out.println(" setTestableAttentionService <service_package>: Bind to a custom"
+ " implementation of attention service");
out.println(" ---<service_package>:");
out.println(
" := Package containing the Attention Service implementation to bind to");
out.println(" ---returns:");
out.println(" := true, if was bound successfully");
out.println(" := false, if was not bound successfully");
out.println(" clearTestableAttentionService: Undo custom bindings. Revert to previous"
+ " behavior");
out.println(" getAttentionServiceComponent: Get the current service component string");
out.println(" ---returns:");
out.println(" := If valid, the component string (in shorten form) for the"
+ " currently bound service.");
out.println(" := else, empty string");
out.println(" call checkAttention: Calls check attention");
out.println(" ---returns:");
out.println(
" := true, if the call was successfully dispatched to the service "
+ "implementation."
+ " (to see the result, call getLastTestCallbackCode)");
out.println(" := false, otherwise");
out.println(" call cancelCheckAttention: Cancels check attention");
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");
}
}
private final class BinderService extends Binder {
AttentionManagerServiceShellCommand mAttentionManagerServiceShellCommand =
new AttentionManagerServiceShellCommand();
@Override
public void onShellCommand(FileDescriptor in, FileDescriptor out,
FileDescriptor err,
String[] args, ShellCallback callback,
ResultReceiver resultReceiver) {
mAttentionManagerServiceShellCommand.exec(this, in, out, err, args, callback,
resultReceiver);
}
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) {
return;
}
dumpInternal(new IndentingPrintWriter(pw, " "));
}
}
}