• 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 #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