Merge "Introduce internal APIs between CarTelemetryService and ScriptExecutor service." into sc-dev
diff --git a/car_product/overlay/frameworks/base/core/res/res/values/config.xml b/car_product/overlay/frameworks/base/core/res/res/values/config.xml
index 52739bf..f675d4e 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values/config.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values/config.xml
@@ -146,4 +146,7 @@
<!-- The name of the package that will hold the system cluster service role. -->
<string name="config_systemAutomotiveCluster" translatable="false">android.car.cluster</string>
+ <!-- Whether this device is supporting the microphone toggle -->
+ <bool name="config_supportsMicToggle">true</bool>
+
</resources>
diff --git a/cpp/telemetry/ARCHITECTURE.md b/cpp/telemetry/ARCHITECTURE.md
index ae60c6b..75dd194 100644
--- a/cpp/telemetry/ARCHITECTURE.md
+++ b/cpp/telemetry/ARCHITECTURE.md
@@ -10,9 +10,13 @@
## Structure
-- aidl/ - AIDL declerations
-- products/ - AAOS Telemetry product, it's included in car_base.mk
-- sepolicy - SELinux policies
-- src/ - Source code
-- *.rc - rc file to start services
-- *.xml - VINTF manifest (TODO: needed?)
+```
+aidl/ - Internal AIDL declerations, for public AIDLs, please see
+ //frameworks/hardware/interfaces/automotive/telemetry
+products/ - AAOS Telemetry product, it's included in car_base.mk
+sepolicy - SELinux policies
+src/ - Source code
+ TelemetryServer.h - The main class.
+*.rc - rc file to start services
+*.xml - VINTF manifest (TODO: needed?)
+```
diff --git a/cpp/telemetry/Android.bp b/cpp/telemetry/Android.bp
index 45e1358..4e6c1c0 100644
--- a/cpp/telemetry/Android.bp
+++ b/cpp/telemetry/Android.bp
@@ -24,10 +24,11 @@
"-Wno-unused-parameter",
],
shared_libs: [
- "android.frameworks.automotive.telemetry-V1-cpp",
+ "android.automotive.telemetry.internal-ndk_platform",
+ "android.frameworks.automotive.telemetry-V1-ndk_platform",
"libbase",
+ "libbinder_ndk",
"liblog",
- "libbinder",
"libutils",
],
product_variables: {
@@ -46,9 +47,11 @@
],
srcs: [
"src/CarTelemetryImpl.cpp",
+ "src/CarTelemetryInternalImpl.cpp",
"src/RingBuffer.cpp",
+ "src/TelemetryServer.cpp",
],
- // Allow dependencies to use header files.
+ // Allow dependents to use the header files.
export_include_dirs: [
"src",
],
@@ -62,12 +65,14 @@
test_suites: ["general-tests"],
srcs: [
"tests/CarTelemetryImplTest.cpp",
+ "tests/CarTelemetryInternalImplTest.cpp",
"tests/RingBufferTest.cpp",
],
// Statically link only in tests, for portability reason.
static_libs: [
"android.automotive.telemetryd@1.0-impl",
- "android.frameworks.automotive.telemetry-V1-cpp",
+ "android.automotive.telemetry.internal-ndk_platform",
+ "android.frameworks.automotive.telemetry-V1-ndk_platform",
"libgmock",
"libgtest",
],
diff --git a/cpp/telemetry/README.md b/cpp/telemetry/README.md
index e1f277b..a13116d 100644
--- a/cpp/telemetry/README.md
+++ b/cpp/telemetry/README.md
@@ -1,3 +1,19 @@
# Automotive Telemetry Service
A structured log collection service for CarTelemetryService. See ARCHITECTURE.md to learn internals.
+
+## Useful Commands
+
+**Dump service information**
+
+`adb shell dumpsys android.automotive.telemetry.internal.ICarTelemetryInternal/default`
+
+**Starting emulator**
+
+`aae emulator run -selinux permissive -writable-system`
+
+**Running tests**
+
+`atest cartelemetryd_impl_test:CarTelemetryInternalImplTest#TestSetListenerReturnsOk`
+
+`atest cartelemetryd_impl_test`
diff --git a/cpp/telemetry/aidl/Android.bp b/cpp/telemetry/aidl/Android.bp
new file mode 100644
index 0000000..b4cd9c7
--- /dev/null
+++ b/cpp/telemetry/aidl/Android.bp
@@ -0,0 +1,35 @@
+// Copyright (C) 2021 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.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+aidl_interface {
+ name: "android.automotive.telemetry.internal",
+ unstable: true,
+ vendor_available: false,
+ srcs: [
+ "android/automotive/telemetry/internal/*.aidl",
+ ],
+ backend: {
+ ndk: {
+ enabled: true,
+ },
+ java: {
+ platform_apis: true,
+ enabled: true,
+ },
+ }
+}
diff --git a/cpp/telemetry/aidl/android/automotive/telemetry/internal/CarDataInternal.aidl b/cpp/telemetry/aidl/android/automotive/telemetry/internal/CarDataInternal.aidl
new file mode 100644
index 0000000..bf31169
--- /dev/null
+++ b/cpp/telemetry/aidl/android/automotive/telemetry/internal/CarDataInternal.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2021, 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.
+ */
+
+package android.automotive.telemetry.internal;
+
+/**
+ * Wrapper for {@code android.frameworks.automotive.telemetry.CarData}.
+ */
+parcelable CarDataInternal {
+ /**
+ * Must be a valid id. Scripts subscribe to data using this id.
+ */
+ int id;
+
+ /**
+ * Content corresponding to the schema defined by the id.
+ */
+ byte[] content;
+}
diff --git a/cpp/telemetry/aidl/android/automotive/telemetry/internal/ICarDataListener.aidl b/cpp/telemetry/aidl/android/automotive/telemetry/internal/ICarDataListener.aidl
new file mode 100644
index 0000000..48ab6f9
--- /dev/null
+++ b/cpp/telemetry/aidl/android/automotive/telemetry/internal/ICarDataListener.aidl
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2021, 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.
+ */
+
+package android.automotive.telemetry.internal;
+
+import android.automotive.telemetry.internal.CarDataInternal;
+
+/**
+ * Listener for {@code ICarTelemetryInternal#registerListener}.
+ */
+oneway interface ICarDataListener {
+ /**
+ * Called by ICarTelemetry when the data are available to be consumed. ICarTelemetry removes
+ * the delivered data when the callback succeeds.
+ *
+ * <p>If the collected data is too large, it will send only chunk of the data, and the callback
+ * will be fired again.
+ *
+ * @param dataList the pushed data.
+ */
+ void onCarDataReceived(in CarDataInternal[] dataList);
+}
diff --git a/cpp/telemetry/aidl/android/automotive/telemetry/internal/ICarTelemetryInternal.aidl b/cpp/telemetry/aidl/android/automotive/telemetry/internal/ICarTelemetryInternal.aidl
new file mode 100644
index 0000000..b8938ff
--- /dev/null
+++ b/cpp/telemetry/aidl/android/automotive/telemetry/internal/ICarTelemetryInternal.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2021, 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.
+ */
+
+package android.automotive.telemetry.internal;
+
+import android.automotive.telemetry.internal.ICarDataListener;
+
+/**
+ * An internal API provided by cartelemetryd for receiving the collected data.
+ */
+interface ICarTelemetryInternal {
+ /**
+ * Sets a listener for CarData. If there are existing CarData in the buffer, the daemon will
+ * start pushing them to the listener. There can be only a single registered listener at a time.
+ *
+ * @param listener the only listener.
+ * @throws IllegalStateException if someone is already registered or the listener is dead.
+ */
+ void setListener(in ICarDataListener listener);
+
+ /**
+ * Clears the listener if exists. Silently ignores if there is no listener.
+ */
+ void clearListener();
+}
diff --git a/cpp/telemetry/sampleclient/README.md b/cpp/telemetry/sampleclient/README.md
index 8e8f3cc..ebbaf16 100644
--- a/cpp/telemetry/sampleclient/README.md
+++ b/cpp/telemetry/sampleclient/README.md
@@ -2,11 +2,27 @@
This is a sample vendor service that sends `CarData` to car telemetry service.
+## Running
+
+**1. Quick mode - under root**
+
+```
+m -j android.automotive.telemetryd-sampleclient
+
+adb remount # make sure run "adb disable-verity" before remounting
+adb push $ANDROID_PRODUCT_OUT/vendor/bin/android.automotive.telemetryd-sampleclient /system/bin/
+
+adb shell /system/bin/android.automotive.telemetryd-sampleclient
+
+# Then check logcat and dumpsys to verify the results.
+```
+
+**2. Under vendor**
+
To include it in the final image, add
`PRODUCT_PACKAGES += android.automotive.telemetryd-sampleclient` to
`//packages/services/Car/cpp/telemetry/products/telemetry.mk` (or other suitable mk file).
-Example:
```
# this goes to products/telemetry.mk
diff --git a/cpp/telemetry/sampleinternalclient/Android.bp b/cpp/telemetry/sampleinternalclient/Android.bp
new file mode 100644
index 0000000..d04ac09
--- /dev/null
+++ b/cpp/telemetry/sampleinternalclient/Android.bp
@@ -0,0 +1,36 @@
+// Copyright (C) 2021 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.
+
+// Sample client for ICarTelemetry service.
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_binary {
+ name: "android.automotive.telemetryd-sampleinternalclient",
+ srcs: [
+ "main.cpp",
+ ],
+ cflags: [
+ "-Werror",
+ "-Wall",
+ "-Wno-unused-parameter",
+ ],
+ shared_libs: [
+ "android.automotive.telemetry.internal-ndk_platform",
+ "libbase",
+ "libbinder_ndk",
+ "libutils",
+ ],
+}
diff --git a/cpp/telemetry/sampleinternalclient/main.cpp b/cpp/telemetry/sampleinternalclient/main.cpp
new file mode 100644
index 0000000..06ca549
--- /dev/null
+++ b/cpp/telemetry/sampleinternalclient/main.cpp
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+// This is a sample reader client for ICarTelemetryInternal.
+// TODO(b/186017953): remove this client when CarTelemetryService is implemented.
+//
+// adb remount # make sure run "adb disable-verity" before remounting
+// adb push $ANDROID_PRODUCT_OUT/system/bin/android.automotive.telemetryd-sampleinternalclient
+// /system/bin/
+//
+// adb shell /system/bin/android.automotive.telemetryd-sampleinternalclient
+
+#define LOG_TAG "cartelemetryd_sampleint"
+
+#include <aidl/android/automotive/telemetry/internal/BnCarDataListener.h>
+#include <aidl/android/automotive/telemetry/internal/CarDataInternal.h>
+#include <aidl/android/automotive/telemetry/internal/ICarTelemetryInternal.h>
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+
+using ::aidl::android::automotive::telemetry::internal::BnCarDataListener;
+using ::aidl::android::automotive::telemetry::internal::CarDataInternal;
+using ::aidl::android::automotive::telemetry::internal::ICarDataListener;
+using ::aidl::android::automotive::telemetry::internal::ICarTelemetryInternal;
+using ::android::base::StringPrintf;
+
+class CarDataListenerImpl : public BnCarDataListener {
+public:
+ ::ndk::ScopedAStatus onCarDataReceived(
+ const std::vector<CarDataInternal>& in_dataList) override;
+};
+
+::ndk::ScopedAStatus CarDataListenerImpl::onCarDataReceived(
+ const std::vector<CarDataInternal>& dataList) {
+ LOG(INFO) << "Received data size = " << dataList.size();
+ for (const auto data : dataList) {
+ LOG(INFO) << "data.id = " << data.id;
+ }
+ return ::ndk::ScopedAStatus::ok();
+}
+
+int main(int argc, char* argv[]) {
+ // The name of the service is described in
+ // https://source.android.com/devices/architecture/aidl/aidl-hals#instance-names
+ const std::string instance = StringPrintf("%s/default", ICarTelemetryInternal::descriptor);
+ LOG(INFO) << "Obtaining: " << instance;
+ std::shared_ptr<ICarTelemetryInternal> service = ICarTelemetryInternal::fromBinder(
+ ndk::SpAIBinder(AServiceManager_getService(instance.c_str())));
+ if (!service) {
+ LOG(FATAL) << "ICarTelemetryInternal service not found, may be still initializing?";
+ }
+
+ LOG(INFO) << "Setting the listener";
+ std::shared_ptr<CarDataListenerImpl> listener = ndk::SharedRefBase::make<CarDataListenerImpl>();
+ auto status = service->setListener(listener);
+ if (!status.isOk()) {
+ LOG(FATAL) << "Failed to set the listener";
+ }
+
+ ::ABinderProcess_startThreadPool();
+ ::ABinderProcess_joinThreadPool();
+ return 1; // not reachable
+}
diff --git a/cpp/telemetry/sepolicy/private/service_contexts b/cpp/telemetry/sepolicy/private/service_contexts
index 24d7df3..f26b5f8 100644
--- a/cpp/telemetry/sepolicy/private/service_contexts
+++ b/cpp/telemetry/sepolicy/private/service_contexts
@@ -1 +1,2 @@
android.frameworks.automotive.telemetry.ICarTelemetry/default u:object_r:cartelemetryd_service:s0
+android.automotive.telemetry.internal.ICarTelemetryInternal/default u:object_r:cartelemetryd_service:s0
diff --git a/cpp/telemetry/src/BufferedCarData.h b/cpp/telemetry/src/BufferedCarData.h
index a15a25f..0c6ff4d 100644
--- a/cpp/telemetry/src/BufferedCarData.h
+++ b/cpp/telemetry/src/BufferedCarData.h
@@ -17,26 +17,25 @@
#ifndef CPP_TELEMETRY_SRC_BUFFEREDCARDATA_H_
#define CPP_TELEMETRY_SRC_BUFFEREDCARDATA_H_
-#include <android/frameworks/automotive/telemetry/CarData.h>
+#include <stdint.h>
+
+#include <tuple>
+#include <vector>
namespace android {
namespace automotive {
namespace telemetry {
+// Internally stored `CarData` with some extras.
struct BufferedCarData {
- BufferedCarData(const android::frameworks::automotive::telemetry::CarData& data, int32_t uid) :
- mId(data.id),
- mContent(std::move(data.content)),
- mLogUid(uid) {}
-
- // Visible for testing.
- BufferedCarData(int32_t id, const std::vector<uint8_t>& content, int32_t uid) :
- mId(id),
- mContent(std::move(content)),
- mLogUid(uid) {}
+ BufferedCarData(BufferedCarData&& other) = default;
+ BufferedCarData(const BufferedCarData&) = default;
+ BufferedCarData& operator=(BufferedCarData&& other) = default;
+ BufferedCarData& operator=(const BufferedCarData&) = default;
inline bool operator==(const BufferedCarData& rhs) const {
- return std::tie(mId, mContent, mLogUid) == std::tie(rhs.mId, rhs.mContent, rhs.mLogUid);
+ return std::tie(mId, mContent, mPublisherUid) ==
+ std::tie(rhs.mId, rhs.mContent, rhs.mPublisherUid);
}
// Returns the size of the stored data. Note that it's not the exact size of the struct.
@@ -45,8 +44,8 @@
const int32_t mId;
const std::vector<uint8_t> mContent;
- // The uid of the logging client (defaults to -1).
- const int32_t mLogUid;
+ // The uid of the logging client.
+ const uid_t mPublisherUid;
};
} // namespace telemetry
diff --git a/cpp/telemetry/src/CarTelemetryImpl.cpp b/cpp/telemetry/src/CarTelemetryImpl.cpp
index bbf7ac5..34a4e60 100644
--- a/cpp/telemetry/src/CarTelemetryImpl.cpp
+++ b/cpp/telemetry/src/CarTelemetryImpl.cpp
@@ -18,9 +18,8 @@
#include "BufferedCarData.h"
-#include <android-base/logging.h>
-#include <android/frameworks/automotive/telemetry/CarData.h>
-#include <binder/IPCThreadState.h>
+#include <aidl/android/frameworks/automotive/telemetry/CarData.h>
+#include <android/binder_ibinder.h>
#include <stdio.h>
@@ -30,27 +29,21 @@
namespace automotive {
namespace telemetry {
-using ::android::binder::Status;
-using ::android::frameworks::automotive::telemetry::CarData;
+using ::aidl::android::frameworks::automotive::telemetry::CarData;
CarTelemetryImpl::CarTelemetryImpl(RingBuffer* buffer) : mRingBuffer(buffer) {}
// TODO(b/174608802): Add 10kb size check for the `dataList`, see the AIDL for the limits
-Status CarTelemetryImpl::write(const std::vector<CarData>& dataList) {
- uid_t uid = IPCThreadState::self()->getCallingUid();
- // NOTE: CarData here will be coped to BufferedCarData, as we don't know what Binder will do
- // with the current allocated CarData.
- for (auto& carData : dataList) {
- mRingBuffer->push(BufferedCarData(carData, uid));
+ndk::ScopedAStatus CarTelemetryImpl::write(const std::vector<CarData>& dataList) {
+ uid_t publisherUid = ::AIBinder_getCallingUid();
+ for (auto&& data : dataList) {
+ mRingBuffer->push({.mId = data.id,
+ .mContent = std::move(data.content),
+ .mPublisherUid = publisherUid});
}
- return Status::ok();
+ return ndk::ScopedAStatus::ok();
}
-status_t CarTelemetryImpl::dump(int fd, const android::Vector<android::String16>& args) {
- dprintf(fd, "CarTelemetryImpl:\n");
- mRingBuffer->dump(fd, /* indent= */ 2);
- return android::OK;
-}
} // namespace telemetry
} // namespace automotive
} // namespace android
diff --git a/cpp/telemetry/src/CarTelemetryImpl.h b/cpp/telemetry/src/CarTelemetryImpl.h
index 173a472..a3bb6c1 100644
--- a/cpp/telemetry/src/CarTelemetryImpl.h
+++ b/cpp/telemetry/src/CarTelemetryImpl.h
@@ -17,14 +17,13 @@
#ifndef CPP_TELEMETRY_SRC_CARTELEMETRYIMPL_H_
#define CPP_TELEMETRY_SRC_CARTELEMETRYIMPL_H_
-#include <android/frameworks/automotive/telemetry/BnCarTelemetry.h>
-#include <android/frameworks/automotive/telemetry/CarData.h>
+#include "RingBuffer.h"
+
+#include <aidl/android/frameworks/automotive/telemetry/BnCarTelemetry.h>
+#include <aidl/android/frameworks/automotive/telemetry/CarData.h>
#include <utils/String16.h>
#include <utils/Vector.h>
-#include <RingBuffer.h>
-
-#include <memory>
#include <vector>
namespace android {
@@ -32,17 +31,15 @@
namespace telemetry {
// Implementation of android.frameworks.automotive.telemetry.ICarTelemetry.
-class CarTelemetryImpl : public android::frameworks::automotive::telemetry::BnCarTelemetry {
+class CarTelemetryImpl : public aidl::android::frameworks::automotive::telemetry::BnCarTelemetry {
public:
// Doesn't own `buffer`.
explicit CarTelemetryImpl(RingBuffer* buffer);
- android::binder::Status write(
- const std::vector<android::frameworks::automotive::telemetry::CarData>& dataList)
+ ndk::ScopedAStatus write(
+ const std::vector<aidl::android::frameworks::automotive::telemetry::CarData>& dataList)
override;
- status_t dump(int fd, const android::Vector<android::String16>& args) override;
-
private:
RingBuffer* mRingBuffer; // not owned
};
diff --git a/cpp/telemetry/src/CarTelemetryInternalImpl.cpp b/cpp/telemetry/src/CarTelemetryInternalImpl.cpp
new file mode 100644
index 0000000..7a3b141
--- /dev/null
+++ b/cpp/telemetry/src/CarTelemetryInternalImpl.cpp
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2021, 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.
+ */
+
+#include "CarTelemetryInternalImpl.h"
+
+#include <aidl/android/automotive/telemetry/internal/BnCarDataListener.h>
+#include <aidl/android/automotive/telemetry/internal/CarDataInternal.h>
+#include <aidl/android/automotive/telemetry/internal/ICarDataListener.h>
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+
+namespace android {
+namespace automotive {
+namespace telemetry {
+
+using ::aidl::android::automotive::telemetry::internal::BnCarDataListener;
+using ::aidl::android::automotive::telemetry::internal::CarDataInternal;
+using ::aidl::android::automotive::telemetry::internal::ICarDataListener;
+using ::android::base::StringPrintf;
+
+CarTelemetryInternalImpl::CarTelemetryInternalImpl(RingBuffer* buffer) :
+ mRingBuffer(buffer),
+ mBinderDeathRecipient(
+ ::AIBinder_DeathRecipient_new(CarTelemetryInternalImpl::listenerBinderDied)) {}
+
+ndk::ScopedAStatus CarTelemetryInternalImpl::setListener(
+ const std::shared_ptr<ICarDataListener>& listener) {
+ const std::scoped_lock<std::mutex> lock(mMutex);
+
+ if (mCarDataListener != nullptr) {
+ return ndk::ScopedAStatus::fromExceptionCodeWithMessage(::EX_ILLEGAL_STATE,
+ "ICarDataListener is already set.");
+ }
+
+ // If passed a local binder, AIBinder_linkToDeath will do nothing and return
+ // STATUS_INVALID_OPERATION. We ignore this case because we only use local binders in tests
+ // where this is not an error.
+ if (listener->isRemote()) {
+ auto status = ndk::ScopedAStatus::fromStatus(
+ ::AIBinder_linkToDeath(listener->asBinder().get(), mBinderDeathRecipient.get(),
+ this));
+ if (!status.isOk()) {
+ return ndk::ScopedAStatus::fromExceptionCodeWithMessage(::EX_ILLEGAL_STATE,
+ status.getMessage());
+ }
+ }
+
+ mCarDataListener = listener;
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus CarTelemetryInternalImpl::clearListener() {
+ const std::scoped_lock<std::mutex> lock(mMutex);
+ if (mCarDataListener == nullptr) {
+ LOG(INFO) << __func__ << ": No ICarDataListener, ignoring the call";
+ return ndk::ScopedAStatus::ok();
+ }
+ auto status = ndk::ScopedAStatus::fromStatus(
+ ::AIBinder_unlinkToDeath(mCarDataListener->asBinder().get(),
+ mBinderDeathRecipient.get(), this));
+ if (!status.isOk()) {
+ LOG(WARNING) << __func__
+ << ": unlinkToDeath failed, continuing anyway: " << status.getMessage();
+ }
+ mCarDataListener = nullptr;
+ return ndk::ScopedAStatus::ok();
+}
+
+binder_status_t CarTelemetryInternalImpl::dump(int fd, const char** args, uint32_t numArgs) {
+ dprintf(fd, "ICarTelemetryInternal:\n");
+ mRingBuffer->dump(fd);
+ return ::STATUS_OK;
+}
+
+// Removes the listener if its binder dies.
+void CarTelemetryInternalImpl::listenerBinderDiedImpl() {
+ LOG(WARNING) << "A ICarDataListener died, removing the listener.";
+ const std::scoped_lock<std::mutex> lock(mMutex);
+ mCarDataListener = nullptr;
+}
+
+void CarTelemetryInternalImpl::listenerBinderDied(void* cookie) {
+ auto thiz = static_cast<CarTelemetryInternalImpl*>(cookie);
+ thiz->listenerBinderDiedImpl();
+}
+
+} // namespace telemetry
+} // namespace automotive
+} // namespace android
diff --git a/cpp/telemetry/src/CarTelemetryInternalImpl.h b/cpp/telemetry/src/CarTelemetryInternalImpl.h
new file mode 100644
index 0000000..12ad5cd
--- /dev/null
+++ b/cpp/telemetry/src/CarTelemetryInternalImpl.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2021, 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.
+ */
+
+#ifndef CPP_TELEMETRY_SRC_CARTELEMETRYINTERNALIMPL_H_
+#define CPP_TELEMETRY_SRC_CARTELEMETRYINTERNALIMPL_H_
+
+#include "RingBuffer.h"
+
+#include <aidl/android/automotive/telemetry/internal/BnCarTelemetryInternal.h>
+#include <aidl/android/automotive/telemetry/internal/CarDataInternal.h>
+#include <aidl/android/automotive/telemetry/internal/ICarDataListener.h>
+#include <android/binder_status.h>
+#include <utils/Mutex.h>
+#include <utils/String16.h>
+#include <utils/Vector.h>
+
+namespace android {
+namespace automotive {
+namespace telemetry {
+
+// Implementation of android.automotive.telemetry.ICarTelemetryInternal.
+class CarTelemetryInternalImpl :
+ public aidl::android::automotive::telemetry::internal::BnCarTelemetryInternal {
+public:
+ // Doesn't own `buffer`.
+ explicit CarTelemetryInternalImpl(RingBuffer* buffer);
+
+ ndk::ScopedAStatus setListener(
+ const std::shared_ptr<aidl::android::automotive::telemetry::internal::ICarDataListener>&
+ listener) override;
+
+ ndk::ScopedAStatus clearListener() override;
+
+ binder_status_t dump(int fd, const char** args, uint32_t numArgs) override;
+
+private:
+ // Death recipient callback that is called when ICarDataListener dies.
+ // The cookie is a pointer to a CarTelemetryInternalImpl object.
+ static void listenerBinderDied(void* cookie);
+
+ void listenerBinderDiedImpl();
+
+ RingBuffer* mRingBuffer; // not owned
+ ndk::ScopedAIBinder_DeathRecipient mBinderDeathRecipient;
+ std::mutex mMutex; // a mutex for the whole instance
+
+ std::shared_ptr<aidl::android::automotive::telemetry::internal::ICarDataListener>
+ mCarDataListener GUARDED_BY(mMutex);
+};
+
+} // namespace telemetry
+} // namespace automotive
+} // namespace android
+
+#endif // CPP_TELEMETRY_SRC_CARTELEMETRYINTERNALIMPL_H_
diff --git a/cpp/telemetry/src/RingBuffer.cpp b/cpp/telemetry/src/RingBuffer.cpp
index 658d9d3..36de3f8 100644
--- a/cpp/telemetry/src/RingBuffer.cpp
+++ b/cpp/telemetry/src/RingBuffer.cpp
@@ -17,7 +17,6 @@
#include "RingBuffer.h"
#include <android-base/logging.h>
-#include <android/frameworks/automotive/telemetry/CarData.h>
#include <inttypes.h> // for PRIu64 and friends
@@ -27,53 +26,35 @@
namespace automotive {
namespace telemetry {
-// Do now allow buffering more than this amount of data. It's to make sure we won't get
-// 200 thousands of small CarData.
-const int kMaxNumberOfItems = 5000;
-
-RingBuffer::RingBuffer(int32_t limit) : mSizeLimitBytes(limit) {}
+RingBuffer::RingBuffer(int32_t limit) : mSizeLimit(limit) {}
void RingBuffer::push(BufferedCarData&& data) {
- int32_t dataSizeBytes = data.contentSizeInBytes();
- if (dataSizeBytes > mSizeLimitBytes) {
- LOG(WARNING) << "CarData(id=" << data.mId << ") size (" << dataSizeBytes
- << "b) is larger than " << mSizeLimitBytes << "b, dropping it.";
- return;
- }
- mCurrentSizeBytes += dataSizeBytes;
+ const std::scoped_lock<std::mutex> lock(mMutex);
mList.push_back(std::move(data));
- while (mCurrentSizeBytes > mSizeLimitBytes || mList.size() > kMaxNumberOfItems) {
- mCurrentSizeBytes -= mList.front().contentSizeInBytes();
+ while (mList.size() > mSizeLimit) {
mList.pop_front();
mTotalDroppedDataCount += 1;
}
}
-std::vector<BufferedCarData> RingBuffer::popAllDataForId(int32_t id) {
- LOG(VERBOSE) << "popAllDataForId id=" << id;
- std::vector<BufferedCarData> result;
- for (auto it = mList.begin(); it != mList.end();) {
- if (it->mId == id) {
- mCurrentSizeBytes -= (*it).contentSizeInBytes();
- result.push_back(std::move(*it));
- it = mList.erase(it);
- } else {
- ++it;
- }
- }
+BufferedCarData RingBuffer::popFront() {
+ const std::scoped_lock<std::mutex> lock(mMutex);
+ auto result = std::move(mList.front());
+ mList.pop_front();
return result;
}
-void RingBuffer::dump(int fd, int indent) const {
- dprintf(fd, "%*sRingBuffer:\n", indent, "");
- dprintf(fd, "%*s mSizeLimitBytes=%d\n", indent, "", mSizeLimitBytes);
- dprintf(fd, "%*s mCurrentSizeBytes=%d\n", indent, "", mCurrentSizeBytes);
- dprintf(fd, "%*s mList.size=%zu\n", indent, "", mList.size());
- dprintf(fd, "%*s mTotalDroppedDataCount=%" PRIu64 "\n", indent, "", mTotalDroppedDataCount);
+void RingBuffer::dump(int fd) const {
+ const std::scoped_lock<std::mutex> lock(mMutex);
+ dprintf(fd, "RingBuffer:\n");
+ dprintf(fd, " mSizeLimit=%d\n", mSizeLimit);
+ dprintf(fd, " mList.size=%zu\n", mList.size());
+ dprintf(fd, " mTotalDroppedDataCount=%" PRIu64 "\n", mTotalDroppedDataCount);
}
-int32_t RingBuffer::currentSizeBytes() const {
- return mCurrentSizeBytes;
+int32_t RingBuffer::size() const {
+ const std::scoped_lock<std::mutex> lock(mMutex);
+ return mList.size();
}
} // namespace telemetry
diff --git a/cpp/telemetry/src/RingBuffer.h b/cpp/telemetry/src/RingBuffer.h
index 8978f32..07ce709 100644
--- a/cpp/telemetry/src/RingBuffer.h
+++ b/cpp/telemetry/src/RingBuffer.h
@@ -17,37 +17,48 @@
#ifndef CPP_TELEMETRY_SRC_RINGBUFFER_H_
#define CPP_TELEMETRY_SRC_RINGBUFFER_H_
-#include <BufferedCarData.h>
+#include "BufferedCarData.h"
#include <list>
+#include <mutex>
namespace android {
namespace automotive {
namespace telemetry {
+// A ring buffer that holds BufferedCarData. It drops old data if it's full.
+// Thread-safe.
class RingBuffer {
public:
- // RingBuffer limits `currentSizeBytes()` to the given param `sizeLimitBytes`.
- explicit RingBuffer(int32_t sizeLimitBytes);
+ // RingBuffer limits the number of elements in the buffer to the given param `sizeLimit`.
+ // Doesn't pre-allocate the memory.
+ explicit RingBuffer(int32_t sizeLimit);
+
+ // Not copyable or movable
+ RingBuffer(const RingBuffer&) = delete;
+ RingBuffer& operator=(const RingBuffer&) = delete;
+ RingBuffer(RingBuffer&&) = delete;
+ RingBuffer& operator=(RingBuffer&&) = delete;
// Pushes the data to the buffer. If the buffer is full, it removes the oldest data.
// Supports moving the data to the RingBuffer.
void push(BufferedCarData&& data);
- // Returns all the CarData with the given `id` and removes them from the buffer.
- // Complexity is O(n), as this method is expected to be called infrequently.
- std::vector<BufferedCarData> popAllDataForId(int32_t id);
+ // Returns the oldest element from the ring buffer and removes it from the buffer.
+ BufferedCarData popFront();
// Dumps the current state for dumpsys.
- void dump(int fd, int indent) const;
+ void dump(int fd) const;
- // Returns the total size of CarData content in the buffer.
- int32_t currentSizeBytes() const;
+ // Returns the number of elements in the buffer.
+ int32_t size() const;
private:
- const int32_t mSizeLimitBytes;
- int32_t mCurrentSizeBytes;
+ mutable std::mutex mMutex; // a mutex for the whole instance
+ const int32_t mSizeLimit;
+
+ // TODO(b/174608802): Improve dropped CarData handling, see ag/13818937 for details.
int64_t mTotalDroppedDataCount;
// Linked list that holds all the data and allows deleting old data when the buffer is full.
diff --git a/cpp/telemetry/src/TelemetryServer.cpp b/cpp/telemetry/src/TelemetryServer.cpp
new file mode 100644
index 0000000..54cd3c4
--- /dev/null
+++ b/cpp/telemetry/src/TelemetryServer.cpp
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2021, 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.
+ */
+
+#include "TelemetryServer.h"
+
+#include "CarTelemetryImpl.h"
+#include "RingBuffer.h"
+
+#include <android-base/chrono_utils.h>
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android/binder_interface_utils.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+
+#include <inttypes.h> // for PRIu64 and friends
+
+#include <memory>
+#include <thread> // NOLINT(build/c++11)
+
+namespace android {
+namespace automotive {
+namespace telemetry {
+
+using ::android::automotive::telemetry::RingBuffer;
+
+constexpr const char kCarTelemetryServiceName[] =
+ "android.frameworks.automotive.telemetry.ICarTelemetry/default";
+constexpr const char kCarTelemetryInternalServiceName[] =
+ "android.automotive.telemetry.internal.ICarTelemetryInternal/default";
+
+// TODO(b/183444070): make it configurable using sysprop
+// CarData count limit in the RingBuffer. In worst case it will use kMaxBufferSize * 10Kb memory,
+// which is ~ 1MB.
+const int kMaxBufferSize = 100;
+
+TelemetryServer::TelemetryServer() : mRingBuffer(kMaxBufferSize) {}
+
+void TelemetryServer::registerServices() {
+ std::shared_ptr<CarTelemetryImpl> telemetry =
+ ndk::SharedRefBase::make<CarTelemetryImpl>(&mRingBuffer);
+ std::shared_ptr<CarTelemetryInternalImpl> telemetryInternal =
+ ndk::SharedRefBase::make<CarTelemetryInternalImpl>(&mRingBuffer);
+
+ // Wait for the service manager before starting ICarTelemetry service.
+ while (android::base::GetProperty("init.svc.servicemanager", "") != "running") {
+ // Poll frequent enough so the writer clients can connect to the service during boot.
+ std::this_thread::sleep_for(250ms);
+ }
+
+ LOG(VERBOSE) << "Registering " << kCarTelemetryServiceName;
+ binder_exception_t exception =
+ ::AServiceManager_addService(telemetry->asBinder().get(), kCarTelemetryServiceName);
+ if (exception != ::EX_NONE) {
+ LOG(FATAL) << "Unable to register " << kCarTelemetryServiceName
+ << ", exception=" << exception;
+ }
+
+ LOG(VERBOSE) << "Registering " << kCarTelemetryInternalServiceName;
+ exception = ::AServiceManager_addService(telemetryInternal->asBinder().get(),
+ kCarTelemetryInternalServiceName);
+ if (exception != ::EX_NONE) {
+ LOG(FATAL) << "Unable to register " << kCarTelemetryInternalServiceName
+ << ", exception=" << exception;
+ }
+}
+
+void TelemetryServer::startAndJoinThreadPool() {
+ ::ABinderProcess_startThreadPool(); // Starts the default 15 binder threads.
+ ::ABinderProcess_joinThreadPool();
+}
+
+} // namespace telemetry
+} // namespace automotive
+} // namespace android
diff --git a/cpp/telemetry/src/TelemetryServer.h b/cpp/telemetry/src/TelemetryServer.h
new file mode 100644
index 0000000..0b400a1
--- /dev/null
+++ b/cpp/telemetry/src/TelemetryServer.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2021, 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.
+ */
+
+#ifndef CPP_TELEMETRY_SRC_TELEMETRYSERVER_H_
+#define CPP_TELEMETRY_SRC_TELEMETRYSERVER_H_
+
+#include "CarTelemetryImpl.h"
+#include "CarTelemetryInternalImpl.h"
+
+#include <utils/Errors.h>
+
+namespace android {
+namespace automotive {
+namespace telemetry {
+
+class TelemetryServer {
+public:
+ TelemetryServer();
+
+ // Registers all the implemented AIDL services. Waits until `servicemanager` is available.
+ // Aborts the process if fails.
+ void registerServices();
+
+ // Blocks the thread.
+ void startAndJoinThreadPool();
+
+private:
+ RingBuffer mRingBuffer;
+};
+
+} // namespace telemetry
+} // namespace automotive
+} // namespace android
+
+#endif // CPP_TELEMETRY_SRC_TELEMETRYSERVER_H_
diff --git a/cpp/telemetry/src/main.cpp b/cpp/telemetry/src/main.cpp
index fcec0ee..1bd0dde 100644
--- a/cpp/telemetry/src/main.cpp
+++ b/cpp/telemetry/src/main.cpp
@@ -14,52 +14,23 @@
* limitations under the License.
*/
-#include "CarTelemetryImpl.h"
-#include "RingBuffer.h"
+#include "TelemetryServer.h"
-#include <android-base/chrono_utils.h>
#include <android-base/logging.h>
-#include <android-base/properties.h>
-#include <binder/IPCThreadState.h>
-#include <binder/IServiceManager.h>
-#include <binder/ProcessState.h>
-#include <thread> // NOLINT(build/c++11)
-
-using ::android::String16;
-using ::android::automotive::telemetry::CarTelemetryImpl;
-using ::android::automotive::telemetry::RingBuffer;
-
-constexpr const char kCarTelemetryServiceName[] =
- "android.frameworks.automotive.telemetry.ICarTelemetry/default";
-// Total CarData content size limit in the RingBuffer. 2MB max memory for buffer is good for now.
-const int kMaxBufferSizeKilobytes = 2048;
+using ::android::automotive::telemetry::TelemetryServer;
// TODO(b/174608802): handle SIGQUIT/SIGTERM
int main(void) {
LOG(INFO) << "Starting cartelemetryd";
- RingBuffer buffer(kMaxBufferSizeKilobytes * 1024);
+ TelemetryServer server;
- android::sp<CarTelemetryImpl> telemetry = new CarTelemetryImpl(&buffer);
-
- // Wait for the service manager before starting ICarTelemetry service.
- while (android::base::GetProperty("init.svc.servicemanager", "") != "running") {
- // Poll frequent enough so the writer clients can connect to the service during boot.
- std::this_thread::sleep_for(250ms);
- }
-
- LOG(VERBOSE) << "Registering " << kCarTelemetryServiceName;
- auto status = android::defaultServiceManager()->addService(String16(kCarTelemetryServiceName),
- telemetry);
- if (status != android::OK) {
- LOG(ERROR) << "Unable to register " << kCarTelemetryServiceName << ", status=" << status;
- return 1;
- }
+ // Register AIDL services. Aborts the server if fails.
+ server.registerServices();
LOG(VERBOSE) << "Service is created, joining the threadpool";
- android::ProcessState::self()->startThreadPool(); // Starts default 15 binder threads.
- android::IPCThreadState::self()->joinThreadPool();
+ server.startAndJoinThreadPool();
return 1; // never reaches
}
diff --git a/cpp/telemetry/tests/CarTelemetryImplTest.cpp b/cpp/telemetry/tests/CarTelemetryImplTest.cpp
index f0af963..0286477 100644
--- a/cpp/telemetry/tests/CarTelemetryImplTest.cpp
+++ b/cpp/telemetry/tests/CarTelemetryImplTest.cpp
@@ -17,8 +17,8 @@
#include "CarTelemetryImpl.h"
#include "RingBuffer.h"
-#include <android/frameworks/automotive/telemetry/CarData.h>
-#include <android/frameworks/automotive/telemetry/ICarTelemetry.h>
+#include <aidl/android/frameworks/automotive/telemetry/CarData.h>
+#include <aidl/android/frameworks/automotive/telemetry/ICarTelemetry.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
@@ -29,13 +29,12 @@
namespace android {
namespace automotive {
namespace telemetry {
-namespace {
-using android::frameworks::automotive::telemetry::CarData;
-using android::frameworks::automotive::telemetry::ICarTelemetry;
-using testing::ContainerEq;
+using ::aidl::android::frameworks::automotive::telemetry::CarData;
+using ::aidl::android::frameworks::automotive::telemetry::ICarTelemetry;
+using ::testing::ContainerEq;
-const size_t kMaxBufferSizeBytes = 1024;
+const size_t kMaxBufferSize = 5;
CarData buildCarData(int id, const std::vector<uint8_t>& content) {
CarData msg;
@@ -44,56 +43,57 @@
return msg;
}
+BufferedCarData buildBufferedCarData(const CarData& data, uid_t publisherUid) {
+ return {.mId = data.id, .mContent = std::move(data.content), .mPublisherUid = publisherUid};
+}
+
class CarTelemetryImplTest : public ::testing::Test {
protected:
CarTelemetryImplTest() :
- mBuffer(RingBuffer(kMaxBufferSizeBytes)),
- mTelemetry(std::make_unique<CarTelemetryImpl>(&mBuffer)) {}
+ mBuffer(RingBuffer(kMaxBufferSize)),
+ mTelemetry(ndk::SharedRefBase::make<CarTelemetryImpl>(&mBuffer)) {}
RingBuffer mBuffer;
- std::unique_ptr<ICarTelemetry> mTelemetry;
+ std::shared_ptr<ICarTelemetry> mTelemetry;
};
-TEST_F(CarTelemetryImplTest, TestWriteReturnsOkStatus) {
+TEST_F(CarTelemetryImplTest, WriteReturnsOkStatus) {
CarData msg = buildCarData(101, {1, 0, 1, 0});
auto status = mTelemetry->write({msg});
- EXPECT_TRUE(status.isOk()) << status;
+ EXPECT_TRUE(status.isOk()) << status.getMessage();
}
-TEST_F(CarTelemetryImplTest, TestWriteAddsCarDataToRingBuffer) {
+TEST_F(CarTelemetryImplTest, WriteAddsCarDataToRingBuffer) {
CarData msg = buildCarData(101, {1, 0, 1, 0});
mTelemetry->write({msg});
- std::vector<BufferedCarData> result = mBuffer.popAllDataForId(101);
- std::vector<BufferedCarData> expected = {BufferedCarData(msg, getuid())};
- EXPECT_THAT(result, ContainerEq(expected));
+ EXPECT_EQ(mBuffer.popFront(), buildBufferedCarData(msg, getuid()));
}
-TEST_F(CarTelemetryImplTest, TestWriteBuffersOnlyLimitedAmount) {
- RingBuffer buffer(15); // bytes
- CarTelemetryImpl telemetry(&buffer);
+TEST_F(CarTelemetryImplTest, WriteBuffersOnlyLimitedAmount) {
+ RingBuffer buffer(/* sizeLimit= */ 3);
+ auto telemetry = ndk::SharedRefBase::make<CarTelemetryImpl>(&buffer);
- CarData msg101_2 = buildCarData(101, {1, 0}); // 2 bytes
- CarData msg101_4 = buildCarData(101, {1, 0, 1, 0}); // 4 bytes
- CarData msg201_3 = buildCarData(201, {3, 3, 3}); // 3 bytes
+ CarData msg101_2 = buildCarData(101, {1, 0});
+ CarData msg101_4 = buildCarData(101, {1, 0, 1, 0});
+ CarData msg201_3 = buildCarData(201, {3, 3, 3});
- telemetry.write({msg101_2, msg101_4, msg101_4, msg201_3, msg201_3});
+ // Inserting 5 elements
+ telemetry->write({msg101_2, msg101_4, msg101_4, msg201_3});
+ telemetry->write({msg201_3});
- // Size without the first msg101_2, because ushing the last msg201_3 will force RingBuffer to
- // drop the earliest msg101_2.
- EXPECT_EQ(buffer.currentSizeBytes(), 14);
- std::vector<BufferedCarData> result = buffer.popAllDataForId(101);
- std::vector<BufferedCarData> expected = {BufferedCarData(msg101_4, getuid()),
- BufferedCarData(msg101_4, getuid())};
+ EXPECT_EQ(buffer.size(), 3);
+ std::vector<BufferedCarData> result = {buffer.popFront(), buffer.popFront(), buffer.popFront()};
+ std::vector<BufferedCarData> expected = {buildBufferedCarData(msg101_4, getuid()),
+ buildBufferedCarData(msg201_3, getuid()),
+ buildBufferedCarData(msg201_3, getuid())};
EXPECT_THAT(result, ContainerEq(expected));
- // Fetching 2x msg101_4 will decrease the size of the RingBuffer
- EXPECT_EQ(buffer.currentSizeBytes(), 6);
+ EXPECT_EQ(buffer.size(), 0);
}
-} // namespace
} // namespace telemetry
} // namespace automotive
} // namespace android
diff --git a/cpp/telemetry/tests/CarTelemetryInternalImplTest.cpp b/cpp/telemetry/tests/CarTelemetryInternalImplTest.cpp
new file mode 100644
index 0000000..22838cd
--- /dev/null
+++ b/cpp/telemetry/tests/CarTelemetryInternalImplTest.cpp
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2021 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.
+ */
+
+#include "CarTelemetryInternalImpl.h"
+#include "RingBuffer.h"
+
+#include <aidl/android/automotive/telemetry/internal/BnCarDataListener.h>
+#include <aidl/android/automotive/telemetry/internal/CarDataInternal.h>
+#include <aidl/android/automotive/telemetry/internal/ICarTelemetryInternal.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <unistd.h>
+
+#include <memory>
+
+namespace android {
+namespace automotive {
+namespace telemetry {
+
+using ::aidl::android::automotive::telemetry::internal::BnCarDataListener;
+using ::aidl::android::automotive::telemetry::internal::CarDataInternal;
+using ::aidl::android::automotive::telemetry::internal::ICarTelemetryInternal;
+using ::ndk::ScopedAStatus;
+
+const size_t kMaxBufferSize = 5;
+
+class MockCarDataListener : public BnCarDataListener {
+public:
+ MOCK_METHOD(ScopedAStatus, onCarDataReceived, (const std::vector<CarDataInternal>& dataList),
+ (override));
+};
+
+// The main test class.
+class CarTelemetryInternalImplTest : public ::testing::Test {
+protected:
+ CarTelemetryInternalImplTest() :
+ mBuffer(RingBuffer(kMaxBufferSize)),
+ mTelemetryInternal(ndk::SharedRefBase::make<CarTelemetryInternalImpl>(&mBuffer)),
+ mMockCarDataListener(ndk::SharedRefBase::make<MockCarDataListener>()) {}
+
+ RingBuffer mBuffer;
+ std::shared_ptr<ICarTelemetryInternal> mTelemetryInternal;
+ std::shared_ptr<MockCarDataListener> mMockCarDataListener;
+};
+
+TEST_F(CarTelemetryInternalImplTest, SetListenerReturnsOk) {
+ auto status = mTelemetryInternal->setListener(mMockCarDataListener);
+
+ EXPECT_TRUE(status.isOk()) << status.getMessage();
+}
+
+TEST_F(CarTelemetryInternalImplTest, SetListenerFailsWhenAlreadySubscribed) {
+ mTelemetryInternal->setListener(mMockCarDataListener);
+
+ auto status = mTelemetryInternal->setListener(ndk::SharedRefBase::make<MockCarDataListener>());
+
+ EXPECT_EQ(status.getExceptionCode(), ::EX_ILLEGAL_STATE) << status.getMessage();
+}
+
+TEST_F(CarTelemetryInternalImplTest, ClearListenerWorks) {
+ mTelemetryInternal->setListener(mMockCarDataListener);
+
+ mTelemetryInternal->clearListener();
+ auto status = mTelemetryInternal->setListener(mMockCarDataListener);
+
+ EXPECT_TRUE(status.isOk()) << status.getMessage();
+}
+
+} // namespace telemetry
+} // namespace automotive
+} // namespace android
diff --git a/cpp/telemetry/tests/RingBufferTest.cpp b/cpp/telemetry/tests/RingBufferTest.cpp
index 077705e..c5b5bc7 100644
--- a/cpp/telemetry/tests/RingBufferTest.cpp
+++ b/cpp/telemetry/tests/RingBufferTest.cpp
@@ -26,43 +26,33 @@
namespace android {
namespace automotive {
namespace telemetry {
-namespace {
using testing::ContainerEq;
BufferedCarData buildBufferedCarData(int32_t id, const std::vector<uint8_t>& content) {
- return BufferedCarData(id, content, /* uid= */ 0);
+ return {.mId = id, .mContent = content, .mPublisherUid = 0};
}
-TEST(RingBufferTest, TestPopAllDataForIdReturnsCorrectResults) {
- RingBuffer buffer(10); // bytes
- buffer.push(buildBufferedCarData(101, {7}));
+TEST(RingBufferTest, PopFrontReturnsCorrectResults) {
+ RingBuffer buffer(/* sizeLimit= */ 10);
buffer.push(buildBufferedCarData(101, {7}));
buffer.push(buildBufferedCarData(102, {7}));
+
+ BufferedCarData result = buffer.popFront();
+
+ EXPECT_EQ(result, buildBufferedCarData(101, {7}));
+}
+
+TEST(RingBufferTest, PopFrontRemovesFromBuffer) {
+ RingBuffer buffer(/* sizeLimit= */ 10);
buffer.push(buildBufferedCarData(101, {7}));
+ buffer.push(buildBufferedCarData(102, {7, 8}));
- std::vector<BufferedCarData> result = buffer.popAllDataForId(101);
+ buffer.popFront();
- std::vector<BufferedCarData> expected = {buildBufferedCarData(101, {7}),
- buildBufferedCarData(101, {7}),
- buildBufferedCarData(101, {7})};
- EXPECT_THAT(result, ContainerEq(expected));
+ EXPECT_EQ(buffer.size(), 1); // only ID=102 left
}
-TEST(RingBufferTest, TestPopAllDataForIdRemovesFromBuffer) {
- RingBuffer buffer(10); // bytes
- buffer.push(buildBufferedCarData(101, {7})); // 1 byte
- buffer.push(buildBufferedCarData(102, {7, 8})); // 2 byte
- buffer.push(buildBufferedCarData(103, {7, 8, 9})); // 3 bytes
-
- buffer.popAllDataForId(101); // also removes CarData with the given ID
-
- EXPECT_EQ(buffer.popAllDataForId(101).size(), 0);
- EXPECT_EQ(buffer.popAllDataForId(102).size(), 1);
- EXPECT_EQ(buffer.currentSizeBytes(), 3); // bytes, because only ID=103 left.
-}
-
-} // namespace
} // namespace telemetry
} // namespace automotive
} // namespace android
diff --git a/service/src/com/android/car/audio/CarAudioContext.java b/service/src/com/android/car/audio/CarAudioContext.java
index 5c76e17..c93eca3 100644
--- a/service/src/com/android/car/audio/CarAudioContext.java
+++ b/service/src/com/android/car/audio/CarAudioContext.java
@@ -254,6 +254,10 @@
return uniqueContexts;
}
+ static boolean isCriticalAudioContext(@CarAudioContext.AudioContext int audioContext) {
+ return CarAudioContext.EMERGENCY == audioContext || CarAudioContext.SAFETY == audioContext;
+ }
+
static String toString(@AudioContext int audioContext) {
String name = CONTEXT_NAMES.get(audioContext);
if (name != null) {
diff --git a/service/src/com/android/car/audio/CarAudioFocus.java b/service/src/com/android/car/audio/CarAudioFocus.java
index 5b5ac10..0b10e2b 100644
--- a/service/src/com/android/car/audio/CarAudioFocus.java
+++ b/service/src/com/android/car/audio/CarAudioFocus.java
@@ -15,6 +15,8 @@
*/
package com.android.car.audio;
+import static com.android.car.audio.CarAudioContext.isCriticalAudioContext;
+
import android.content.pm.PackageManager;
import android.media.AudioAttributes;
import android.media.AudioFocusInfo;
@@ -26,7 +28,6 @@
import android.util.Slog;
import com.android.car.CarLog;
-import com.android.car.audio.CarAudioContext.AudioContext;
import com.android.internal.annotations.GuardedBy;
import java.util.ArrayList;
@@ -131,10 +132,6 @@
}
}
- private boolean isCriticalAudioContext(@AudioContext int audioContext) {
- return CarAudioContext.EMERGENCY == audioContext || CarAudioContext.SAFETY == audioContext;
- }
-
// This sends a focus loss message to the targeted requester.
private void sendFocusLossLocked(AudioFocusInfo loser, int lossType) {
int result = mAudioManager.dispatchAudioFocusChange(loser, lossType,
diff --git a/service/src/com/android/car/audio/CarAudioPowerListener.java b/service/src/com/android/car/audio/CarAudioPowerListener.java
index eed53cb..0ee885c 100644
--- a/service/src/com/android/car/audio/CarAudioPowerListener.java
+++ b/service/src/com/android/car/audio/CarAudioPowerListener.java
@@ -78,7 +78,7 @@
void startListeningForPolicyChanges() {
if (mCarPowerManagementService == null) {
Slog.w(TAG, "Cannot find CarPowerManagementService");
- mCarAudioService.enableAudio();
+ mCarAudioService.setAudioEnabled(/* isAudioEnabled= */ true);
return;
}
@@ -100,7 +100,7 @@
if (policy == null) {
Slog.w(TAG, "Policy is null. Defaulting to enabled");
- mCarAudioService.enableAudio();
+ mCarAudioService.setAudioEnabled(/* isAudioEnabled= */ true);
return;
}
@@ -112,11 +112,6 @@
@GuardedBy("mLock")
private void updateAudioPowerStateLocked(CarPowerPolicy policy) {
mIsAudioEnabled = policy.isComponentEnabled(AUDIO);
-
- if (mIsAudioEnabled) {
- mCarAudioService.enableAudio();
- } else {
- mCarAudioService.disableAudio();
- }
+ mCarAudioService.setAudioEnabled(mIsAudioEnabled);
}
}
diff --git a/service/src/com/android/car/audio/CarAudioService.java b/service/src/com/android/car/audio/CarAudioService.java
index 08e648d..1e81c94 100644
--- a/service/src/com/android/car/audio/CarAudioService.java
+++ b/service/src/com/android/car/audio/CarAudioService.java
@@ -1243,22 +1243,16 @@
return getCarAudioZone(zoneId).getInputAudioDevices();
}
- void disableAudio() {
- // TODO(b/176258537) mute everything
+ void setAudioEnabled(boolean isAudioEnabled) {
if (Slogf.isLoggable(CarLog.TAG_AUDIO, Log.DEBUG)) {
- Slogf.d(CarLog.TAG_AUDIO, "Disabling audio");
+ Slogf.d(CarLog.TAG_AUDIO, "Setting isAudioEnabled to %b", isAudioEnabled);
}
- mFocusHandler.setRestrictFocus(/* isFocusRestricted= */ true);
- }
-
- void enableAudio() {
- // TODO(b/176258537) unmute appropriate things
- if (Slogf.isLoggable(CarLog.TAG_AUDIO, Log.DEBUG)) {
- Slogf.d(CarLog.TAG_AUDIO, "Enabling audio");
+ mFocusHandler.setRestrictFocus(/* isFocusRestricted= */ !isAudioEnabled);
+ if (mUseCarVolumeGroupMuting) {
+ mCarVolumeGroupMuting.setRestrictMuting(/* isMutingRestricted= */ !isAudioEnabled);
}
-
- mFocusHandler.setRestrictFocus(/* isFocusRestricted= */ false);
+ // TODO(b/176258537) if not using group volume, then set master mute accordingly
}
private void enforcePermission(String permissionName) {
diff --git a/service/src/com/android/car/audio/CarAudioZone.java b/service/src/com/android/car/audio/CarAudioZone.java
index 33d5d40..de540d2 100644
--- a/service/src/com/android/car/audio/CarAudioZone.java
+++ b/service/src/com/android/car/audio/CarAudioZone.java
@@ -115,7 +115,8 @@
*
* Note that it is fine that there are devices which do not appear in any group. Those devices
* may be reserved for other purposes.
- * Step value validation is done in {@link CarVolumeGroup#bind(int, CarAudioDeviceInfo)}
+ * Step value validation is done in
+ * {@link CarVolumeGroup.Builder#setDeviceInfoForContext(int, CarAudioDeviceInfo)}
*/
boolean validateVolumeGroups() {
Set<Integer> contexts = new HashSet<>();
diff --git a/service/src/com/android/car/audio/CarAudioZonesHelper.java b/service/src/com/android/car/audio/CarAudioZonesHelper.java
index cf85356..7956ea2 100644
--- a/service/src/com/android/car/audio/CarAudioZonesHelper.java
+++ b/service/src/com/android/car/audio/CarAudioZonesHelper.java
@@ -119,9 +119,10 @@
}
}
- static void bindNonLegacyContexts(CarVolumeGroup group, CarAudioDeviceInfo info) {
+ static void setNonLegacyContexts(CarVolumeGroup.Builder groupBuilder,
+ CarAudioDeviceInfo info) {
for (@AudioContext int audioContext : NON_LEGACY_CONTEXTS) {
- group.bind(audioContext, info);
+ groupBuilder.setDeviceInfoForContext(audioContext, info);
}
}
@@ -401,19 +402,20 @@
private CarVolumeGroup parseVolumeGroup(XmlPullParser parser, int zoneId, int groupId)
throws XmlPullParserException, IOException {
- CarVolumeGroup group =
- new CarVolumeGroup(zoneId, groupId, mCarAudioSettings, mUseCarVolumeGroupMute);
+ CarVolumeGroup.Builder groupBuilder =
+ new CarVolumeGroup.Builder(zoneId, groupId, mCarAudioSettings,
+ mUseCarVolumeGroupMute);
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.getEventType() != XmlPullParser.START_TAG) continue;
if (TAG_AUDIO_DEVICE.equals(parser.getName())) {
String address = parser.getAttributeValue(NAMESPACE, ATTR_DEVICE_ADDRESS);
validateOutputDeviceExist(address);
- parseVolumeGroupContexts(parser, group, address);
+ parseVolumeGroupContexts(parser, groupBuilder, address);
} else {
skip(parser);
}
}
- return group;
+ return groupBuilder.build();
}
private void validateOutputDeviceExist(String address) {
@@ -425,7 +427,7 @@
}
private void parseVolumeGroupContexts(
- XmlPullParser parser, CarVolumeGroup group, String address)
+ XmlPullParser parser, CarVolumeGroup.Builder groupBuilder, String address)
throws XmlPullParserException, IOException {
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.getEventType() != XmlPullParser.START_TAG) continue;
@@ -434,11 +436,11 @@
parser.getAttributeValue(NAMESPACE, ATTR_CONTEXT_NAME));
validateCarAudioContextSupport(carAudioContext);
CarAudioDeviceInfo info = mAddressToCarAudioDeviceInfo.get(address);
- group.bind(carAudioContext, info);
+ groupBuilder.setDeviceInfoForContext(carAudioContext, info);
// If V1, default new contexts to same device as DEFAULT_AUDIO_USAGE
if (isVersionOne() && carAudioContext == CarAudioService.DEFAULT_AUDIO_CONTEXT) {
- bindNonLegacyContexts(group, info);
+ setNonLegacyContexts(groupBuilder, info);
}
}
// Always skip to upper level since we're at the lowest.
diff --git a/service/src/com/android/car/audio/CarAudioZonesHelperLegacy.java b/service/src/com/android/car/audio/CarAudioZonesHelperLegacy.java
index 42fccf2..173252a 100644
--- a/service/src/com/android/car/audio/CarAudioZonesHelperLegacy.java
+++ b/service/src/com/android/car/audio/CarAudioZonesHelperLegacy.java
@@ -127,29 +127,15 @@
}
SparseArray<CarAudioZone> loadAudioZones() {
- final CarAudioZone zone = new CarAudioZone(PRIMARY_AUDIO_ZONE,
- "Primary zone");
- for (CarVolumeGroup group : loadVolumeGroups()) {
- zone.addVolumeGroup(group);
- bindContextsForVolumeGroup(group);
+ CarAudioZone zone = new CarAudioZone(PRIMARY_AUDIO_ZONE, "Primary zone");
+ for (CarVolumeGroup volumeGroup : loadVolumeGroups()) {
+ zone.addVolumeGroup(volumeGroup);
}
SparseArray<CarAudioZone> carAudioZones = new SparseArray<>();
carAudioZones.put(PRIMARY_AUDIO_ZONE, zone);
return carAudioZones;
}
- private void bindContextsForVolumeGroup(CarVolumeGroup group) {
- for (int legacyAudioContext : group.getContexts()) {
- int busNumber = mLegacyAudioContextToBus.get(legacyAudioContext);
- CarAudioDeviceInfo info = mBusToCarAudioDeviceInfo.get(busNumber);
- group.bind(legacyAudioContext, info);
-
- if (legacyAudioContext == CarAudioService.DEFAULT_AUDIO_CONTEXT) {
- CarAudioZonesHelper.bindNonLegacyContexts(group, info);
- }
- }
- }
-
/**
* @return all {@link CarVolumeGroup} read from configuration.
*/
@@ -186,8 +172,33 @@
return carVolumeGroups;
}
- private CarVolumeGroup parseVolumeGroup(int id, AttributeSet attrs, XmlResourceParser parser)
- throws XmlPullParserException, IOException {
+ private CarVolumeGroup parseVolumeGroup(int id, AttributeSet attrs,
+ XmlResourceParser parser) throws XmlPullParserException, IOException {
+ CarVolumeGroup.Builder builder = new CarVolumeGroup.Builder(PRIMARY_AUDIO_ZONE, id,
+ mCarAudioSettings, /* useCarVolumeGroupMute= */ false);
+
+ List<Integer> audioContexts = parseAudioContexts(parser, attrs);
+
+ for (int i = 0; i < audioContexts.size(); i++) {
+ bindContextToBuilder(builder, audioContexts.get(i));
+ }
+
+ return builder.build();
+ }
+
+
+ private void bindContextToBuilder(CarVolumeGroup.Builder groupBuilder, int legacyAudioContext) {
+ int busNumber = mLegacyAudioContextToBus.get(legacyAudioContext);
+ CarAudioDeviceInfo info = mBusToCarAudioDeviceInfo.get(busNumber);
+ groupBuilder.setDeviceInfoForContext(legacyAudioContext, info);
+
+ if (legacyAudioContext == CarAudioService.DEFAULT_AUDIO_CONTEXT) {
+ CarAudioZonesHelper.setNonLegacyContexts(groupBuilder, info);
+ }
+ }
+
+ private List<Integer> parseAudioContexts(XmlResourceParser parser, AttributeSet attrs)
+ throws IOException, XmlPullParserException {
List<Integer> contexts = new ArrayList<>();
int type;
int innerDepth = parser.getDepth();
@@ -204,8 +215,7 @@
}
}
- return new CarVolumeGroup(mCarAudioSettings, PRIMARY_AUDIO_ZONE, id,
- contexts.stream().mapToInt(i -> i).filter(i -> i >= 0).toArray());
+ return contexts;
}
/**
diff --git a/service/src/com/android/car/audio/CarVolumeGroup.java b/service/src/com/android/car/audio/CarVolumeGroup.java
index cabce82..4394be0 100644
--- a/service/src/com/android/car/audio/CarVolumeGroup.java
+++ b/service/src/com/android/car/audio/CarVolumeGroup.java
@@ -28,6 +28,7 @@
import com.android.car.CarLog;
import com.android.car.audio.CarAudioContext.AudioContext;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
import java.util.ArrayList;
@@ -46,42 +47,50 @@
/* package */ final class CarVolumeGroup {
private final boolean mUseCarVolumeGroupMute;
+ private final boolean mHasCriticalAudioContexts;
private final CarAudioSettings mSettingsManager;
- private final int mZoneId;
+ private final int mDefaultGain;
private final int mId;
- private final SparseArray<String> mContextToAddress = new SparseArray<>();
- private final Map<String, CarAudioDeviceInfo> mAddressToCarAudioDeviceInfo = new HashMap<>();
+ private final int mMaxGain;
+ private final int mMinGain;
+ private final int mStepSize;
+ private final int mZoneId;
+ private final SparseArray<String> mContextToAddress;
+ private final Map<String, CarAudioDeviceInfo> mAddressToCarAudioDeviceInfo;
private final Object mLock = new Object();
- private int mDefaultGain = Integer.MIN_VALUE;
- private int mMaxGain = Integer.MIN_VALUE;
- private int mMinGain = Integer.MAX_VALUE;
- private int mStepSize = 0;
+ @GuardedBy("mLock")
private int mStoredGainIndex;
+ @GuardedBy("mLock")
private int mCurrentGainIndex = -1;
+ @GuardedBy("mLock")
private boolean mIsMuted;
+ @GuardedBy("mLock")
private @UserIdInt int mUserId = UserHandle.USER_CURRENT;
- CarVolumeGroup(int zoneId, int id, CarAudioSettings settings, boolean useCarVolumeGroupMute) {
- mSettingsManager = settings;
+ private CarVolumeGroup(int zoneId, int id, CarAudioSettings settingsManager, int stepSize,
+ int defaultGain, int minGain, int maxGain, SparseArray<String> contextToAddress,
+ Map<String, CarAudioDeviceInfo> addressToCarAudioDeviceInfo,
+ boolean useCarVolumeGroupMute) {
+
+ mSettingsManager = settingsManager;
mZoneId = zoneId;
mId = id;
- mStoredGainIndex = mSettingsManager.getStoredVolumeGainIndexForUser(mUserId, mZoneId, mId);
+ mStepSize = stepSize;
+ mDefaultGain = defaultGain;
+ mMinGain = minGain;
+ mMaxGain = maxGain;
+ mContextToAddress = contextToAddress;
+ mAddressToCarAudioDeviceInfo = addressToCarAudioDeviceInfo;
mUseCarVolumeGroupMute = useCarVolumeGroupMute;
+
+ mHasCriticalAudioContexts = containsCriticalAudioContext(contextToAddress);
}
- /**
- * @deprecated In favor of {@link #CarVolumeGroup(int, int, CarAudioSettings, boolean)}
- * Only used for legacy configuration via IAudioControl@1.0
- */
- @Deprecated
- CarVolumeGroup(CarAudioSettings settings, int zoneId, int id, @NonNull int[] contexts) {
- this(zoneId, id, settings, false);
- // Deal with the pre-populated car audio contexts
- for (int audioContext : contexts) {
- mContextToAddress.put(audioContext, null);
- }
+ void init() {
+ mStoredGainIndex = mSettingsManager.getStoredVolumeGainIndexForUser(mUserId, mZoneId, mId);
+ updateCurrentGainIndexLocked();
}
@Nullable
@@ -89,7 +98,8 @@
return mAddressToCarAudioDeviceInfo.get(address);
}
- @AudioContext int[] getContexts() {
+ @AudioContext
+ int[] getContexts() {
final int[] carAudioContexts = new int[mContextToAddress.size()];
for (int i = 0; i < carAudioContexts.length; i++) {
carAudioContexts[i] = mContextToAddress.keyAt(i);
@@ -106,7 +116,8 @@
return mContextToAddress.get(audioContext);
}
- @AudioContext List<Integer> getContextsForAddress(@NonNull String address) {
+ @AudioContext
+ List<Integer> getContextsForAddress(@NonNull String address) {
List<Integer> carAudioContexts = new ArrayList<>();
for (int i = 0; i < mContextToAddress.size(); i++) {
String value = mContextToAddress.valueAt(i);
@@ -121,57 +132,15 @@
return new ArrayList<>(mAddressToCarAudioDeviceInfo.keySet());
}
- /**
- * Binds the context number to physical address and audio device port information.
- * Because this may change the groups min/max values, thus invalidating an index computed from
- * a gain before this call, all calls to this function must happen at startup before any
- * set/getGainIndex calls.
- *
- * @param carAudioContext Context to bind audio to {@link CarAudioContext}
- * @param info {@link CarAudioDeviceInfo} instance relates to the physical address
- */
- void bind(int carAudioContext, CarAudioDeviceInfo info) {
- Preconditions.checkArgument(mContextToAddress.get(carAudioContext) == null,
- String.format("Context %s has already been bound to %s",
- CarAudioContext.toString(carAudioContext),
- mContextToAddress.get(carAudioContext)));
-
- synchronized (mLock) {
- if (mAddressToCarAudioDeviceInfo.size() == 0) {
- mStepSize = info.getStepValue();
- } else {
- Preconditions.checkArgument(
- info.getStepValue() == mStepSize,
- "Gain controls within one group must have same step value");
- }
-
- mAddressToCarAudioDeviceInfo.put(info.getAddress(), info);
- mContextToAddress.put(carAudioContext, info.getAddress());
-
- if (info.getDefaultGain() > mDefaultGain) {
- // We're arbitrarily selecting the highest
- // device default gain as the group's default.
- mDefaultGain = info.getDefaultGain();
- }
- if (info.getMaxGain() > mMaxGain) {
- mMaxGain = info.getMaxGain();
- }
- if (info.getMinGain() < mMinGain) {
- mMinGain = info.getMinGain();
- }
- updateCurrentGainIndexLocked();
- }
- }
-
int getMaxGainIndex() {
synchronized (mLock) {
- return getIndexForGainLocked(mMaxGain);
+ return getIndexForGain(mMaxGain);
}
}
int getMinGainIndex() {
synchronized (mLock) {
- return getIndexForGainLocked(mMinGain);
+ return getIndexForGain(mMinGain);
}
}
@@ -183,19 +152,13 @@
/**
* Sets the gain on this group, gain will be set on all devices within volume group.
- * @param gainIndex The gain index
*/
void setCurrentGainIndex(int gainIndex) {
+ int gainInMillibels = getGainForIndex(gainIndex);
+ Preconditions.checkArgument(isValidGainIndex(gainIndex),
+ "Gain out of range (%d:%d) %d index %d", mMinGain, mMaxGain,
+ gainInMillibels, gainIndex);
synchronized (mLock) {
- int gainInMillibels = getGainForIndexLocked(gainIndex);
- Preconditions.checkArgument(
- gainInMillibels >= mMinGain && gainInMillibels <= mMaxGain,
- "Gain out of range ("
- + mMinGain + ":"
- + mMaxGain + ") "
- + gainInMillibels + "index "
- + gainIndex);
-
for (String address : mAddressToCarAudioDeviceInfo.keySet()) {
CarAudioDeviceInfo info = mAddressToCarAudioDeviceInfo.get(address);
info.setCurrentGain(gainInMillibels);
@@ -217,6 +180,10 @@
return mAddressToCarAudioDeviceInfo.get(address).getAudioDevicePort();
}
+ boolean hasCriticalAudioContexts() {
+ return mHasCriticalAudioContexts;
+ }
+
@Override
public String toString() {
return "CarVolumeGroup id: " + mId
@@ -229,12 +196,14 @@
synchronized (mLock) {
writer.printf("CarVolumeGroup(%d)\n", mId);
writer.increaseIndent();
+ writer.printf("Zone Id(%b)\n", mZoneId);
writer.printf("Is Muted(%b)\n", mIsMuted);
writer.printf("UserId(%d)\n", mUserId);
writer.printf("Persist Volume Group Mute(%b)\n",
mSettingsManager.isPersistVolumeGroupMuteEnabled(mUserId));
+ writer.printf("Step size: %d\n", mStepSize);
writer.printf("Gain values (min / max / default/ current): %d %d %d %d\n", mMinGain,
- mMaxGain, mDefaultGain, getGainForIndexLocked(mCurrentGainIndex));
+ mMaxGain, mDefaultGain, getGainForIndex(mCurrentGainIndex));
writer.printf("Gain indexes (min / max / default / current): %d %d %d %d\n",
getMinGainIndex(), getMaxGainIndex(), getDefaultGainIndex(), mCurrentGainIndex);
for (int i = 0; i < mContextToAddress.size(); i++) {
@@ -279,6 +248,16 @@
}
}
+ private static boolean containsCriticalAudioContext(SparseArray<String> contextToAddress) {
+ for (int i = 0; i < contextToAddress.size(); i++) {
+ int audioContext = contextToAddress.keyAt(i);
+ if (CarAudioContext.isCriticalAudioContext(audioContext)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
@GuardedBy("mLock")
private void updateUserIdLocked(@UserIdInt int userId) {
mUserId = userId;
@@ -299,21 +278,21 @@
*/
@GuardedBy("mLock")
private void updateCurrentGainIndexLocked() {
- if (isValidGainLocked(mStoredGainIndex)) {
+ if (isValidGainIndex(mStoredGainIndex)) {
mCurrentGainIndex = mStoredGainIndex;
} else {
- mCurrentGainIndex = getIndexForGainLocked(mDefaultGain);
+ mCurrentGainIndex = getIndexForGain(mDefaultGain);
}
}
- @GuardedBy("mLock")
- private boolean isValidGainLocked(int gain) {
- return gain >= getIndexForGainLocked(mMinGain) && gain <= getIndexForGainLocked(mMaxGain);
+ private boolean isValidGainIndex(int gainIndex) {
+ return gainIndex >= getIndexForGain(mMinGain)
+ && gainIndex <= getIndexForGain(mMaxGain);
}
private int getDefaultGainIndex() {
synchronized (mLock) {
- return getIndexForGainLocked(mDefaultGain);
+ return getIndexForGain(mDefaultGain);
}
}
@@ -323,12 +302,11 @@
mZoneId, mId, gainIndex);
}
- private int getGainForIndexLocked(int gainIndex) {
+ private int getGainForIndex(int gainIndex) {
return mMinGain + gainIndex * mStepSize;
}
- @GuardedBy("mLock")
- private int getIndexForGainLocked(int gainInMillibel) {
+ private int getIndexForGain(int gainInMillibel) {
return (gainInMillibel - mMinGain) / mStepSize;
}
@@ -343,4 +321,75 @@
}
mIsMuted = mSettingsManager.getVolumeGroupMuteForUser(mUserId, mZoneId, mId);
}
+
+ static final class Builder {
+ private static final int UNSET_STEP_SIZE = -1;
+
+ private final int mId;
+ private final int mZoneId;
+ private final boolean mUseCarVolumeGroupMute;
+ private final CarAudioSettings mCarAudioSettings;
+ private final SparseArray<String> mContextToAddress = new SparseArray<>();
+ private final Map<String, CarAudioDeviceInfo> mAddressToCarAudioDeviceInfo =
+ new HashMap<>();
+
+ @VisibleForTesting
+ int mStepSize = UNSET_STEP_SIZE;
+ @VisibleForTesting
+ int mDefaultGain = Integer.MIN_VALUE;
+ @VisibleForTesting
+ int mMaxGain = Integer.MIN_VALUE;
+ @VisibleForTesting
+ int mMinGain = Integer.MAX_VALUE;
+
+ Builder(int zoneId, int id, CarAudioSettings carAudioSettings,
+ boolean useCarVolumeGroupMute) {
+ mZoneId = zoneId;
+ mId = id;
+ mCarAudioSettings = carAudioSettings;
+ mUseCarVolumeGroupMute = useCarVolumeGroupMute;
+ }
+
+ Builder setDeviceInfoForContext(int carAudioContext, CarAudioDeviceInfo info) {
+ Preconditions.checkArgument(mContextToAddress.get(carAudioContext) == null,
+ "Context %s has already been set to %s",
+ CarAudioContext.toString(carAudioContext),
+ mContextToAddress.get(carAudioContext));
+
+ if (mAddressToCarAudioDeviceInfo.isEmpty()) {
+ mStepSize = info.getStepValue();
+ } else {
+ Preconditions.checkArgument(
+ info.getStepValue() == mStepSize,
+ "Gain controls within one group must have same step value");
+ }
+
+ mAddressToCarAudioDeviceInfo.put(info.getAddress(), info);
+ mContextToAddress.put(carAudioContext, info.getAddress());
+
+ if (info.getDefaultGain() > mDefaultGain) {
+ // We're arbitrarily selecting the highest
+ // device default gain as the group's default.
+ mDefaultGain = info.getDefaultGain();
+ }
+ if (info.getMaxGain() > mMaxGain) {
+ mMaxGain = info.getMaxGain();
+ }
+ if (info.getMinGain() < mMinGain) {
+ mMinGain = info.getMinGain();
+ }
+
+ return this;
+ }
+
+ CarVolumeGroup build() {
+ Preconditions.checkArgument(mStepSize != UNSET_STEP_SIZE,
+ "setDeviceInfoForContext has to be called at least once before building");
+ CarVolumeGroup group = new CarVolumeGroup(mZoneId, mId, mCarAudioSettings, mStepSize,
+ mDefaultGain, mMinGain, mMaxGain, mContextToAddress,
+ mAddressToCarAudioDeviceInfo, mUseCarVolumeGroupMute);
+ group.init();
+ return group;
+ }
+ }
}
diff --git a/service/src/com/android/car/audio/CarVolumeGroupMuting.java b/service/src/com/android/car/audio/CarVolumeGroupMuting.java
index f6d2ce9..7101127 100644
--- a/service/src/com/android/car/audio/CarVolumeGroupMuting.java
+++ b/service/src/com/android/car/audio/CarVolumeGroupMuting.java
@@ -44,6 +44,8 @@
private final Object mLock = new Object();
@GuardedBy("mLock")
private List<MutingInfo> mLastMutingInformation;
+ @GuardedBy("mLock")
+ private boolean mIsMutingRestricted;
CarVolumeGroupMuting(@NonNull SparseArray<CarAudioZone> carAudioZones,
@NonNull AudioControlWrapper audioControlWrapper) {
@@ -72,11 +74,26 @@
if (Log.isLoggable(TAG, Log.DEBUG)) {
Slog.d(TAG, "carMuteChanged");
}
+
List<MutingInfo> mutingInfo = generateMutingInfo();
setLastMutingInfo(mutingInfo);
mAudioControlWrapper.onDevicesToMuteChange(mutingInfo);
}
+ public void setRestrictMuting(boolean isMutingRestricted) {
+ synchronized (mLock) {
+ mIsMutingRestricted = isMutingRestricted;
+ }
+
+ carMuteChanged();
+ }
+
+ private boolean isMutingRestricted() {
+ synchronized (mLock) {
+ return mIsMutingRestricted;
+ }
+ }
+
private void setLastMutingInfo(List<MutingInfo> mutingInfo) {
synchronized (mLock) {
mLastMutingInformation = mutingInfo;
@@ -92,8 +109,11 @@
private List<MutingInfo> generateMutingInfo() {
List<MutingInfo> mutingInformation = new ArrayList<>(mCarAudioZones.size());
+
+ boolean isMutingRestricted = isMutingRestricted();
for (int index = 0; index < mCarAudioZones.size(); index++) {
- mutingInformation.add(generateMutingInfoFromZone(mCarAudioZones.valueAt(index)));
+ mutingInformation.add(generateMutingInfoFromZone(mCarAudioZones.valueAt(index),
+ isMutingRestricted));
}
return mutingInformation;
@@ -106,6 +126,7 @@
writer.println(TAG);
writer.increaseIndent();
synchronized (mLock) {
+ writer.printf("Is muting restricted? %b\n", mIsMutingRestricted);
for (int index = 0; index < mLastMutingInformation.size(); index++) {
dumpCarMutingInfo(writer, mLastMutingInformation.get(index));
}
@@ -134,7 +155,8 @@
}
@VisibleForTesting
- static MutingInfo generateMutingInfoFromZone(CarAudioZone audioZone) {
+ static MutingInfo generateMutingInfoFromZone(CarAudioZone audioZone,
+ boolean isMutingRestricted) {
MutingInfo mutingInfo = new MutingInfo();
mutingInfo.zoneId = audioZone.getId();
@@ -144,11 +166,12 @@
for (int groupIndex = 0; groupIndex < groups.length; groupIndex++) {
CarVolumeGroup group = groups[groupIndex];
- if (group.isMuted()) {
+
+ if (group.isMuted() || (isMutingRestricted && !group.hasCriticalAudioContexts())) {
mutedDevices.addAll(group.getAddresses());
- continue;
+ } else {
+ unMutedDevices.addAll(group.getAddresses());
}
- unMutedDevices.addAll(group.getAddresses());
}
mutingInfo.deviceAddressesToMute = mutedDevices.toArray(new String[mutedDevices.size()]);
diff --git a/tests/carservice_test/src/com/android/car/audio/CarVolumeGroupTest.java b/tests/carservice_test/src/com/android/car/audio/CarVolumeGroupTest.java
deleted file mode 100644
index cb78b50..0000000
--- a/tests/carservice_test/src/com/android/car/audio/CarVolumeGroupTest.java
+++ /dev/null
@@ -1,593 +0,0 @@
-/*
- * Copyright (C) 2019 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.
- */
-package com.android.car.audio;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-import static org.testng.Assert.expectThrows;
-
-import android.annotation.UserIdInt;
-import android.app.ActivityManager;
-import android.car.test.mocks.AbstractExtendedMockitoTestCase;
-import android.os.UserHandle;
-import android.util.SparseBooleanArray;
-import android.util.SparseIntArray;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import com.google.common.primitives.Ints;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mockito;
-
-import java.util.List;
-
-@RunWith(AndroidJUnit4.class)
-public class CarVolumeGroupTest extends AbstractExtendedMockitoTestCase{
- private static final int STEP_VALUE = 2;
- private static final int MIN_GAIN = 0;
- private static final int MAX_GAIN = 5;
- private static final int DEFAULT_GAIN = 0;
- private static final int TEST_USER_10 = 10;
- private static final int TEST_USER_11 = 11;
- private static final String OTHER_ADDRESS = "other_address";
- private static final String MEDIA_DEVICE_ADDRESS = "music";
- private static final String NAVIGATION_DEVICE_ADDRESS = "navigation";
-
-
- private CarAudioDeviceInfo mMediaDevice;
- private CarAudioDeviceInfo mNavigationDevice;
-
- @Override
- protected void onSessionBuilder(CustomMockitoSessionBuilder session) {
- session.spyStatic(ActivityManager.class);
- }
-
- @Before
- public void setUp() {
- mMediaDevice = generateCarAudioDeviceInfo(MEDIA_DEVICE_ADDRESS);
- mNavigationDevice = generateCarAudioDeviceInfo(NAVIGATION_DEVICE_ADDRESS);
- }
-
- @Test
- public void bind_associatesDeviceAddresses() {
- CarVolumeGroup carVolumeGroup =
- getVolumeGroupWithGainAndUser(2, UserHandle.USER_CURRENT);
-
- carVolumeGroup.bind(CarAudioContext.MUSIC, mMediaDevice);
- assertEquals(1, carVolumeGroup.getAddresses().size());
-
- carVolumeGroup.bind(CarAudioContext.NAVIGATION, mNavigationDevice);
-
- List<String> addresses = carVolumeGroup.getAddresses();
- assertEquals(2, addresses.size());
- assertTrue(addresses.contains(MEDIA_DEVICE_ADDRESS));
- assertTrue(addresses.contains(NAVIGATION_DEVICE_ADDRESS));
- }
-
- @Test
- public void bind_checksForSameStepSize() {
- CarVolumeGroup carVolumeGroup =
- getVolumeGroupWithGainAndUser(2, UserHandle.USER_CURRENT);
-
- carVolumeGroup.bind(CarAudioContext.MUSIC, mMediaDevice);
- CarAudioDeviceInfo differentStepValueDevice = generateCarAudioDeviceInfo(
- NAVIGATION_DEVICE_ADDRESS, STEP_VALUE + 1,
- MIN_GAIN, MAX_GAIN);
-
- IllegalArgumentException thrown = expectThrows(IllegalArgumentException.class,
- () -> carVolumeGroup.bind(CarAudioContext.NAVIGATION, differentStepValueDevice));
- assertThat(thrown).hasMessageThat()
- .contains("Gain controls within one group must have same step value");
- }
-
- @Test
- public void bind_updatesMinGainToSmallestValue() {
- CarVolumeGroup carVolumeGroup =
- getVolumeGroupWithGainAndUser(2, UserHandle.USER_CURRENT);
-
- CarAudioDeviceInfo largestMinGain = generateCarAudioDeviceInfo(
- NAVIGATION_DEVICE_ADDRESS, 1, 10, 10);
- carVolumeGroup.bind(CarAudioContext.NAVIGATION, largestMinGain);
-
- assertEquals(0, carVolumeGroup.getMaxGainIndex());
-
- CarAudioDeviceInfo smallestMinGain = generateCarAudioDeviceInfo(
- NAVIGATION_DEVICE_ADDRESS, 1, 2, 10);
- carVolumeGroup.bind(CarAudioContext.NOTIFICATION, smallestMinGain);
-
- assertEquals(8, carVolumeGroup.getMaxGainIndex());
-
- CarAudioDeviceInfo middleMinGain = generateCarAudioDeviceInfo(
- NAVIGATION_DEVICE_ADDRESS, 1, 7, 10);
- carVolumeGroup.bind(CarAudioContext.VOICE_COMMAND, middleMinGain);
-
- assertEquals(8, carVolumeGroup.getMaxGainIndex());
- }
-
- @Test
- public void bind_updatesMaxGainToLargestValue() {
- CarVolumeGroup carVolumeGroup =
- getVolumeGroupWithGainAndUser(2, UserHandle.USER_CURRENT);
-
- CarAudioDeviceInfo smallestMaxGain = generateCarAudioDeviceInfo(
- NAVIGATION_DEVICE_ADDRESS, 1, 1, 5);
- carVolumeGroup.bind(CarAudioContext.NAVIGATION, smallestMaxGain);
-
- assertEquals(4, carVolumeGroup.getMaxGainIndex());
-
- CarAudioDeviceInfo largestMaxGain = generateCarAudioDeviceInfo(
- NAVIGATION_DEVICE_ADDRESS, 1, 1, 10);
- carVolumeGroup.bind(CarAudioContext.NOTIFICATION, largestMaxGain);
-
- assertEquals(9, carVolumeGroup.getMaxGainIndex());
-
- CarAudioDeviceInfo middleMaxGain = generateCarAudioDeviceInfo(
- NAVIGATION_DEVICE_ADDRESS, 1, 1, 7);
- carVolumeGroup.bind(CarAudioContext.VOICE_COMMAND, middleMaxGain);
-
- assertEquals(9, carVolumeGroup.getMaxGainIndex());
- }
-
- @Test
- public void bind_checksThatTheSameContextIsNotBoundTwice() {
- CarVolumeGroup carVolumeGroup =
- getVolumeGroupWithGainAndUser(2, UserHandle.USER_CURRENT);
-
- carVolumeGroup.bind(CarAudioContext.NAVIGATION, mMediaDevice);
-
- IllegalArgumentException thrown = expectThrows(IllegalArgumentException.class,
- () -> carVolumeGroup.bind(CarAudioContext.NAVIGATION, mMediaDevice));
- assertThat(thrown).hasMessageThat()
- .contains("Context NAVIGATION has already been bound to " + MEDIA_DEVICE_ADDRESS);
- }
-
- @Test
- public void getContexts_returnsAllContextsBoundToVolumeGroup() {
- CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
-
- int[] contexts = carVolumeGroup.getContexts();
-
- assertEquals(6, contexts.length);
-
- List<Integer> contextsList = Ints.asList(contexts);
- assertTrue(contextsList.contains(CarAudioContext.MUSIC));
- assertTrue(contextsList.contains(CarAudioContext.CALL));
- assertTrue(contextsList.contains(CarAudioContext.CALL_RING));
- assertTrue(contextsList.contains(CarAudioContext.NAVIGATION));
- assertTrue(contextsList.contains(CarAudioContext.ALARM));
- assertTrue(contextsList.contains(CarAudioContext.NOTIFICATION));
- }
-
- @Test
- public void getContextsForAddress_returnsContextsBoundToThatAddress() {
- CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
-
- List<Integer> contextsList = carVolumeGroup.getContextsForAddress(MEDIA_DEVICE_ADDRESS);
-
- assertThat(contextsList).containsExactly(CarAudioContext.MUSIC,
- CarAudioContext.CALL, CarAudioContext.CALL_RING);
- }
-
- @Test
- public void getContextsForAddress_returnsEmptyArrayIfAddressNotBound() {
- CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
-
- List<Integer> contextsList = carVolumeGroup.getContextsForAddress(OTHER_ADDRESS);
-
- assertThat(contextsList).isEmpty();
- }
-
- @Test
- public void getCarAudioDeviceInfoForAddress_returnsExpectedDevice() {
- CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
-
- CarAudioDeviceInfo actualDevice = carVolumeGroup.getCarAudioDeviceInfoForAddress(
- MEDIA_DEVICE_ADDRESS);
-
- assertEquals(mMediaDevice, actualDevice);
- }
-
- @Test
- public void getCarAudioDeviceInfoForAddress_returnsNullIfAddressNotBound() {
- CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
-
- CarAudioDeviceInfo actualDevice = carVolumeGroup.getCarAudioDeviceInfoForAddress(
- OTHER_ADDRESS);
-
- assertNull(actualDevice);
- }
-
- @Test
- public void setCurrentGainIndex_setsGainOnAllBoundDevices() {
- CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
-
- carVolumeGroup.setCurrentGainIndex(2);
- verify(mMediaDevice).setCurrentGain(4);
- verify(mNavigationDevice).setCurrentGain(4);
- }
-
- @Test
- public void setCurrentGainIndex_updatesCurrentGainIndex() {
- CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
-
- carVolumeGroup.setCurrentGainIndex(2);
-
- assertEquals(2, carVolumeGroup.getCurrentGainIndex());
- }
-
- @Test
- public void setCurrentGainIndex_checksNewGainIsAboveMin() {
- CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
-
- IllegalArgumentException thrown = expectThrows(IllegalArgumentException.class,
- () -> carVolumeGroup.setCurrentGainIndex(-1));
- assertThat(thrown).hasMessageThat().contains("Gain out of range (0:5) -2index -1");
- }
-
- @Test
- public void setCurrentGainIndex_checksNewGainIsBelowMax() {
- CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
-
- IllegalArgumentException thrown = expectThrows(IllegalArgumentException.class,
- () -> carVolumeGroup.setCurrentGainIndex(3));
- assertThat(thrown).hasMessageThat().contains("Gain out of range (0:5) 6index 3");
- }
-
- @Test
- public void getMinGainIndex_alwaysReturnsZero() {
- CarVolumeGroup carVolumeGroup =
- getVolumeGroupWithGainAndUser(2, UserHandle.USER_CURRENT);
- CarAudioDeviceInfo minGainPlusOneDevice = generateCarAudioDeviceInfo(
- NAVIGATION_DEVICE_ADDRESS, STEP_VALUE, 10, MAX_GAIN);
- carVolumeGroup.bind(CarAudioContext.NAVIGATION, minGainPlusOneDevice);
-
- assertEquals(0, carVolumeGroup.getMinGainIndex());
-
- CarAudioDeviceInfo minGainDevice = generateCarAudioDeviceInfo(
- NAVIGATION_DEVICE_ADDRESS, STEP_VALUE, 1, MAX_GAIN);
- carVolumeGroup.bind(CarAudioContext.NOTIFICATION, minGainDevice);
-
- assertEquals(0, carVolumeGroup.getMinGainIndex());
- }
-
- @Test
- public void loadVolumesSettingsForUser_setsCurrentGainIndexForUser() {
- CarAudioSettings settings = new CarVolumeGroupSettingsBuilder(0, 0)
- .setGainIndexForUser(TEST_USER_10, 2)
- .setGainIndexForUser(TEST_USER_11, 0)
- .build();
-
- CarVolumeGroup carVolumeGroup = new CarVolumeGroup(0, 0, settings, false);
-
- CarAudioDeviceInfo deviceInfo = generateCarAudioDeviceInfo(
- NAVIGATION_DEVICE_ADDRESS, STEP_VALUE, MIN_GAIN, MAX_GAIN);
- carVolumeGroup.bind(CarAudioContext.NAVIGATION, deviceInfo);
- carVolumeGroup.loadVolumesSettingsForUser(TEST_USER_10);
-
- assertEquals(2, carVolumeGroup.getCurrentGainIndex());
-
- carVolumeGroup.loadVolumesSettingsForUser(TEST_USER_11);
-
- assertEquals(0, carVolumeGroup.getCurrentGainIndex());
- }
-
- @Test
- public void loadVolumesSettingsForUser_setsCurrentGainIndexToDefault() {
- CarAudioSettings settings = new CarVolumeGroupSettingsBuilder(0, 0)
- .setGainIndexForUser(TEST_USER_10, 10)
- .build();
- CarVolumeGroup carVolumeGroup = new CarVolumeGroup(0, 0, settings, false);
-
- CarAudioDeviceInfo deviceInfo = generateCarAudioDeviceInfo(
- NAVIGATION_DEVICE_ADDRESS, STEP_VALUE, MIN_GAIN, MAX_GAIN);
- carVolumeGroup.bind(CarAudioContext.NAVIGATION, deviceInfo);
-
- carVolumeGroup.setCurrentGainIndex(2);
-
- assertEquals(2, carVolumeGroup.getCurrentGainIndex());
-
- carVolumeGroup.loadVolumesSettingsForUser(0);
-
- assertEquals(0, carVolumeGroup.getCurrentGainIndex());
- }
-
- @Test
- public void setCurrentGainIndex_setsCurrentGainIndexForUser() {
- CarAudioSettings settings = new CarVolumeGroupSettingsBuilder(0, 0)
- .setGainIndexForUser(TEST_USER_11, 2)
- .build();
- CarVolumeGroup carVolumeGroup = new CarVolumeGroup(0, 0, settings, false);
-
- CarAudioDeviceInfo deviceInfo = generateCarAudioDeviceInfo(
- NAVIGATION_DEVICE_ADDRESS, STEP_VALUE, MIN_GAIN, MAX_GAIN);
- carVolumeGroup.bind(CarAudioContext.NAVIGATION, deviceInfo);
- carVolumeGroup.loadVolumesSettingsForUser(TEST_USER_11);
-
- carVolumeGroup.setCurrentGainIndex(MIN_GAIN);
-
- verify(settings).storeVolumeGainIndexForUser(TEST_USER_11, 0, 0, MIN_GAIN);
- }
-
- @Test
- public void setCurrentGainIndex_setsCurrentGainIndexForDefaultUser() {
- CarAudioSettings settings = new CarVolumeGroupSettingsBuilder(0, 0)
- .setGainIndexForUser(UserHandle.USER_CURRENT, 2)
- .build();
- CarVolumeGroup carVolumeGroup = new CarVolumeGroup(0, 0, settings, false);
-
- CarAudioDeviceInfo deviceInfo = generateCarAudioDeviceInfo(
- NAVIGATION_DEVICE_ADDRESS, STEP_VALUE, MIN_GAIN, MAX_GAIN);
- carVolumeGroup.bind(CarAudioContext.NAVIGATION, deviceInfo);
-
- carVolumeGroup.setCurrentGainIndex(MIN_GAIN);
-
- verify(settings)
- .storeVolumeGainIndexForUser(UserHandle.USER_CURRENT, 0, 0, MIN_GAIN);
- }
-
- @Test
- public void bind_setsCurrentGainIndexToStoredGainIndex() {
- CarVolumeGroup carVolumeGroup =
- getVolumeGroupWithGainAndUser(2, UserHandle.USER_CURRENT);
-
- CarAudioDeviceInfo deviceInfo = generateCarAudioDeviceInfo(
- NAVIGATION_DEVICE_ADDRESS, STEP_VALUE, MIN_GAIN, MAX_GAIN);
- carVolumeGroup.bind(CarAudioContext.NAVIGATION, deviceInfo);
-
-
- assertEquals(2, carVolumeGroup.getCurrentGainIndex());
- }
-
- @Test
- public void getAddressForContext_returnsExpectedDeviceAddress() {
- CarVolumeGroup carVolumeGroup =
- getVolumeGroupWithGainAndUser(2, UserHandle.USER_CURRENT);
-
- carVolumeGroup.bind(CarAudioContext.MUSIC, mMediaDevice);
-
- String mediaAddress = carVolumeGroup.getAddressForContext(CarAudioContext.MUSIC);
-
- assertEquals(mMediaDevice.getAddress(), mediaAddress);
- }
-
- @Test
- public void getAddressForContext_returnsNull() {
- CarVolumeGroup carVolumeGroup =
- getVolumeGroupWithGainAndUser(2, UserHandle.USER_CURRENT);
-
- String nullAddress = carVolumeGroup.getAddressForContext(CarAudioContext.MUSIC);
-
- assertNull(nullAddress);
- }
-
- @Test
- public void isMuted_whenDefault_returnsFalse() {
- CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
-
- assertThat(carVolumeGroup.isMuted()).isFalse();
- }
-
- @Test
- public void isMuted_afterMuting_returnsTrue() {
- CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
- carVolumeGroup.setMute(true);
-
- assertThat(carVolumeGroup.isMuted()).isTrue();
- }
-
- @Test
- public void isMuted_afterUnMuting_returnsFalse() {
- CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
- carVolumeGroup.setMute(false);
-
- assertThat(carVolumeGroup.isMuted()).isFalse();
- }
-
- @Test
- public void setMute_withMutedState_storesValueToSetting() {
- CarAudioSettings settings = new CarVolumeGroupSettingsBuilder(0, 0)
- .setMuteForUser(TEST_USER_10, false)
- .setIsPersistVolumeGroupEnabled(true)
- .build();
- CarVolumeGroup carVolumeGroup = new CarVolumeGroup(0, 0, settings, true);
- CarAudioDeviceInfo deviceInfo = generateCarAudioDeviceInfo(
- NAVIGATION_DEVICE_ADDRESS, STEP_VALUE, MIN_GAIN, MAX_GAIN);
- carVolumeGroup.bind(CarAudioContext.NAVIGATION, deviceInfo);
- carVolumeGroup.loadVolumesSettingsForUser(TEST_USER_10);
-
- carVolumeGroup.setMute(true);
-
- verify(settings)
- .storeVolumeGroupMuteForUser(TEST_USER_10, 0, 0, true);
- }
-
- @Test
- public void setMute_withUnMutedState_storesValueToSetting() {
- CarAudioSettings settings = new CarVolumeGroupSettingsBuilder(0, 0)
- .setMuteForUser(TEST_USER_10, false)
- .setIsPersistVolumeGroupEnabled(true)
- .build();
- CarVolumeGroup carVolumeGroup = new CarVolumeGroup(0, 0, settings, true);
- CarAudioDeviceInfo deviceInfo = generateCarAudioDeviceInfo(
- NAVIGATION_DEVICE_ADDRESS, STEP_VALUE, MIN_GAIN, MAX_GAIN);
- carVolumeGroup.bind(CarAudioContext.NAVIGATION, deviceInfo);
- carVolumeGroup.loadVolumesSettingsForUser(TEST_USER_10);
-
- carVolumeGroup.setMute(false);
-
- verify(settings)
- .storeVolumeGroupMuteForUser(TEST_USER_10, 0, 0, false);
- }
-
- @Test
- public void loadVolumesSettingsForUser_withMutedState_loadsMuteStateForUser() {
- CarVolumeGroup carVolumeGroup = getVolumeGroupWithMuteForUser(true, true,
- TEST_USER_10);
- CarAudioDeviceInfo deviceInfo = generateCarAudioDeviceInfo(
- NAVIGATION_DEVICE_ADDRESS, STEP_VALUE, MIN_GAIN, MAX_GAIN);
- carVolumeGroup.bind(CarAudioContext.NAVIGATION, deviceInfo);
-
- carVolumeGroup.loadVolumesSettingsForUser(TEST_USER_10);
-
- assertEquals(true, carVolumeGroup.isMuted());
- }
-
- @Test
- public void loadVolumesSettingsForUser_withDisabledUseVolumeGroupMute_doesNotLoadMute() {
- CarAudioSettings settings = new CarVolumeGroupSettingsBuilder(0, 0)
- .setMuteForUser(TEST_USER_10, true)
- .setIsPersistVolumeGroupEnabled(true)
- .build();
- CarVolumeGroup carVolumeGroup = new CarVolumeGroup(0, 0, settings, false);
- CarAudioDeviceInfo deviceInfo = generateCarAudioDeviceInfo(
- NAVIGATION_DEVICE_ADDRESS, STEP_VALUE, MIN_GAIN, MAX_GAIN);
- carVolumeGroup.bind(CarAudioContext.NAVIGATION, deviceInfo);
-
- carVolumeGroup.loadVolumesSettingsForUser(TEST_USER_10);
-
- assertEquals(false, carVolumeGroup.isMuted());
- }
-
- @Test
- public void loadVolumesSettingsForUser_withUnMutedState_loadsMuteStateForUser() {
- CarVolumeGroup carVolumeGroup = getVolumeGroupWithMuteForUser(false, true,
- TEST_USER_10);
- CarAudioDeviceInfo deviceInfo = generateCarAudioDeviceInfo(
- NAVIGATION_DEVICE_ADDRESS, STEP_VALUE, MIN_GAIN, MAX_GAIN);
- carVolumeGroup.bind(CarAudioContext.NAVIGATION, deviceInfo);
-
- carVolumeGroup.loadVolumesSettingsForUser(TEST_USER_10);
-
- assertEquals(false, carVolumeGroup.isMuted());
- }
-
- @Test
- public void loadVolumesSettingsForUser_withMutedStateAndNoPersist_returnsDefaultMuteState() {
- CarVolumeGroup carVolumeGroup = getVolumeGroupWithMuteForUser(true, false,
- TEST_USER_10);
- CarAudioDeviceInfo deviceInfo = generateCarAudioDeviceInfo(
- NAVIGATION_DEVICE_ADDRESS, STEP_VALUE, MIN_GAIN, MAX_GAIN);
- carVolumeGroup.bind(CarAudioContext.NAVIGATION, deviceInfo);
-
- carVolumeGroup.loadVolumesSettingsForUser(TEST_USER_10);
-
- assertEquals(false, carVolumeGroup.isMuted());
- }
-
- CarVolumeGroup getVolumeGroupWithGainAndUser(int gain, @UserIdInt int userId) {
- CarAudioSettings settings = new CarVolumeGroupSettingsBuilder(0, 0)
- .setGainIndexForUser(userId, gain)
- .build();
- CarVolumeGroup carVolumeGroup = new CarVolumeGroup(0, 0, settings, false);
-
- return carVolumeGroup;
- }
-
- CarVolumeGroup getVolumeGroupWithMuteForUser(boolean isMuted, boolean persistMute,
- @UserIdInt int userId) {
- CarAudioSettings settings = new CarVolumeGroupSettingsBuilder(0, 0)
- .setMuteForUser(userId, isMuted)
- .setIsPersistVolumeGroupEnabled(persistMute)
- .build();
- CarVolumeGroup carVolumeGroup = new CarVolumeGroup(0, 0, settings, true);
-
- return carVolumeGroup;
- }
-
- private CarVolumeGroup testVolumeGroupSetup() {
- CarVolumeGroup carVolumeGroup =
- getVolumeGroupWithGainAndUser(2, UserHandle.USER_CURRENT);
-
- carVolumeGroup.bind(CarAudioContext.MUSIC, mMediaDevice);
- carVolumeGroup.bind(CarAudioContext.CALL, mMediaDevice);
- carVolumeGroup.bind(CarAudioContext.CALL_RING, mMediaDevice);
-
- carVolumeGroup.bind(CarAudioContext.NAVIGATION, mNavigationDevice);
- carVolumeGroup.bind(CarAudioContext.ALARM, mNavigationDevice);
- carVolumeGroup.bind(CarAudioContext.NOTIFICATION, mNavigationDevice);
-
- return carVolumeGroup;
- }
-
- private CarAudioDeviceInfo generateCarAudioDeviceInfo(String address) {
- return generateCarAudioDeviceInfo(address, STEP_VALUE, MIN_GAIN, MAX_GAIN);
- }
-
- private CarAudioDeviceInfo generateCarAudioDeviceInfo(String address, int stepValue,
- int minGain, int maxGain) {
- CarAudioDeviceInfo cadiMock = Mockito.mock(CarAudioDeviceInfo.class);
- when(cadiMock.getStepValue()).thenReturn(stepValue);
- when(cadiMock.getDefaultGain()).thenReturn(DEFAULT_GAIN);
- when(cadiMock.getMaxGain()).thenReturn(maxGain);
- when(cadiMock.getMinGain()).thenReturn(minGain);
- when(cadiMock.getAddress()).thenReturn(address);
- return cadiMock;
- }
-
- private static final class CarVolumeGroupSettingsBuilder {
- private SparseIntArray mStoredGainIndexes = new SparseIntArray();
- private SparseBooleanArray mStoreMuteStates = new SparseBooleanArray();
- private boolean mPersistMute;
- private final int mZoneId;
- private final int mGroupId;
-
- CarVolumeGroupSettingsBuilder(int zoneId, int groupId) {
- mZoneId = zoneId;
- mGroupId = groupId;
- }
-
- CarVolumeGroupSettingsBuilder setGainIndexForUser(@UserIdInt int userId, int gainIndex) {
- mStoredGainIndexes.put(userId, gainIndex);
- return this;
- }
-
- CarVolumeGroupSettingsBuilder setMuteForUser(@UserIdInt int userId, boolean mute) {
- mStoreMuteStates.put(userId, mute);
- return this;
- }
-
- CarVolumeGroupSettingsBuilder setIsPersistVolumeGroupEnabled(boolean persistMute) {
- mPersistMute = persistMute;
- return this;
- }
-
- CarAudioSettings build() {
- CarAudioSettings settingsMock = Mockito.mock(CarAudioSettings.class);
- for (int storeIndex = 0; storeIndex < mStoredGainIndexes.size(); storeIndex++) {
- int gainUserId = mStoredGainIndexes.keyAt(storeIndex);
- when(settingsMock
- .getStoredVolumeGainIndexForUser(gainUserId, mZoneId,
- mGroupId)).thenReturn(mStoredGainIndexes.get(gainUserId, DEFAULT_GAIN));
- }
- for (int muteIndex = 0; muteIndex < mStoreMuteStates.size(); muteIndex++) {
- int muteUserId = mStoreMuteStates.keyAt(muteIndex);
- when(settingsMock.getVolumeGroupMuteForUser(muteUserId, mZoneId, mGroupId))
- .thenReturn(mStoreMuteStates.get(muteUserId, false));
- when(settingsMock.isPersistVolumeGroupMuteEnabled(muteUserId))
- .thenReturn(mPersistMute);
- }
- return settingsMock;
- }
- }
-}
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioContextTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioContextTest.java
index be2e6dd..88ac26c 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioContextTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioContextTest.java
@@ -27,8 +27,10 @@
import static com.android.car.audio.CarAudioContext.INVALID;
import static com.android.car.audio.CarAudioContext.MUSIC;
import static com.android.car.audio.CarAudioContext.NAVIGATION;
+import static com.android.car.audio.CarAudioContext.isCriticalAudioContext;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static org.testng.Assert.assertThrows;
@@ -125,4 +127,38 @@
assertThat(result).containsExactly(MUSIC, NAVIGATION, EMERGENCY);
}
+
+ @Test
+ public void isCriticalAudioContext_forNonCritialContexts_returnsFalse() {
+ assertWithMessage("Non-critical context INVALID")
+ .that(isCriticalAudioContext(CarAudioContext.INVALID)).isFalse();
+ assertWithMessage("Non-critical context MUSIC")
+ .that(isCriticalAudioContext(CarAudioContext.MUSIC)).isFalse();
+ assertWithMessage("Non-critical context NAVIGATION")
+ .that(isCriticalAudioContext(CarAudioContext.NAVIGATION)).isFalse();
+ assertWithMessage("Non-critical context VOICE_COMMAND")
+ .that(isCriticalAudioContext(CarAudioContext.VOICE_COMMAND)).isFalse();
+ assertWithMessage("Non-critical context CALL_RING")
+ .that(isCriticalAudioContext(CarAudioContext.CALL_RING)).isFalse();
+ assertWithMessage("Non-critical context CALL")
+ .that(isCriticalAudioContext(CarAudioContext.CALL)).isFalse();
+ assertWithMessage("Non-critical context ALARM")
+ .that(isCriticalAudioContext(CarAudioContext.ALARM)).isFalse();
+ assertWithMessage("Non-critical context NOTIFICATION")
+ .that(isCriticalAudioContext(CarAudioContext.NOTIFICATION)).isFalse();
+ assertWithMessage("Non-critical context SYSTEM_SOUND")
+ .that(isCriticalAudioContext(CarAudioContext.SYSTEM_SOUND)).isFalse();
+ assertWithMessage("Non-critical context VEHICLE_STATUS")
+ .that(isCriticalAudioContext(CarAudioContext.VEHICLE_STATUS)).isFalse();
+ assertWithMessage("Non-critical context ANNOUNCEMENT")
+ .that(isCriticalAudioContext(CarAudioContext.ANNOUNCEMENT)).isFalse();
+ }
+
+ @Test
+ public void isCriticalAudioContext_forCriticalContexts_returnsTrue() {
+ assertWithMessage("Critical context EMERGENCY")
+ .that(isCriticalAudioContext(CarAudioContext.EMERGENCY)).isTrue();
+ assertWithMessage("Critical context SAFETY")
+ .that(isCriticalAudioContext(CarAudioContext.SAFETY)).isTrue();
+ }
}
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioPowerListenerTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioPowerListenerTest.java
index 1002897..130bee3 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioPowerListenerTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioPowerListenerTest.java
@@ -67,7 +67,7 @@
listener.startListeningForPolicyChanges();
- verify(mMockCarAudioService).enableAudio();
+ verify(mMockCarAudioService).setAudioEnabled(true);
}
@Test
@@ -94,7 +94,7 @@
listener.startListeningForPolicyChanges();
- verify(mMockCarAudioService).enableAudio();
+ verify(mMockCarAudioService).setAudioEnabled(true);
}
@Test
@@ -105,7 +105,7 @@
listener.startListeningForPolicyChanges();
- verify(mMockCarAudioService).enableAudio();
+ verify(mMockCarAudioService).setAudioEnabled(true);
}
@Test
@@ -116,53 +116,53 @@
listener.startListeningForPolicyChanges();
- verify(mMockCarAudioService).disableAudio();
+ verify(mMockCarAudioService).setAudioEnabled(false);
}
@Test
public void onPolicyChange_withPowerSwitchingToEnabled_enablesAudio() throws Exception {
withAudioInitiallyDisabled();
ICarPowerPolicyListener changeListener = registerAndGetChangeListener();
- verify(mMockCarAudioService, never()).enableAudio();
+ verify(mMockCarAudioService, never()).setAudioEnabled(true);
changeListener.onPolicyChanged(EMPTY_POLICY, ENABLED_POLICY);
- verify(mMockCarAudioService).enableAudio();
+ verify(mMockCarAudioService).setAudioEnabled(true);
}
@Test
public void onPolicyChange_withPowerRemainingEnabled_doesNothing() throws Exception {
withAudioInitiallyEnabled();
ICarPowerPolicyListener changeListener = registerAndGetChangeListener();
- verify(mMockCarAudioService).enableAudio();
+ verify(mMockCarAudioService).setAudioEnabled(true);
changeListener.onPolicyChanged(EMPTY_POLICY, ENABLED_POLICY);
- verify(mMockCarAudioService).enableAudio();
- verify(mMockCarAudioService, never()).disableAudio();
+ verify(mMockCarAudioService).setAudioEnabled(true);
+ verify(mMockCarAudioService, never()).setAudioEnabled(false);
}
@Test
public void onPolicyChange_withPowerSwitchingToDisabled_disablesAudio() throws Exception {
withAudioInitiallyEnabled();
ICarPowerPolicyListener changeListener = registerAndGetChangeListener();
- verify(mMockCarAudioService, never()).disableAudio();
+ verify(mMockCarAudioService, never()).setAudioEnabled(false);
changeListener.onPolicyChanged(EMPTY_POLICY, DISABLED_POLICY);
- verify(mMockCarAudioService).disableAudio();
+ verify(mMockCarAudioService).setAudioEnabled(false);
}
@Test
public void onPolicyChange_withPowerStayingDisabled_doesNothing() throws Exception {
withAudioInitiallyDisabled();
ICarPowerPolicyListener changeListener = registerAndGetChangeListener();
- verify(mMockCarAudioService).disableAudio();
+ verify(mMockCarAudioService).setAudioEnabled(false);
changeListener.onPolicyChanged(EMPTY_POLICY, DISABLED_POLICY);
- verify(mMockCarAudioService).disableAudio();
- verify(mMockCarAudioService, never()).enableAudio();
+ verify(mMockCarAudioService).setAudioEnabled(false);
+ verify(mMockCarAudioService, never()).setAudioEnabled(true);
}
private void withAudioInitiallyEnabled() {
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CarVolumeGroupMutingTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CarVolumeGroupMutingTest.java
index 18bbbea..5c04921 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/CarVolumeGroupMutingTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CarVolumeGroupMutingTest.java
@@ -16,8 +16,10 @@
package com.android.car.audio;
+import static com.android.car.audio.CarAudioContext.EMERGENCY;
import static com.android.car.audio.CarAudioContext.MUSIC;
import static com.android.car.audio.CarAudioContext.NAVIGATION;
+import static com.android.car.audio.CarAudioContext.SAFETY;
import static com.android.car.audio.CarAudioContext.VOICE_COMMAND;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -48,8 +50,10 @@
private static final String PRIMARY_MEDIA_ADDRESS = "media";
private static final String PRIMARY_NAVIGATION_ADDRESS = "navigation";
private static final String PRIMARY_VOICE_ADDRESS = "voice";
- private static final String SECONDARY_ADDRESS = "media";
- private static final String TERTIARY_ADDRESS = "media";
+ private static final String SECONDARY_ADDRESS = "secondary";
+ private static final String TERTIARY_ADDRESS = "tertiary";
+ private static final String EMERGENCY_ADDRESS = "emergency";
+ private static final String SAFETY_ADDRESS = "safety";
private static final int PRIMARY_ZONE_ID = CarAudioManager.PRIMARY_AUDIO_ZONE;
private static final int SECONDARY_ZONE_ID = CarAudioManager.PRIMARY_AUDIO_ZONE + 1;
private static final int TERTIARY_ZONE_ID = CarAudioManager.PRIMARY_AUDIO_ZONE + 2;
@@ -70,21 +74,12 @@
@Before
public void setUp() {
- mMusicCarVolumeGroup = new VolumeGroupBuilder()
- .addDeviceAddressAndContexts(MUSIC, PRIMARY_MEDIA_ADDRESS)
- .build();
- mNavigationCarVolumeGroup = new VolumeGroupBuilder()
- .addDeviceAddressAndContexts(NAVIGATION, PRIMARY_NAVIGATION_ADDRESS)
- .build();
- mVoiceCarVolumeGroup = new VolumeGroupBuilder()
- .addDeviceAddressAndContexts(VOICE_COMMAND, PRIMARY_VOICE_ADDRESS)
- .build();
- mSecondaryZoneVolumeGroup = new VolumeGroupBuilder()
- .addDeviceAddressAndContexts(MUSIC, SECONDARY_ADDRESS)
- .build();
- mTertiaryZoneVolumeGroup = new VolumeGroupBuilder()
- .addDeviceAddressAndContexts(MUSIC, TERTIARY_ADDRESS)
- .build();
+ mMusicCarVolumeGroup = groupWithContextAndAddress(MUSIC, PRIMARY_MEDIA_ADDRESS);
+ mNavigationCarVolumeGroup = groupWithContextAndAddress(NAVIGATION,
+ PRIMARY_NAVIGATION_ADDRESS);
+ mVoiceCarVolumeGroup = groupWithContextAndAddress(VOICE_COMMAND, PRIMARY_VOICE_ADDRESS);
+ mSecondaryZoneVolumeGroup = groupWithContextAndAddress(MUSIC, SECONDARY_ADDRESS);
+ mTertiaryZoneVolumeGroup = groupWithContextAndAddress(MUSIC, TERTIARY_ADDRESS);
mPrimaryAudioZone =
new TestCarAudioZoneBuilder("Primary Zone", PRIMARY_ZONE_ID)
@@ -93,20 +88,14 @@
.addVolumeGroup(mVoiceCarVolumeGroup)
.build();
- mSingleDevicePrimaryZone =
- new TestCarAudioZoneBuilder("Primary Zone", PRIMARY_ZONE_ID)
- .addVolumeGroup(mMusicCarVolumeGroup)
- .build();
+ mSingleDevicePrimaryZone = createAudioZone(mMusicCarVolumeGroup, "Primary Zone",
+ PRIMARY_ZONE_ID);
- mSingleDeviceSecondaryZone =
- new TestCarAudioZoneBuilder("Secondary Zone", SECONDARY_ZONE_ID)
- .addVolumeGroup(mSecondaryZoneVolumeGroup)
- .build();
+ mSingleDeviceSecondaryZone = createAudioZone(mSecondaryZoneVolumeGroup, "Secondary Zone",
+ SECONDARY_ZONE_ID);
- mSingleDeviceTertiaryZone =
- new TestCarAudioZoneBuilder("Tertiary Zone", TERTIARY_ZONE_ID)
- .addVolumeGroup(mTertiaryZoneVolumeGroup)
- .build();
+ mSingleDeviceTertiaryZone = createAudioZone(mTertiaryZoneVolumeGroup, "Tertiary Zone",
+ TERTIARY_ZONE_ID);
when(mMockAudioControlWrapper
.supportsFeature(AudioControlWrapper.AUDIOCONTROL_FEATURE_AUDIO_GROUP_MUTING))
@@ -234,7 +223,7 @@
carGroupMuting.carMuteChanged();
- List<MutingInfo> mutingInfo = captureMutingInfoList();
+ List<MutingInfo> mutingInfo = captureMutingInfoList();
MutingInfo info = mutingInfo.get(mutingInfo.size() - 1);
assertWithMessage("Device addresses to un-mute")
.that(info.deviceAddressesToUnmute).asList().containsExactly(
@@ -331,6 +320,49 @@
}
}
+ @Test
+ public void setRestrictMuting_isMutingRestrictedTrue_mutesNonCriticalVolumeGroups() {
+ setUpCarVolumeGroupIsMuted(mSecondaryZoneVolumeGroup, false);
+ setUpCarVolumeGroupIsMuted(mMusicCarVolumeGroup, false);
+ setUpCarVolumeGroupIsMuted(mTertiaryZoneVolumeGroup, false);
+ CarVolumeGroupMuting carGroupMuting =
+ new CarVolumeGroupMuting(getAudioZones(mSingleDevicePrimaryZone,
+ mSingleDeviceSecondaryZone, mSingleDeviceTertiaryZone),
+ mMockAudioControlWrapper);
+
+ carGroupMuting.setRestrictMuting(true);
+
+ for (MutingInfo info : captureMutingInfoList()) {
+ assertWithMessage("Devices addresses to mute for zone %s", info.zoneId)
+ .that(info.deviceAddressesToMute).asList().hasSize(1);
+ }
+ }
+
+ @Test
+ public void setRestrictMuting_isMutingRestrictedTrue_leavesCriticalGroupsAsIs() {
+ setUpCarVolumeGroupIsMuted(mMusicCarVolumeGroup, false);
+ setUpCarVolumeGroupHasCriticalAudioContexts(mMusicCarVolumeGroup);
+ setUpCarVolumeGroupIsMuted(mSecondaryZoneVolumeGroup, true);
+ setUpCarVolumeGroupHasCriticalAudioContexts(mSecondaryZoneVolumeGroup);
+ CarVolumeGroupMuting carGroupMuting = new CarVolumeGroupMuting(
+ getAudioZones(mSingleDevicePrimaryZone, mSingleDeviceSecondaryZone),
+ mMockAudioControlWrapper);
+
+ carGroupMuting.setRestrictMuting(true);
+
+ for (MutingInfo info : captureMutingInfoList()) {
+ if (info.zoneId == PRIMARY_ZONE_ID) {
+ assertWithMessage("Devices addresses to unmute for zone %s", info.zoneId)
+ .that(info.deviceAddressesToUnmute).asList().containsExactly(
+ PRIMARY_MEDIA_ADDRESS);
+
+ } else if (info.zoneId == SECONDARY_ZONE_ID) {
+ assertWithMessage("Devices addresses to mute for zone %s", info.zoneId)
+ .that(info.deviceAddressesToMute).asList().containsExactly(
+ SECONDARY_ADDRESS);
+ }
+ }
+ }
@Test
public void generateMutingInfoFromZone_withNoGroupsMuted_returnsEmptyMutedList() {
@@ -338,7 +370,8 @@
setUpCarVolumeGroupIsMuted(mNavigationCarVolumeGroup, false);
setUpCarVolumeGroupIsMuted(mVoiceCarVolumeGroup, false);
- MutingInfo info = CarVolumeGroupMuting.generateMutingInfoFromZone(mPrimaryAudioZone);
+ MutingInfo info = CarVolumeGroupMuting.generateMutingInfoFromZone(mPrimaryAudioZone,
+ /* isMutingRestricted= */ false);
assertWithMessage("Device addresses to mute")
.that(info.deviceAddressesToMute).asList().isEmpty();
@@ -350,7 +383,8 @@
setUpCarVolumeGroupIsMuted(mNavigationCarVolumeGroup, false);
setUpCarVolumeGroupIsMuted(mVoiceCarVolumeGroup, false);
- MutingInfo info = CarVolumeGroupMuting.generateMutingInfoFromZone(mPrimaryAudioZone);
+ MutingInfo info = CarVolumeGroupMuting.generateMutingInfoFromZone(mPrimaryAudioZone,
+ /* isMutingRestricted= */ false);
assertWithMessage("Device addresses to mute")
.that(info.deviceAddressesToMute).asList().containsExactly(PRIMARY_MEDIA_ADDRESS);
@@ -362,7 +396,8 @@
setUpCarVolumeGroupIsMuted(mNavigationCarVolumeGroup, true);
setUpCarVolumeGroupIsMuted(mVoiceCarVolumeGroup, true);
- MutingInfo info = CarVolumeGroupMuting.generateMutingInfoFromZone(mPrimaryAudioZone);
+ MutingInfo info = CarVolumeGroupMuting.generateMutingInfoFromZone(mPrimaryAudioZone,
+ /* isMutingRestricted= */ false);
assertWithMessage("Device addresses to mute")
.that(info.deviceAddressesToMute).asList().containsExactly(PRIMARY_MEDIA_ADDRESS,
@@ -371,17 +406,16 @@
@Test
public void generateMutingInfoFromZone_withMutedMultiDeviceGroup_returnsAllDevicesMuted() {
- CarAudioZone primaryZone =
- new TestCarAudioZoneBuilder("Primary Zone", PRIMARY_ZONE_ID)
- .addVolumeGroup(new VolumeGroupBuilder()
- .addDeviceAddressAndContexts(MUSIC, PRIMARY_MEDIA_ADDRESS)
- .addDeviceAddressAndContexts(VOICE_COMMAND, PRIMARY_VOICE_ADDRESS)
- .addDeviceAddressAndContexts(NAVIGATION, PRIMARY_NAVIGATION_ADDRESS)
- .setIsMuted(true)
- .build())
- .build();
+ CarAudioZone primaryZone = createAudioZone(
+ new VolumeGroupBuilder()
+ .addDeviceAddressAndContexts(MUSIC, PRIMARY_MEDIA_ADDRESS)
+ .addDeviceAddressAndContexts(VOICE_COMMAND, PRIMARY_VOICE_ADDRESS)
+ .addDeviceAddressAndContexts(NAVIGATION, PRIMARY_NAVIGATION_ADDRESS)
+ .setIsMuted(true)
+ .build(), "Primary Zone", PRIMARY_ZONE_ID);
- MutingInfo info = CarVolumeGroupMuting.generateMutingInfoFromZone(primaryZone);
+ MutingInfo info = CarVolumeGroupMuting.generateMutingInfoFromZone(primaryZone,
+ /* isMutingRestricted= */ false);
assertWithMessage("Device addresses to mute")
.that(info.deviceAddressesToMute).asList().containsExactly(PRIMARY_MEDIA_ADDRESS,
@@ -390,35 +424,95 @@
@Test
public void generateMutingInfoFromZone_withUnMutedMultiDeviceGroup_returnsAllDevicesUnMuted() {
- CarAudioZone primaryZone =
- new TestCarAudioZoneBuilder("Primary Zone", PRIMARY_ZONE_ID)
- .addVolumeGroup(new VolumeGroupBuilder()
- .addDeviceAddressAndContexts(MUSIC, PRIMARY_MEDIA_ADDRESS)
- .addDeviceAddressAndContexts(VOICE_COMMAND, PRIMARY_VOICE_ADDRESS)
- .addDeviceAddressAndContexts(NAVIGATION, PRIMARY_NAVIGATION_ADDRESS)
- .build())
- .build();
+ CarAudioZone primaryZone = createAudioZone(
+ new VolumeGroupBuilder()
+ .addDeviceAddressAndContexts(MUSIC, PRIMARY_MEDIA_ADDRESS)
+ .addDeviceAddressAndContexts(VOICE_COMMAND, PRIMARY_VOICE_ADDRESS)
+ .addDeviceAddressAndContexts(NAVIGATION, PRIMARY_NAVIGATION_ADDRESS)
+ .build(), "Primary Zone", PRIMARY_ZONE_ID);
- MutingInfo info = CarVolumeGroupMuting.generateMutingInfoFromZone(primaryZone);
+ MutingInfo info = CarVolumeGroupMuting.generateMutingInfoFromZone(primaryZone,
+ /* isMutingRestricted= */ false);
assertWithMessage("Device addresses to un-mute")
.that(info.deviceAddressesToUnmute).asList().containsExactly(PRIMARY_MEDIA_ADDRESS,
PRIMARY_NAVIGATION_ADDRESS, PRIMARY_VOICE_ADDRESS);
}
+ @Test
+ public void generateMutingInfoFromZone_mutingRestricted_mutesAllNonCriticalDevices() {
+ CarAudioZone primaryZone = createAudioZone(
+ new VolumeGroupBuilder()
+ .addDeviceAddressAndContexts(MUSIC, PRIMARY_MEDIA_ADDRESS)
+ .addDeviceAddressAndContexts(VOICE_COMMAND, PRIMARY_VOICE_ADDRESS)
+ .addDeviceAddressAndContexts(NAVIGATION, PRIMARY_NAVIGATION_ADDRESS)
+ .build(), "Primary Zone", PRIMARY_ZONE_ID);
+
+ MutingInfo info = CarVolumeGroupMuting.generateMutingInfoFromZone(primaryZone,
+ /* isMutingRestricted= */ true);
+
+ assertWithMessage("Device addresses to un-mute")
+ .that(info.deviceAddressesToMute).asList().containsExactly(PRIMARY_MEDIA_ADDRESS,
+ PRIMARY_NAVIGATION_ADDRESS, PRIMARY_VOICE_ADDRESS);
+ }
+
+ @Test
+ public void generateMutingInfoFromZone_mutingRestricted_setsAllCriticalGroupsToTheirState() {
+ CarAudioZone primaryZone =
+ new TestCarAudioZoneBuilder("Primary Zone", PRIMARY_ZONE_ID)
+ .addVolumeGroup(new VolumeGroupBuilder()
+ .addDeviceAddressAndContexts(EMERGENCY, EMERGENCY_ADDRESS)
+ .addDeviceAddressAndContexts(VOICE_COMMAND, PRIMARY_VOICE_ADDRESS)
+ .build())
+ .addVolumeGroup(new VolumeGroupBuilder()
+ .addDeviceAddressAndContexts(SAFETY, SAFETY_ADDRESS)
+ .addDeviceAddressAndContexts(NAVIGATION, PRIMARY_NAVIGATION_ADDRESS)
+ .setIsMuted(true)
+ .build()
+ )
+ .build();
+ setUpCarVolumeGroupHasCriticalAudioContexts(primaryZone.getVolumeGroups()[0]);
+ setUpCarVolumeGroupHasCriticalAudioContexts(primaryZone.getVolumeGroups()[1]);
+
+ MutingInfo info = CarVolumeGroupMuting.generateMutingInfoFromZone(primaryZone,
+ /* isMutingRestricted= */ true);
+
+ assertWithMessage("Device addresses to mute")
+ .that(info.deviceAddressesToMute).asList()
+ .containsExactly(SAFETY_ADDRESS, PRIMARY_NAVIGATION_ADDRESS);
+ assertWithMessage("Device addresses to un-mute")
+ .that(info.deviceAddressesToUnmute).asList()
+ .containsExactly(EMERGENCY_ADDRESS, PRIMARY_VOICE_ADDRESS);
+ }
+
+
+ private CarAudioZone createAudioZone(CarVolumeGroup volumeGroup, String name, int zoneId) {
+ return new TestCarAudioZoneBuilder(name, zoneId)
+ .addVolumeGroup(volumeGroup)
+ .build();
+ }
+
+ private CarVolumeGroup groupWithContextAndAddress(int context, String address) {
+ return new VolumeGroupBuilder().addDeviceAddressAndContexts(context, address).build();
+ }
+
private List<MutingInfo> captureMutingInfoList() {
ArgumentCaptor<List<MutingInfo>> captor = ArgumentCaptor.forClass(List.class);
verify(mMockAudioControlWrapper).onDevicesToMuteChange(captor.capture());
return captor.getValue();
}
- private void setUpCarVolumeGroupIsMuted(CarVolumeGroup musicCarVolumeGroup, boolean muted) {
- when(musicCarVolumeGroup.isMuted()).thenReturn(muted);
+ private void setUpCarVolumeGroupIsMuted(CarVolumeGroup carVolumeGroup, boolean muted) {
+ when(carVolumeGroup.isMuted()).thenReturn(muted);
}
- private SparseArray<CarAudioZone> getAudioZones(CarAudioZone ...zones) {
+ private void setUpCarVolumeGroupHasCriticalAudioContexts(CarVolumeGroup carVolumeGroup) {
+ when(carVolumeGroup.hasCriticalAudioContexts()).thenReturn(true);
+ }
+
+ private SparseArray<CarAudioZone> getAudioZones(CarAudioZone... zones) {
SparseArray<CarAudioZone> audioZones = new SparseArray<>();
- for (CarAudioZone zone: zones) {
+ for (CarAudioZone zone : zones) {
audioZones.put(zone.getId(), zone);
}
return audioZones;
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CarVolumeGroupUnitTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CarVolumeGroupUnitTest.java
new file mode 100644
index 0000000..9e38583
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CarVolumeGroupUnitTest.java
@@ -0,0 +1,635 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+package com.android.car.audio;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.expectThrows;
+
+import android.annotation.UserIdInt;
+import android.os.UserHandle;
+import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.List;
+
+@RunWith(MockitoJUnitRunner.class)
+public class CarVolumeGroupUnitTest {
+ private static final int ZONE_ID = 0;
+ private static final int GROUP_ID = 0;
+ private static final int STEP_VALUE = 2;
+ private static final int MIN_GAIN = 3;
+ private static final int MAX_GAIN = 10;
+ private static final int DEFAULT_GAIN = 5;
+ private static final int DEFAULT_GAIN_INDEX = (DEFAULT_GAIN - MIN_GAIN) / STEP_VALUE;
+ private static final int MIN_GAIN_INDEX = 0;
+ private static final int MAX_GAIN_INDEX = (MAX_GAIN - MIN_GAIN) / STEP_VALUE;
+ private static final int TEST_GAIN_INDEX = 2;
+ private static final int TEST_USER_10 = 10;
+ private static final int TEST_USER_11 = 11;
+ private static final String MEDIA_DEVICE_ADDRESS = "music";
+ private static final String NAVIGATION_DEVICE_ADDRESS = "navigation";
+ private static final String OTHER_ADDRESS = "other_address";
+
+ private CarAudioDeviceInfo mMediaDeviceInfo;
+ private CarAudioDeviceInfo mNavigationDeviceInfo;
+
+ @Mock
+ CarAudioSettings mSettingsMock;
+
+ @Before
+ public void setUp() {
+ mMediaDeviceInfo = new InfoBuilder(MEDIA_DEVICE_ADDRESS).build();
+ mNavigationDeviceInfo = new InfoBuilder(NAVIGATION_DEVICE_ADDRESS).build();
+ }
+
+ @Test
+ public void setDeviceInfoForContext_associatesDeviceAddresses() {
+ CarVolumeGroup.Builder builder = getBuilder();
+
+ builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+ builder.setDeviceInfoForContext(CarAudioContext.NAVIGATION, mNavigationDeviceInfo);
+ CarVolumeGroup carVolumeGroup = builder.build();
+
+ assertThat(carVolumeGroup.getAddresses()).containsExactly(MEDIA_DEVICE_ADDRESS,
+ NAVIGATION_DEVICE_ADDRESS);
+ }
+
+ @Test
+ public void setDeviceInfoForContext_associatesContexts() {
+ CarVolumeGroup.Builder builder = getBuilder();
+
+ builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+ builder.setDeviceInfoForContext(CarAudioContext.NAVIGATION, mNavigationDeviceInfo);
+ CarVolumeGroup carVolumeGroup = builder.build();
+
+ assertThat(carVolumeGroup.getContexts()).asList().containsExactly(CarAudioContext.MUSIC,
+ CarAudioContext.NAVIGATION);
+ }
+
+ @Test
+ public void setDeviceInfoForContext_withDifferentStepSize_throws() {
+ CarVolumeGroup.Builder builder = getBuilder();
+ builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+ CarAudioDeviceInfo differentStepValueDevice = new InfoBuilder(NAVIGATION_DEVICE_ADDRESS)
+ .setStepValue(mMediaDeviceInfo.getStepValue() + 1).build();
+
+ IllegalArgumentException thrown = expectThrows(IllegalArgumentException.class,
+ () -> builder.setDeviceInfoForContext(CarAudioContext.NAVIGATION,
+ differentStepValueDevice));
+
+ assertThat(thrown).hasMessageThat()
+ .contains("Gain controls within one group must have same step value");
+ }
+
+ @Test
+ public void setDeviceInfoForContext_withSameContext_throws() {
+ CarVolumeGroup.Builder builder = getBuilder();
+ builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+
+ IllegalArgumentException thrown = expectThrows(IllegalArgumentException.class,
+ () -> builder.setDeviceInfoForContext(CarAudioContext.MUSIC,
+ mNavigationDeviceInfo));
+
+ assertThat(thrown).hasMessageThat()
+ .contains("has already been set to");
+ }
+
+ @Test
+ public void setDeviceInfoForContext_withFirstCall_setsMinGain() {
+ CarVolumeGroup.Builder builder = getBuilder();
+
+ builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+
+ assertThat(builder.mMinGain).isEqualTo(mMediaDeviceInfo.getMinGain());
+ }
+
+ @Test
+ public void setDeviceInfoForContext_withFirstCall_setsMaxGain() {
+ CarVolumeGroup.Builder builder = getBuilder();
+
+ builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+
+ assertThat(builder.mMaxGain).isEqualTo(mMediaDeviceInfo.getMaxGain());
+ }
+
+ @Test
+ public void setDeviceInfoForContext_withFirstCall_setsDefaultGain() {
+ CarVolumeGroup.Builder builder = getBuilder();
+
+ builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+
+ assertThat(builder.mDefaultGain).isEqualTo(mMediaDeviceInfo.getDefaultGain());
+ }
+
+ @Test
+ public void setDeviceInfoForContext_SecondCallWithSmallerMinGain_updatesMinGain() {
+ CarVolumeGroup.Builder builder = getBuilder();
+ builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+ CarAudioDeviceInfo secondInfo = new InfoBuilder(NAVIGATION_DEVICE_ADDRESS)
+ .setMinGain(mMediaDeviceInfo.getMinGain() - 1).build();
+
+ builder.setDeviceInfoForContext(CarAudioContext.NAVIGATION, secondInfo);
+
+ assertThat(builder.mMinGain).isEqualTo(secondInfo.getMinGain());
+ }
+
+ @Test
+ public void setDeviceInfoForContext_SecondCallWithLargerMinGain_keepsFirstMinGain() {
+ CarVolumeGroup.Builder builder = getBuilder();
+ builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+ CarAudioDeviceInfo secondInfo = new InfoBuilder(NAVIGATION_DEVICE_ADDRESS)
+ .setMinGain(mMediaDeviceInfo.getMinGain() + 1).build();
+
+ builder.setDeviceInfoForContext(CarAudioContext.NAVIGATION, secondInfo);
+
+ assertThat(builder.mMinGain).isEqualTo(mMediaDeviceInfo.getMinGain());
+ }
+
+ @Test
+ public void setDeviceInfoForContext_SecondCallWithLargerMaxGain_updatesMaxGain() {
+ CarVolumeGroup.Builder builder = getBuilder();
+ builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+ CarAudioDeviceInfo secondInfo = new InfoBuilder(NAVIGATION_DEVICE_ADDRESS)
+ .setMaxGain(mMediaDeviceInfo.getMaxGain() + 1).build();
+
+ builder.setDeviceInfoForContext(CarAudioContext.NAVIGATION, secondInfo);
+
+ assertThat(builder.mMaxGain).isEqualTo(secondInfo.getMaxGain());
+ }
+
+ @Test
+ public void setDeviceInfoForContext_SecondCallWithSmallerMaxGain_keepsFirstMaxGain() {
+ CarVolumeGroup.Builder builder = getBuilder();
+ builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+ CarAudioDeviceInfo secondInfo = new InfoBuilder(NAVIGATION_DEVICE_ADDRESS)
+ .setMaxGain(mMediaDeviceInfo.getMaxGain() - 1).build();
+
+ builder.setDeviceInfoForContext(CarAudioContext.NAVIGATION, secondInfo);
+
+ assertThat(builder.mMaxGain).isEqualTo(mMediaDeviceInfo.getMaxGain());
+ }
+
+ @Test
+ public void setDeviceInfoForContext_SecondCallWithLargerDefaultGain_updatesDefaultGain() {
+ CarVolumeGroup.Builder builder = getBuilder();
+ builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+ CarAudioDeviceInfo secondInfo = new InfoBuilder(NAVIGATION_DEVICE_ADDRESS)
+ .setDefaultGain(mMediaDeviceInfo.getDefaultGain() + 1).build();
+
+ builder.setDeviceInfoForContext(CarAudioContext.NAVIGATION, secondInfo);
+
+ assertThat(builder.mDefaultGain).isEqualTo(secondInfo.getDefaultGain());
+ }
+
+ @Test
+ public void setDeviceInfoForContext_SecondCallWithSmallerDefaultGain_keepsFirstDefaultGain() {
+ CarVolumeGroup.Builder builder = getBuilder();
+ builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+ CarAudioDeviceInfo secondInfo = new InfoBuilder(NAVIGATION_DEVICE_ADDRESS)
+ .setDefaultGain(mMediaDeviceInfo.getDefaultGain() - 1).build();
+
+ builder.setDeviceInfoForContext(CarAudioContext.NAVIGATION, secondInfo);
+
+ assertThat(builder.mDefaultGain).isEqualTo(mMediaDeviceInfo.getDefaultGain());
+ }
+
+ @Test
+ public void builderBuild_withNoCallToSetDeviceInfoForContext_throws() {
+ CarVolumeGroup.Builder builder = getBuilder();
+
+ Exception e = expectThrows(IllegalArgumentException.class, builder::build);
+
+ assertThat(e).hasMessageThat().isEqualTo(
+ "setDeviceInfoForContext has to be called at least once before building");
+ }
+
+ @Test
+ public void builderBuild_withNoStoredGain_usesDefaultGain() {
+ CarVolumeGroup.Builder builder = getBuilder().setDeviceInfoForContext(CarAudioContext.MUSIC,
+ mMediaDeviceInfo);
+ when(mSettingsMock.getStoredVolumeGainIndexForUser(UserHandle.USER_CURRENT, ZONE_ID,
+ GROUP_ID)).thenReturn(-1);
+
+
+ CarVolumeGroup carVolumeGroup = builder.build();
+
+ assertThat(carVolumeGroup.getCurrentGainIndex()).isEqualTo(DEFAULT_GAIN_INDEX);
+ }
+
+ @Test
+ public void builderBuild_withTooLargeStoredGain_usesDefaultGain() {
+ CarVolumeGroup.Builder builder = getBuilder().setDeviceInfoForContext(CarAudioContext.MUSIC,
+ mMediaDeviceInfo);
+ when(mSettingsMock.getStoredVolumeGainIndexForUser(UserHandle.USER_CURRENT, ZONE_ID,
+ GROUP_ID)).thenReturn(MAX_GAIN_INDEX + 1);
+
+ CarVolumeGroup carVolumeGroup = builder.build();
+
+ assertThat(carVolumeGroup.getCurrentGainIndex()).isEqualTo(DEFAULT_GAIN_INDEX);
+ }
+
+ @Test
+ public void builderBuild_withTooSmallStoredGain_usesDefaultGain() {
+ CarVolumeGroup.Builder builder = getBuilder().setDeviceInfoForContext(CarAudioContext.MUSIC,
+ mMediaDeviceInfo);
+ when(mSettingsMock.getStoredVolumeGainIndexForUser(UserHandle.USER_CURRENT, ZONE_ID,
+ GROUP_ID)).thenReturn(MIN_GAIN_INDEX - 1);
+
+ CarVolumeGroup carVolumeGroup = builder.build();
+
+ assertThat(carVolumeGroup.getCurrentGainIndex()).isEqualTo(DEFAULT_GAIN_INDEX);
+ }
+
+ @Test
+ public void builderBuild_withValidStoredGain_usesStoredGain() {
+ CarVolumeGroup.Builder builder = getBuilder().setDeviceInfoForContext(CarAudioContext.MUSIC,
+ mMediaDeviceInfo);
+ when(mSettingsMock.getStoredVolumeGainIndexForUser(UserHandle.USER_CURRENT, ZONE_ID,
+ GROUP_ID)).thenReturn(MAX_GAIN_INDEX - 1);
+
+ CarVolumeGroup carVolumeGroup = builder.build();
+
+ assertThat(carVolumeGroup.getCurrentGainIndex()).isEqualTo(MAX_GAIN_INDEX - 1);
+ }
+
+ @Test
+ public void getAddressForContext_withSupportedContext_returnsAddress() {
+ CarVolumeGroup carVolumeGroup = getCarVolumeGroupWithMusicBound();
+
+ assertThat(carVolumeGroup.getAddressForContext(CarAudioContext.MUSIC))
+ .isEqualTo(mMediaDeviceInfo.getAddress());
+ }
+
+ @Test
+ public void getAddressForContext_withUnsupportedContext_returnsNull() {
+ CarVolumeGroup carVolumeGroup = getCarVolumeGroupWithMusicBound();
+
+ assertThat(carVolumeGroup.getAddressForContext(CarAudioContext.NAVIGATION)).isNull();
+ }
+
+ @Test
+ public void isMuted_whenDefault_returnsFalse() {
+ CarVolumeGroup carVolumeGroup = getCarVolumeGroupWithMusicBound();
+
+ assertThat(carVolumeGroup.isMuted()).isFalse();
+ }
+
+ @Test
+ public void isMuted_afterMuting_returnsTrue() {
+ CarVolumeGroup carVolumeGroup = getCarVolumeGroupWithMusicBound();
+
+ carVolumeGroup.setMute(true);
+
+ assertThat(carVolumeGroup.isMuted()).isTrue();
+ }
+
+ @Test
+ public void isMuted_afterUnMuting_returnsFalse() {
+ CarVolumeGroup carVolumeGroup = getCarVolumeGroupWithMusicBound();
+
+ carVolumeGroup.setMute(false);
+
+ assertThat(carVolumeGroup.isMuted()).isFalse();
+ }
+
+ @Test
+ public void setMute_withMutedState_storesValueToSetting() {
+ CarAudioSettings settings = new SettingsBuilder(0, 0)
+ .setMuteForUser10(false)
+ .setIsPersistVolumeGroupEnabled(true)
+ .build();
+ CarVolumeGroup carVolumeGroup = getCarVolumeGroupWithNavigationBound(settings, true);
+ carVolumeGroup.loadVolumesSettingsForUser(TEST_USER_10);
+
+ carVolumeGroup.setMute(true);
+
+ verify(settings)
+ .storeVolumeGroupMuteForUser(TEST_USER_10, 0, 0, true);
+ }
+
+ @Test
+ public void setMute_withUnMutedState_storesValueToSetting() {
+ CarAudioSettings settings = new SettingsBuilder(0, 0)
+ .setMuteForUser10(false)
+ .setIsPersistVolumeGroupEnabled(true)
+ .build();
+ CarVolumeGroup carVolumeGroup = getCarVolumeGroupWithNavigationBound(settings, true);
+ carVolumeGroup.loadVolumesSettingsForUser(TEST_USER_10);
+
+ carVolumeGroup.setMute(false);
+
+ verify(settings)
+ .storeVolumeGroupMuteForUser(TEST_USER_10, 0, 0, false);
+ }
+
+ @Test
+ public void getContextsForAddress_returnsContextsBoundToThatAddress() {
+ CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
+
+ List<Integer> contextsList = carVolumeGroup.getContextsForAddress(MEDIA_DEVICE_ADDRESS);
+
+ assertThat(contextsList).containsExactly(CarAudioContext.MUSIC,
+ CarAudioContext.CALL, CarAudioContext.CALL_RING);
+ }
+
+ @Test
+ public void getContextsForAddress_returnsEmptyArrayIfAddressNotBound() {
+ CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
+
+ List<Integer> contextsList = carVolumeGroup.getContextsForAddress(OTHER_ADDRESS);
+
+ assertThat(contextsList).isEmpty();
+ }
+
+ @Test
+ public void getCarAudioDeviceInfoForAddress_returnsExpectedDevice() {
+ CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
+
+ CarAudioDeviceInfo actualDevice = carVolumeGroup.getCarAudioDeviceInfoForAddress(
+ MEDIA_DEVICE_ADDRESS);
+
+ assertThat(actualDevice).isEqualTo(mMediaDeviceInfo);
+ }
+
+ @Test
+ public void getCarAudioDeviceInfoForAddress_returnsNullIfAddressNotBound() {
+ CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
+
+ CarAudioDeviceInfo actualDevice = carVolumeGroup.getCarAudioDeviceInfoForAddress(
+ OTHER_ADDRESS);
+
+ assertThat(actualDevice).isNull();
+ }
+
+ @Test
+ public void setCurrentGainIndex_setsGainOnAllBoundDevices() {
+ CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
+
+ carVolumeGroup.setCurrentGainIndex(TEST_GAIN_INDEX);
+
+ verify(mMediaDeviceInfo).setCurrentGain(7);
+ verify(mNavigationDeviceInfo).setCurrentGain(7);
+ }
+
+ @Test
+ public void setCurrentGainIndex_updatesCurrentGainIndex() {
+ CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
+
+ carVolumeGroup.setCurrentGainIndex(TEST_GAIN_INDEX);
+
+ assertThat(carVolumeGroup.getCurrentGainIndex()).isEqualTo(TEST_GAIN_INDEX);
+ }
+
+ @Test
+ public void setCurrentGainIndex_checksNewGainIsAboveMin() {
+ CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
+
+ IllegalArgumentException thrown = expectThrows(IllegalArgumentException.class,
+ () -> carVolumeGroup.setCurrentGainIndex(MIN_GAIN_INDEX - 1));
+ assertThat(thrown).hasMessageThat()
+ .contains("Gain out of range (" + MIN_GAIN + ":" + MAX_GAIN + ")");
+ }
+
+ @Test
+ public void setCurrentGainIndex_checksNewGainIsBelowMax() {
+ CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
+
+ IllegalArgumentException thrown = expectThrows(IllegalArgumentException.class,
+ () -> carVolumeGroup.setCurrentGainIndex(MAX_GAIN_INDEX + 1));
+ assertThat(thrown).hasMessageThat()
+ .contains("Gain out of range (" + MIN_GAIN + ":" + MAX_GAIN + ")");
+ }
+
+ @Test
+ public void setCurrentGainIndex_setsCurrentGainIndexForUser() {
+ CarAudioSettings settings = new SettingsBuilder(0, 0)
+ .setGainIndexForUser(TEST_USER_11)
+ .build();
+ CarVolumeGroup carVolumeGroup = getCarVolumeGroupWithNavigationBound(settings, false);
+ carVolumeGroup.loadVolumesSettingsForUser(TEST_USER_11);
+
+ carVolumeGroup.setCurrentGainIndex(MIN_GAIN);
+
+ verify(settings).storeVolumeGainIndexForUser(TEST_USER_11, 0, 0, MIN_GAIN);
+ }
+
+ @Test
+ public void setCurrentGainIndex_setsCurrentGainIndexForDefaultUser() {
+ CarAudioSettings settings = new SettingsBuilder(0, 0)
+ .setGainIndexForUser(UserHandle.USER_CURRENT)
+ .build();
+ CarVolumeGroup carVolumeGroup = getCarVolumeGroupWithNavigationBound(settings, false);
+
+ carVolumeGroup.setCurrentGainIndex(MIN_GAIN);
+
+ verify(settings)
+ .storeVolumeGainIndexForUser(UserHandle.USER_CURRENT, 0, 0, MIN_GAIN);
+ }
+
+ @Test
+ public void loadVolumesSettingsForUser_withMutedState_loadsMuteStateForUser() {
+ CarVolumeGroup carVolumeGroup = getVolumeGroupWithMuteAndNavBound(true, true, true);
+
+ carVolumeGroup.loadVolumesSettingsForUser(TEST_USER_10);
+
+ assertThat(carVolumeGroup.isMuted()).isTrue();
+ }
+
+ @Test
+ public void loadVolumesSettingsForUser_withDisabledUseVolumeGroupMute_doesNotLoadMute() {
+ CarVolumeGroup carVolumeGroup = getVolumeGroupWithMuteAndNavBound(true, true, false);
+
+ carVolumeGroup.loadVolumesSettingsForUser(TEST_USER_10);
+
+ assertThat(carVolumeGroup.isMuted()).isFalse();
+ }
+
+ @Test
+ public void loadVolumesSettingsForUser_withUnMutedState_loadsMuteStateForUser() {
+ CarVolumeGroup carVolumeGroup = getVolumeGroupWithMuteAndNavBound(false, true, true);
+
+ carVolumeGroup.loadVolumesSettingsForUser(TEST_USER_10);
+
+ assertThat(carVolumeGroup.isMuted()).isFalse();
+ }
+
+ @Test
+ public void loadVolumesSettingsForUser_withMutedStateAndNoPersist_returnsDefaultMuteState() {
+ CarVolumeGroup carVolumeGroup = getVolumeGroupWithMuteAndNavBound(true, false, true);
+
+ carVolumeGroup.loadVolumesSettingsForUser(TEST_USER_10);
+
+ assertThat(carVolumeGroup.isMuted()).isFalse();
+ }
+
+ @Test
+ public void hasCriticalAudioContexts_withoutCriticalContexts_returnsFalse() {
+ CarVolumeGroup carVolumeGroup = getCarVolumeGroupWithMusicBound();
+
+ assertThat(carVolumeGroup.hasCriticalAudioContexts()).isFalse();
+ }
+
+ @Test
+ public void hasCriticalAudioContexts_withCriticalContexts_returnsTrue() {
+ CarVolumeGroup carVolumeGroup = getBuilder()
+ .setDeviceInfoForContext(CarAudioContext.EMERGENCY, mMediaDeviceInfo)
+ .build();
+
+ assertThat(carVolumeGroup.hasCriticalAudioContexts()).isTrue();
+ }
+
+ private CarVolumeGroup getCarVolumeGroupWithMusicBound() {
+ return getBuilder()
+ .setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo)
+ .build();
+ }
+
+ private CarVolumeGroup getCarVolumeGroupWithNavigationBound(CarAudioSettings settings,
+ boolean useCarVolumeGroupMute) {
+ return new CarVolumeGroup.Builder(0, 0, settings, useCarVolumeGroupMute)
+ .setDeviceInfoForContext(CarAudioContext.NAVIGATION, mNavigationDeviceInfo)
+ .build();
+ }
+
+ CarVolumeGroup getVolumeGroupWithMuteAndNavBound(boolean isMuted, boolean persistMute,
+ boolean useCarVolumeGroupMute) {
+ CarAudioSettings settings = new SettingsBuilder(0, 0)
+ .setMuteForUser10(isMuted)
+ .setIsPersistVolumeGroupEnabled(persistMute)
+ .build();
+ return getCarVolumeGroupWithNavigationBound(settings, useCarVolumeGroupMute);
+ }
+
+ private CarVolumeGroup testVolumeGroupSetup() {
+ CarVolumeGroup.Builder builder = getBuilder();
+
+ builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+ builder.setDeviceInfoForContext(CarAudioContext.CALL, mMediaDeviceInfo);
+ builder.setDeviceInfoForContext(CarAudioContext.CALL_RING, mMediaDeviceInfo);
+
+ builder.setDeviceInfoForContext(CarAudioContext.NAVIGATION, mNavigationDeviceInfo);
+ builder.setDeviceInfoForContext(CarAudioContext.ALARM, mNavigationDeviceInfo);
+ builder.setDeviceInfoForContext(CarAudioContext.NOTIFICATION, mNavigationDeviceInfo);
+
+ return builder.build();
+ }
+
+ CarVolumeGroup.Builder getBuilder() {
+ return new CarVolumeGroup.Builder(ZONE_ID, GROUP_ID, mSettingsMock, true);
+ }
+
+ private static final class SettingsBuilder {
+ private final SparseIntArray mStoredGainIndexes = new SparseIntArray();
+ private final SparseBooleanArray mStoreMuteStates = new SparseBooleanArray();
+ private final int mZoneId;
+ private final int mGroupId;
+
+ private boolean mPersistMute;
+
+ SettingsBuilder(int zoneId, int groupId) {
+ mZoneId = zoneId;
+ mGroupId = groupId;
+ }
+
+ SettingsBuilder setGainIndexForUser(@UserIdInt int userId) {
+ mStoredGainIndexes.put(userId, TEST_GAIN_INDEX);
+ return this;
+ }
+
+ SettingsBuilder setMuteForUser10(boolean mute) {
+ mStoreMuteStates.put(CarVolumeGroupUnitTest.TEST_USER_10, mute);
+ return this;
+ }
+
+ SettingsBuilder setIsPersistVolumeGroupEnabled(boolean persistMute) {
+ mPersistMute = persistMute;
+ return this;
+ }
+
+ CarAudioSettings build() {
+ CarAudioSettings settingsMock = Mockito.mock(CarAudioSettings.class);
+ for (int storeIndex = 0; storeIndex < mStoredGainIndexes.size(); storeIndex++) {
+ int gainUserId = mStoredGainIndexes.keyAt(storeIndex);
+ when(settingsMock
+ .getStoredVolumeGainIndexForUser(gainUserId, mZoneId,
+ mGroupId)).thenReturn(
+ mStoredGainIndexes.get(gainUserId, DEFAULT_GAIN));
+ }
+ for (int muteIndex = 0; muteIndex < mStoreMuteStates.size(); muteIndex++) {
+ int muteUserId = mStoreMuteStates.keyAt(muteIndex);
+ when(settingsMock.getVolumeGroupMuteForUser(muteUserId, mZoneId, mGroupId))
+ .thenReturn(mStoreMuteStates.get(muteUserId, false));
+ when(settingsMock.isPersistVolumeGroupMuteEnabled(muteUserId))
+ .thenReturn(mPersistMute);
+ }
+ return settingsMock;
+ }
+ }
+
+ private static final class InfoBuilder {
+ private final String mAddress;
+
+ private int mStepValue = STEP_VALUE;
+ private int mDefaultGain = DEFAULT_GAIN;
+ private int mMinGain = MIN_GAIN;
+ private int mMaxGain = MAX_GAIN;
+
+ InfoBuilder(String address) {
+ mAddress = address;
+ }
+
+ InfoBuilder setStepValue(int stepValue) {
+ mStepValue = stepValue;
+ return this;
+ }
+
+ InfoBuilder setDefaultGain(int defaultGain) {
+ mDefaultGain = defaultGain;
+ return this;
+ }
+
+ InfoBuilder setMinGain(int minGain) {
+ mMinGain = minGain;
+ return this;
+ }
+
+ InfoBuilder setMaxGain(int maxGain) {
+ mMaxGain = maxGain;
+ return this;
+ }
+
+ CarAudioDeviceInfo build() {
+ CarAudioDeviceInfo infoMock = Mockito.mock(CarAudioDeviceInfo.class);
+ when(infoMock.getStepValue()).thenReturn(mStepValue);
+ when(infoMock.getDefaultGain()).thenReturn(mDefaultGain);
+ when(infoMock.getMaxGain()).thenReturn(mMaxGain);
+ when(infoMock.getMinGain()).thenReturn(mMinGain);
+ when(infoMock.getAddress()).thenReturn(mAddress);
+ return infoMock;
+ }
+ }
+}