Merge "Update OWNER for IRadio" into main
diff --git a/audio/aidl/default/Configuration.cpp b/audio/aidl/default/Configuration.cpp
index d63e353..2a8e58f 100644
--- a/audio/aidl/default/Configuration.cpp
+++ b/audio/aidl/default/Configuration.cpp
@@ -321,9 +321,9 @@
//
// Mix ports:
// * "r_submix output", maximum 10 opened streams, maximum 10 active streams
-// - profile PCM 16-bit; MONO, STEREO; 8000, 11025, 16000, 32000, 44100, 48000
+// - profile PCM 16-bit; STEREO; 8000, 11025, 16000, 32000, 44100, 48000
// * "r_submix input", maximum 10 opened streams, maximum 10 active streams
-// - profile PCM 16-bit; MONO, STEREO; 8000, 11025, 16000, 32000, 44100, 48000
+// - profile PCM 16-bit; STEREO; 8000, 11025, 16000, 32000, 44100, 48000
//
// Routes:
// "r_submix output" -> "Remote Submix Out"
diff --git a/audio/aidl/default/r_submix/ModuleRemoteSubmix.cpp b/audio/aidl/default/r_submix/ModuleRemoteSubmix.cpp
index 3e8dd7c..2f42889 100644
--- a/audio/aidl/default/r_submix/ModuleRemoteSubmix.cpp
+++ b/audio/aidl/default/r_submix/ModuleRemoteSubmix.cpp
@@ -112,7 +112,7 @@
static constexpr int32_t kMaxLatencyMs =
(r_submix::kDefaultPipeSizeInFrames * 1000) / r_submix::kDefaultSampleRateHz;
static constexpr int32_t kMinLatencyMs = kMaxLatencyMs / r_submix::kDefaultPipePeriodCount;
- return (kMaxLatencyMs + kMinLatencyMs) / 2;
+ return kMinLatencyMs;
}
} // namespace aidl::android::hardware::audio::core
diff --git a/audio/aidl/default/r_submix/StreamRemoteSubmix.cpp b/audio/aidl/default/r_submix/StreamRemoteSubmix.cpp
index 6258c93..d238aa4 100644
--- a/audio/aidl/default/r_submix/StreamRemoteSubmix.cpp
+++ b/audio/aidl/default/r_submix/StreamRemoteSubmix.cpp
@@ -73,10 +73,8 @@
LOG(ERROR) << __func__ << ": nullptr sink when opening stream";
return ::android::NO_INIT;
}
- // If the sink has been shutdown or pipe recreation is forced, delete the pipe and
- // recreate it.
- if (sink->isShutdown()) {
- LOG(DEBUG) << __func__ << ": Non-nullptr shut down sink when opening stream";
+ if ((!mIsInput || mCurrentRoute->isStreamInOpen()) && sink->isShutdown()) {
+ LOG(DEBUG) << __func__ << ": Shut down sink when opening stream";
if (::android::OK != mCurrentRoute->resetPipe()) {
LOG(ERROR) << __func__ << ": reset pipe failed";
return ::android::NO_INIT;
diff --git a/audio/aidl/default/r_submix/SubmixRoute.cpp b/audio/aidl/default/r_submix/SubmixRoute.cpp
index f04e607..235c9a3 100644
--- a/audio/aidl/default/r_submix/SubmixRoute.cpp
+++ b/audio/aidl/default/r_submix/SubmixRoute.cpp
@@ -98,6 +98,9 @@
}
mStreamInStandby = true;
mReadCounterFrames = 0;
+ if (mSink != nullptr) {
+ mSink->shutdown(false);
+ }
} else {
mStreamOutOpen = true;
}
@@ -106,8 +109,7 @@
void SubmixRoute::closeStream(bool isInput) {
std::lock_guard guard(mLock);
if (isInput) {
- mInputRefCount--;
- if (mInputRefCount == 0) {
+ if (--mInputRefCount == 0) {
mStreamInOpen = false;
if (mSink != nullptr) {
mSink->shutdown(true);
diff --git a/health/aidl/default/Health.cpp b/health/aidl/default/Health.cpp
index 8b512c4..b2c0f0a 100644
--- a/health/aidl/default/Health.cpp
+++ b/health/aidl/default/Health.cpp
@@ -62,6 +62,18 @@
Health::~Health() {}
+static inline ndk::ScopedAStatus TranslateStatus(::android::status_t err) {
+ switch (err) {
+ case ::android::OK:
+ return ndk::ScopedAStatus::ok();
+ case ::android::NAME_NOT_FOUND:
+ return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+ default:
+ return ndk::ScopedAStatus::fromServiceSpecificErrorWithMessage(
+ IHealth::STATUS_UNKNOWN, ::android::statusToString(err).c_str());
+ }
+}
+
//
// Getters.
//
@@ -78,16 +90,7 @@
LOG(DEBUG) << "getProperty(" << id << ")"
<< " fails: (" << err << ") " << ::android::statusToString(err);
}
-
- switch (err) {
- case ::android::OK:
- return ndk::ScopedAStatus::ok();
- case ::android::NAME_NOT_FOUND:
- return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
- default:
- return ndk::ScopedAStatus::fromServiceSpecificErrorWithMessage(
- IHealth::STATUS_UNKNOWN, ::android::statusToString(err).c_str());
- }
+ return TranslateStatus(err);
}
ndk::ScopedAStatus Health::getChargeCounterUah(int32_t* out) {
@@ -153,8 +156,21 @@
!res.isOk()) {
LOG(WARNING) << "Cannot get Battery_state_of_health: " << res.getDescription();
}
- out->batterySerialNumber = std::nullopt;
- out->batteryPartStatus = BatteryPartStatus::UNSUPPORTED;
+ if (auto res = battery_monitor_.getSerialNumber(&out->batterySerialNumber);
+ res != ::android::OK) {
+ LOG(WARNING) << "Cannot get Battery_serial_number: "
+ << TranslateStatus(res).getDescription();
+ }
+
+ int64_t part_status = static_cast<int64_t>(BatteryPartStatus::UNSUPPORTED);
+ if (auto res = GetProperty<int64_t>(&battery_monitor_, ::android::BATTERY_PROP_PART_STATUS,
+ static_cast<int64_t>(BatteryPartStatus::UNSUPPORTED),
+ &part_status);
+ !res.isOk()) {
+ LOG(WARNING) << "Cannot get Battery_part_status: " << res.getDescription();
+ }
+ out->batteryPartStatus = static_cast<BatteryPartStatus>(part_status);
+
return ndk::ScopedAStatus::ok();
}
diff --git a/ir/aidl/default/android.hardware.ir-service.example.rc b/ir/aidl/default/android.hardware.ir-service.example.rc
index 1a721da..d27f282 100644
--- a/ir/aidl/default/android.hardware.ir-service.example.rc
+++ b/ir/aidl/default/android.hardware.ir-service.example.rc
@@ -1,4 +1,4 @@
-service vendor.ir-default /apex/com.android.hardware.ir/bin/hw/android.hardware.ir-service.example
+service vendor.ir-default /vendor/bin/hw/android.hardware.ir-service.example
class hal
user system
group system
diff --git a/security/secretkeeper/aidl/android/hardware/security/secretkeeper/SecretManagement.cddl b/security/secretkeeper/aidl/android/hardware/security/secretkeeper/SecretManagement.cddl
index 5631937..62a1bd7 100644
--- a/security/secretkeeper/aidl/android/hardware/security/secretkeeper/SecretManagement.cddl
+++ b/security/secretkeeper/aidl/android/hardware/security/secretkeeper/SecretManagement.cddl
@@ -111,6 +111,12 @@
ErrorCode_UnexpectedServerError: 1,
; Indicate the Request was malformed & hence couldnt be served.
ErrorCode_RequestMalformed: 2,
+ ; Requested Entry not found.
+ ErrorCode_EntryNotFound: 3,
+ ; Error happened while serialization or deserialization.
+ SerializationError: 4,
+ ; Indicates that Dice Policy matching did not succeed & hence access not granted.
+ ErrorCode_DicePolicyError: 5,
)
; INCLUDE DicePolicy.cddl for: DicePolicy
\ No newline at end of file
diff --git a/security/secretkeeper/aidl/vts/Android.bp b/security/secretkeeper/aidl/vts/Android.bp
index 93192e9..def5d5d 100644
--- a/security/secretkeeper/aidl/vts/Android.bp
+++ b/security/secretkeeper/aidl/vts/Android.bp
@@ -32,6 +32,7 @@
"libcoset",
"libauthgraph_vts_test",
"libbinder_rs",
+ "libcoset",
"liblog_rust",
],
require_root: true,
diff --git a/security/secretkeeper/aidl/vts/secretkeeper_test_client.rs b/security/secretkeeper/aidl/vts/secretkeeper_test_client.rs
index 8c6b4fe..cc606b1 100644
--- a/security/secretkeeper/aidl/vts/secretkeeper_test_client.rs
+++ b/security/secretkeeper/aidl/vts/secretkeeper_test_client.rs
@@ -21,8 +21,9 @@
use secretkeeper_comm::data_types::error::SecretkeeperError;
use secretkeeper_comm::data_types::request::Request;
use secretkeeper_comm::data_types::request_response_impl::{
- GetVersionRequest, GetVersionResponse,
-};
+ GetVersionRequest, GetVersionResponse, GetSecretRequest, GetSecretResponse, StoreSecretRequest,
+ StoreSecretResponse };
+use secretkeeper_comm::data_types::{Id, ID_SIZE, Secret, SECRET_SIZE};
use secretkeeper_comm::data_types::response::Response;
use secretkeeper_comm::data_types::packet::{ResponsePacket, ResponseType};
use android_hardware_security_secretkeeper::aidl::android::hardware::security::secretkeeper::ISecretkeeper::ISecretkeeper;
@@ -33,6 +34,33 @@
"android.hardware.security.secretkeeper.ISecretkeeper/nonsecure";
const CURRENT_VERSION: u64 = 1;
+// TODO(b/291238565): This will change once libdice_policy switches to Explicit-key DiceCertChain
+// This is generated by patching libdice_policy such that it dumps an example dice chain &
+// a policy, such that the former matches the latter.
+const HYPOTHETICAL_DICE_POLICY: [u8; 43] = [
+ 0x83, 0x01, 0x81, 0x83, 0x01, 0x80, 0xA1, 0x01, 0x00, 0x82, 0x83, 0x01, 0x81, 0x01, 0x73, 0x74,
+ 0x65, 0x73, 0x74, 0x69, 0x6E, 0x67, 0x5F, 0x64, 0x69, 0x63, 0x65, 0x5F, 0x70, 0x6F, 0x6C, 0x69,
+ 0x63, 0x79, 0x83, 0x02, 0x82, 0x03, 0x18, 0x64, 0x19, 0xE9, 0x75,
+];
+
+// Random bytes (of ID_SIZE/SECRET_SIZE) generated for tests.
+const ID_EXAMPLE: [u8; ID_SIZE] = [
+ 0xF1, 0xB2, 0xED, 0x3B, 0xD1, 0xBD, 0xF0, 0x7D, 0xE1, 0xF0, 0x01, 0xFC, 0x61, 0x71, 0xD3, 0x42,
+ 0xE5, 0x8A, 0xAF, 0x33, 0x6C, 0x11, 0xDC, 0xC8, 0x6F, 0xAE, 0x12, 0x5C, 0x26, 0x44, 0x6B, 0x86,
+ 0xCC, 0x24, 0xFD, 0xBF, 0x91, 0x4A, 0x54, 0x84, 0xF9, 0x01, 0x59, 0x25, 0x70, 0x89, 0x38, 0x8D,
+ 0x5E, 0xE6, 0x91, 0xDF, 0x68, 0x60, 0x69, 0x26, 0xBE, 0xFE, 0x79, 0x58, 0xF7, 0xEA, 0x81, 0x7D,
+];
+const ID_NOT_STORED: [u8; ID_SIZE] = [
+ 0x56, 0xD0, 0x4E, 0xAA, 0xC1, 0x7B, 0x55, 0x6B, 0xA0, 0x2C, 0x65, 0x43, 0x39, 0x0A, 0x6C, 0xE9,
+ 0x1F, 0xD0, 0x0E, 0x20, 0x3E, 0xFB, 0xF5, 0xF9, 0x3F, 0x5B, 0x11, 0x1B, 0x18, 0x73, 0xF6, 0xBB,
+ 0xAB, 0x9F, 0xF2, 0xD6, 0xBD, 0xBA, 0x25, 0x68, 0x22, 0x30, 0xF2, 0x1F, 0x90, 0x05, 0xF3, 0x64,
+ 0xE7, 0xEF, 0xC6, 0xB6, 0xA0, 0x85, 0xC9, 0x40, 0x40, 0xF0, 0xB4, 0xB9, 0xD8, 0x28, 0xEE, 0x9C,
+];
+const SECRET_EXAMPLE: [u8; SECRET_SIZE] = [
+ 0xA9, 0x89, 0x97, 0xFE, 0xAE, 0x97, 0x55, 0x4B, 0x32, 0x35, 0xF0, 0xE8, 0x93, 0xDA, 0xEA, 0x24,
+ 0x06, 0xAC, 0x36, 0x8B, 0x3C, 0x95, 0x50, 0x16, 0x67, 0x71, 0x65, 0x26, 0xEB, 0xD0, 0xC3, 0x98,
+];
+
fn get_connection() -> Option<binder::Strong<dyn ISecretkeeper>> {
match binder::get_interface(SECRETKEEPER_IDENTIFIER) {
Ok(sk) => Some(sk),
@@ -161,3 +189,93 @@
let err = *SecretkeeperError::deserialize_from_packet(response_packet).unwrap();
assert_eq!(err, SecretkeeperError::RequestMalformed);
}
+
+#[test]
+fn secret_management_store_get_secret_found() {
+ let secretkeeper = match get_connection() {
+ Some(sk) => sk,
+ None => {
+ warn!("Secretkeeper HAL is unavailable, skipping test");
+ return;
+ }
+ };
+
+ let store_request = StoreSecretRequest {
+ id: Id(ID_EXAMPLE),
+ secret: Secret(SECRET_EXAMPLE),
+ sealing_policy: HYPOTHETICAL_DICE_POLICY.to_vec(),
+ };
+
+ let store_request = store_request.serialize_to_packet().to_vec().unwrap();
+ let store_response = secretkeeper
+ .processSecretManagementRequest(&store_request)
+ .unwrap();
+ let store_response = ResponsePacket::from_slice(&store_response).unwrap();
+
+ assert_eq!(
+ store_response.response_type().unwrap(),
+ ResponseType::Success
+ );
+ // Really just checking that the response is indeed StoreSecretResponse
+ let _ = StoreSecretResponse::deserialize_from_packet(store_response).unwrap();
+
+ // Get the secret that was just stored
+ let get_request = GetSecretRequest {
+ id: Id(ID_EXAMPLE),
+ updated_sealing_policy: None,
+ };
+ let get_request = get_request.serialize_to_packet().to_vec().unwrap();
+
+ let get_response = secretkeeper
+ .processSecretManagementRequest(&get_request)
+ .unwrap();
+ let get_response = ResponsePacket::from_slice(&get_response).unwrap();
+ assert_eq!(get_response.response_type().unwrap(), ResponseType::Success);
+ let get_response = *GetSecretResponse::deserialize_from_packet(get_response).unwrap();
+ assert_eq!(get_response.secret.0, SECRET_EXAMPLE);
+}
+
+#[test]
+fn secret_management_store_get_secret_not_found() {
+ let secretkeeper = match get_connection() {
+ Some(sk) => sk,
+ None => {
+ warn!("Secretkeeper HAL is unavailable, skipping test");
+ return;
+ }
+ };
+
+ // Store a secret (corresponding to an id).
+ let store_request = StoreSecretRequest {
+ id: Id(ID_EXAMPLE),
+ secret: Secret(SECRET_EXAMPLE),
+ sealing_policy: HYPOTHETICAL_DICE_POLICY.to_vec(),
+ };
+
+ let store_request = store_request.serialize_to_packet().to_vec().unwrap();
+ let store_response = secretkeeper
+ .processSecretManagementRequest(&store_request)
+ .unwrap();
+ let store_response = ResponsePacket::from_slice(&store_response).unwrap();
+
+ assert_eq!(
+ store_response.response_type().unwrap(),
+ ResponseType::Success
+ );
+
+ // (Try to) Get the secret that was never stored
+ let get_request = GetSecretRequest {
+ id: Id(ID_NOT_STORED),
+ updated_sealing_policy: None,
+ };
+ let get_request = get_request.serialize_to_packet().to_vec().unwrap();
+ let get_response = secretkeeper
+ .processSecretManagementRequest(&get_request)
+ .unwrap();
+
+ // Check that response is `SecretkeeperError::EntryNotFound`
+ let get_response = ResponsePacket::from_slice(&get_response).unwrap();
+ assert_eq!(get_response.response_type().unwrap(), ResponseType::Error);
+ let err = *SecretkeeperError::deserialize_from_packet(get_response).unwrap();
+ assert_eq!(err, SecretkeeperError::EntryNotFound);
+}
diff --git a/security/secretkeeper/default/Android.bp b/security/secretkeeper/default/Android.bp
index 6612ea2..08cc67a 100644
--- a/security/secretkeeper/default/Android.bp
+++ b/security/secretkeeper/default/Android.bp
@@ -35,6 +35,7 @@
"libauthgraph_hal",
"libbinder_rs",
"liblog_rust",
+ "libsecretkeeper_comm_nostd",
"libsecretkeeper_core_nostd",
"libsecretkeeper_hal",
],
diff --git a/security/secretkeeper/default/src/main.rs b/security/secretkeeper/default/src/main.rs
index a291017..c8c1521 100644
--- a/security/secretkeeper/default/src/main.rs
+++ b/security/secretkeeper/default/src/main.rs
@@ -15,17 +15,21 @@
*/
//! Non-secure implementation of the Secretkeeper HAL.
+mod store;
-use log::{error, info, Level};
-use std::sync::{Arc, Mutex};
use authgraph_boringssl as boring;
-use authgraph_core::ta::{Role, AuthGraphTa};
-use authgraph_core::keyexchange::{MAX_OPENED_SESSIONS, AuthGraphParticipant};
+use authgraph_core::keyexchange::{AuthGraphParticipant, MAX_OPENED_SESSIONS};
+use authgraph_core::ta::{AuthGraphTa, Role};
+use authgraph_hal::channel::SerializedChannel;
+use log::{error, info, Level};
use secretkeeper_core::ta::SecretkeeperTa;
use secretkeeper_hal::SecretkeeperService;
-use authgraph_hal::channel::SerializedChannel;
+use std::sync::Arc;
+use std::sync::Mutex;
+use store::InMemoryStore;
+
use android_hardware_security_secretkeeper::aidl::android::hardware::security::secretkeeper::ISecretkeeper::{
- ISecretkeeper, BpSecretkeeper,
+ BpSecretkeeper, ISecretkeeper,
};
use std::cell::RefCell;
use std::rc::Rc;
@@ -53,8 +57,9 @@
// The TA code expects to run single threaded, so spawn a thread to run it in.
std::thread::spawn(move || {
let mut crypto_impls = boring::crypto_trait_impls();
+ let storage_impl = Box::new(InMemoryStore::default());
let sk_ta = Rc::new(RefCell::new(
- SecretkeeperTa::new(&mut crypto_impls)
+ SecretkeeperTa::new(&mut crypto_impls, storage_impl)
.expect("Failed to create local Secretkeeper TA"),
));
let mut ag_ta = AuthGraphTa::new(
diff --git a/security/secretkeeper/default/src/store.rs b/security/secretkeeper/default/src/store.rs
new file mode 100644
index 0000000..7b2d0b9
--- /dev/null
+++ b/security/secretkeeper/default/src/store.rs
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+use secretkeeper_comm::data_types::error::Error;
+use secretkeeper_core::store::KeyValueStore;
+use std::collections::HashMap;
+
+/// An in-memory implementation of KeyValueStore. Please note that this is entirely for
+/// testing purposes. Refer to the documentation of `PolicyGatedStorage` & Secretkeeper HAL for
+/// persistence requirements.
+#[derive(Default)]
+pub struct InMemoryStore(HashMap<Vec<u8>, Vec<u8>>);
+impl KeyValueStore for InMemoryStore {
+ fn store(&mut self, key: &[u8], val: &[u8]) -> Result<(), Error> {
+ // This will overwrite the value if key is already present.
+ let _ = self.0.insert(key.to_vec(), val.to_vec());
+ Ok(())
+ }
+
+ fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>, Error> {
+ let optional_val = self.0.get(key);
+ Ok(optional_val.cloned())
+ }
+}
diff --git a/tv/hdmi/connection/aidl/default/HdmiConnectionMock.cpp b/tv/hdmi/connection/aidl/default/HdmiConnectionMock.cpp
index 8f4411b..954982e 100644
--- a/tv/hdmi/connection/aidl/default/HdmiConnectionMock.cpp
+++ b/tv/hdmi/connection/aidl/default/HdmiConnectionMock.cpp
@@ -15,12 +15,11 @@
*/
#define LOG_TAG "android.hardware.tv.hdmi.connection"
+#include "HdmiConnectionMock.h"
#include <android-base/logging.h>
#include <fcntl.h>
#include <utils/Log.h>
-#include "HdmiConnectionMock.h"
-
using ndk::ScopedAStatus;
namespace android {
@@ -34,6 +33,7 @@
ALOGE("HdmiConnectionMock died");
auto hdmi = static_cast<HdmiConnectionMock*>(cookie);
hdmi->mHdmiThreadRun = false;
+ pthread_join(hdmi->mThreadId, NULL);
}
ScopedAStatus HdmiConnectionMock::getPortInfo(std::vector<HdmiPortInfo>* _aidl_return) {
@@ -55,12 +55,15 @@
ScopedAStatus HdmiConnectionMock::setCallback(
const std::shared_ptr<IHdmiConnectionCallback>& callback) {
if (mCallback != nullptr) {
+ stopThread();
mCallback = nullptr;
}
-
if (callback != nullptr) {
mCallback = callback;
- AIBinder_linkToDeath(this->asBinder().get(), mDeathRecipient.get(), 0 /* cookie */);
+ mDeathRecipient =
+ ndk::ScopedAIBinder_DeathRecipient(AIBinder_DeathRecipient_new(serviceDied));
+
+ AIBinder_linkToDeath(callback->asBinder().get(), mDeathRecipient.get(), this /* cookie */);
mInputFile = open(HDMI_MSG_IN_FIFO, O_RDWR | O_CLOEXEC);
pthread_create(&mThreadId, NULL, __threadLoop, this);
@@ -153,7 +156,7 @@
int r = -1;
// Open the input pipe
- while (mInputFile < 0) {
+ while (mHdmiThreadRun && mInputFile < 0) {
usleep(1000 * 1000);
mInputFile = open(HDMI_MSG_IN_FIFO, O_RDONLY | O_CLOEXEC);
}
@@ -193,7 +196,21 @@
.physicalAddress = mPhysicalAddress};
mPortConnectionStatus[0] = false;
mHpdSignal[0] = HpdSignal::HDMI_HPD_PHYSICAL;
- mDeathRecipient = ndk::ScopedAIBinder_DeathRecipient(AIBinder_DeathRecipient_new(serviceDied));
+ mDeathRecipient = ndk::ScopedAIBinder_DeathRecipient(nullptr);
+}
+
+void HdmiConnectionMock::stopThread() {
+ if (mCallback != nullptr) {
+ ALOGE("[halimp_aidl] HdmiConnectionMock shutting down.");
+ mCallback = nullptr;
+ mDeathRecipient = ndk::ScopedAIBinder_DeathRecipient(nullptr);
+ mHdmiThreadRun = false;
+ pthread_join(mThreadId, NULL);
+ }
+}
+
+HdmiConnectionMock::~HdmiConnectionMock() {
+ stopThread();
}
} // namespace implementation
diff --git a/tv/hdmi/connection/aidl/default/HdmiConnectionMock.h b/tv/hdmi/connection/aidl/default/HdmiConnectionMock.h
index c013fdd..8c66f08 100644
--- a/tv/hdmi/connection/aidl/default/HdmiConnectionMock.h
+++ b/tv/hdmi/connection/aidl/default/HdmiConnectionMock.h
@@ -41,7 +41,7 @@
struct HdmiConnectionMock : public BnHdmiConnection {
HdmiConnectionMock();
-
+ ~HdmiConnectionMock();
::ndk::ScopedAStatus getPortInfo(std::vector<HdmiPortInfo>* _aidl_return) override;
::ndk::ScopedAStatus isConnected(int32_t portId, bool* _aidl_return) override;
::ndk::ScopedAStatus setCallback(
@@ -56,6 +56,7 @@
void threadLoop();
int readMessageFromFifo(unsigned char* buf, int msgCount);
void handleHotplugMessage(unsigned char* msgBuf);
+ void stopThread();
private:
static void serviceDied(void* cookie);