1 /*
2 * Copyright (c) 2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "adapter/ohos/entrance/picker/picker_haptic_controller.h"
17 #include "core/common/vibrator/vibrator_utils.h"
18
19 namespace OHOS::Ace::NG {
20 namespace {
21 using std::chrono_literals::operator""s;
22 using std::chrono_literals::operator""ms;
23 const std::string AUDIO_TEST_URI = "/system/etc/arkui/timepicker.ogg";
24 const std::string EFFECT_ID_NAME = "haptic.slide";
25 constexpr size_t SPEED_THRESHOLD = 1560;
26 constexpr size_t TREND_COUNT = 3;
27 #ifdef SUPPORT_DIGITAL_CROWN
28 constexpr char CROWN_VIBRATOR_WEAK[] = "watchhaptic.feedback.crown.strength2";
29 #else
30 constexpr size_t SPEED_MAX = 5000;
31 constexpr std::chrono::milliseconds DEFAULT_DELAY(40);
32 constexpr std::chrono::milliseconds EXTENDED_DELAY(50);
33 #endif
34 constexpr size_t SPEED_PLAY_ONCE = 0;
35 } // namespace
36
PickerHapticController(const std::string & uri,const std::string & effectId)37 PickerHapticController::PickerHapticController(const std::string& uri, const std::string& effectId) noexcept
38 {
39 std::string effectiveUri = uri.empty() ? AUDIO_TEST_URI : uri;
40 std::string effectiveEffectId = effectId.empty() ? EFFECT_ID_NAME : effectId;
41 audioHapticManager_ = Media::AudioHapticManagerFactory::CreateAudioHapticManager();
42 if (audioHapticManager_) {
43 effectSourceId_ = audioHapticManager_->RegisterSourceWithEffectId(effectiveUri, effectiveEffectId);
44 Media::AudioLatencyMode latencyMode = Media::AudioLatencyMode::AUDIO_LATENCY_MODE_FAST;
45 audioHapticManager_->SetAudioLatencyMode(effectSourceId_, latencyMode);
46 AudioStandard::StreamUsage streamUsage = AudioStandard::StreamUsage::STREAM_USAGE_NOTIFICATION;
47 audioHapticManager_->SetStreamUsage(effectSourceId_, streamUsage);
48 Media::AudioHapticPlayerOptions options;
49 options.muteAudio = false;
50 options.muteHaptics = false;
51 options.parallelPlayFlag = true;
52 effectAudioHapticPlayer_ = audioHapticManager_->CreatePlayer(effectSourceId_, options);
53 if (effectAudioHapticPlayer_) {
54 effectAudioHapticPlayer_->Prepare();
55 }
56 auto audioSystemMgr = AudioStandard::AudioSystemManager::GetInstance();
57 audioGroupMngr_ = audioSystemMgr->GetGroupManager(AudioStandard::DEFAULT_VOLUME_GROUP_ID);
58 InitPlayThread();
59 }
60 }
61
~PickerHapticController()62 PickerHapticController::~PickerHapticController() noexcept
63 {
64 ThreadRelease();
65 if (effectAudioHapticPlayer_) {
66 effectAudioHapticPlayer_->Stop();
67 }
68 if (effectAudioHapticPlayer_) {
69 effectAudioHapticPlayer_->Release();
70 }
71 if (audioHapticManager_) {
72 audioHapticManager_->UnregisterSource(effectSourceId_);
73 }
74 }
75
ThreadRelease()76 void PickerHapticController::ThreadRelease()
77 {
78 if (playThread_) {
79 {
80 std::lock_guard<std::recursive_mutex> guard(threadMutex_);
81 playThreadStatus_ = ThreadStatus::NONE;
82 }
83 threadCv_.notify_one();
84 playThread_ = nullptr;
85 }
86 playThreadStatus_ = ThreadStatus::NONE;
87 }
88
IsThreadReady()89 bool PickerHapticController::IsThreadReady()
90 {
91 std::lock_guard<std::recursive_mutex> guard(threadMutex_);
92 return playThreadStatus_ == ThreadStatus::READY;
93 }
94
IsThreadPlaying()95 bool PickerHapticController::IsThreadPlaying()
96 {
97 std::lock_guard<std::recursive_mutex> guard(threadMutex_);
98 return playThreadStatus_ == ThreadStatus::PLAYING;
99 }
100
IsThreadPlayOnce()101 bool PickerHapticController::IsThreadPlayOnce()
102 {
103 std::lock_guard<std::recursive_mutex> guard(threadMutex_);
104 return playThreadStatus_ == ThreadStatus::PLAY_ONCE;
105 }
106
IsThreadNone()107 bool PickerHapticController::IsThreadNone()
108 {
109 std::lock_guard<std::recursive_mutex> guard(threadMutex_);
110 return playThreadStatus_ == ThreadStatus::NONE;
111 }
112
InitPlayThread()113 void PickerHapticController::InitPlayThread()
114 {
115 ThreadRelease();
116 playThreadStatus_ = ThreadStatus::START;
117 playThread_ = std::make_unique<std::thread>(&PickerHapticController::ThreadLoop, this);
118 if (playThread_) {
119 playThread_->detach();
120 playThreadStatus_ = ThreadStatus::READY;
121 } else {
122 playThreadStatus_ = ThreadStatus::NONE;
123 }
124 }
125
ThreadLoop()126 void PickerHapticController::ThreadLoop()
127 {
128 while (!IsThreadNone()) {
129 {
130 std::unique_lock<std::recursive_mutex> lock(threadMutex_);
131 threadCv_.wait(lock, [this]() { return IsThreadPlaying() || IsThreadPlayOnce(); });
132 if (IsThreadNone()) {
133 return;
134 }
135 }
136 #ifdef SUPPORT_DIGITAL_CROWN
137 playThreadStatus_ = ThreadStatus::READY;
138 VibratorUtils::StartVibraFeedback(CROWN_VIBRATOR_WEAK);
139 #else
140 CHECK_NULL_VOID(audioGroupMngr_);
141 CHECK_NULL_VOID(effectAudioHapticPlayer_);
142 isInHapticLoop_ = true;
143 auto vol = audioGroupMngr_->GetVolume(AudioStandard::AudioVolumeType::STREAM_RING);
144 auto userVolume = audioGroupMngr_->GetSystemVolumeInDb(
145 AudioStandard::AudioVolumeType::STREAM_RING, vol, AudioStandard::DEVICE_TYPE_SPEAKER);
146
147 // Set different volumes for different sliding speeds:
148 float maxVolume = 0.6f + 0.4f * userVolume;
149 float volume = 0.6f + (maxVolume - 0.6f) * ((static_cast<float>(absSpeedInMm_) - SPEED_THRESHOLD) /
150 (SPEED_MAX - SPEED_THRESHOLD));
151 volume = std::clamp(volume, 0.6f, 1.f);
152
153 // Different vibration parameters for different sliding speeds:
154 float haptic = absSpeedInMm_ * 0.01f + 50.f;
155 haptic = std::clamp(haptic, 50.f, 98.f);
156 effectAudioHapticPlayer_->SetVolume(volume);
157 effectAudioHapticPlayer_->SetHapticIntensity(haptic);
158 effectAudioHapticPlayer_->Start();
159
160 {
161 auto startTime = std::chrono::high_resolution_clock::now();
162 std::unique_lock<std::recursive_mutex> lock(threadMutex_);
163 std::chrono::milliseconds delayTime = DEFAULT_DELAY;
164 if (IsThreadPlayOnce() && isLoopReadyToStop_) { // 50ms delay after 40ms loop ends
165 delayTime = EXTENDED_DELAY;
166 }
167 threadCv_.wait_until(lock, startTime + delayTime);
168 if (IsThreadPlayOnce() || isLoopReadyToStop_) {
169 playThreadStatus_ = ThreadStatus::READY;
170 }
171 }
172 isInHapticLoop_ = false;
173 #endif
174 }
175 }
176
Play(size_t speed)177 void PickerHapticController::Play(size_t speed)
178 {
179 if (!playThread_) {
180 InitPlayThread();
181 }
182 bool needNotify = !IsThreadPlaying() && !IsThreadPlayOnce();
183 {
184 std::lock_guard<std::recursive_mutex> guard(threadMutex_);
185 absSpeedInMm_ = speed;
186 playThreadStatus_ = ThreadStatus::PLAYING;
187 }
188 if (needNotify) {
189 threadCv_.notify_one();
190 }
191 }
192
PlayOnce()193 void PickerHapticController::PlayOnce()
194 {
195 if (IsThreadPlaying()) {
196 return;
197 }
198 if (!playThread_) {
199 InitPlayThread();
200 }
201
202 bool needNotify = !IsThreadPlaying() && !IsThreadPlayOnce();
203 {
204 std::lock_guard<std::recursive_mutex> guard(threadMutex_);
205 playThreadStatus_ = ThreadStatus::PLAY_ONCE;
206 absSpeedInMm_ = SPEED_PLAY_ONCE;
207 }
208 if (needNotify) {
209 threadCv_.notify_one();
210 }
211 #ifndef SUPPORT_DIGITAL_CROWN
212 isHapticCanLoopPlay_ = true;
213 #endif
214 }
215
Stop()216 void PickerHapticController::Stop()
217 {
218 {
219 std::lock_guard<std::recursive_mutex> guard(threadMutex_);
220 playThreadStatus_ = ThreadStatus::READY;
221 }
222 threadCv_.notify_one();
223 lastHandleDeltaTime_ = 0;
224 }
225
HandleDelta(double dy)226 void PickerHapticController::HandleDelta(double dy)
227 {
228 #ifndef SUPPORT_DIGITAL_CROWN
229 uint64_t currentTime = GetMilliseconds();
230 uint64_t intervalTime = currentTime - lastHandleDeltaTime_;
231 CHECK_EQUAL_VOID(intervalTime, 0);
232
233 lastHandleDeltaTime_ = currentTime;
234 auto scrollSpeed = std::abs(ConvertPxToMillimeters(dy) / intervalTime) * 1000;
235 if (scrollSpeed > SPEED_MAX) {
236 scrollSpeed = SPEED_MAX;
237 }
238 recentSpeeds_.push_back(scrollSpeed);
239 if (recentSpeeds_.size() > TREND_COUNT) {
240 recentSpeeds_.pop_front();
241 }
242
243 if (!isInHapticLoop_ && isLoopReadyToStop_) {
244 // play haptic after 40ms 1oop
245 isLoopReadyToStop_ = false;
246 playThreadStatus_ = ThreadStatus::READY;
247 PlayOnce();
248 } else if (isHapticCanLoopPlay_ && GetPlayStatus() == 1) { // 40ms 1oop
249 Play(scrollSpeed);
250 } else if (GetPlayStatus() == -1 && IsThreadPlaying() && !isLoopReadyToStop_) {
251 // judging the time of next haptic after 40ms loop ends
252 isLoopReadyToStop_ = true;
253 isHapticCanLoopPlay_ = false;
254 recentSpeeds_.clear();
255 absSpeedInMm_ = scrollSpeed;
256 }
257 #endif
258 }
259
ConvertPxToMillimeters(double px) const260 double PickerHapticController::ConvertPxToMillimeters(double px) const
261 {
262 auto& manager = ScreenSystemManager::GetInstance();
263 return px / manager.GetDensity();
264 }
265
GetCurrentSpeedInMm()266 size_t PickerHapticController::GetCurrentSpeedInMm()
267 {
268 double velocityInPixels = velocityTracker_.GetVelocity().GetVelocityY();
269 return std::abs(ConvertPxToMillimeters(velocityInPixels));
270 }
271
GetPlayStatus()272 int8_t PickerHapticController::GetPlayStatus()
273 {
274 if (recentSpeeds_.size() < TREND_COUNT) {
275 return 0;
276 }
277 bool allAbove = true;
278 bool allBelow = true;
279 for (size_t i = 0; i < TREND_COUNT; ++i) {
280 const double speed = recentSpeeds_[i];
281 if (speed <= SPEED_THRESHOLD) {
282 allAbove = false;
283 }
284 if (speed >= SPEED_THRESHOLD) {
285 allBelow = false;
286 }
287 }
288 return allAbove ? 1 : (allBelow ? -1 : 0); // 1 : play -1 : playnoce 0 : other
289 }
290 } // namespace OHOS::Ace::NG
291