1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.vibrator; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.content.Context; 22 import android.os.ExternalVibration; 23 import android.os.ExternalVibrationScale; 24 import android.os.IBinder; 25 import android.os.VibrationAttributes; 26 import android.os.vibrator.Flags; 27 import android.util.Slog; 28 29 import com.android.internal.util.FrameworkStatsLog; 30 31 /** 32 * A vibration session holding a single {@link ExternalVibration} request. 33 */ 34 final class ExternalVibrationSession extends Vibration 35 implements VibrationSession, IBinder.DeathRecipient { 36 private static final String TAG = "ExternalVibrationSession"; 37 38 /** Calls into VibratorManager functionality needed for playing an {@link ExternalVibration}. */ 39 interface VibratorManagerHooks { 40 41 /** 42 * Tells the manager that the external vibration is finished and the vibrators can now be 43 * used for another vibration. 44 */ onExternalVibrationReleased(long vibrationId)45 void onExternalVibrationReleased(long vibrationId); 46 } 47 48 private final long mSessionId = VibrationSession.nextSessionId(); 49 private final ExternalVibration mExternalVibration; 50 private final ExternalVibrationScale mScale = new ExternalVibrationScale(); 51 private final VibratorManagerHooks mManagerHooks; 52 ExternalVibrationSession(ExternalVibration externalVibration, VibratorManagerHooks managerHooks)53 ExternalVibrationSession(ExternalVibration externalVibration, 54 VibratorManagerHooks managerHooks) { 55 super(new CallerInfo( 56 externalVibration.getVibrationAttributes(), externalVibration.getUid(), 57 // TODO(b/249785241): Find a way to link ExternalVibration to a VirtualDevice 58 // instead of using DEVICE_ID_INVALID here and relying on the UID checks. 59 Context.DEVICE_ID_INVALID, externalVibration.getPackage(), null)); 60 mExternalVibration = externalVibration; 61 mManagerHooks = managerHooks; 62 } 63 getScale()64 public ExternalVibrationScale getScale() { 65 return mScale; 66 } 67 68 @Override getSessionId()69 public long getSessionId() { 70 return mSessionId; 71 } 72 73 @Override getCreateUptimeMillis()74 public long getCreateUptimeMillis() { 75 return stats.getCreateUptimeMillis(); 76 } 77 78 @Override getCallerInfo()79 public CallerInfo getCallerInfo() { 80 return callerInfo; 81 } 82 83 @Override getCallerToken()84 public IBinder getCallerToken() { 85 return mExternalVibration.getToken(); 86 } 87 88 @Override getDebugInfo()89 public VibrationSession.DebugInfo getDebugInfo() { 90 return new Vibration.DebugInfoImpl(getStatus(), callerInfo, 91 FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__EXTERNAL, stats, 92 /* playedEffect= */ null, /* originalEffect= */ null, mScale.scaleLevel, 93 mScale.adaptiveHapticsScale); 94 } 95 96 @Override isRepeating()97 public boolean isRepeating() { 98 // We don't currently know if the external vibration is repeating, so we just use a 99 // heuristic based on the usage. Ideally this would be propagated in the ExternalVibration. 100 int usage = mExternalVibration.getVibrationAttributes().getUsage(); 101 return usage == VibrationAttributes.USAGE_RINGTONE 102 || usage == VibrationAttributes.USAGE_ALARM; 103 } 104 105 @Override wasEndRequested()106 public boolean wasEndRequested() { 107 // End request is immediate, so just check if vibration has already ended. 108 return hasEnded(); 109 } 110 111 @Override linkToDeath()112 public boolean linkToDeath() { 113 mExternalVibration.linkToDeath(this); 114 return true; 115 } 116 117 @Override unlinkToDeath()118 public void unlinkToDeath() { 119 mExternalVibration.unlinkToDeath(this); 120 } 121 122 @Override binderDied()123 public void binderDied() { 124 Slog.d(TAG, "Binder died, cancelling external vibration..."); 125 requestEnd(Status.CANCELLED_BINDER_DIED); 126 } 127 128 @Override end(EndInfo endInfo)129 void end(EndInfo endInfo) { 130 super.end(endInfo); 131 if (stats.hasStarted()) { 132 // Notify external client that this vibration should stop sending data to the vibrator. 133 mExternalVibration.mute(); 134 // External vibration doesn't have feedback from total time the vibrator was playing 135 // with non-zero amplitude, so we use the duration between start and end times of 136 // the vibration as the time the vibrator was ON, since the haptic channels are 137 // open for this duration and can receive vibration waveform data. 138 stats.reportVibratorOn(stats.getEndUptimeMillis() - stats.getStartUptimeMillis()); 139 // Notify the manager that external client has released the vibrator control. 140 mManagerHooks.onExternalVibrationReleased(id); 141 } 142 } 143 144 @Override requestEnd(@onNull Status status, @Nullable CallerInfo endedBy, boolean immediate)145 public void requestEnd(@NonNull Status status, @Nullable CallerInfo endedBy, 146 boolean immediate) { 147 end(new EndInfo(status, endedBy)); 148 } 149 150 @Override notifyVibratorCallback(int vibratorId, long vibrationId, long stepId)151 public void notifyVibratorCallback(int vibratorId, long vibrationId, long stepId) { 152 // ignored, external control does not expect callbacks from the vibrator 153 } 154 155 @Override notifySyncedVibratorsCallback(long vibrationId)156 public void notifySyncedVibratorsCallback(long vibrationId) { 157 // ignored, external control does not expect callbacks from the vibrator manager for sync 158 } 159 160 @Override notifySessionCallback()161 public void notifySessionCallback() { 162 // ignored, external control does not expect callbacks from the vibrator manager for session 163 } 164 isHoldingSameVibration(ExternalVibration vib)165 boolean isHoldingSameVibration(ExternalVibration vib) { 166 return mExternalVibration.equals(vib); 167 } 168 muteScale()169 void muteScale() { 170 mScale.scaleLevel = ExternalVibrationScale.ScaleLevel.SCALE_MUTE; 171 if (Flags.hapticsScaleV2Enabled()) { 172 mScale.scaleFactor = 0; 173 } 174 } 175 scale(VibrationScaler scaler, int usage)176 void scale(VibrationScaler scaler, int usage) { 177 mScale.scaleLevel = scaler.getScaleLevel(usage); 178 if (Flags.hapticsScaleV2Enabled()) { 179 mScale.scaleFactor = scaler.getScaleFactor(usage); 180 } 181 mScale.adaptiveHapticsScale = scaler.getAdaptiveHapticsScale(usage); 182 stats.reportAdaptiveScale(mScale.adaptiveHapticsScale); 183 } 184 185 @Override toString()186 public String toString() { 187 return "ExternalVibrationSession{" 188 + "sessionId=" + mSessionId 189 + ", vibrationId=" + id 190 + ", callerInfo=" + callerInfo 191 + ", externalVibration=" + mExternalVibration 192 + ", scale=" + mScale 193 + '}'; 194 } 195 } 196