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/timepicker/timepicker_haptic_controller.h"
17 
18 namespace OHOS::Ace::NG {
19 namespace {
20 const std::string AUDIO_TEST_URI = "/system/etc/arkui/timepicker.ogg";
21 const std::string EFFECT_ID_NAME = "haptic.clock.timer";
22 constexpr size_t SPEED_THRESHOLD_156_MM_PER_SEC = 156;
23 constexpr size_t SPEED_PLAY_ONCE_5_MM_PER_SEC = 5;
24 } // namespace
25 
TimePickerHapticController()26 TimePickerHapticController::TimePickerHapticController() noexcept
27 {
28     audioHapticManager_ = Media::AudioHapticManagerFactory::CreateAudioHapticManager();
29     if (audioHapticManager_) {
30         effectSourceId_ = audioHapticManager_->RegisterSourceWithEffectId(AUDIO_TEST_URI, EFFECT_ID_NAME);
31         Media::AudioLatencyMode latencyMode = Media::AudioLatencyMode::AUDIO_LATENCY_MODE_FAST;
32         audioHapticManager_->SetAudioLatencyMode(effectSourceId_, latencyMode);
33         AudioStandard::StreamUsage streamUsage = AudioStandard::StreamUsage::STREAM_USAGE_NOTIFICATION;
34         audioHapticManager_->SetStreamUsage(effectSourceId_, streamUsage);
35         Media::AudioHapticPlayerOptions options;
36         options.muteAudio = false;
37         options.muteHaptics = false;
38         effectAudioHapticPlayer_ = audioHapticManager_->CreatePlayer(effectSourceId_, options);
39         if (effectAudioHapticPlayer_) {
40             effectAudioHapticPlayer_->Prepare();
41         }
42         auto audioSystemMgr = AudioStandard::AudioSystemManager::GetInstance();
43         audioGroupMngr_ = audioSystemMgr->GetGroupManager(AudioStandard::DEFAULT_VOLUME_GROUP_ID);
44         InitPlayThread();
45     }
46 }
47 
~TimePickerHapticController()48 TimePickerHapticController::~TimePickerHapticController() noexcept
49 {
50     ThreadRelease();
51     if (effectAudioHapticPlayer_) {
52         effectAudioHapticPlayer_->Stop();
53     }
54     if (effectAudioHapticPlayer_) {
55         effectAudioHapticPlayer_->Release();
56     }
57     if (audioHapticManager_) {
58         audioHapticManager_->UnregisterSource(effectSourceId_);
59     }
60 }
61 
ThreadRelease()62 void TimePickerHapticController::ThreadRelease()
63 {
64     if (playThread_) {
65         {
66             std::lock_guard<std::recursive_mutex> guard(threadMutex_);
67             playThreadStatus_ = ThreadStatus::NONE;
68         }
69         threadCv_.notify_one();
70         playThread_ = nullptr;
71     }
72     playThreadStatus_ = ThreadStatus::NONE;
73 }
74 
IsThreadReady()75 bool TimePickerHapticController::IsThreadReady()
76 {
77     std::lock_guard<std::recursive_mutex> guard(threadMutex_);
78     return playThreadStatus_ == ThreadStatus::READY;
79 }
80 
IsThreadPlaying()81 bool TimePickerHapticController::IsThreadPlaying()
82 {
83     std::lock_guard<std::recursive_mutex> guard(threadMutex_);
84     return playThreadStatus_ == ThreadStatus::PLAYING;
85 }
86 
IsThreadPlayOnce()87 bool TimePickerHapticController::IsThreadPlayOnce()
88 {
89     std::lock_guard<std::recursive_mutex> guard(threadMutex_);
90     return playThreadStatus_ == ThreadStatus::PLAY_ONCE;
91 }
92 
IsThreadNone()93 bool TimePickerHapticController::IsThreadNone()
94 {
95     std::lock_guard<std::recursive_mutex> guard(threadMutex_);
96     return playThreadStatus_ == ThreadStatus::NONE;
97 }
98 
InitPlayThread()99 void TimePickerHapticController::InitPlayThread()
100 {
101     ThreadRelease();
102     playThreadStatus_ = ThreadStatus::START;
103     playThread_ = std::make_unique<std::thread>(&TimePickerHapticController::ThreadLoop, this);
104     if (playThread_) {
105         playThread_->detach();
106         playThreadStatus_ = ThreadStatus::READY;
107     } else {
108         playThreadStatus_ = ThreadStatus::NONE;
109     }
110 }
111 
ThreadLoop()112 void TimePickerHapticController::ThreadLoop()
113 {
114     while (!IsThreadNone()) {
115         {
116             std::unique_lock<std::recursive_mutex> lock(threadMutex_);
117             threadCv_.wait(lock, [this]() { return IsThreadPlaying() || IsThreadPlayOnce() || IsThreadNone(); });
118             if (IsThreadNone()) {
119                 return;
120             }
121         }
122         CHECK_NULL_VOID(audioGroupMngr_);
123         CHECK_NULL_VOID(effectAudioHapticPlayer_);
124         auto vol = audioGroupMngr_->GetVolume(AudioStandard::AudioVolumeType::STREAM_RING);
125         auto userVolume = audioGroupMngr_->GetSystemVolumeInDb(
126             AudioStandard::AudioVolumeType::STREAM_RING, vol, AudioStandard::DEVICE_TYPE_SPEAKER);
127 
128         // Set different volumes for different sliding speeds:
129         //    sound effect loudness
130         //    (dB) = sound effect dB set by the user + (0.0066 screen movement speed (mm/s) - 0.01)
131         //    the range of volume interface setting is [0.0f, 1.0f]
132         float volume = userVolume + 0.0066 * absSpeedInMm_ - 0.01;
133         volume = std::clamp(volume, 0.0f, 1.0f);
134 
135         // Different vibration parameters for different sliding speeds:
136         //    the frequency is between 260~300Hz and fixed, the vibration amount
137         //    (g) = (0.007 * screen movement speed (mm/s) + 0.3) * 100
138         //    the range of haptic intensity interface setting is [1.0f, 100.0f]
139         float haptic = ((absSpeedInMm_ == 0) ? 0 : absSpeedInMm_ * 0.007 + 0.3) * 100;
140         haptic = std::clamp(haptic, 1.0f, 100.0f);
141 
142         auto startTime = std::chrono::high_resolution_clock::now();
143         effectAudioHapticPlayer_->SetVolume(volume);
144         effectAudioHapticPlayer_->SetHapticIntensity(haptic);
145         effectAudioHapticPlayer_->Start();
146         if (IsThreadPlaying()) {
147             std::unique_lock<std::recursive_mutex> lock(threadMutex_);
148             threadCv_.wait_until(lock, startTime + 40ms);
149         } else if (IsThreadPlayOnce()) {
150             std::unique_lock<std::recursive_mutex> lock(threadMutex_);
151             playThreadStatus_ = ThreadStatus::READY;
152         }
153     }
154 }
155 
Play(size_t speed)156 void TimePickerHapticController::Play(size_t speed)
157 {
158     if (!playThread_) {
159         InitPlayThread();
160     }
161     bool needNotify = !IsThreadPlaying();
162     {
163         std::lock_guard<std::recursive_mutex> guard(threadMutex_);
164         absSpeedInMm_ = speed;
165         playThreadStatus_ = ThreadStatus::PLAYING;
166     }
167     if (needNotify) {
168         threadCv_.notify_one();
169     }
170 }
171 
PlayOnce()172 void TimePickerHapticController::PlayOnce()
173 {
174     if (IsThreadPlaying()) {
175         return;
176     }
177 
178     {
179         std::lock_guard<std::recursive_mutex> guard(threadMutex_);
180         playThreadStatus_ = ThreadStatus::PLAY_ONCE;
181         absSpeedInMm_ = SPEED_PLAY_ONCE_5_MM_PER_SEC;
182     }
183     threadCv_.notify_one();
184 }
185 
Stop()186 void TimePickerHapticController::Stop()
187 {
188     {
189         std::lock_guard<std::recursive_mutex> guard(threadMutex_);
190         playThreadStatus_ = ThreadStatus::READY;
191     }
192     threadCv_.notify_one();
193     scrollValue_ = 0.0;
194 }
195 
HandleDelta(double dy)196 void TimePickerHapticController::HandleDelta(double dy)
197 {
198     auto startTime = std::chrono::high_resolution_clock::now();
199     scrollValue_ += dy;
200     velocityTracker_.UpdateTrackerPoint(0, scrollValue_, startTime);
201     auto scrollSpeed = GetCurrentSpeedInMm();
202     if (GreatOrEqual(scrollSpeed, SPEED_THRESHOLD_156_MM_PER_SEC)) {
203         Play(scrollSpeed);
204     } else {
205         Stop();
206     }
207 }
208 
ConvertPxToMillimeters(double px) const209 double TimePickerHapticController::ConvertPxToMillimeters(double px) const
210 {
211     auto& manager = ScreenSystemManager::GetInstance();
212     return px / manager.GetDensity();
213 }
214 
GetCurrentSpeedInMm()215 size_t TimePickerHapticController::GetCurrentSpeedInMm()
216 {
217     double velocityInPixels = velocityTracker_.GetVelocity().GetVelocityY();
218     return std::abs(ConvertPxToMillimeters(velocityInPixels));
219 }
220 
221 } // namespace OHOS::Ace::NG
222