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