diff options
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; +    } +}  |