blob: 9007c2cc37445247e7e1c75d6a0691d2d76b2af1 [file]
/*
* Copyright 2025 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.
*/
#include <vibrationflinger/HapticGeneratorConstants.h>
#include <vibrationflinger/HapticGeneratorStream.h>
#include <utils/Log.h>
#include <utils/Timers.h>
namespace android::vibrator {
namespace fmq = aidl::android::hardware::common::fmq;
using namespace ::std::chrono_literals;
using aidl::android::hardware::vibrator::HapticGeneratorCommand;
using aidl::android::hardware::vibrator::HapticGeneratorReply;
using aidl::android::hardware::vibrator::VibrationEffectContent;
using CommandQueue = android::AidlMessageQueue<HapticGeneratorCommand, fmq::SynchronizedReadWrite>;
using EffectQueue = android::AidlMessageQueue<VibrationEffectContent, fmq::SynchronizedReadWrite>;
using ReplyQueue = android::AidlMessageQueue<HapticGeneratorReply, fmq::SynchronizedReadWrite>;
using PcmQueue = android::AidlMessageQueue<int8_t, fmq::SynchronizedReadWrite>;
static constexpr size_t kDrainBufferSize = 256;
// Helper to send a command and block until a reply is received
static status_t sendCommandAndReceiveReply(VibratorQueues& queues,
const HapticGeneratorCommand& command,
HapticGeneratorReply* reply) {
if (!queues.command->writeBlocking(&command, 1,
std::chrono::nanoseconds(kFmqTimeout).count())) {
ALOGE("Haptic stream: Timed out writing command.");
return TIMED_OUT;
}
if (!queues.reply->readBlocking(reply, 1, std::chrono::nanoseconds(kFmqTimeout).count())) {
ALOGE("Haptic stream: Timed out waiting for reply.");
return TIMED_OUT;
}
return reply->status;
}
HapticGeneratorStream::HapticGeneratorStream(int32_t vibratorId,
const std::vector<VibrationEffectContent>& effect)
: mVibratorId(vibratorId), mPendingEffects(effect.begin(), effect.end()) {}
status_t HapticGeneratorStream::start(VibratorQueues& queues) {
if (mIsActive) {
ALOGE("Haptic stream for vibrator %d already started.", mVibratorId);
return INVALID_OPERATION;
}
// Send START command to reset the HAL's state for a new conversion
HapticGeneratorCommand command;
command.set<HapticGeneratorCommand::Tag::effect>(HapticGeneratorCommand::Effect::START);
HapticGeneratorReply reply;
status_t status = sendCommandAndReceiveReply(queues, command, &reply);
if (status != OK) {
ALOGE("Haptic stream failed to send START command for vibrator %d: %d", mVibratorId,
status);
return status;
}
// Clear the pcm queue of any stale data from a previous stream
if (queues.pcm->availableToRead() > 0) {
int8_t drainBuffer[kDrainBufferSize];
while (queues.pcm->read(drainBuffer, kDrainBufferSize)) {
// Discard data.
}
ALOGD("Drained leftover PCM data for vibrator %d", mVibratorId);
}
mIsActive = true;
ALOGD("Haptic stream started successfully for vibrator %d", mVibratorId);
return status;
}
android::base::Result<size_t> HapticGeneratorStream::read(VibratorQueues& queues,
std::span<int8_t> buffer) {
if (!mIsActive) {
ALOGE("Haptic stream read called on an inactive stream for vibrator %d", mVibratorId);
return INVALID_OPERATION;
}
if (mIsPcmComplete) {
return OK; // End of stream was already reached
}
const nsecs_t deadline =
systemTime(SYSTEM_TIME_MONOTONIC) + std::chrono::nanoseconds(kReadPcmTimeout).count();
size_t totalBytesRead = 0;
while (systemTime(SYSTEM_TIME_MONOTONIC) < deadline && totalBytesRead < buffer.size()) {
// Try to write any pending effect data to the HAL
status_t writeStatus = maybeWriteEffects(queues);
if (writeStatus != OK) {
return android::base::Error(writeStatus) << "Failed to write effects to FMQ";
}
// Create a subspan for the current write position.
auto remainingBuffer = buffer.subspan(totalBytesRead);
// Send a burst command to request the remaining PCM data
HapticGeneratorCommand command;
command.set<HapticGeneratorCommand::Tag::burstBytes>(
static_cast<int32_t>(remainingBuffer.size()));
HapticGeneratorReply reply;
status_t status = sendCommandAndReceiveReply(queues, command, &reply);
if (status == OK) {
if (reply.burstBytesReady == 0) {
// End of stream
mIsPcmComplete = true;
ALOGV("Haptic stream for vibrator %d fully consumed.", mVibratorId);
break;
}
size_t bytesToRead =
std::min(remainingBuffer.size(), static_cast<size_t>(reply.burstBytesReady));
if (!queues.pcm->read(remainingBuffer.data(), bytesToRead)) {
ALOGE("Haptic stream failed to read PCM data from queue for vibrator %d",
mVibratorId);
return android::base::Error(INVALID_OPERATION) << "Failed to read from PCM FMQ";
}
totalBytesRead += bytesToRead;
} else if (status == NOT_ENOUGH_DATA) {
if (mIsEffectComplete) {
// This is an error. The HAL received the 'COMPLETE' command
// but is incorrectly asking for more effect data.
ALOGE("Haptic stream for vibrator %d: HAL replied NOT_ENOUGH_DATA "
"after effect was complete.",
mVibratorId);
return android::base::Error(UNKNOWN_ERROR)
<< "HAL requested data after effect was complete";
}
ALOGD("Haptic stream for vibrator %d needs more data, retrying...", mVibratorId);
continue;
} else {
ALOGE("Haptic stream for vibrator %d received error status from HAL: %d", mVibratorId,
status);
return android::base::Error(status) << "HAL returned an unrecoverable error";
}
}
return android::base::Result<size_t>(totalBytesRead);
}
status_t HapticGeneratorStream::stop(VibratorQueues& queues) {
if (!mIsActive || mIsPcmComplete) {
// Stream is already closed
return OK;
}
ALOGW("Haptic stream for vibrator %d is being closed. Cancelling effect.", mVibratorId);
HapticGeneratorCommand command;
command.set<HapticGeneratorCommand::Tag::effect>(HapticGeneratorCommand::Effect::CANCEL);
HapticGeneratorReply reply;
status_t status = sendCommandAndReceiveReply(queues, command, &reply);
if (status != OK) {
ALOGE("Haptic stream failed to send CANCEL command for vibrator %d: %d", mVibratorId,
status);
return status;
}
mIsActive = false;
return OK;
}
status_t HapticGeneratorStream::maybeWriteEffects(android::vibrator::VibratorQueues& queues) {
if (mIsEffectComplete) {
return OK; // All data has been sent
}
while (!mPendingEffects.empty() && queues.effect->write(&mPendingEffects.front())) {
mPendingEffects.pop_front();
}
// If we have successfully sent all pending segments, send the COMPLETE command
if (mPendingEffects.empty()) {
HapticGeneratorCommand command;
command.set<HapticGeneratorCommand::Tag::effect>(HapticGeneratorCommand::Effect::COMPLETE);
HapticGeneratorReply reply;
status_t status = sendCommandAndReceiveReply(queues, command, &reply);
if (status != OK) {
ALOGE("Haptic stream failed to send COMPLETE command for vibrator %d: %d", mVibratorId,
status);
return status;
}
mIsEffectComplete = true;
}
return OK;
}
} // namespace android::vibrator