/*
|
* 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, " "));
|
}
|
}
|
}
|