diff options
| author | 2018-12-05 01:36:10 +0000 | |
|---|---|---|
| committer | 2018-12-05 01:36:10 +0000 | |
| commit | b5857c748fa072d45969a457beea36a9b04eeccc (patch) | |
| tree | 30fa91b70df06fb3ebb5453ba2636de2012df9f9 | |
| parent | b03bf399ee1776c4adec50d511849dfbd86cb9ef (diff) | |
| parent | 5933efbd26146a206a83b48383cf3feaf8bd9ec7 (diff) | |
Merge "Initial implementation of Intelligence Service Shell commands."
9 files changed, 373 insertions, 40 deletions
diff --git a/api/system-current.txt b/api/system-current.txt index 2a5598ebd8ce..0f054841c669 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -108,6 +108,7 @@ package android { 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 83f30573fda8..df17b4c453d9 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 cb552318b908..83e83693375b 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 c56f31efd953..0da07ae52857 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 @@ public final class AutofillManagerService } }; - // 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 @@ public final class AutofillManagerService 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 @@ public final class AutofillManagerService // 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 @@ public final class AutofillManagerService // 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 @@ public final class AutofillManagerService // 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 @@ public final class AutofillManagerService // 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 @@ public final class AutofillManagerService // 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 @@ public final class AutofillManagerService // Called by Shell command. int getMaxPartitions() { - getContext().enforceCallingPermission(MANAGE_AUTO_FILL, TAG); + enforceCallingPermissionForManagement(); synchronized (mLock) { return sPartitionMaxCount; @@ -408,8 +409,8 @@ public final class AutofillManagerService // 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 @@ public final class AutofillManagerService // Called by Shell command. int getMaxVisibleDatasets() { - getContext().enforceCallingPermission(MANAGE_AUTO_FILL, TAG); + enforceCallingPermissionForManagement(); synchronized (sLock) { return sVisibleDatasetsMaxCount; @@ -442,8 +443,8 @@ public final class AutofillManagerService // 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 @@ public final class AutofillManagerService // 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 @@ public final class AutofillManagerService // 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 @@ public final class AutofillManagerService 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 9c1e3cdec5b1..1759ce195485 100644 --- a/services/core/java/com/android/server/AbstractMasterSystemService.java +++ b/services/core/java/com/android/server/AbstractMasterSystemService.java @@ -93,6 +93,12 @@ public abstract class AbstractMasterSystemService<M extends AbstractMasterSystem 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 @@ public abstract class AbstractMasterSystemService<M extends AbstractMasterSystem } /** + * 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 @@ public abstract class AbstractMasterSystemService<M extends AbstractMasterSystem 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 108f91c7fe4c..14912c474995 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 @@ final class ContentCaptureSession implements RemoteIntelligenceServiceCallbacks } @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 @@ final class ContentCaptureSession implements RemoteIntelligenceServiceCallbacks 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 e0d47d2c8802..4c68064b46f6 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.ComponentName; 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 com.android.server.LocalServices; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.util.ArrayList; import java.util.List; /** @@ -56,6 +63,8 @@ public final class IntelligenceManagerService extends private static final String TAG = "IntelligenceManagerService"; + static final String RECEIVER_BUNDLE_EXTRA_SESSIONS = "sessions"; + @GuardedBy("mLock") private ActivityManagerInternal mAm; @@ -90,6 +99,61 @@ public final class IntelligenceManagerService extends 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 @@ public final class IntelligenceManagerService extends synchronized (mLock) { final IntelligencePerUserService service = getServiceForUserLocked(userId); service.startSessionLocked(activityToken, componentName, taskId, displayId, - sessionId, flags, result); + sessionId, flags, mAllowInstantService, result); } } @@ -154,6 +218,14 @@ public final class IntelligenceManagerService extends 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 e3b09c630499..dbf8601849bb 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.AbstractPerUserSystemService; import com.android.server.intelligence.IntelligenceManagerInternal.AugmentedAutofillCallback; import java.io.PrintWriter; +import java.util.ArrayList; import java.util.List; /** @@ -108,7 +109,7 @@ final class IntelligencePerUserService @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 @@ final class IntelligencePerUserService 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 @@ final class IntelligencePerUserService @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 @@ final class IntelligencePerUserService 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 000000000000..b7c1f789e46c --- /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; + } +} |