Loading core/java/android/os/vibrator/VendorVibrationSession.java +13 −0 Original line number Diff line number Diff line Loading @@ -188,6 +188,19 @@ public final class VendorVibrationSession implements AutoCloseable { } } /** @hide */ public static String sessionStatusToString(@Status int status) { return switch (status) { case STATUS_SUCCESS -> "SUCCESS"; case STATUS_IGNORED -> "IGNORED"; case STATUS_UNSUPPORTED -> "UNSUPPORTED"; case STATUS_CANCELED -> "CANCELED"; case STATUS_UNKNOWN -> "UNKNOWN"; case STATUS_UNKNOWN_ERROR -> "UNKNOWN_ERROR"; default -> Integer.toString(status); }; } /** * Callbacks for {@link VendorVibrationSession} events. * Loading services/core/java/com/android/server/vibrator/VendorVibrationSession.java +4 −2 Original line number Diff line number Diff line Loading @@ -394,9 +394,11 @@ final class VendorVibrationSession extends IVibrationSession.Stub mEndedByVendor = isVendorRequest; mCallback.notifyFinishing(); if (mConductor != null) { boolean isFinished = status == Status.FINISHED; // Vibration is being dispatched when session end was requested, cancel it. mConductor.notifyCancelled(new Vibration.EndInfo(status), /* immediate= */ status != Status.FINISHED); mConductor.notifyCancelled( new Vibration.EndInfo(isFinished ? Status.CANCELLED_BY_USER : status), /* immediate= */ !isFinished); } } Loading services/core/java/com/android/server/vibrator/VibratorManagerService.java +103 −12 Original line number Diff line number Diff line Loading @@ -62,6 +62,7 @@ import android.os.VibrationAttributes; import android.os.VibrationEffect; import android.os.VibratorInfo; import android.os.vibrator.Flags; import android.os.vibrator.IVibrationSession; import android.os.vibrator.IVibrationSessionCallback; import android.os.vibrator.PrebakedSegment; import android.os.vibrator.PrimitiveSegment; Loading Loading @@ -101,6 +102,7 @@ import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; Loading Loading @@ -2687,9 +2689,10 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { public static final long VIBRATION_END_TIMEOUT_MS = 500; // Clean up shouldn't be too long. private final class CommonOptions { public boolean force = false; public boolean shouldForce = false; public boolean isInBackground = false; public boolean isInSession = false; public String description = "Shell command"; public boolean background = false; @VibrationAttributes.Usage public int usage; CommonOptions() { Loading @@ -2710,11 +2713,15 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { switch (nextArg) { case "-f": getNextArgRequired(); // consume "-f" force = true; shouldForce = true; break; case "-S": getNextArgRequired(); // consume "-S" isInSession = true; break; case "-B": getNextArgRequired(); // consume "-B" background = true; isInBackground = true; break; case "-d": getNextArgRequired(); // consume "-d" Loading Loading @@ -2796,19 +2803,93 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { */ private void runVibrate(CommonOptions commonOptions, CombinedVibration combined) { VibrationAttributes attrs = createVibrationAttributes(commonOptions); if (commonOptions.isInSession && commonOptions.isInBackground) { getOutPrintWriter().println( "Session vibrations cannot run in background, running in foreground..."); } // If running in the background, bind to death of the server binder rather than the // client, and the cancel command likewise uses the server binder reference to // only cancel background vibrations. IBinder deathBinder = commonOptions.background ? VibratorManagerService.this IBinder deathBinder = !commonOptions.isInSession && commonOptions.isInBackground ? VibratorManagerService.this : mShellCallbacksToken; int uid = Binder.getCallingUid(); // Resolve the package name for the client based on the process UID, to cover cases like // rooted shell clients using ROOT_UID. String resolvedPackageName = AppOpsManager.resolvePackageName(uid, SHELL_PACKAGE_NAME); if (commonOptions.isInSession) { ShellVibrationSessionCallback cb = new ShellVibrationSessionCallback(mHandler, getOutPrintWriter(), deathBinder, combined, commonOptions.description); VendorVibrationSession session = startVendorVibrationSessionInternal(uid, Context.DEVICE_ID_DEFAULT, resolvedPackageName, mVibratorManager.getVibratorIds(), attrs, commonOptions.description, cb); waitOnSession(cb); } else { HalVibration vib = vibrateWithPermissionCheck(uid, Context.DEVICE_ID_DEFAULT, resolvedPackageName, combined, attrs, commonOptions.description, deathBinder); resolvedPackageName, combined, attrs, commonOptions.description, deathBinder); maybeWaitOnVibration(vib, commonOptions); } } /** Vibration session callback implementation for shell vibrations in session. */ private static class ShellVibrationSessionCallback extends IVibrationSessionCallback.Stub { private final Handler mHandler; private final PrintWriter mPrinter; private final IBinder mDeathBinder; private final CombinedVibration mVibration; private final String mReason; /** A {@link CountDownLatch} to enable waiting for completion. */ private final CountDownLatch mCompletionLatch = new CountDownLatch(1); ShellVibrationSessionCallback(Handler handler, PrintWriter printer, IBinder deathBinder, CombinedVibration vibration, String reason) { mHandler = handler; mPrinter = printer; mDeathBinder = deathBinder; mVibration = vibration; mReason = reason; } @Override public IBinder asBinder() { return mDeathBinder; } @Override public void onStarted(IVibrationSession session) throws RemoteException { mPrinter.println("Session started, vibrating..."); session.vibrate(mVibration, mReason); // Wait for vibration to be dispatched by VibrationThread before ending session. mHandler.postDelayed(() -> { try { mPrinter.println("Finishing session..."); session.finishSession(); } catch (RemoteException e) { throw new RuntimeException(e); } }, 500); } @Override public void onFinishing() { mPrinter.println("Session finishing..."); } @Override public void onFinished(int status) { mPrinter.println("Session finished with status " + android.os.vibrator.VendorVibrationSession.sessionStatusToString(status)); mCompletionLatch.countDown(); } /** Waits indefinitely until service ends this session. */ public void waitForEnd() throws InterruptedException { mCompletionLatch.await(); } } private int runMono() { runVibrate(new CommonOptions(), CombinedVibration.createParallel(nextEffect())); Loading Loading @@ -2859,9 +2940,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { CommonOptions commonOptions = new CommonOptions(/* defaultUsage= */ USAGE_UNKNOWN); int constant = parseInt(getNextArgRequired(), "Expected haptic feedback constant id"); IBinder deathBinder = commonOptions.background ? VibratorManagerService.this IBinder deathBinder = commonOptions.isInBackground ? VibratorManagerService.this : mShellCallbacksToken; int flags = commonOptions.force int flags = commonOptions.shouldForce ? HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING : 0; HalVibration vib = performHapticFeedbackInternal(Binder.getCallingUid(), Context.DEVICE_ID_DEFAULT, SHELL_PACKAGE_NAME, constant, commonOptions.usage, Loading Loading @@ -3177,7 +3258,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { private VibrationAttributes createVibrationAttributes(CommonOptions commonOptions) { // This will bypass user settings, Do Not Disturb and other interruption policies. final int flags = commonOptions.force ? ATTRIBUTES_ALL_BYPASS_FLAGS : 0; final int flags = commonOptions.shouldForce ? ATTRIBUTES_ALL_BYPASS_FLAGS : 0; return new VibrationAttributes.Builder() .setFlags(flags) .setUsage(commonOptions.usage) Loading @@ -3203,7 +3284,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } private void maybeWaitOnVibration(HalVibration vib, CommonOptions commonOptions) { if (vib != null && !commonOptions.background) { if (vib != null && !commonOptions.isInBackground) { try { // Waits for the client vibration to finish, but the VibrationThread may still // do cleanup after this. Loading @@ -3216,6 +3297,13 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } } private void waitOnSession(ShellVibrationSessionCallback callback) { try { callback.waitForEnd(); } catch (InterruptedException e) { } } private static int parseInt(String text, String errorMessage) { try { return Integer.parseInt(text); Loading Loading @@ -3313,6 +3401,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { pw.println(" -B"); pw.println(" Run in the background; without this option the shell cmd will"); pw.println(" block until the vibration has completed."); pw.println(" -S"); pw.println(" Run vibration in a vendor session. Only vibration commands will"); pw.println(" apply this option. The -B option will be ignored."); pw.println(" -u <usage>"); pw.println(" Specify the usage for the haptic feedback or vibration."); pw.println(" -d <description>"); Loading Loading
core/java/android/os/vibrator/VendorVibrationSession.java +13 −0 Original line number Diff line number Diff line Loading @@ -188,6 +188,19 @@ public final class VendorVibrationSession implements AutoCloseable { } } /** @hide */ public static String sessionStatusToString(@Status int status) { return switch (status) { case STATUS_SUCCESS -> "SUCCESS"; case STATUS_IGNORED -> "IGNORED"; case STATUS_UNSUPPORTED -> "UNSUPPORTED"; case STATUS_CANCELED -> "CANCELED"; case STATUS_UNKNOWN -> "UNKNOWN"; case STATUS_UNKNOWN_ERROR -> "UNKNOWN_ERROR"; default -> Integer.toString(status); }; } /** * Callbacks for {@link VendorVibrationSession} events. * Loading
services/core/java/com/android/server/vibrator/VendorVibrationSession.java +4 −2 Original line number Diff line number Diff line Loading @@ -394,9 +394,11 @@ final class VendorVibrationSession extends IVibrationSession.Stub mEndedByVendor = isVendorRequest; mCallback.notifyFinishing(); if (mConductor != null) { boolean isFinished = status == Status.FINISHED; // Vibration is being dispatched when session end was requested, cancel it. mConductor.notifyCancelled(new Vibration.EndInfo(status), /* immediate= */ status != Status.FINISHED); mConductor.notifyCancelled( new Vibration.EndInfo(isFinished ? Status.CANCELLED_BY_USER : status), /* immediate= */ !isFinished); } } Loading
services/core/java/com/android/server/vibrator/VibratorManagerService.java +103 −12 Original line number Diff line number Diff line Loading @@ -62,6 +62,7 @@ import android.os.VibrationAttributes; import android.os.VibrationEffect; import android.os.VibratorInfo; import android.os.vibrator.Flags; import android.os.vibrator.IVibrationSession; import android.os.vibrator.IVibrationSessionCallback; import android.os.vibrator.PrebakedSegment; import android.os.vibrator.PrimitiveSegment; Loading Loading @@ -101,6 +102,7 @@ import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; Loading Loading @@ -2687,9 +2689,10 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { public static final long VIBRATION_END_TIMEOUT_MS = 500; // Clean up shouldn't be too long. private final class CommonOptions { public boolean force = false; public boolean shouldForce = false; public boolean isInBackground = false; public boolean isInSession = false; public String description = "Shell command"; public boolean background = false; @VibrationAttributes.Usage public int usage; CommonOptions() { Loading @@ -2710,11 +2713,15 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { switch (nextArg) { case "-f": getNextArgRequired(); // consume "-f" force = true; shouldForce = true; break; case "-S": getNextArgRequired(); // consume "-S" isInSession = true; break; case "-B": getNextArgRequired(); // consume "-B" background = true; isInBackground = true; break; case "-d": getNextArgRequired(); // consume "-d" Loading Loading @@ -2796,19 +2803,93 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { */ private void runVibrate(CommonOptions commonOptions, CombinedVibration combined) { VibrationAttributes attrs = createVibrationAttributes(commonOptions); if (commonOptions.isInSession && commonOptions.isInBackground) { getOutPrintWriter().println( "Session vibrations cannot run in background, running in foreground..."); } // If running in the background, bind to death of the server binder rather than the // client, and the cancel command likewise uses the server binder reference to // only cancel background vibrations. IBinder deathBinder = commonOptions.background ? VibratorManagerService.this IBinder deathBinder = !commonOptions.isInSession && commonOptions.isInBackground ? VibratorManagerService.this : mShellCallbacksToken; int uid = Binder.getCallingUid(); // Resolve the package name for the client based on the process UID, to cover cases like // rooted shell clients using ROOT_UID. String resolvedPackageName = AppOpsManager.resolvePackageName(uid, SHELL_PACKAGE_NAME); if (commonOptions.isInSession) { ShellVibrationSessionCallback cb = new ShellVibrationSessionCallback(mHandler, getOutPrintWriter(), deathBinder, combined, commonOptions.description); VendorVibrationSession session = startVendorVibrationSessionInternal(uid, Context.DEVICE_ID_DEFAULT, resolvedPackageName, mVibratorManager.getVibratorIds(), attrs, commonOptions.description, cb); waitOnSession(cb); } else { HalVibration vib = vibrateWithPermissionCheck(uid, Context.DEVICE_ID_DEFAULT, resolvedPackageName, combined, attrs, commonOptions.description, deathBinder); resolvedPackageName, combined, attrs, commonOptions.description, deathBinder); maybeWaitOnVibration(vib, commonOptions); } } /** Vibration session callback implementation for shell vibrations in session. */ private static class ShellVibrationSessionCallback extends IVibrationSessionCallback.Stub { private final Handler mHandler; private final PrintWriter mPrinter; private final IBinder mDeathBinder; private final CombinedVibration mVibration; private final String mReason; /** A {@link CountDownLatch} to enable waiting for completion. */ private final CountDownLatch mCompletionLatch = new CountDownLatch(1); ShellVibrationSessionCallback(Handler handler, PrintWriter printer, IBinder deathBinder, CombinedVibration vibration, String reason) { mHandler = handler; mPrinter = printer; mDeathBinder = deathBinder; mVibration = vibration; mReason = reason; } @Override public IBinder asBinder() { return mDeathBinder; } @Override public void onStarted(IVibrationSession session) throws RemoteException { mPrinter.println("Session started, vibrating..."); session.vibrate(mVibration, mReason); // Wait for vibration to be dispatched by VibrationThread before ending session. mHandler.postDelayed(() -> { try { mPrinter.println("Finishing session..."); session.finishSession(); } catch (RemoteException e) { throw new RuntimeException(e); } }, 500); } @Override public void onFinishing() { mPrinter.println("Session finishing..."); } @Override public void onFinished(int status) { mPrinter.println("Session finished with status " + android.os.vibrator.VendorVibrationSession.sessionStatusToString(status)); mCompletionLatch.countDown(); } /** Waits indefinitely until service ends this session. */ public void waitForEnd() throws InterruptedException { mCompletionLatch.await(); } } private int runMono() { runVibrate(new CommonOptions(), CombinedVibration.createParallel(nextEffect())); Loading Loading @@ -2859,9 +2940,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { CommonOptions commonOptions = new CommonOptions(/* defaultUsage= */ USAGE_UNKNOWN); int constant = parseInt(getNextArgRequired(), "Expected haptic feedback constant id"); IBinder deathBinder = commonOptions.background ? VibratorManagerService.this IBinder deathBinder = commonOptions.isInBackground ? VibratorManagerService.this : mShellCallbacksToken; int flags = commonOptions.force int flags = commonOptions.shouldForce ? HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING : 0; HalVibration vib = performHapticFeedbackInternal(Binder.getCallingUid(), Context.DEVICE_ID_DEFAULT, SHELL_PACKAGE_NAME, constant, commonOptions.usage, Loading Loading @@ -3177,7 +3258,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { private VibrationAttributes createVibrationAttributes(CommonOptions commonOptions) { // This will bypass user settings, Do Not Disturb and other interruption policies. final int flags = commonOptions.force ? ATTRIBUTES_ALL_BYPASS_FLAGS : 0; final int flags = commonOptions.shouldForce ? ATTRIBUTES_ALL_BYPASS_FLAGS : 0; return new VibrationAttributes.Builder() .setFlags(flags) .setUsage(commonOptions.usage) Loading @@ -3203,7 +3284,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } private void maybeWaitOnVibration(HalVibration vib, CommonOptions commonOptions) { if (vib != null && !commonOptions.background) { if (vib != null && !commonOptions.isInBackground) { try { // Waits for the client vibration to finish, but the VibrationThread may still // do cleanup after this. Loading @@ -3216,6 +3297,13 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } } private void waitOnSession(ShellVibrationSessionCallback callback) { try { callback.waitForEnd(); } catch (InterruptedException e) { } } private static int parseInt(String text, String errorMessage) { try { return Integer.parseInt(text); Loading Loading @@ -3313,6 +3401,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { pw.println(" -B"); pw.println(" Run in the background; without this option the shell cmd will"); pw.println(" block until the vibration has completed."); pw.println(" -S"); pw.println(" Run vibration in a vendor session. Only vibration commands will"); pw.println(" apply this option. The -B option will be ignored."); pw.println(" -u <usage>"); pw.println(" Specify the usage for the haptic feedback or vibration."); pw.println(" -d <description>"); Loading