1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #ifndef ANDROID_HARDWARE_CONTEXTHUB_COMMON_CONTEXTHUB_H
18 #define ANDROID_HARDWARE_CONTEXTHUB_COMMON_CONTEXTHUB_H
19 
20 #include <condition_variable>
21 #include <functional>
22 #include <mutex>
23 #include <optional>
24 
25 #include <android/hardware/contexthub/1.0/IContexthub.h>
26 #include <hidl/MQDescriptor.h>
27 #include <hidl/Status.h>
28 #include <log/log.h>
29 
30 #include "chre_host/fragmented_load_transaction.h"
31 #include "chre_host/host_protocol_host.h"
32 #include "chre_host/socket_client.h"
33 
34 namespace android {
35 namespace hardware {
36 namespace contexthub {
37 namespace common {
38 namespace implementation {
39 
40 using ::android::sp;
41 using ::android::chre::FragmentedLoadRequest;
42 using ::android::chre::FragmentedLoadTransaction;
43 using ::android::chre::getStringFromByteVector;
44 using ::android::chre::HostProtocolHost;
45 using ::android::hardware::hidl_handle;
46 using ::android::hardware::hidl_string;
47 using ::android::hardware::hidl_vec;
48 using ::android::hardware::Return;
49 using ::android::hardware::contexthub::V1_0::AsyncEventType;
50 using ::android::hardware::contexthub::V1_0::ContextHub;
51 using ::android::hardware::contexthub::V1_0::ContextHubMsg;
52 using ::android::hardware::contexthub::V1_0::HubAppInfo;
53 using ::android::hardware::contexthub::V1_0::IContexthubCallback;
54 using ::android::hardware::contexthub::V1_0::NanoAppBinary;
55 using ::android::hardware::contexthub::V1_0::Result;
56 using ::android::hardware::contexthub::V1_0::TransactionResult;
57 using ::flatbuffers::FlatBufferBuilder;
58 
59 constexpr uint32_t kDefaultHubId = 0;
60 
61 inline constexpr uint8_t extractChreApiMajorVersion(uint32_t chreVersion) {
62   return static_cast<uint8_t>(chreVersion >> 24);
63 }
64 
65 inline constexpr uint8_t extractChreApiMinorVersion(uint32_t chreVersion) {
66   return static_cast<uint8_t>(chreVersion >> 16);
67 }
68 
69 inline constexpr uint16_t extractChrePatchVersion(uint32_t chreVersion) {
70   return static_cast<uint16_t>(chreVersion);
71 }
72 
73 /**
74  * @return file descriptor contained in the hidl_handle, or -1 if there is none
75  */
76 inline int hidlHandleToFileDescriptor(const hidl_handle &hh) {
77   const native_handle_t *handle = hh.getNativeHandle();
78   return (handle != nullptr && handle->numFds >= 1) ? handle->data[0] : -1;
79 }
80 
81 template <class IContexthubT>
82 class GenericContextHubBase : public IContexthubT {
83  public:
84   GenericContextHubBase() {
85     constexpr char kChreSocketName[] = "chre";
86 
87     mSocketCallbacks = new SocketCallbacks(*this);
88     if (!mClient.connectInBackground(kChreSocketName, mSocketCallbacks)) {
89       ALOGE("Couldn't start socket client");
90     }
91 
92     mDeathRecipient = new DeathRecipient(this);
93   }
94 
95   Return<void> debug(const hidl_handle &fd,
96                      const hidl_vec<hidl_string> & /* options */) override {
97     // Timeout inside CHRE is typically 5 seconds, grant 500ms extra here to let
98     // the data reach us
99     constexpr auto kDebugDumpTimeout = std::chrono::milliseconds(5500);
100 
101     mDebugFd = hidlHandleToFileDescriptor(fd);
102     if (mDebugFd < 0) {
103       ALOGW("Can't dump debug info to invalid fd");
104     } else {
105       writeToDebugFile("-- Dumping CHRE/ASH debug info --\n");
106 
107       ALOGV("Sending debug dump request");
108       FlatBufferBuilder builder;
109       HostProtocolHost::encodeDebugDumpRequest(builder);
110       std::unique_lock<std::mutex> lock(mDebugDumpMutex);
111       mDebugDumpPending = true;
112       if (!mClient.sendMessage(builder.GetBufferPointer(), builder.GetSize())) {
113         ALOGW("Couldn't send debug dump request");
114       } else {
115         mDebugDumpCond.wait_for(lock, kDebugDumpTimeout,
116                                 [this]() { return !mDebugDumpPending; });
117         if (mDebugDumpPending) {
118           ALOGI("Timed out waiting on debug dump data");
119           mDebugDumpPending = false;
120         }
121       }
122       writeToDebugFile("\n-- End of CHRE/ASH debug info --\n");
123 
124       mDebugFd = kInvalidFd;
125       ALOGV("Debug dump complete");
126     }
127 
128     return Void();
129   }
130 
131   // Methods from ::android::hardware::contexthub::V1_0::IContexthub follow.
132   Return<void> getHubs(V1_0::IContexthub::getHubs_cb _hidl_cb) override {
133     constexpr auto kHubInfoQueryTimeout = std::chrono::seconds(5);
134     std::vector<ContextHub> hubs;
135     ALOGV("%s", __func__);
136 
137     // If we're not connected yet, give it some time
138     // TODO refactor from polling into conditional wait
139     int maxSleepIterations = 250;
140     while (!mHubInfoValid && !mClient.isConnected() &&
141            --maxSleepIterations > 0) {
142       std::this_thread::sleep_for(std::chrono::milliseconds(20));
143     }
144 
145     if (!mClient.isConnected()) {
146       ALOGE("Couldn't connect to hub daemon");
147     } else if (!mHubInfoValid) {
148       // We haven't cached the hub details yet, so send a request and block
149       // waiting on a response
150       std::unique_lock<std::mutex> lock(mHubInfoMutex);
151       FlatBufferBuilder builder;
152       HostProtocolHost::encodeHubInfoRequest(builder);
153 
154       ALOGD("Sending hub info request");
155       if (!mClient.sendMessage(builder.GetBufferPointer(), builder.GetSize())) {
156         ALOGE("Couldn't send hub info request");
157       } else {
158         mHubInfoCond.wait_for(lock, kHubInfoQueryTimeout,
159                               [this]() { return mHubInfoValid; });
160       }
161     }
162 
163     if (mHubInfoValid) {
164       hubs.push_back(mHubInfo);
165     } else {
166       ALOGE("Unable to get hub info from CHRE");
167     }
168 
169     _hidl_cb(hubs);
170     return Void();
171   }
172 
173   Return<Result> registerCallback(uint32_t hubId,
174                                   const sp<IContexthubCallback> &cb) override {
175     Result result;
176     ALOGV("%s", __func__);
177 
178     // TODO: currently we only support 1 hub behind this HAL implementation
179     if (hubId == kDefaultHubId) {
180       std::lock_guard<std::mutex> lock(mCallbacksLock);
181 
182       if (cb != nullptr) {
183         if (mCallbacks != nullptr) {
184           ALOGD("Modifying callback for hubId %" PRIu32, hubId);
185           mCallbacks->unlinkToDeath(mDeathRecipient);
186         }
187         Return<bool> linkReturn = cb->linkToDeath(mDeathRecipient, hubId);
188         if (!linkReturn.withDefault(false)) {
189           ALOGW("Could not link death recipient to hubId %" PRIu32, hubId);
190         }
191       }
192 
193       mCallbacks = cb;
194       result = Result::OK;
195     } else {
196       result = Result::BAD_PARAMS;
197     }
198 
199     return result;
200   }
201 
202   Return<Result> sendMessageToHub(uint32_t hubId,
203                                   const ContextHubMsg &msg) override {
204     Result result;
205     ALOGV("%s", __func__);
206 
207     if (hubId != kDefaultHubId) {
208       result = Result::BAD_PARAMS;
209     } else {
210       FlatBufferBuilder builder(1024);
211       HostProtocolHost::encodeNanoappMessage(builder, msg.appName, msg.msgType,
212                                              msg.hostEndPoint, msg.msg.data(),
213                                              msg.msg.size());
214 
215       if (!mClient.sendMessage(builder.GetBufferPointer(), builder.GetSize())) {
216         result = Result::UNKNOWN_FAILURE;
217       } else {
218         result = Result::OK;
219       }
220     }
221 
222     return result;
223   }
224 
225   Return<Result> loadNanoApp(uint32_t hubId, const NanoAppBinary &appBinary,
226                              uint32_t transactionId) override {
227     Result result;
228     ALOGV("%s", __func__);
229 
230     if (hubId != kDefaultHubId) {
231       result = Result::BAD_PARAMS;
232     } else {
233       std::lock_guard<std::mutex> lock(mPendingLoadTransactionMutex);
234 
235       if (mPendingLoadTransaction.has_value()) {
236         ALOGE("Pending load transaction exists. Overriding pending request");
237       }
238 
239       uint32_t targetApiVersion = (appBinary.targetChreApiMajorVersion << 24) |
240                                   (appBinary.targetChreApiMinorVersion << 16);
241       mPendingLoadTransaction = FragmentedLoadTransaction(
242           transactionId, appBinary.appId, appBinary.appVersion,
243           targetApiVersion, appBinary.customBinary, kLoadFragmentSizeBytes);
244 
245       result =
246           sendFragmentedLoadNanoAppRequest(mPendingLoadTransaction.value());
247       if (result != Result::OK) {
248         mPendingLoadTransaction.reset();
249       }
250     }
251 
252     ALOGD(
253         "Attempted to send load nanoapp request for app of size %zu with ID "
254         "0x%016" PRIx64 " as transaction ID %" PRIu32 ": result %" PRIu32,
255         appBinary.customBinary.size(), appBinary.appId, transactionId, result);
256 
257     return result;
258   }
259 
260   Return<Result> unloadNanoApp(uint32_t hubId, uint64_t appId,
261                                uint32_t transactionId) override {
262     Result result;
263     ALOGV("%s", __func__);
264 
265     if (hubId != kDefaultHubId) {
266       result = Result::BAD_PARAMS;
267     } else {
268       FlatBufferBuilder builder(64);
269       HostProtocolHost::encodeUnloadNanoappRequest(
270           builder, transactionId, appId, false /* allowSystemNanoappUnload */);
271       if (!mClient.sendMessage(builder.GetBufferPointer(), builder.GetSize())) {
272         result = Result::UNKNOWN_FAILURE;
273       } else {
274         result = Result::OK;
275       }
276     }
277 
278     ALOGD("Attempted to send unload nanoapp request for app ID 0x%016" PRIx64
279           " as transaction ID %" PRIu32 ": result %" PRIu32,
280           appId, transactionId, result);
281 
282     return result;
283   }
284 
285   Return<Result> enableNanoApp(uint32_t /* hubId */, uint64_t appId,
286                                uint32_t /* transactionId */) override {
287     // TODO
288     ALOGW("Attempted to enable app ID 0x%016" PRIx64 ", but not supported",
289           appId);
290     return Result::TRANSACTION_FAILED;
291   }
292 
293   Return<Result> disableNanoApp(uint32_t /* hubId */, uint64_t appId,
294                                 uint32_t /* transactionId */) override {
295     // TODO
296     ALOGW("Attempted to disable app ID 0x%016" PRIx64 ", but not supported",
297           appId);
298     return Result::TRANSACTION_FAILED;
299   }
300 
301   Return<Result> queryApps(uint32_t hubId) override {
302     Result result;
303     ALOGV("%s", __func__);
304 
305     if (hubId != kDefaultHubId) {
306       result = Result::BAD_PARAMS;
307     } else {
308       FlatBufferBuilder builder(64);
309       HostProtocolHost::encodeNanoappListRequest(builder);
310       if (!mClient.sendMessage(builder.GetBufferPointer(), builder.GetSize())) {
311         result = Result::UNKNOWN_FAILURE;
312       } else {
313         result = Result::OK;
314       }
315     }
316 
317     return result;
318   }
319 
320  protected:
321   ::android::chre::SocketClient mClient;
322   sp<IContexthubCallback> mCallbacks;
323   std::mutex mCallbacksLock;
324 
325   class SocketCallbacks : public ::android::chre::SocketClient::ICallbacks,
326                           public ::android::chre::IChreMessageHandlers {
327    public:
328     explicit SocketCallbacks(GenericContextHubBase &parent) : mParent(parent) {}
329 
330     void onMessageReceived(const void *data, size_t length) override {
331       if (!HostProtocolHost::decodeMessageFromChre(data, length, *this)) {
332         ALOGE("Failed to decode message");
333       }
334     }
335 
336     void onConnected() override {
337       if (mHaveConnected) {
338         ALOGI("Reconnected to CHRE daemon");
339         invokeClientCallback([&]() {
340           return mParent.mCallbacks->handleHubEvent(AsyncEventType::RESTARTED);
341         });
342       }
343       mHaveConnected = true;
344     }
345 
346     void onDisconnected() override {
347       ALOGW("Lost connection to CHRE daemon");
348     }
349 
350     void handleNanoappMessage(
351         const ::chre::fbs::NanoappMessageT &message) override {
352       ContextHubMsg msg;
353       msg.appName = message.app_id;
354       msg.hostEndPoint = message.host_endpoint;
355       msg.msgType = message.message_type;
356       msg.msg = message.message;
357 
358       invokeClientCallback(
359           [&]() { return mParent.mCallbacks->handleClientMsg(msg); });
360     }
361 
362     void handleHubInfoResponse(
363         const ::chre::fbs::HubInfoResponseT &response) override {
364       ALOGD("Got hub info response");
365 
366       std::lock_guard<std::mutex> lock(mParent.mHubInfoMutex);
367       if (mParent.mHubInfoValid) {
368         ALOGI("Ignoring duplicate/unsolicited hub info response");
369       } else {
370         mParent.mHubInfo.name = getStringFromByteVector(response.name);
371         mParent.mHubInfo.vendor = getStringFromByteVector(response.vendor);
372         mParent.mHubInfo.toolchain =
373             getStringFromByteVector(response.toolchain);
374         mParent.mHubInfo.platformVersion = response.platform_version;
375         mParent.mHubInfo.toolchainVersion = response.toolchain_version;
376         mParent.mHubInfo.hubId = kDefaultHubId;
377 
378         mParent.mHubInfo.peakMips = response.peak_mips;
379         mParent.mHubInfo.stoppedPowerDrawMw = response.stopped_power;
380         mParent.mHubInfo.sleepPowerDrawMw = response.sleep_power;
381         mParent.mHubInfo.peakPowerDrawMw = response.peak_power;
382 
383         mParent.mHubInfo.maxSupportedMsgLen = response.max_msg_len;
384         mParent.mHubInfo.chrePlatformId = response.platform_id;
385 
386         uint32_t version = response.chre_platform_version;
387         mParent.mHubInfo.chreApiMajorVersion =
388             extractChreApiMajorVersion(version);
389         mParent.mHubInfo.chreApiMinorVersion =
390             extractChreApiMinorVersion(version);
391         mParent.mHubInfo.chrePatchVersion = extractChrePatchVersion(version);
392 
393         mParent.mHubInfoValid = true;
394         mParent.mHubInfoCond.notify_all();
395       }
396     }
397 
398     void handleNanoappListResponse(
399         const ::chre::fbs::NanoappListResponseT &response) override {
400       std::vector<HubAppInfo> appInfoList;
401 
402       ALOGV("Got nanoapp list response with %zu apps",
403             response.nanoapps.size());
404       for (const std::unique_ptr<::chre::fbs::NanoappListEntryT> &nanoapp :
405            response.nanoapps) {
406         // TODO: determine if this is really required, and if so, have
407         // HostProtocolHost strip out null entries as part of decode
408         if (nanoapp == nullptr) {
409           continue;
410         }
411 
412         ALOGV("App 0x%016" PRIx64 " ver 0x%" PRIx32 " enabled %d system %d",
413               nanoapp->app_id, nanoapp->version, nanoapp->enabled,
414               nanoapp->is_system);
415         if (!nanoapp->is_system) {
416           HubAppInfo appInfo;
417 
418           appInfo.appId = nanoapp->app_id;
419           appInfo.version = nanoapp->version;
420           appInfo.enabled = nanoapp->enabled;
421 
422           appInfoList.push_back(appInfo);
423         }
424       }
425 
426       invokeClientCallback(
427           [&]() { return mParent.mCallbacks->handleAppsInfo(appInfoList); });
428     }
429 
430     void handleLoadNanoappResponse(
431         const ::chre::fbs::LoadNanoappResponseT &response) override {
432       ALOGV("Got load nanoapp response for transaction %" PRIu32
433             " fragment %" PRIu32 " with result %d",
434             response.transaction_id, response.fragment_id, response.success);
435       std::unique_lock<std::mutex> lock(mParent.mPendingLoadTransactionMutex);
436 
437       // TODO: Handle timeout in receiving load response
438       if (!mParent.mPendingLoadTransaction.has_value()) {
439         ALOGE(
440             "Dropping unexpected load response (no pending transaction "
441             "exists)");
442       } else {
443         FragmentedLoadTransaction &transaction =
444             mParent.mPendingLoadTransaction.value();
445 
446         if (!mParent.isExpectedLoadResponseLocked(response)) {
447           ALOGE(
448               "Dropping unexpected load response, expected transaction %" PRIu32
449               " fragment %" PRIu32 ", received transaction %" PRIu32
450               " fragment %" PRIu32,
451               transaction.getTransactionId(), mParent.mCurrentFragmentId,
452               response.transaction_id, response.fragment_id);
453         } else {
454           TransactionResult result;
455           bool continueLoadRequest = false;
456           if (response.success && !transaction.isComplete()) {
457             if (mParent.sendFragmentedLoadNanoAppRequest(transaction) ==
458                 Result::OK) {
459               continueLoadRequest = true;
460               result = TransactionResult::SUCCESS;
461             } else {
462               result = TransactionResult::FAILURE;
463             }
464           } else {
465             result = (response.success) ? TransactionResult::SUCCESS
466                                         : TransactionResult::FAILURE;
467           }
468 
469           if (!continueLoadRequest) {
470             mParent.mPendingLoadTransaction.reset();
471             lock.unlock();
472             invokeClientCallback([&]() {
473               return mParent.mCallbacks->handleTxnResult(
474                   response.transaction_id, result);
475             });
476           }
477         }
478       }
479     }
480 
481     void handleUnloadNanoappResponse(
482         const ::chre::fbs::UnloadNanoappResponseT &response) override {
483       ALOGV("Got unload nanoapp response for transaction %" PRIu32
484             " with result %d",
485             response.transaction_id, response.success);
486 
487       invokeClientCallback([&]() {
488         TransactionResult result = (response.success)
489                                        ? TransactionResult::SUCCESS
490                                        : TransactionResult::FAILURE;
491         return mParent.mCallbacks->handleTxnResult(response.transaction_id,
492                                                    result);
493       });
494     }
495 
496     void handleDebugDumpData(const ::chre::fbs::DebugDumpDataT &data) override {
497       ALOGV("Got debug dump data, size %zu", data.debug_str.size());
498       if (mParent.mDebugFd == kInvalidFd) {
499         ALOGW("Got unexpected debug dump data message");
500       } else {
501         mParent.writeToDebugFile(
502             reinterpret_cast<const char *>(data.debug_str.data()),
503             data.debug_str.size());
504       }
505     }
506 
507     void handleDebugDumpResponse(
508         const ::chre::fbs::DebugDumpResponseT &response) override {
509       ALOGV("Got debug dump response, success %d, data count %" PRIu32,
510             response.success, response.data_count);
511       std::lock_guard<std::mutex> lock(mParent.mDebugDumpMutex);
512       if (!mParent.mDebugDumpPending) {
513         ALOGI("Ignoring duplicate/unsolicited debug dump response");
514       } else {
515         mParent.mDebugDumpPending = false;
516         mParent.mDebugDumpCond.notify_all();
517       }
518     }
519 
520    private:
521     GenericContextHubBase &mParent;
522     bool mHaveConnected = false;
523 
524     /**
525      * Acquires mParent.mCallbacksLock and invokes the synchronous callback
526      * argument if mParent.mCallbacks is not null.
527      */
528     void invokeClientCallback(std::function<Return<void>()> callback) {
529       std::lock_guard<std::mutex> lock(mParent.mCallbacksLock);
530       if (mParent.mCallbacks != nullptr && !callback().isOk()) {
531         ALOGE("Failed to invoke client callback");
532       }
533     }
534   };
535 
536   class DeathRecipient : public hidl_death_recipient {
537    public:
538     explicit DeathRecipient(const sp<GenericContextHubBase> contexthub)
539         : mGenericContextHub(contexthub) {}
540     void serviceDied(
541         uint64_t cookie,
542         const wp<::android::hidl::base::V1_0::IBase> & /* who */) override {
543       uint32_t hubId = static_cast<uint32_t>(cookie);
544       mGenericContextHub->handleServiceDeath(hubId);
545     }
546 
547    private:
548     sp<GenericContextHubBase> mGenericContextHub;
549   };
550 
551   sp<SocketCallbacks> mSocketCallbacks;
552   sp<DeathRecipient> mDeathRecipient;
553 
554   // Cached hub info used for getHubs(), and synchronization primitives to make
555   // that function call synchronous if we need to query it
556   ContextHub mHubInfo;
557   bool mHubInfoValid = false;
558   std::mutex mHubInfoMutex;
559   std::condition_variable mHubInfoCond;
560 
561   static constexpr int kInvalidFd = -1;
562   int mDebugFd = kInvalidFd;
563   bool mDebugDumpPending = false;
564   std::mutex mDebugDumpMutex;
565   std::condition_variable mDebugDumpCond;
566 
567   // The pending fragmented load request
568   uint32_t mCurrentFragmentId = 0;
569   std::optional<FragmentedLoadTransaction> mPendingLoadTransaction;
570   std::mutex mPendingLoadTransactionMutex;
571 
572   // Use 30KB fragment size to fit within 32KB memory fragments at the kernel
573   static constexpr size_t kLoadFragmentSizeBytes = 30 * 1024;
574 
575   // Write a string to mDebugFd
576   void writeToDebugFile(const char *str) {
577     writeToDebugFile(str, strlen(str));
578   }
579 
580   void writeToDebugFile(const char *str, size_t len) {
581     ssize_t written = write(mDebugFd, str, len);
582     if (written != (ssize_t)len) {
583       ALOGW(
584           "Couldn't write to debug header: returned %zd, expected %zu (errno "
585           "%d)",
586           written, len, errno);
587     }
588   }
589 
590   // Unregisters callback when context hub service dies
591   void handleServiceDeath(uint32_t hubId) {
592     std::lock_guard<std::mutex> lock(mCallbacksLock);
593     ALOGI("Context hub service died for hubId %" PRIu32, hubId);
594     mCallbacks.clear();
595   }
596 
597   /**
598    * Checks to see if a load response matches the currently pending
599    * fragmented load transaction. mPendingLoadTransactionMutex must
600    * be acquired prior to calling this function.
601    *
602    * @param response the received load response
603    *
604    * @return true if the response matches a pending load transaction
605    *         (if any), false otherwise
606    */
607   bool isExpectedLoadResponseLocked(
608       const ::chre::fbs::LoadNanoappResponseT &response) {
609     return mPendingLoadTransaction.has_value() &&
610            (mPendingLoadTransaction->getTransactionId() ==
611             response.transaction_id) &&
612            (response.fragment_id == 0 ||
613             mCurrentFragmentId == response.fragment_id);
614   }
615 
616   /**
617    * Sends a fragmented load request to CHRE. The caller must ensure that
618    * transaction.isComplete() returns false prior to invoking this method.
619    *
620    * @param transaction the FragmentedLoadTransaction object
621    *
622    * @return the result of the request
623    */
624   Result sendFragmentedLoadNanoAppRequest(
625       FragmentedLoadTransaction &transaction) {
626     Result result;
627     const FragmentedLoadRequest &request = transaction.getNextRequest();
628 
629     FlatBufferBuilder builder(128 + request.binary.size());
630     HostProtocolHost::encodeFragmentedLoadNanoappRequest(builder, request);
631 
632     if (!mClient.sendMessage(builder.GetBufferPointer(), builder.GetSize())) {
633       ALOGE("Failed to send load request message (fragment ID = %zu)",
634             request.fragmentId);
635       result = Result::UNKNOWN_FAILURE;
636     } else {
637       mCurrentFragmentId = request.fragmentId;
638       result = Result::OK;
639     }
640 
641     return result;
642   }
643 };
644 
645 }  // namespace implementation
646 }  // namespace common
647 }  // namespace contexthub
648 }  // namespace hardware
649 }  // namespace android
650 
651 #endif  // ANDROID_HARDWARE_CONTEXTHUB_COMMON_CONTEXTHUB_H
652