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