Add new ExternalVibratorService.

This lets services outside of the vibrator service (and outside of
system_server) take control of the vibrator hardware. We still apply
policy in vibrator service, however, and can ask them to mute their
operations so we can give more critical haptic feedback when necessary.

Bug: 111457573
Test: manual
Change-Id: Ib7d06aa5940790cac627499acb23e4f0bda1b035
diff --git a/Android.bp b/Android.bp
index d4a04cc..9584767 100644
--- a/Android.bp
+++ b/Android.bp
@@ -271,6 +271,7 @@
         "core/java/android/os/IThermalService.aidl",
         "core/java/android/os/IUpdateLock.aidl",
         "core/java/android/os/IUserManager.aidl",
+        ":libvibrator_aidl",
         "core/java/android/os/IVibratorService.aidl",
         "core/java/android/os/storage/IStorageManager.aidl",
         "core/java/android/os/storage/IStorageEventListener.aidl",
@@ -794,6 +795,14 @@
     ],
 }
 
+filegroup {
+    name: "libvibrator_aidl",
+    srcs: [
+        "core/java/android/os/IExternalVibrationController.aidl",
+        "core/java/android/os/IExternalVibratorService.aidl",
+    ],
+}
+
 java_library {
     name: "framework",
     defaults: ["framework-defaults"],
diff --git a/core/java/android/os/ExternalVibration.aidl b/core/java/android/os/ExternalVibration.aidl
new file mode 100644
index 0000000..2629a40
--- /dev/null
+++ b/core/java/android/os/ExternalVibration.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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 android.os;
+
+parcelable ExternalVibration cpp_header "vibrator/ExternalVibration.h";
diff --git a/core/java/android/os/ExternalVibration.java b/core/java/android/os/ExternalVibration.java
new file mode 100644
index 0000000..69ab1d9
--- /dev/null
+++ b/core/java/android/os/ExternalVibration.java
@@ -0,0 +1,171 @@
+/*
+ * 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 android.os;
+
+import android.annotation.NonNull;
+import android.media.AudioAttributes;
+import android.util.Slog;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * An ExternalVibration represents an on-going vibration being controlled by something other than
+ * the core vibrator service.
+ *
+ * @hide
+ */
+public class ExternalVibration implements Parcelable {
+    private static final String TAG = "ExternalVibration";
+    private int mUid;
+    @NonNull
+    private String mPkg;
+    @NonNull
+    private AudioAttributes mAttrs;
+    @NonNull
+    private IExternalVibrationController mController;
+    // A token used to maintain equality comparisons when passing objects across process
+    // boundaries.
+    @NonNull
+    private IBinder mToken;
+
+    public ExternalVibration(int uid, @NonNull String pkg, @NonNull AudioAttributes attrs,
+            @NonNull IExternalVibrationController controller) {
+        mUid = uid;
+        mPkg = Preconditions.checkNotNull(pkg);
+        mAttrs = Preconditions.checkNotNull(attrs);
+        mController = Preconditions.checkNotNull(controller);
+        mToken = new Binder();
+    }
+
+    private ExternalVibration(Parcel in) {
+        mUid = in.readInt();
+        mPkg = in.readString();
+        mAttrs = readAudioAttributes(in);
+        mController = IExternalVibrationController.Stub.asInterface(in.readStrongBinder());
+        mToken = in.readStrongBinder();
+    }
+
+    private AudioAttributes readAudioAttributes(Parcel in) {
+        int usage = in.readInt();
+        int contentType = in.readInt();
+        int capturePreset = in.readInt();
+        int flags = in.readInt();
+        AudioAttributes.Builder builder = new AudioAttributes.Builder();
+        return builder.setUsage(usage)
+                .setContentType(contentType)
+                .setCapturePreset(capturePreset)
+                .setFlags(flags)
+                .build();
+    }
+
+    public int getUid() {
+        return mUid;
+    }
+
+    public String getPackage() {
+        return mPkg;
+    }
+
+    public AudioAttributes getAudioAttributes() {
+        return mAttrs;
+    }
+
+    /**
+     * Mutes the external vibration if it's playing and unmuted.
+     *
+     * @return whether the muting operation was successful
+     */
+    public boolean mute() {
+        try {
+            mController.mute();
+        } catch (RemoteException e) {
+            Slog.wtf(TAG, "Failed to mute vibration stream: " + this, e);
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Unmutes the external vibration if it's playing and muted.
+     *
+     * @return whether the unmuting operation was successful
+     */
+    public boolean unmute() {
+        try {
+            mController.unmute();
+        } catch (RemoteException e) {
+            Slog.wtf(TAG, "Failed to unmute vibration stream: " + this, e);
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == null || !(o instanceof ExternalVibration)) {
+            return false;
+        }
+        ExternalVibration other = (ExternalVibration) o;
+        return mToken.equals(other.mToken);
+    }
+
+    @Override
+    public String toString() {
+        return "ExternalVibration{"
+            + "uid=" + mUid + ", "
+            + "pkg=" + mPkg + ", "
+            + "attrs=" + mAttrs + ", "
+            + "controller=" + mController
+            + "token=" + mController
+            + "}";
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(mUid);
+        out.writeString(mPkg);
+        writeAudioAttributes(mAttrs, out, flags);
+        out.writeParcelable(mAttrs, flags);
+        out.writeStrongBinder(mController.asBinder());
+        out.writeStrongBinder(mToken);
+    }
+
+    private static void writeAudioAttributes(AudioAttributes attrs, Parcel out, int flags) {
+        out.writeInt(attrs.getUsage());
+        out.writeInt(attrs.getContentType());
+        out.writeInt(attrs.getCapturePreset());
+        out.writeInt(attrs.getAllFlags());
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Parcelable.Creator<ExternalVibration> CREATOR =
+            new Parcelable.Creator<ExternalVibration>() {
+                @Override
+                public ExternalVibration createFromParcel(Parcel in) {
+                    return new ExternalVibration(in);
+                }
+
+                @Override
+                public ExternalVibration[] newArray(int size) {
+                    return new ExternalVibration[size];
+                }
+            };
+}
diff --git a/core/java/android/os/IExternalVibrationController.aidl b/core/java/android/os/IExternalVibrationController.aidl
new file mode 100644
index 0000000..56da8c7
--- /dev/null
+++ b/core/java/android/os/IExternalVibrationController.aidl
@@ -0,0 +1,45 @@
+/*
+ * Copyright 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 android.os;
+
+/**
+ * {@hide}
+ */
+
+interface IExternalVibrationController {
+    /**
+     * A method to ask a currently playing vibration to mute (i.e. not vibrate).
+     *
+     * This method is only valid from the time that
+     * {@link IExternalVibratorService#onExternalVibrationStart} returns until
+     * {@link IExternalVibratorService#onExternalVibrationStop} returns.
+     *
+     * @return whether the mute operation was successful
+     */
+    boolean mute();
+
+    /**
+     * A method to ask a currently playing vibration to unmute (i.e. start vibrating).
+     *
+     * This method is only valid from the time that
+     * {@link IExternalVibratorService#onExternalVibrationStart} returns until
+     * {@link IExternalVibratorService#onExternalVibrationStop} returns.
+     *
+     * @return whether the unmute operation was successful
+     */
+    boolean unmute();
+}
diff --git a/core/java/android/os/IExternalVibratorService.aidl b/core/java/android/os/IExternalVibratorService.aidl
new file mode 100644
index 0000000..666171f
--- /dev/null
+++ b/core/java/android/os/IExternalVibratorService.aidl
@@ -0,0 +1,63 @@
+/**
+ * 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 android.os;
+
+import android.os.ExternalVibration;
+
+/**
+ * The communication channel by which an external system that wants to control the system
+ * vibrator can notify the vibrator subsystem.
+ *
+ * Some vibrators can be driven via multiple paths (e.g. as an audio channel) in addition to
+ * the usual interface, but we typically only want one vibration at a time playing because they
+ * don't mix well. In order to synchronize the two places where vibration might be controlled,
+ * we provide this interface so the vibrator subsystem has a chance to:
+ *
+ * 1) Decide whether the current vibration should play based on the current system policy.
+ * 2) Stop any currently on-going vibrations.
+ * {@hide}
+ */
+interface IExternalVibratorService {
+    const int SCALE_MUTE = -100;
+    const int SCALE_VERY_LOW = -2;
+    const int SCALE_LOW = -1;
+    const int SCALE_NONE = 0;
+    const int SCALE_HIGH = 1;
+    const int SCALE_VERY_HIGH = 2;
+
+    /**
+     * A method called by the external system to start a vibration.
+     *
+     * If this returns {@code SCALE_MUTE}, then the vibration should <em>not</em> play. If this
+     * returns any other scale level, then any currently playing vibration controlled by the
+     * requesting system must be muted and this vibration can begin playback.
+     *
+     * Note that the IExternalVibratorService implementation will not call mute on any currently
+     * playing external vibrations in order to avoid re-entrancy with the system on the other side.
+     *
+     * @param vibration An ExternalVibration
+     *
+     * @return {@code SCALE_MUTE} if the external vibration should not play, and any other scale
+     *         level if it should.
+     */
+    int onExternalVibrationStart(in ExternalVibration vib);
+
+    /**
+     * A method called by the external system when a vibration no longer wants to play.
+     */
+    void onExternalVibrationStop(in ExternalVibration vib);
+}
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index ea6d435..f6e698f 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -33,8 +33,10 @@
 import android.media.AudioManager;
 import android.os.BatteryStats;
 import android.os.Binder;
+import android.os.ExternalVibration;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.IExternalVibratorService;
 import android.os.IVibratorService;
 import android.os.PowerManager;
 import android.os.PowerManager.ServiceType;
@@ -75,16 +77,18 @@
     private static final String TAG = "VibratorService";
     private static final boolean DEBUG = false;
     private static final String SYSTEM_UI_PACKAGE = "com.android.systemui";
+    private static final String EXTERNAL_VIBRATOR_SERVICE = "external_vibrator_service";
 
     private static final long[] DOUBLE_CLICK_EFFECT_FALLBACK_TIMINGS = { 0, 30, 100, 30 };
 
-    // Scale levels. Each level is defined as the delta between the current setting and the default
-    // intensity for that type of vibration (i.e. current - default).
-    private static final int SCALE_VERY_LOW = -2;
-    private static final int SCALE_LOW = -1;
-    private static final int SCALE_NONE = 0;
-    private static final int SCALE_HIGH = 1;
-    private static final int SCALE_VERY_HIGH = 2;
+    // Scale levels. Each level, except MUTE, is defined as the delta between the current setting
+    // and the default intensity for that type of vibration (i.e. current - default).
+    private static final int SCALE_MUTE = IExternalVibratorService.SCALE_MUTE; // -100
+    private static final int SCALE_VERY_LOW = IExternalVibratorService.SCALE_VERY_LOW; // -2
+    private static final int SCALE_LOW = IExternalVibratorService.SCALE_LOW; // -1
+    private static final int SCALE_NONE = IExternalVibratorService.SCALE_NONE; // 0
+    private static final int SCALE_HIGH = IExternalVibratorService.SCALE_HIGH; // 1
+    private static final int SCALE_VERY_HIGH = IExternalVibratorService.SCALE_VERY_HIGH; // 2
 
     // Gamma adjustments for scale levels.
     private static final float SCALE_VERY_LOW_GAMMA = 2.0f;
@@ -111,6 +115,7 @@
     private final int mPreviousVibrationsLimit;
     private final boolean mAllowPriorityVibrationsInLowPowerMode;
     private final boolean mSupportsAmplitudeControl;
+    private final boolean mSupportsExternalControl;
     private final int mDefaultVibrationAmplitude;
     private final SparseArray<VibrationEffect> mFallbackEffects;
     private final SparseArray<Integer> mProcStatesCache = new SparseArray();
@@ -138,18 +143,20 @@
     @GuardedBy("mLock")
     private Vibration mCurrentVibration;
     private int mCurVibUid = -1;
+    private ExternalVibration mCurrentExternalVibration;
+    private boolean mVibratorUnderExternalControl;
     private boolean mLowPowerMode;
     private int mHapticFeedbackIntensity;
     private int mNotificationIntensity;
     private int mRingIntensity;
 
-    native static boolean vibratorExists();
-    native static void vibratorInit();
-    native static void vibratorOn(long milliseconds);
-    native static void vibratorOff();
-    native static boolean vibratorSupportsAmplitudeControl();
-    native static void vibratorSetAmplitude(int amplitude);
-    native static long vibratorPerformEffect(long effect, long strength);
+    static native boolean vibratorExists();
+    static native void vibratorInit();
+    static native void vibratorOn(long milliseconds);
+    static native void vibratorOff();
+    static native boolean vibratorSupportsAmplitudeControl();
+    static native void vibratorSetAmplitude(int amplitude);
+    static native long vibratorPerformEffect(long effect, long strength);
     static native boolean vibratorSupportsExternalControl();
     static native void vibratorSetExternalControl(boolean enabled);
 
@@ -218,6 +225,9 @@
         }
 
         public boolean isHapticFeedback() {
+            if (VibratorService.this.isHapticFeedback(usageHint)) {
+                return true;
+            }
             if (effect instanceof VibrationEffect.Prebaked) {
                 VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect;
                 switch (prebaked.getId()) {
@@ -239,19 +249,11 @@
         }
 
         public boolean isNotification() {
-            switch (usageHint) {
-                case AudioAttributes.USAGE_NOTIFICATION:
-                case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST:
-                case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT:
-                case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED:
-                    return true;
-                default:
-                    return false;
-            }
+            return VibratorService.this.isNotification(usageHint);
         }
 
         public boolean isRingtone() {
-            return usageHint == AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
+            return VibratorService.this.isRingtone(usageHint);
         }
 
         public boolean isFromSystem() {
@@ -332,6 +334,7 @@
         vibratorOff();
 
         mSupportsAmplitudeControl = vibratorSupportsAmplitudeControl();
+        mSupportsExternalControl = vibratorSupportsExternalControl();
 
         mContext = context;
         PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
@@ -379,6 +382,8 @@
         mScaleLevels.put(SCALE_NONE, new ScaleLevel(SCALE_NONE_GAMMA));
         mScaleLevels.put(SCALE_HIGH, new ScaleLevel(SCALE_HIGH_GAMMA));
         mScaleLevels.put(SCALE_VERY_HIGH, new ScaleLevel(SCALE_VERY_HIGH_GAMMA));
+
+        ServiceManager.addService(EXTERNAL_VIBRATOR_SERVICE, new ExternalVibratorService());
     }
 
     private VibrationEffect createEffectFromResource(int resId) {
@@ -562,6 +567,16 @@
                     }
                 }
 
+
+                // If something has external control of the vibrator, assume that it's more
+                // important for now.
+                if (mCurrentExternalVibration != null) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "Ignoring incoming vibration for current external vibration");
+                    }
+                    return;
+                }
+
                 // If the current vibration is repeating and the incoming one is non-repeating,
                 // then ignore the non-repeating vibration. This is so that we don't cancel
                 // vibrations that are meant to grab the attention of the user, like ringtones and
@@ -648,6 +663,11 @@
                 mThread.cancel();
                 mThread = null;
             }
+            if (mCurrentExternalVibration != null) {
+                mCurrentExternalVibration.mute();
+                mCurrentExternalVibration = null;
+                setVibratorUnderExternalControl(false);
+            }
             doVibratorOff();
             reportFinishVibrationLocked();
         } finally {
@@ -1095,6 +1115,26 @@
         }
     }
 
+    private static boolean isNotification(int usageHint) {
+        switch (usageHint) {
+            case AudioAttributes.USAGE_NOTIFICATION:
+            case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST:
+            case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT:
+            case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    private static boolean isRingtone(int usageHint) {
+        return usageHint == AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
+    }
+
+    private static boolean isHapticFeedback(int usageHint) {
+        return usageHint == AudioAttributes.USAGE_ASSISTANCE_SONIFICATION;
+    }
+
     private void noteVibratorOnLocked(int uid, long millis) {
         try {
             mBatteryStatsService.noteVibratorOn(uid, millis);
@@ -1116,6 +1156,18 @@
         }
     }
 
+    private void setVibratorUnderExternalControl(boolean externalControl) {
+        if (DEBUG) {
+            if (externalControl) {
+                Slog.d(TAG, "Vibrator going under external control.");
+            } else {
+                Slog.d(TAG, "Taking back control of vibrator.");
+            }
+        }
+        mVibratorUnderExternalControl = externalControl;
+        vibratorSetExternalControl(externalControl);
+    }
+
     private class VibrateThread extends Thread {
         private final VibrationEffect.Waveform mWaveform;
         private final int mUid;
@@ -1290,6 +1342,13 @@
             } else {
                 pw.println("null");
             }
+            pw.print("  mCurrentExternalVibration=");
+            if (mCurrentExternalVibration != null) {
+                pw.println(mCurrentExternalVibration.toString());
+            } else {
+                pw.println("null");
+            }
+            pw.println("  mVibratorUnderExternalControl=" + mVibratorUnderExternalControl);
             pw.println("  mLowPowerMode=" + mLowPowerMode);
             pw.println("  mHapticFeedbackIntensity=" + mHapticFeedbackIntensity);
             pw.println("  mNotificationIntensity=" + mNotificationIntensity);
@@ -1310,6 +1369,87 @@
         new VibratorShellCommand(this).exec(this, in, out, err, args, callback, resultReceiver);
     }
 
+    final class ExternalVibratorService extends IExternalVibratorService.Stub {
+        @Override
+        public int onExternalVibrationStart(ExternalVibration vib) {
+            if (!mSupportsExternalControl) {
+                return SCALE_MUTE;
+            }
+            if (ActivityManager.checkComponentPermission(android.Manifest.permission.VIBRATE,
+                        vib.getUid(), -1 /*owningUid*/, true /*exported*/)
+                    != PackageManager.PERMISSION_GRANTED) {
+                Slog.w(TAG, "pkg=" + vib.getPackage() + ", uid=" + vib.getUid()
+                        + " tried to play externally controlled vibration"
+                        + " without VIBRATE permission, ignoring.");
+                return SCALE_MUTE;
+            }
+
+            final int scaleLevel;
+            synchronized (mLock) {
+                if (!vib.equals(mCurrentExternalVibration)) {
+                    if (mCurrentExternalVibration == null) {
+                        // If we're not under external control right now, then cancel any normal
+                        // vibration that may be playing and ready the vibrator for external
+                        // control.
+                        doCancelVibrateLocked();
+                        setVibratorUnderExternalControl(true);
+                    }
+                    // At this point we either have an externally controlled vibration playing, or
+                    // no vibration playing. Since the interface defines that only one externally
+                    // controlled vibration can play at a time, by returning something other than
+                    // SCALE_MUTE from this function we can be assured that if we are currently
+                    // playing vibration, it will be muted in favor of the new vibration.
+                    //
+                    // Note that this doesn't support multiple concurrent external controls, as we
+                    // would need to mute the old one still if it came from a different controller.
+                    mCurrentExternalVibration = vib;
+                    if (DEBUG) {
+                        Slog.e(TAG, "Playing external vibration: " + vib);
+                    }
+                }
+                final int usage = vib.getAudioAttributes().getUsage();
+                final int defaultIntensity;
+                final int currentIntensity;
+                if (isRingtone(usage)) {
+                    defaultIntensity = mVibrator.getDefaultRingVibrationIntensity();
+                    currentIntensity = mRingIntensity;
+                } else if (isNotification(usage)) {
+                    defaultIntensity = mVibrator.getDefaultNotificationVibrationIntensity();
+                    currentIntensity = mNotificationIntensity;
+                } else if (isHapticFeedback(usage)) {
+                    defaultIntensity = mVibrator.getDefaultHapticFeedbackIntensity();
+                    currentIntensity = mHapticFeedbackIntensity;
+                } else {
+                    defaultIntensity = 0;
+                    currentIntensity = 0;
+                }
+                scaleLevel = currentIntensity - defaultIntensity;
+            }
+            if (scaleLevel >= SCALE_VERY_LOW && scaleLevel <= SCALE_VERY_HIGH) {
+                return scaleLevel;
+            } else {
+                // Presumably we want to play this but something about our scaling has gone
+                // wrong, so just play with no scaling.
+                Slog.w(TAG, "Error in scaling calculations, ended up with invalid scale level "
+                        + scaleLevel + " for vibration " + vib);
+                return SCALE_NONE;
+            }
+        }
+
+        @Override
+        public void onExternalVibrationStop(ExternalVibration vib) {
+            synchronized (mLock) {
+                if (vib.equals(mCurrentExternalVibration)) {
+                    mCurrentExternalVibration = null;
+                    setVibratorUnderExternalControl(false);
+                    if (DEBUG) {
+                        Slog.e(TAG, "Stopping external vibration" + vib);
+                    }
+                }
+            }
+        }
+    }
+
     private final class VibratorShellCommand extends ShellCommand {
 
         private final IBinder mToken;