Initial implementation of Intelligence Service Shell commands.
Bug: 111276913
Test: adb shell cmd intelligence
Test: cts-tradefed run commandAndExit cts-instant-dev -m CtsContentCaptureServiceTestCases
Test: atest CtsContentCaptureServiceTestCases CtsAutoFillServiceTestCases
Change-Id: Icc677d03db803fb66d7ee14a0375a765b8941bc2
diff --git a/api/system-current.txt b/api/system-current.txt
index 7a2c233..6477b3a 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -108,6 +108,7 @@
field public static final java.lang.String MANAGE_DEVICE_ADMINS = "android.permission.MANAGE_DEVICE_ADMINS";
field public static final java.lang.String MANAGE_IPSEC_TUNNELS = "android.permission.MANAGE_IPSEC_TUNNELS";
field public static final java.lang.String MANAGE_ROLE_HOLDERS = "android.permission.MANAGE_ROLE_HOLDERS";
+ field public static final java.lang.String MANAGE_SMART_SUGGESTIONS = "android.permission.MANAGE_SMART_SUGGESTIONS";
field public static final java.lang.String MANAGE_SOUND_TRIGGER = "android.permission.MANAGE_SOUND_TRIGGER";
field public static final java.lang.String MANAGE_SUBSCRIPTION_PLANS = "android.permission.MANAGE_SUBSCRIPTION_PLANS";
field public static final java.lang.String MANAGE_USB = "android.permission.MANAGE_USB";
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 83f3057..df17b4c 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -4170,6 +4170,11 @@
<permission android:name="android.permission.MANAGE_AUTO_FILL"
android:protectionLevel="signature" />
+ <!-- @SystemApi Allows an application to manage the smart suggestions service.
+ @hide <p>Not for use by third-party applications.</p> -->
+ <permission android:name="android.permission.MANAGE_SMART_SUGGESTIONS"
+ android:protectionLevel="signature" />
+
<!-- Allows an app to set the theme overlay in /vendor/overlay
being used.
@hide <p>Not for use by third-party applications.</p> -->
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index cb55231..83e8369 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -130,6 +130,7 @@
<uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" />
<uses-permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" />
<uses-permission android:name="android.permission.MANAGE_AUTO_FILL" />
+ <uses-permission android:name="android.permission.MANAGE_SMART_SUGGESTIONS" />
<uses-permission android:name="android.permission.NETWORK_SETTINGS" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.SET_TIME" />
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index c56f31e..0da07ae 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -175,10 +175,6 @@
}
};
- // TODO(b/117779333): move to superclass / create super-class for ShellCommand
- @GuardedBy("mLock")
- private boolean mAllowInstantService;
-
/**
* Supported modes for Augmented Autofill Smart Suggestions.
*/
@@ -271,6 +267,11 @@
addCompatibilityModeRequestsLocked(service, userId);
}
+ @Override // from AbstractMasterSystemService
+ protected void enforceCallingPermissionForManagement() {
+ getContext().enforceCallingPermission(MANAGE_AUTO_FILL, TAG);
+ }
+
@Override // from SystemService
public void onStart() {
publishBinderService(AUTOFILL_MANAGER_SERVICE, new AutoFillManagerServiceStub());
@@ -290,7 +291,7 @@
// Called by Shell command.
void destroySessions(@UserIdInt int userId, IResultReceiver receiver) {
Slog.i(TAG, "destroySessions() for userId " + userId);
- getContext().enforceCallingPermission(MANAGE_AUTO_FILL, TAG);
+ enforceCallingPermissionForManagement();
synchronized (mLock) {
if (userId != UserHandle.USER_ALL) {
@@ -313,7 +314,7 @@
// Called by Shell command.
void listSessions(int userId, IResultReceiver receiver) {
Slog.i(TAG, "listSessions() for userId " + userId);
- getContext().enforceCallingPermission(MANAGE_AUTO_FILL, TAG);
+ enforceCallingPermissionForManagement();
final Bundle resultData = new Bundle();
final ArrayList<String> sessions = new ArrayList<>();
@@ -340,7 +341,7 @@
// Called by Shell command.
void reset() {
Slog.i(TAG, "reset()");
- getContext().enforceCallingPermission(MANAGE_AUTO_FILL, TAG);
+ enforceCallingPermissionForManagement();
synchronized (mLock) {
visitServicesLocked((s) -> s.destroyLocked());
@@ -351,7 +352,7 @@
// Called by Shell command.
void setLogLevel(int level) {
Slog.i(TAG, "setLogLevel(): " + level);
- getContext().enforceCallingPermission(MANAGE_AUTO_FILL, TAG);
+ enforceCallingPermissionForManagement();
final long token = Binder.clearCallingIdentity();
try {
@@ -388,7 +389,7 @@
// Called by Shell command.
int getLogLevel() {
- getContext().enforceCallingPermission(MANAGE_AUTO_FILL, TAG);
+ enforceCallingPermissionForManagement();
synchronized (mLock) {
if (sVerbose) return AutofillManager.FLAG_ADD_CLIENT_VERBOSE;
@@ -399,7 +400,7 @@
// Called by Shell command.
int getMaxPartitions() {
- getContext().enforceCallingPermission(MANAGE_AUTO_FILL, TAG);
+ enforceCallingPermissionForManagement();
synchronized (mLock) {
return sPartitionMaxCount;
@@ -408,8 +409,8 @@
// Called by Shell command.
void setMaxPartitions(int max) {
- getContext().enforceCallingPermission(MANAGE_AUTO_FILL, TAG);
Slog.i(TAG, "setMaxPartitions(): " + max);
+ enforceCallingPermissionForManagement();
final long token = Binder.clearCallingIdentity();
try {
@@ -433,7 +434,7 @@
// Called by Shell command.
int getMaxVisibleDatasets() {
- getContext().enforceCallingPermission(MANAGE_AUTO_FILL, TAG);
+ enforceCallingPermissionForManagement();
synchronized (sLock) {
return sVisibleDatasetsMaxCount;
@@ -442,8 +443,8 @@
// Called by Shell command.
void setMaxVisibleDatasets(int max) {
- getContext().enforceCallingPermission(MANAGE_AUTO_FILL, TAG);
Slog.i(TAG, "setMaxVisibleDatasets(): " + max);
+ enforceCallingPermissionForManagement();
final long token = Binder.clearCallingIdentity();
try {
@@ -480,7 +481,7 @@
// Called by Shell command.
void getScore(@Nullable String algorithmName, @NonNull String value1,
@NonNull String value2, @NonNull RemoteCallback callback) {
- getContext().enforceCallingPermission(MANAGE_AUTO_FILL, TAG);
+ enforceCallingPermissionForManagement();
final FieldClassificationStrategy strategy =
new FieldClassificationStrategy(getContext(), UserHandle.USER_CURRENT);
@@ -491,33 +492,16 @@
// Called by Shell command.
Boolean getFullScreenMode() {
- getContext().enforceCallingPermission(MANAGE_AUTO_FILL, TAG);
+ enforceCallingPermissionForManagement();
return sFullScreenMode;
}
// Called by Shell command.
void setFullScreenMode(@Nullable Boolean mode) {
- getContext().enforceCallingPermission(MANAGE_AUTO_FILL, TAG);
+ enforceCallingPermissionForManagement();
sFullScreenMode = mode;
}
- // Called by Shell command.
- boolean getAllowInstantService() {
- getContext().enforceCallingPermission(MANAGE_AUTO_FILL, TAG);
- synchronized (mLock) {
- return mAllowInstantService;
- }
- }
-
- // Called by Shell command.
- void setAllowInstantService(boolean mode) {
- getContext().enforceCallingPermission(MANAGE_AUTO_FILL, TAG);
- Slog.i(TAG, "setAllowInstantService(): " + mode);
- synchronized (mLock) {
- mAllowInstantService = mode;
- }
- }
-
private void setLoggingLevelsLocked(boolean debug, boolean verbose) {
com.android.server.autofill.Helper.sDebug = debug;
android.view.autofill.Helper.sDebug = debug;
@@ -1218,7 +1202,6 @@
mAutofillCompatState.dump(prefix, pw);
pw.print("from settings: ");
pw.println(getWhitelistedCompatModePackagesFromSettings());
- pw.print("Allow instant service: "); pw.println(mAllowInstantService);
if (mSupportedSmartSuggestionModes != 0) {
pw.print("Smart Suggestion modes: ");
pw.println(smartSuggestionFlagsToString(mSupportedSmartSuggestionModes));
diff --git a/services/core/java/com/android/server/AbstractMasterSystemService.java b/services/core/java/com/android/server/AbstractMasterSystemService.java
index 9c1e3cd..1759ce1 100644
--- a/services/core/java/com/android/server/AbstractMasterSystemService.java
+++ b/services/core/java/com/android/server/AbstractMasterSystemService.java
@@ -93,6 +93,12 @@
public boolean debug = false;
/**
+ * Whether the service is allowed to bind to an instant-app.
+ */
+ @GuardedBy("mLock")
+ protected boolean mAllowInstantService;
+
+ /**
* Users disabled due to {@link UserManager} restrictions, or {@code null} if the service cannot
* be disabled through {@link UserManager}.
*/
@@ -176,6 +182,47 @@
}
/**
+ * Gets whether the service is allowed to bind to an instant-app.
+ *
+ * <p>Typically called by {@code ShellCommand} during CTS tests.
+ *
+ * @throws SecurityException if caller is not allowed to manage this service's settings.
+ */
+ public final boolean getAllowInstantService() {
+ enforceCallingPermissionForManagement();
+ synchronized (mLock) {
+ return mAllowInstantService;
+ }
+ }
+
+ /**
+ * Sets whether the service is allowed to bind to an instant-app.
+ *
+ * <p>Typically called by {@code ShellCommand} during CTS tests.
+ *
+ * @throws SecurityException if caller is not allowed to manage this service's settings.
+ */
+ public final void setAllowInstantService(boolean mode) {
+ Slog.i(mTag, "setAllowInstantService(): " + mode);
+ enforceCallingPermissionForManagement();
+ synchronized (mLock) {
+ mAllowInstantService = mode;
+ }
+ }
+
+ /**
+ * Asserts that the caller has permissions to manage this service.
+ *
+ * <p>Typically called by {@code ShellCommand} implementations.
+ *
+ * @throws UnsupportedOperationException if subclass doesn't override it.
+ * @throws SecurityException if caller is not allowed to manage this service's settings.
+ */
+ protected void enforceCallingPermissionForManagement() {
+ throw new UnsupportedOperationException("Not implemented by " + getClass());
+ }
+
+ /**
* Creates a new service that will be added to the cache.
*
* @param resolvedUserId the resolved user id for the service.
@@ -362,6 +409,7 @@
pw.print(prefix); pw.print("Debug: "); pw.print(realDebug);
pw.print(" Verbose: "); pw.println(realVerbose);
pw.print(prefix); pw.print("Disabled users: "); pw.println(mDisabledUsers);
+ pw.print(prefix); pw.print("Allow instant service: "); pw.println(mAllowInstantService);
pw.print(prefix); pw.print("Settings property: "); pw.println(
getServiceSettingsProperty());
pw.print(prefix); pw.print("Cached services: ");
diff --git a/services/intelligence/java/com/android/server/intelligence/ContentCaptureSession.java b/services/intelligence/java/com/android/server/intelligence/ContentCaptureSession.java
index 108f91c..14912c4 100644
--- a/services/intelligence/java/com/android/server/intelligence/ContentCaptureSession.java
+++ b/services/intelligence/java/com/android/server/intelligence/ContentCaptureSession.java
@@ -146,7 +146,7 @@
}
@Override // from RemoteScreenObservationServiceCallbacks
- public void onServiceDied(AbstractRemoteService service) {
+ public void onServiceDied(AbstractRemoteService<?> service) {
// TODO(b/111276913): implement (remove session from PerUserSession?)
if (mService.isDebug()) {
Slog.d(TAG, "onServiceDied() for " + mId);
@@ -176,6 +176,10 @@
pw.println(mAutofillCallback != null);
}
+ String toShortString() {
+ return mId.getValue() + ":" + mActivityToken;
+ }
+
@Override
public String toString() {
return "ContentCaptureSession[id=" + mId.getValue() + ", act=" + mActivityToken + "]";
diff --git a/services/intelligence/java/com/android/server/intelligence/IntelligenceManagerService.java b/services/intelligence/java/com/android/server/intelligence/IntelligenceManagerService.java
index e0d47d2..4c68064 100644
--- a/services/intelligence/java/com/android/server/intelligence/IntelligenceManagerService.java
+++ b/services/intelligence/java/com/android/server/intelligence/IntelligenceManagerService.java
@@ -16,6 +16,7 @@
package com.android.server.intelligence;
+import static android.Manifest.permission.MANAGE_SMART_SUGGESTIONS;
import static android.content.Context.CONTENT_CAPTURE_MANAGER_SERVICE;
import android.annotation.NonNull;
@@ -26,8 +27,13 @@
import android.content.Context;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
+import android.os.UserHandle;
import android.os.UserManager;
import android.service.intelligence.InteractionSessionId;
+import android.util.Slog;
import android.view.autofill.AutofillId;
import android.view.autofill.IAutoFillManagerClient;
import android.view.intelligence.ContentCaptureEvent;
@@ -42,6 +48,7 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.List;
/**
@@ -56,6 +63,8 @@
private static final String TAG = "IntelligenceManagerService";
+ static final String RECEIVER_BUNDLE_EXTRA_SESSIONS = "sessions";
+
@GuardedBy("mLock")
private ActivityManagerInternal mAm;
@@ -90,6 +99,61 @@
service.destroyLocked();
}
+ @Override // from AbstractMasterSystemService
+ protected void enforceCallingPermissionForManagement() {
+ getContext().enforceCallingPermission(MANAGE_SMART_SUGGESTIONS, TAG);
+ }
+
+ // Called by Shell command.
+ void destroySessions(@UserIdInt int userId, @NonNull IResultReceiver receiver) {
+ Slog.i(TAG, "destroySessions() for userId " + userId);
+ enforceCallingPermissionForManagement();
+
+ synchronized (mLock) {
+ if (userId != UserHandle.USER_ALL) {
+ final IntelligencePerUserService service = peekServiceForUserLocked(userId);
+ if (service != null) {
+ service.destroySessionsLocked();
+ }
+ } else {
+ visitServicesLocked((s) -> s.destroySessionsLocked());
+ }
+ }
+
+ try {
+ receiver.send(0, new Bundle());
+ } catch (RemoteException e) {
+ // Just ignore it...
+ }
+ }
+
+ // Called by Shell command.
+ void listSessions(int userId, IResultReceiver receiver) {
+ Slog.i(TAG, "listSessions() for userId " + userId);
+ enforceCallingPermissionForManagement();
+
+ final Bundle resultData = new Bundle();
+ final ArrayList<String> sessions = new ArrayList<>();
+
+ synchronized (mLock) {
+ if (userId != UserHandle.USER_ALL) {
+ final IntelligencePerUserService service = peekServiceForUserLocked(userId);
+ if (service != null) {
+ service.listSessionsLocked(sessions);
+ }
+ } else {
+ visitServicesLocked((s) -> s.listSessionsLocked(sessions));
+ }
+ }
+
+ resultData.putStringArrayList(RECEIVER_BUNDLE_EXTRA_SESSIONS, sessions);
+ try {
+ receiver.send(0, resultData);
+ } catch (RemoteException e) {
+ // Just ignore it...
+ }
+ }
+
private ActivityManagerInternal getAmInternal() {
synchronized (mLock) {
if (mAm == null) {
@@ -119,7 +183,7 @@
synchronized (mLock) {
final IntelligencePerUserService service = getServiceForUserLocked(userId);
service.startSessionLocked(activityToken, componentName, taskId, displayId,
- sessionId, flags, result);
+ sessionId, flags, mAllowInstantService, result);
}
}
@@ -154,6 +218,14 @@
dumpLocked("", pw);
}
}
+
+ @Override
+ public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+ String[] args, ShellCallback callback, ResultReceiver resultReceiver)
+ throws RemoteException {
+ new IntelligenceServiceShellCommand(IntelligenceManagerService.this).exec(
+ this, in, out, err, args, callback, resultReceiver);
+ }
}
private final class LocalService extends IntelligenceManagerInternal {
diff --git a/services/intelligence/java/com/android/server/intelligence/IntelligencePerUserService.java b/services/intelligence/java/com/android/server/intelligence/IntelligencePerUserService.java
index e3b09c6..dbf8601 100644
--- a/services/intelligence/java/com/android/server/intelligence/IntelligencePerUserService.java
+++ b/services/intelligence/java/com/android/server/intelligence/IntelligencePerUserService.java
@@ -49,6 +49,7 @@
import com.android.server.intelligence.IntelligenceManagerInternal.AugmentedAutofillCallback;
import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.List;
/**
@@ -108,7 +109,7 @@
@GuardedBy("mLock")
public void startSessionLocked(@NonNull IBinder activityToken,
@NonNull ComponentName componentName, int taskId, int displayId,
- @NonNull InteractionSessionId sessionId, int flags,
+ @NonNull InteractionSessionId sessionId, int flags, boolean bindInstantServiceAllowed,
@NonNull IResultReceiver resultReceiver) {
if (!isEnabledLocked()) {
sendToClient(resultReceiver, ContentCaptureManager.STATE_DISABLED);
@@ -138,9 +139,6 @@
return;
}
- // TODO(b/117779333): get from mMaster once it's moved to superclass
- final boolean bindInstantServiceAllowed = false;
-
session = new ContentCaptureSession(getContext(), mUserId, mLock, activityToken,
this, serviceComponentName, componentName, taskId, displayId, sessionId, flags,
bindInstantServiceAllowed, mMaster.verbose);
@@ -253,6 +251,11 @@
@GuardedBy("mLock")
public void destroyLocked() {
if (mMaster.debug) Slog.d(TAG, "destroyLocked()");
+ destroySessionsLocked();
+ }
+
+ @GuardedBy("mLock")
+ void destroySessionsLocked() {
final int numSessions = mSessions.size();
for (int i = 0; i < numSessions; i++) {
final ContentCaptureSession session = mSessions.valueAt(i);
@@ -261,6 +264,15 @@
mSessions.clear();
}
+ @GuardedBy("mLock")
+ void listSessionsLocked(ArrayList<String> output) {
+ final int numSessions = mSessions.size();
+ for (int i = 0; i < numSessions; i++) {
+ final ContentCaptureSession session = mSessions.valueAt(i);
+ output.add(session.toShortString());
+ }
+ }
+
public AugmentedAutofillCallback requestAutofill(@NonNull IAutoFillManagerClient client,
@NonNull IBinder activityToken, int autofillSessionId, @NonNull AutofillId focusedId) {
synchronized (mLock) {
diff --git a/services/intelligence/java/com/android/server/intelligence/IntelligenceServiceShellCommand.java b/services/intelligence/java/com/android/server/intelligence/IntelligenceServiceShellCommand.java
new file mode 100644
index 0000000..b7c1f78
--- /dev/null
+++ b/services/intelligence/java/com/android/server/intelligence/IntelligenceServiceShellCommand.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2018 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.intelligence;
+
+import static com.android.server.intelligence.IntelligenceManagerService.RECEIVER_BUNDLE_EXTRA_SESSIONS;
+
+import android.annotation.NonNull;
+import android.os.Bundle;
+import android.os.ShellCommand;
+import android.os.UserHandle;
+
+import com.android.internal.os.IResultReceiver;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Shell Command implementation for {@link IntelligenceManagerService}.
+ */
+//TODO(b/111276913): rename once the final name is defined
+public final class IntelligenceServiceShellCommand extends ShellCommand {
+
+ private final IntelligenceManagerService mService;
+
+ public IntelligenceServiceShellCommand(@NonNull IntelligenceManagerService service) {
+ mService = service;
+ }
+
+ @Override
+ public int onCommand(String cmd) {
+ if (cmd == null) {
+ return handleDefaultCommands(cmd);
+ }
+ final PrintWriter pw = getOutPrintWriter();
+ switch (cmd) {
+ case "list":
+ return requestList(pw);
+ case "destroy":
+ return requestDestroy(pw);
+ case "get":
+ return requestGet(pw);
+ case "set":
+ return requestSet(pw);
+ default:
+ return handleDefaultCommands(cmd);
+ }
+ }
+
+ @Override
+ public void onHelp() {
+ try (PrintWriter pw = getOutPrintWriter();) {
+ // TODO(b/111276913): rename "intelligence" once SELinux rule changed
+ pw.println("Intelligence Service (intelligence) commands:");
+ pw.println(" help");
+ pw.println(" Prints this help text.");
+ pw.println("");
+ pw.println(" get bind-instant-service-allowed");
+ pw.println(" Gets whether binding to services provided by instant apps is allowed");
+ pw.println("");
+ pw.println(" set bind-instant-service-allowed [true | false]");
+ pw.println(" Sets whether binding to services provided by instant apps is allowed");
+ pw.println("");
+ pw.println(" list sessions [--user USER_ID]");
+ pw.println(" Lists all pending sessions.");
+ pw.println("");
+ pw.println(" destroy sessions [--user USER_ID]");
+ pw.println(" Destroys all pending sessions.");
+ pw.println("");
+ }
+ }
+
+ private int requestGet(PrintWriter pw) {
+ final String what = getNextArgRequired();
+ switch(what) {
+ case "bind-instant-service-allowed":
+ return getBindInstantService(pw);
+ default:
+ pw.println("Invalid set: " + what);
+ return -1;
+ }
+ }
+
+ private int requestSet(PrintWriter pw) {
+ final String what = getNextArgRequired();
+
+ switch(what) {
+ case "bind-instant-service-allowed":
+ return setBindInstantService(pw);
+ default:
+ pw.println("Invalid set: " + what);
+ return -1;
+ }
+ }
+
+ private int getBindInstantService(PrintWriter pw) {
+ if (mService.getAllowInstantService()) {
+ pw.println("true");
+ } else {
+ pw.println("false");
+ }
+ return 0;
+ }
+
+ private int setBindInstantService(PrintWriter pw) {
+ final String mode = getNextArgRequired();
+ switch (mode.toLowerCase()) {
+ case "true":
+ mService.setAllowInstantService(true);
+ return 0;
+ case "false":
+ mService.setAllowInstantService(false);
+ return 0;
+ default:
+ pw.println("Invalid mode: " + mode);
+ return -1;
+ }
+ }
+
+ private int requestDestroy(PrintWriter pw) {
+ if (!isNextArgSessions(pw)) {
+ return -1;
+ }
+
+ final int userId = getUserIdFromArgsOrAllUsers();
+ final CountDownLatch latch = new CountDownLatch(1);
+ final IResultReceiver receiver = new IResultReceiver.Stub() {
+ @Override
+ public void send(int resultCode, Bundle resultData) {
+ latch.countDown();
+ }
+ };
+ return requestSessionCommon(pw, latch, () -> mService.destroySessions(userId, receiver));
+ }
+
+ private int requestList(PrintWriter pw) {
+ if (!isNextArgSessions(pw)) {
+ return -1;
+ }
+
+ final int userId = getUserIdFromArgsOrAllUsers();
+ final CountDownLatch latch = new CountDownLatch(1);
+ final IResultReceiver receiver = new IResultReceiver.Stub() {
+ @Override
+ public void send(int resultCode, Bundle resultData) {
+ final ArrayList<String> sessions = resultData
+ .getStringArrayList(RECEIVER_BUNDLE_EXTRA_SESSIONS);
+ for (String session : sessions) {
+ pw.println(session);
+ }
+ latch.countDown();
+ }
+ };
+ return requestSessionCommon(pw, latch, () -> mService.listSessions(userId, receiver));
+ }
+
+ private boolean isNextArgSessions(PrintWriter pw) {
+ final String type = getNextArgRequired();
+ if (!type.equals("sessions")) {
+ pw.println("Error: invalid list type");
+ return false;
+ }
+ return true;
+ }
+
+ private int requestSessionCommon(PrintWriter pw, CountDownLatch latch,
+ Runnable command) {
+ command.run();
+ return waitForLatch(pw, latch);
+ }
+
+ private int waitForLatch(PrintWriter pw, CountDownLatch latch) {
+ try {
+ final boolean received = latch.await(5, TimeUnit.SECONDS);
+ if (!received) {
+ pw.println("Timed out after 5 seconds");
+ return -1;
+ }
+ } catch (InterruptedException e) {
+ pw.println("System call interrupted");
+ Thread.currentThread().interrupt();
+ return -1;
+ }
+ return 0;
+ }
+
+ private int getUserIdFromArgsOrAllUsers() {
+ if ("--user".equals(getNextArg())) {
+ return UserHandle.parseUserArg(getNextArgRequired());
+ }
+ return UserHandle.USER_ALL;
+ }
+}