Merge "[RESTRICT AUTOMERGE] Remove obsolete logic from VirtualCamera" into sc-dev
diff --git a/car-lib/src/android/car/telemetry/IScriptExecutor.aidl b/car-lib/src/android/car/telemetry/IScriptExecutor.aidl
new file mode 100644
index 0000000..d7b967b
--- /dev/null
+++ b/car-lib/src/android/car/telemetry/IScriptExecutor.aidl
@@ -0,0 +1,43 @@
+/*
+ * 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.car.telemetry;
+
+import android.car.telemetry.IScriptExecutorListener;
+import android.os.Bundle;
+
+/**
+ * An internal API provided by isolated Script Executor process
+ * for executing Lua scripts in a sandboxed environment
+ *
+ * @hide
+ */
+interface IScriptExecutor {
+ /**
+ * Executes a specified function in provided Lua script with given input arguments.
+ *
+ * @param scriptBody complete body of Lua script that also contains the function to be invoked
+ * @param functionName the name of the function to execute
+ * @param publishedData input data provided by the source which the function handles
+ * @param savedState key-value pairs preserved from the previous invocation of the function
+ * @param listener callback for the sandboxed environent to report back script execution results, errors, and logs
+ */
+ void invokeScript(String scriptBody,
+ String functionName,
+ in byte[] publishedData,
+ in @nullable Bundle savedState,
+ in IScriptExecutorListener listener);
+}
diff --git a/car-lib/src/android/car/telemetry/IScriptExecutorListener.aidl b/car-lib/src/android/car/telemetry/IScriptExecutorListener.aidl
new file mode 100644
index 0000000..d751a61
--- /dev/null
+++ b/car-lib/src/android/car/telemetry/IScriptExecutorListener.aidl
@@ -0,0 +1,73 @@
+/*
+ * 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.car.telemetry;
+
+import android.os.Bundle;
+
+/**
+ * Listener for {@code IScriptExecutor#invokeScript}.
+ *
+ * An invocation of a script by Script Executor will result in a call of only one
+ * of the three methods below. If a script fully completes its objective, onScriptFinished
+ * is called. If a script's invocation completes normally, onSuccess is called.
+ * onError is called if any error happens before or during script execution and we
+ * should abandon this run of the script.
+ */
+interface IScriptExecutorListener {
+ /**
+ * Called by ScriptExecutor when the script declares itself as "finished".
+ *
+ * @param result final results of the script that will be uploaded.
+ */
+ void onScriptFinished(in byte[] result);
+
+ /**
+ * Called by ScriptExecutor when a function completes successfully and also provides
+ * optional state that the script wants CarTelemetryService to persist.
+ *
+ * @param stateToPersist key-value pairs to persist
+ */
+ void onSuccess(in @nullable Bundle stateToPersist);
+
+ /**
+ * Default error type.
+ */
+ const int ERROR_TYPE_UNSPECIFIED = 0;
+
+ /**
+ * Used when an error occurs in the ScriptExecutor code.
+ */
+ const int ERROR_TYPE_SCRIPT_EXECUTOR_ERROR = 1;
+
+ /**
+ * Used when an error occurs while executing the Lua script (such as
+ * errors returned by lua_pcall)
+ */
+ const int ERROR_TYPE_LUA_RUNTIME_ERROR = 2;
+
+
+ /**
+ * Called by ScriptExecutor to report errors that prevented the script
+ * from running or completing execution successfully.
+ *
+ * @param errorType type of the error message as defined in this aidl file.
+ * @param messsage the human-readable message containing information helpful for analysis or debugging.
+ * @param stackTrace the stack trace of the error if available.
+ */
+ void onError(int errorType, String message, @nullable String stackTrace);
+}
+
diff --git a/car-lib/src/android/car/watchdog/CarWatchdogManager.java b/car-lib/src/android/car/watchdog/CarWatchdogManager.java
index 588ac13..05cba98 100644
--- a/car-lib/src/android/car/watchdog/CarWatchdogManager.java
+++ b/car-lib/src/android/car/watchdog/CarWatchdogManager.java
@@ -694,6 +694,7 @@
* @param resourceOveruseFlag Flag to indicate the types of resource overuse configurations to
* return.
*
+ * @throws IllegalStateException if the system is in an invalid state.
* @hide
*/
@SystemApi
diff --git a/car-test-lib/src/android/car/test/mocks/JavaMockitoHelper.java b/car-test-lib/src/android/car/test/mocks/JavaMockitoHelper.java
index aaf12a7..9430723 100644
--- a/car-test-lib/src/android/car/test/mocks/JavaMockitoHelper.java
+++ b/car-test-lib/src/android/car/test/mocks/JavaMockitoHelper.java
@@ -89,8 +89,7 @@
* {@value #ASYNC_TIMEOUT_MS} ms.
*/
@NonNull
- public static <T> T getResult(@NonNull Future<T> future)
- throws InterruptedException, ExecutionException {
+ public static <T> T getResult(@NonNull Future<T> future) {
return getResult(future, ASYNC_TIMEOUT_MS);
}
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 11ed4dc..4e6c1c0 100644
--- a/cpp/telemetry/Android.bp
+++ b/cpp/telemetry/Android.bp
@@ -12,6 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
cc_defaults {
name: "cartelemetryd_defaults",
cflags: [
@@ -20,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: {
@@ -42,6 +47,34 @@
],
srcs: [
"src/CarTelemetryImpl.cpp",
+ "src/CarTelemetryInternalImpl.cpp",
+ "src/RingBuffer.cpp",
+ "src/TelemetryServer.cpp",
+ ],
+ // Allow dependents to use the header files.
+ export_include_dirs: [
+ "src",
+ ],
+}
+
+cc_test {
+ name: "cartelemetryd_impl_test",
+ defaults: [
+ "cartelemetryd_defaults",
+ ],
+ 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.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/Android.bp b/cpp/telemetry/sampleclient/Android.bp
index ec608c4..17fdee0 100644
--- a/cpp/telemetry/sampleclient/Android.bp
+++ b/cpp/telemetry/sampleclient/Android.bp
@@ -13,6 +13,10 @@
// limitations under the License.
// Sample client for ICarTelemetry service.
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
cc_binary {
name: "android.automotive.telemetryd-sampleclient",
srcs: [
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
new file mode 100644
index 0000000..0c6ff4d
--- /dev/null
+++ b/cpp/telemetry/src/BufferedCarData.h
@@ -0,0 +1,55 @@
+/*
+ * 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_BUFFEREDCARDATA_H_
+#define CPP_TELEMETRY_SRC_BUFFEREDCARDATA_H_
+
+#include <stdint.h>
+
+#include <tuple>
+#include <vector>
+
+namespace android {
+namespace automotive {
+namespace telemetry {
+
+// Internally stored `CarData` with some extras.
+struct BufferedCarData {
+ 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, 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.
+ int32_t contentSizeInBytes() const { return mContent.size(); }
+
+ const int32_t mId;
+ const std::vector<uint8_t> mContent;
+
+ // The uid of the logging client.
+ const uid_t mPublisherUid;
+};
+
+} // namespace telemetry
+} // namespace automotive
+} // namespace android
+
+#endif // CPP_TELEMETRY_SRC_BUFFEREDCARDATA_H_
diff --git a/cpp/telemetry/src/CarTelemetryImpl.cpp b/cpp/telemetry/src/CarTelemetryImpl.cpp
index f3187ec..34a4e60 100644
--- a/cpp/telemetry/src/CarTelemetryImpl.cpp
+++ b/cpp/telemetry/src/CarTelemetryImpl.cpp
@@ -16,24 +16,34 @@
#include "CarTelemetryImpl.h"
-#include <android-base/logging.h>
-#include <android/frameworks/automotive/telemetry/CarData.h>
+#include "BufferedCarData.h"
+
+#include <aidl/android/frameworks/automotive/telemetry/CarData.h>
+#include <android/binder_ibinder.h>
+
+#include <stdio.h>
+
+#include <memory>
namespace android {
namespace automotive {
namespace telemetry {
-using ::android::binder::Status;
-using ::android::frameworks::automotive::telemetry::CarData;
+using ::aidl::android::frameworks::automotive::telemetry::CarData;
-Status CarTelemetryImpl::write(const std::vector<CarData>& dataList) {
- LOG(INFO) << "write called";
- return Status::ok();
+CarTelemetryImpl::CarTelemetryImpl(RingBuffer* buffer) : mRingBuffer(buffer) {}
+
+// TODO(b/174608802): Add 10kb size check for the `dataList`, see the AIDL for the limits
+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 ndk::ScopedAStatus::ok();
}
-status_t CarTelemetryImpl::dump(int fd, const Vector<String16>& args) {
- return android::OK;
-}
} // namespace telemetry
} // namespace automotive
} // namespace android
diff --git a/cpp/telemetry/src/CarTelemetryImpl.h b/cpp/telemetry/src/CarTelemetryImpl.h
index f342248..a3bb6c1 100644
--- a/cpp/telemetry/src/CarTelemetryImpl.h
+++ b/cpp/telemetry/src/CarTelemetryImpl.h
@@ -17,22 +17,31 @@
#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 <vector>
+
namespace android {
namespace automotive {
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:
- android::binder::Status write(
- const std::vector<android::frameworks::automotive::telemetry::CarData>& dataList)
+ // Doesn't own `buffer`.
+ explicit CarTelemetryImpl(RingBuffer* buffer);
+
+ 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
};
} // namespace telemetry
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
new file mode 100644
index 0000000..36de3f8
--- /dev/null
+++ b/cpp/telemetry/src/RingBuffer.cpp
@@ -0,0 +1,62 @@
+/*
+ * 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 "RingBuffer.h"
+
+#include <android-base/logging.h>
+
+#include <inttypes.h> // for PRIu64 and friends
+
+#include <memory>
+
+namespace android {
+namespace automotive {
+namespace telemetry {
+
+RingBuffer::RingBuffer(int32_t limit) : mSizeLimit(limit) {}
+
+void RingBuffer::push(BufferedCarData&& data) {
+ const std::scoped_lock<std::mutex> lock(mMutex);
+ mList.push_back(std::move(data));
+ while (mList.size() > mSizeLimit) {
+ mList.pop_front();
+ mTotalDroppedDataCount += 1;
+ }
+}
+
+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) 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::size() const {
+ const std::scoped_lock<std::mutex> lock(mMutex);
+ return mList.size();
+}
+
+} // namespace telemetry
+} // namespace automotive
+} // namespace android
diff --git a/cpp/telemetry/src/RingBuffer.h b/cpp/telemetry/src/RingBuffer.h
new file mode 100644
index 0000000..07ce709
--- /dev/null
+++ b/cpp/telemetry/src/RingBuffer.h
@@ -0,0 +1,72 @@
+/*
+ * 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_RINGBUFFER_H_
+#define CPP_TELEMETRY_SRC_RINGBUFFER_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 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 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) const;
+
+ // Returns the number of elements in the buffer.
+ int32_t size() const;
+
+private:
+ 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.
+ std::list<BufferedCarData> mList;
+};
+
+} // namespace telemetry
+} // namespace automotive
+} // namespace android
+
+#endif // CPP_TELEMETRY_SRC_RINGBUFFER_H_
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 1864c56..1bd0dde 100644
--- a/cpp/telemetry/src/main.cpp
+++ b/cpp/telemetry/src/main.cpp
@@ -14,46 +14,23 @@
* limitations under the License.
*/
-#include "CarTelemetryImpl.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;
-
-constexpr const char kCarTelemetryServiceName[] =
- "android.frameworks.automotive.telemetry.ICarTelemetry/default";
+using ::android::automotive::telemetry::TelemetryServer;
// TODO(b/174608802): handle SIGQUIT/SIGTERM
int main(void) {
LOG(INFO) << "Starting cartelemetryd";
- android::sp<CarTelemetryImpl> telemetry = new CarTelemetryImpl();
+ TelemetryServer server;
- // 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
new file mode 100644
index 0000000..0286477
--- /dev/null
+++ b/cpp/telemetry/tests/CarTelemetryImplTest.cpp
@@ -0,0 +1,99 @@
+/*
+ * 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 "CarTelemetryImpl.h"
+#include "RingBuffer.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>
+
+#include <unistd.h>
+
+#include <memory>
+
+namespace android {
+namespace automotive {
+namespace telemetry {
+
+using ::aidl::android::frameworks::automotive::telemetry::CarData;
+using ::aidl::android::frameworks::automotive::telemetry::ICarTelemetry;
+using ::testing::ContainerEq;
+
+const size_t kMaxBufferSize = 5;
+
+CarData buildCarData(int id, const std::vector<uint8_t>& content) {
+ CarData msg;
+ msg.id = id;
+ msg.content = content;
+ 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(kMaxBufferSize)),
+ mTelemetry(ndk::SharedRefBase::make<CarTelemetryImpl>(&mBuffer)) {}
+
+ RingBuffer mBuffer;
+ std::shared_ptr<ICarTelemetry> mTelemetry;
+};
+
+TEST_F(CarTelemetryImplTest, WriteReturnsOkStatus) {
+ CarData msg = buildCarData(101, {1, 0, 1, 0});
+
+ auto status = mTelemetry->write({msg});
+
+ EXPECT_TRUE(status.isOk()) << status.getMessage();
+}
+
+TEST_F(CarTelemetryImplTest, WriteAddsCarDataToRingBuffer) {
+ CarData msg = buildCarData(101, {1, 0, 1, 0});
+
+ mTelemetry->write({msg});
+
+ EXPECT_EQ(mBuffer.popFront(), buildBufferedCarData(msg, getuid()));
+}
+
+TEST_F(CarTelemetryImplTest, WriteBuffersOnlyLimitedAmount) {
+ RingBuffer buffer(/* sizeLimit= */ 3);
+ auto telemetry = ndk::SharedRefBase::make<CarTelemetryImpl>(&buffer);
+
+ CarData msg101_2 = buildCarData(101, {1, 0});
+ CarData msg101_4 = buildCarData(101, {1, 0, 1, 0});
+ CarData msg201_3 = buildCarData(201, {3, 3, 3});
+
+ // Inserting 5 elements
+ telemetry->write({msg101_2, msg101_4, msg101_4, msg201_3});
+ telemetry->write({msg201_3});
+
+ 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));
+ EXPECT_EQ(buffer.size(), 0);
+}
+
+} // 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
new file mode 100644
index 0000000..c5b5bc7
--- /dev/null
+++ b/cpp/telemetry/tests/RingBufferTest.cpp
@@ -0,0 +1,58 @@
+/*
+ * 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 "RingBuffer.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <memory>
+
+// NOTE: many of RingBuffer's behaviors are tested as part of CarTelemetryImpl.
+
+namespace android {
+namespace automotive {
+namespace telemetry {
+
+using testing::ContainerEq;
+
+BufferedCarData buildBufferedCarData(int32_t id, const std::vector<uint8_t>& content) {
+ return {.mId = id, .mContent = content, .mPublisherUid = 0};
+}
+
+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}));
+
+ buffer.popFront();
+
+ EXPECT_EQ(buffer.size(), 1); // only ID=102 left
+}
+
+} // namespace telemetry
+} // namespace automotive
+} // namespace android
diff --git a/cpp/watchdog/aidl/android/automotive/watchdog/internal/ICarWatchdogServiceForSystem.aidl b/cpp/watchdog/aidl/android/automotive/watchdog/internal/ICarWatchdogServiceForSystem.aidl
index 9aa234f..ef68dbc 100644
--- a/cpp/watchdog/aidl/android/automotive/watchdog/internal/ICarWatchdogServiceForSystem.aidl
+++ b/cpp/watchdog/aidl/android/automotive/watchdog/internal/ICarWatchdogServiceForSystem.aidl
@@ -52,7 +52,8 @@
* @param uids List of UIDs to resolve the package infos.
* @param vendorPackagePrefixes List of vendor package prefixes.
*/
- List<PackageInfo> getPackageInfosForUids(in int[] uids, in List<String> vendorPackagePrefixes);
+ List<PackageInfo> getPackageInfosForUids(
+ in int[] uids, in @utf8InCpp List<String> vendorPackagePrefixes);
/**
* Pushes the latest I/O overuse stats to the watchdog server.
diff --git a/cpp/watchdog/aidl/android/automotive/watchdog/internal/PackageIdentifier.aidl b/cpp/watchdog/aidl/android/automotive/watchdog/internal/PackageIdentifier.aidl
index fa6e36b..c0dd86e 100644
--- a/cpp/watchdog/aidl/android/automotive/watchdog/internal/PackageIdentifier.aidl
+++ b/cpp/watchdog/aidl/android/automotive/watchdog/internal/PackageIdentifier.aidl
@@ -23,7 +23,7 @@
/**
* Name of the package.
*/
- String name;
+ @utf8InCpp String name;
/**
* UID of the package.
diff --git a/cpp/watchdog/aidl/android/automotive/watchdog/internal/PackageInfo.aidl b/cpp/watchdog/aidl/android/automotive/watchdog/internal/PackageInfo.aidl
index dfcfa9a..2deb5f3 100644
--- a/cpp/watchdog/aidl/android/automotive/watchdog/internal/PackageInfo.aidl
+++ b/cpp/watchdog/aidl/android/automotive/watchdog/internal/PackageInfo.aidl
@@ -38,7 +38,7 @@
/**
* List of packages owned by the package. This list is empty when the UID is not a shared UID.
*/
- List<String> sharedUidPackages;
+ @utf8InCpp List<String> sharedUidPackages;
/**
* Component type of the package and the owned packages.
diff --git a/cpp/watchdog/aidl/android/automotive/watchdog/internal/PerStateIoOveruseThreshold.aidl b/cpp/watchdog/aidl/android/automotive/watchdog/internal/PerStateIoOveruseThreshold.aidl
index 00a3c93..65f50ce 100644
--- a/cpp/watchdog/aidl/android/automotive/watchdog/internal/PerStateIoOveruseThreshold.aidl
+++ b/cpp/watchdog/aidl/android/automotive/watchdog/internal/PerStateIoOveruseThreshold.aidl
@@ -29,7 +29,7 @@
* 2. package name for package specific thresholds.
* 3. string equivalent of ApplicationCategoryType enum for category specific thresholds.
*/
- String name;
+ @utf8InCpp String name;
/**
* Defines the I/O overuse thresholds for a package. The thresholds are defined in terms of
diff --git a/cpp/watchdog/aidl/android/automotive/watchdog/internal/ResourceOveruseConfiguration.aidl b/cpp/watchdog/aidl/android/automotive/watchdog/internal/ResourceOveruseConfiguration.aidl
index 6cb6f23..26ae106 100644
--- a/cpp/watchdog/aidl/android/automotive/watchdog/internal/ResourceOveruseConfiguration.aidl
+++ b/cpp/watchdog/aidl/android/automotive/watchdog/internal/ResourceOveruseConfiguration.aidl
@@ -33,14 +33,14 @@
* List of only non-critical system and vendor packages that are safe to kill on disk I/O
* overuse. All third-party packages are considered safe to kill.
*/
- List<String> safeToKillPackages;
+ @utf8InCpp List<String> safeToKillPackages;
/**
* Defines the list of vendor package prefixes. Any pre-installed package name starting with one
* of these prefixes will be identified as a vendor package in addition to packages under the
* vendor partition. This must be defined only by the vendor component.
*/
- List<String> vendorPackagePrefixes;
+ @utf8InCpp List<String> vendorPackagePrefixes;
/**
* Defines the package metadata.
diff --git a/cpp/watchdog/car-watchdog-lib/src/android/car/watchdoglib/CarWatchdogDaemonHelper.java b/cpp/watchdog/car-watchdog-lib/src/android/car/watchdoglib/CarWatchdogDaemonHelper.java
index 0f6cce1..57d18b0 100644
--- a/cpp/watchdog/car-watchdog-lib/src/android/car/watchdoglib/CarWatchdogDaemonHelper.java
+++ b/cpp/watchdog/car-watchdog-lib/src/android/car/watchdoglib/CarWatchdogDaemonHelper.java
@@ -24,6 +24,7 @@
import android.automotive.watchdog.internal.ICarWatchdogMonitor;
import android.automotive.watchdog.internal.ICarWatchdogServiceForSystem;
import android.automotive.watchdog.internal.PackageResourceOveruseAction;
+import android.automotive.watchdog.internal.ResourceOveruseConfiguration;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -34,6 +35,7 @@
import com.android.internal.annotations.GuardedBy;
+import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -46,9 +48,11 @@
public final class CarWatchdogDaemonHelper {
private static final String TAG = CarWatchdogDaemonHelper.class.getSimpleName();
- // Carwatchdog daemon polls for the service manager status once every 250 milliseconds.
- // CAR_WATCHDOG_DAEMON_BIND_RETRY_INTERVAL_MS value should be at least twice the poll interval
- // used by the daemon.
+ /*
+ * Car watchdog daemon polls for the service manager status once every 250 milliseconds.
+ * CAR_WATCHDOG_DAEMON_BIND_RETRY_INTERVAL_MS value should be at least twice the poll interval
+ * used by the daemon.
+ */
private static final long CAR_WATCHDOG_DAEMON_BIND_RETRY_INTERVAL_MS = 500;
private static final long CAR_WATCHDOG_DAEMON_FIND_MARGINAL_TIME_MS = 300;
private static final int CAR_WATCHDOG_DAEMON_BIND_MAX_RETRY = 3;
@@ -250,10 +254,37 @@
}
/**
+ * Sets the given resource overuse configurations.
+ *
+ * @param configurations Resource overuse configuration per component type.
+ * @throws IllegalArgumentException If the configurations are invalid.
+ * @throws RemoteException
+ */
+ public void updateResourceOveruseConfigurations(
+ List<ResourceOveruseConfiguration> configurations) throws RemoteException {
+ invokeDaemonMethod((daemon) -> daemon.updateResourceOveruseConfigurations(configurations));
+ }
+
+ /**
+ * Returns the available resource overuse configurations.
+ *
+ * @throws RemoteException
+ */
+ public List<ResourceOveruseConfiguration> getResourceOveruseConfigurations()
+ throws RemoteException {
+ List<ResourceOveruseConfiguration> configurations = new ArrayList<>();
+ invokeDaemonMethod((daemon) -> {
+ configurations.addAll(daemon.getResourceOveruseConfigurations());
+ });
+ return configurations;
+ }
+
+ /**
* Notifies car watchdog daemon with the actions taken on resource overuse.
*
* @param actions List of actions taken on resource overuse. One action taken per resource
* overusing user package.
+ * @throws RemoteException
*/
public void actionTakenOnResourceOveruse(List<PackageResourceOveruseAction> actions)
throws RemoteException {
diff --git a/cpp/watchdog/server/src/IoOveruseConfigs.cpp b/cpp/watchdog/server/src/IoOveruseConfigs.cpp
index e595e89..90a73df 100644
--- a/cpp/watchdog/server/src/IoOveruseConfigs.cpp
+++ b/cpp/watchdog/server/src/IoOveruseConfigs.cpp
@@ -21,7 +21,6 @@
#include "PackageInfoResolver.h"
#include <android-base/strings.h>
-#include <utils/String8.h>
#include <inttypes.h>
@@ -31,8 +30,6 @@
namespace automotive {
namespace watchdog {
-using ::android::String16;
-using ::android::String8;
using ::android::automotive::watchdog::PerStateBytes;
using ::android::automotive::watchdog::internal::ApplicationCategoryType;
using ::android::automotive::watchdog::internal::ComponentType;
@@ -73,11 +70,11 @@
PER_CATEGORY_THRESHOLDS;
const int32_t kThirdPartyComponentUpdatableConfigs = COMPONENT_SPECIFIC_GENERIC_THRESHOLDS;
-const std::vector<String16> toString16Vector(const std::unordered_set<std::string>& values) {
- std::vector<String16> output;
+const std::vector<std::string> toStringVector(const std::unordered_set<std::string>& values) {
+ std::vector<std::string> output;
for (const auto& v : values) {
if (!v.empty()) {
- output.emplace_back(String16(String8(v.c_str())));
+ output.emplace_back(v);
}
}
return output;
@@ -92,14 +89,13 @@
std::string toString(const PerStateIoOveruseThreshold& thresholds) {
return StringPrintf("name=%s, foregroundBytes=%" PRId64 ", backgroundBytes=%" PRId64
", garageModeBytes=%" PRId64,
- String8(thresholds.name).c_str(),
- thresholds.perStateWriteBytes.foregroundBytes,
+ thresholds.name.c_str(), thresholds.perStateWriteBytes.foregroundBytes,
thresholds.perStateWriteBytes.backgroundBytes,
thresholds.perStateWriteBytes.garageModeBytes);
}
Result<void> containsValidThresholds(const PerStateIoOveruseThreshold& thresholds) {
- if (thresholds.name.size() == 0) {
+ if (thresholds.name.empty()) {
return Error() << "Doesn't contain threshold name";
}
@@ -145,7 +141,7 @@
return Error() << "Invalid " << toString(componentType)
<< " component level generic thresholds: " << result.error();
}
- if (String8(ioOveruseConfig.componentLevelThresholds.name).string() != componentTypeStr) {
+ if (ioOveruseConfig.componentLevelThresholds.name != componentTypeStr) {
return Error() << "Invalid component name "
<< ioOveruseConfig.componentLevelThresholds.name
<< " in component level generic thresholds for component "
@@ -231,38 +227,36 @@
}
std::string errorMsgs;
for (const auto& packageThreshold : thresholds) {
- std::string packageName = std::string(String8(packageThreshold.name));
- if (packageName.empty()) {
+ if (packageThreshold.name.empty()) {
StringAppendF(&errorMsgs, "\tSkipping per-package threshold without package name\n");
continue;
}
- maybeAppendVendorPackagePrefixes(packageName);
+ maybeAppendVendorPackagePrefixes(packageThreshold.name);
if (auto result = containsValidThresholds(packageThreshold); !result.ok()) {
StringAppendF(&errorMsgs,
"\tSkipping invalid package specific thresholds for package %s: %s\n",
- packageName.c_str(), result.error().message().c_str());
+ packageThreshold.name.c_str(), result.error().message().c_str());
continue;
}
- if (const auto& it = mPerPackageThresholds.find(packageName);
+ if (const auto& it = mPerPackageThresholds.find(packageThreshold.name);
it != mPerPackageThresholds.end()) {
StringAppendF(&errorMsgs, "\tDuplicate threshold received for package '%s'\n",
- packageName.c_str());
+ packageThreshold.name.c_str());
}
- mPerPackageThresholds[packageName] = packageThreshold;
+ mPerPackageThresholds[packageThreshold.name] = packageThreshold;
}
return errorMsgs.empty() ? Result<void>{} : Error() << errorMsgs;
}
Result<void> ComponentSpecificConfig::updateSafeToKillPackages(
- const std::vector<String16>& packages,
+ const std::vector<std::string>& packages,
const std::function<void(const std::string&)>& maybeAppendVendorPackagePrefixes) {
mSafeToKillPackages.clear();
if (packages.empty()) {
return Error() << "\tNo safe-to-kill packages provided so clearing it\n";
}
std::string errorMsgs;
- for (const auto& packageNameStr16 : packages) {
- std::string packageName = std::string(String8(packageNameStr16));
+ for (const auto& packageName : packages) {
if (packageName.empty()) {
StringAppendF(&errorMsgs, "\tSkipping empty safe-to-kill package name");
continue;
@@ -296,15 +290,15 @@
result.error().message().c_str());
continue;
}
- std::string name = std::string(String8(categoryThreshold.name));
- if (auto category = toApplicationCategoryType(name);
+ if (auto category = toApplicationCategoryType(categoryThreshold.name);
category == ApplicationCategoryType::OTHERS) {
- StringAppendF(&errorMsgs, "\tInvalid application category %s\n", name.c_str());
+ StringAppendF(&errorMsgs, "\tInvalid application category %s\n",
+ categoryThreshold.name.c_str());
} else {
if (const auto& it = mPerCategoryThresholds.find(category);
it != mPerCategoryThresholds.end()) {
StringAppendF(&errorMsgs, "\tDuplicate threshold received for category: '%s'\n",
- name.c_str());
+ categoryThreshold.name.c_str());
}
mPerCategoryThresholds[category] = categoryThreshold;
}
@@ -393,8 +387,8 @@
std::string nonUpdatableConfigMsgs;
if (updatableConfigsFilter & OveruseConfigEnum::VENDOR_PACKAGE_PREFIXES) {
mVendorPackagePrefixes.clear();
- for (const auto& prefixStr16 : resourceOveruseConfiguration.vendorPackagePrefixes) {
- if (auto prefix = std::string(String8(prefixStr16)); !prefix.empty()) {
+ for (const auto& prefix : resourceOveruseConfiguration.vendorPackagePrefixes) {
+ if (!prefix.empty()) {
mVendorPackagePrefixes.insert(prefix);
}
}
@@ -509,7 +503,7 @@
std::optional<ResourceOveruseConfiguration> IoOveruseConfigs::get(
const ComponentSpecificConfig& componentSpecificConfig, const int32_t componentFilter) {
- if (componentSpecificConfig.mGeneric.name == String16(kDefaultThresholdName)) {
+ if (componentSpecificConfig.mGeneric.name == kDefaultThresholdName) {
return {};
}
ResourceOveruseConfiguration resourceOveruseConfiguration;
@@ -518,8 +512,7 @@
ioOveruseConfiguration.componentLevelThresholds = componentSpecificConfig.mGeneric;
}
if (componentFilter & OveruseConfigEnum::VENDOR_PACKAGE_PREFIXES) {
- resourceOveruseConfiguration.vendorPackagePrefixes =
- toString16Vector(mVendorPackagePrefixes);
+ resourceOveruseConfiguration.vendorPackagePrefixes = toStringVector(mVendorPackagePrefixes);
}
if (componentFilter & OveruseConfigEnum::PACKAGE_APP_CATEGORY_MAPPINGS) {
for (const auto& [packageName, appCategoryType] : mPackagesToAppCategories) {
@@ -536,7 +529,7 @@
}
if (componentFilter & OveruseConfigEnum::COMPONENT_SPECIFIC_SAFE_TO_KILL_PACKAGES) {
resourceOveruseConfiguration.safeToKillPackages =
- toString16Vector(componentSpecificConfig.mSafeToKillPackages);
+ toStringVector(componentSpecificConfig.mSafeToKillPackages);
}
if (componentFilter & OveruseConfigEnum::PER_CATEGORY_THRESHOLDS) {
for (const auto& [category, threshold] : mPerCategoryThresholds) {
@@ -557,10 +550,10 @@
}
PerStateBytes IoOveruseConfigs::fetchThreshold(const PackageInfo& packageInfo) const {
- const std::string packageName = std::string(String8(packageInfo.packageIdentifier.name));
switch (packageInfo.componentType) {
case ComponentType::SYSTEM:
- if (const auto it = mSystemConfig.mPerPackageThresholds.find(packageName);
+ if (const auto it = mSystemConfig.mPerPackageThresholds.find(
+ packageInfo.packageIdentifier.name);
it != mSystemConfig.mPerPackageThresholds.end()) {
return it->second.perStateWriteBytes;
}
@@ -570,7 +563,8 @@
}
return mSystemConfig.mGeneric.perStateWriteBytes;
case ComponentType::VENDOR:
- if (const auto it = mVendorConfig.mPerPackageThresholds.find(packageName);
+ if (const auto it = mVendorConfig.mPerPackageThresholds.find(
+ packageInfo.packageIdentifier.name);
it != mVendorConfig.mPerPackageThresholds.end()) {
return it->second.perStateWriteBytes;
}
@@ -597,13 +591,12 @@
// Native packages can't be disabled so don't kill them on I/O overuse.
return false;
}
- const std::string packageName = std::string(String8(packageInfo.packageIdentifier.name));
switch (packageInfo.componentType) {
case ComponentType::SYSTEM:
- return mSystemConfig.mSafeToKillPackages.find(packageName) !=
+ return mSystemConfig.mSafeToKillPackages.find(packageInfo.packageIdentifier.name) !=
mSystemConfig.mSafeToKillPackages.end();
case ComponentType::VENDOR:
- return mVendorConfig.mSafeToKillPackages.find(packageName) !=
+ return mVendorConfig.mSafeToKillPackages.find(packageInfo.packageIdentifier.name) !=
mVendorConfig.mSafeToKillPackages.end();
default:
return true;
diff --git a/cpp/watchdog/server/src/IoOveruseConfigs.h b/cpp/watchdog/server/src/IoOveruseConfigs.h
index 40a0b7b..cd80a95 100644
--- a/cpp/watchdog/server/src/IoOveruseConfigs.h
+++ b/cpp/watchdog/server/src/IoOveruseConfigs.h
@@ -26,7 +26,6 @@
#include <android/automotive/watchdog/internal/PackageInfo.h>
#include <android/automotive/watchdog/internal/PerStateIoOveruseThreshold.h>
#include <android/automotive/watchdog/internal/ResourceOveruseConfiguration.h>
-#include <utils/String16.h>
#include <optional>
#include <string>
@@ -42,7 +41,7 @@
inline const android::automotive::watchdog::internal::PerStateIoOveruseThreshold
defaultThreshold() {
android::automotive::watchdog::internal::PerStateIoOveruseThreshold threshold;
- threshold.name = android::String16(kDefaultThresholdName);
+ threshold.name = kDefaultThresholdName;
threshold.perStateWriteBytes.foregroundBytes = std::numeric_limits<uint64_t>::max();
threshold.perStateWriteBytes.backgroundBytes = std::numeric_limits<uint64_t>::max();
threshold.perStateWriteBytes.garageModeBytes = std::numeric_limits<uint64_t>::max();
@@ -130,7 +129,7 @@
* Updates |mSafeToKillPackages|.
*/
android::base::Result<void> updateSafeToKillPackages(
- const std::vector<android::String16>& packages,
+ const std::vector<std::string>& packages,
const std::function<void(const std::string&)>& maybeAppendVendorPackagePrefixes);
/*
diff --git a/cpp/watchdog/server/src/IoOveruseMonitor.cpp b/cpp/watchdog/server/src/IoOveruseMonitor.cpp
index fd9f61f..080b284 100644
--- a/cpp/watchdog/server/src/IoOveruseMonitor.cpp
+++ b/cpp/watchdog/server/src/IoOveruseMonitor.cpp
@@ -15,6 +15,7 @@
*/
#define LOG_TAG "carwatchdogd"
+#define DEBUG false // STOPSHIP if true.
#include "IoOveruseMonitor.h"
@@ -27,6 +28,8 @@
#include <binder/Status.h>
#include <cutils/multiuser.h>
+#include <limits>
+
namespace android {
namespace automotive {
namespace watchdog {
@@ -44,13 +47,17 @@
using ::android::base::Result;
using ::android::binder::Status;
+// Minimum written bytes to sync the stats with the Watchdog service.
+constexpr uint64_t kMinSyncWrittenBytes = 100 * 1024;
+// Minimum percentage of threshold to warn killable applications.
constexpr double kDefaultIoOveruseWarnPercentage = 80;
+// Maximum numer of system-wide stats (from periodic monitoring) to cache.
constexpr size_t kMaxPeriodicMonitorBufferSize = 1000;
namespace {
std::string uniquePackageIdStr(const PackageIdentifier& id) {
- return StringPrintf("%s:%" PRId32, String8(id.name).c_str(), multiuser_get_user_id(id.uid));
+ return StringPrintf("%s:%" PRId32, id.name.c_str(), multiuser_get_user_id(id.uid));
}
PerStateBytes diff(const PerStateBytes& lhs, const PerStateBytes& rhs) {
@@ -76,6 +83,15 @@
return std::make_tuple(startTime, currentEpochSeconds - startTime);
}
+uint64_t totalPerStateBytes(PerStateBytes perStateBytes) {
+ const auto sum = [](const uint64_t& l, const uint64_t& r) -> uint64_t {
+ return std::numeric_limits<uint64_t>::max() - l > r ? (l + r)
+ : std::numeric_limits<uint64_t>::max();
+ };
+ return sum(perStateBytes.foregroundBytes,
+ sum(perStateBytes.backgroundBytes, perStateBytes.garageModeBytes));
+}
+
} // namespace
std::tuple<int64_t, int64_t> calculateStartAndDuration(const time_t& currentTime) {
@@ -84,6 +100,19 @@
return calculateStartAndDuration(currentGmt);
}
+IoOveruseMonitor::IoOveruseMonitor(
+ const android::sp<IWatchdogServiceHelperInterface>& watchdogServiceHelper) :
+ mMinSyncWrittenBytes(kMinSyncWrittenBytes),
+ mWatchdogServiceHelper(watchdogServiceHelper),
+ mSystemWideWrittenBytes({}),
+ mPeriodicMonitorBufferSize(0),
+ mLastSystemWideIoMonitorTime(0),
+ mUserPackageDailyIoUsageById({}),
+ mIoOveruseWarnPercentage(0),
+ mLastUserPackageIoMonitorTime(0),
+ mOveruseListenersByUid({}),
+ mBinderDeathRecipient(new BinderDeathRecipient(this)) {}
+
Result<void> IoOveruseMonitor::init() {
std::unique_lock writeLock(mRwMutex);
if (isInitializedLocked()) {
@@ -100,7 +129,7 @@
mIoOveruseWarnPercentage = static_cast<double>(
sysprop::ioOveruseWarnPercentage().value_or(kDefaultIoOveruseWarnPercentage));
/*
- * TODO(b/167240592): Read the latest I/O overuse config.
+ * TODO(b/185287136): Read the latest I/O overuse config.
* The latest I/O overuse config is read in this order:
* 1. From /data partition as this contains the latest config and any updates received from OEM
* and system applications.
@@ -109,10 +138,13 @@
*/
mIoOveruseConfigs = new IoOveruseConfigs();
- // TODO(b/167240592): Read the vendor package prefixes from disk before the below call.
+ // TODO(b/185287136): Read the vendor package prefixes from disk before the below call.
mPackageInfoResolver = PackageInfoResolver::getInstance();
mPackageInfoResolver->setPackageConfigurations(mIoOveruseConfigs->vendorPackagePrefixes(),
mIoOveruseConfigs->packagesToAppCategories());
+ if (DEBUG) {
+ ALOGD("Initialized %s data processor", name().c_str());
+ }
return {};
}
@@ -129,6 +161,9 @@
}
mBinderDeathRecipient.clear();
mOveruseListenersByUid.clear();
+ if (DEBUG) {
+ ALOGD("Terminated %s data processor", name().c_str());
+ }
return;
}
@@ -148,7 +183,7 @@
/*
* Date changed so reset the daily I/O usage cache.
*
- * TODO(b/170741935): Ping CarWatchdogService on date change so it can re-enable the daily
+ * TODO(b/185287136): Ping CarWatchdogService on date change so it can re-enable the daily
* disabled packages. Also sync prev day's stats with CarWatchdogService.
*/
mUserPackageDailyIoUsageById.clear();
@@ -156,14 +191,26 @@
mLastUserPackageIoMonitorTime = time;
const auto [startTime, durationInSeconds] = calculateStartAndDuration(curGmt);
- const auto perUidIoUsage = uidIoStats.promote()->deltaStats();
+ auto perUidIoUsage = uidIoStats.promote()->deltaStats();
/*
- * TODO(b/167240592): Maybe move the packageInfo fetching logic into UidIoStats module.
+ * TODO(b/185849350): Maybe move the packageInfo fetching logic into UidIoStats module.
* This will also help avoid fetching package names in IoPerfCollection module.
*/
std::vector<uid_t> seenUids;
- for (const auto& [uid, uidIoStats] : perUidIoUsage) {
- seenUids.push_back(uid);
+ for (auto it = perUidIoUsage.begin(); it != perUidIoUsage.end();) {
+ /*
+ * UidIoStats::deltaStats returns entries with zero write bytes because other metrics
+ * in these entries are non-zero.
+ */
+ if (it->second.ios.sumWriteBytes() == 0) {
+ it = perUidIoUsage.erase(it);
+ continue;
+ }
+ seenUids.push_back(it->first);
+ ++it;
+ }
+ if (perUidIoUsage.empty()) {
+ return {};
}
const auto packageInfosByUid = mPackageInfoResolver->getPackageInfosForUids(seenUids);
std::unordered_map<uid_t, IoOveruseStats> overusingNativeStats;
@@ -173,7 +220,7 @@
continue;
}
/*
- * TODO(b/167240592): Derive the garage mode status from the collection flag, which will
+ * TODO(b/185498771): Derive the garage mode status from the collection flag, which will
* be added to the |onPeriodicCollection| API.
*/
UserPackageIoUsage curUsage(packageInfo->second, uidIoStats.ios,
@@ -204,6 +251,16 @@
mIoOveruseConfigs->isSafeToKill(dailyIoUsage->packageInfo);
const auto& remainingWriteBytes = stats.ioOveruseStats.remainingWriteBytes;
+ const auto exceedsWarnThreshold = [&](double remaining, double threshold) {
+ if (threshold == 0) {
+ return true;
+ }
+ double usedPercent = (100 - (remaining / threshold) * 100);
+ return usedPercent > mIoOveruseWarnPercentage;
+ };
+ bool shouldSyncWatchdogService =
+ (totalPerStateBytes(dailyIoUsage->writtenBytes) -
+ dailyIoUsage->lastSyncedWrittenBytes) >= mMinSyncWrittenBytes;
if (remainingWriteBytes.foregroundBytes == 0 || remainingWriteBytes.backgroundBytes == 0 ||
remainingWriteBytes.garageModeBytes == 0) {
stats.ioOveruseStats.totalOveruses = ++dailyIoUsage->totalOveruses;
@@ -221,45 +278,44 @@
if (dailyIoUsage->packageInfo.uidType == UidType::NATIVE) {
overusingNativeStats[uid] = stats.ioOveruseStats;
}
- mLatestIoOveruseStats.emplace_back(std::move(stats));
- continue;
- }
- if (dailyIoUsage->packageInfo.uidType == UidType::NATIVE ||
- !stats.ioOveruseStats.killableOnOveruse || dailyIoUsage->isPackageWarned) {
+ shouldSyncWatchdogService = true;
+ } else if (dailyIoUsage->packageInfo.uidType != UidType::NATIVE &&
+ stats.ioOveruseStats.killableOnOveruse && !dailyIoUsage->isPackageWarned &&
+ (exceedsWarnThreshold(remainingWriteBytes.foregroundBytes,
+ threshold.foregroundBytes) ||
+ exceedsWarnThreshold(remainingWriteBytes.backgroundBytes,
+ threshold.backgroundBytes) ||
+ exceedsWarnThreshold(remainingWriteBytes.garageModeBytes,
+ threshold.garageModeBytes))) {
/*
* No need to warn native services or applications that won't be killed on I/O overuse
* as they will be sent a notification when they exceed their daily threshold.
*/
- mLatestIoOveruseStats.emplace_back(std::move(stats));
- continue;
- }
- const auto exceedsWarnThreshold = [&](double remaining, double threshold) {
- if (threshold == 0) {
- return true;
- }
- double usedPercent = (100 - (remaining / threshold) * 100);
- return usedPercent > mIoOveruseWarnPercentage;
- };
- if (exceedsWarnThreshold(remainingWriteBytes.foregroundBytes, threshold.foregroundBytes) ||
- exceedsWarnThreshold(remainingWriteBytes.backgroundBytes, threshold.backgroundBytes) ||
- exceedsWarnThreshold(remainingWriteBytes.garageModeBytes, threshold.garageModeBytes)) {
stats.shouldNotify = true;
// Avoid duplicate warning before the daily threshold exceeded notification is sent.
dailyIoUsage->isPackageWarned = true;
+ shouldSyncWatchdogService = true;
}
- mLatestIoOveruseStats.emplace_back(std::move(stats));
+ if (shouldSyncWatchdogService) {
+ dailyIoUsage->lastSyncedWrittenBytes = totalPerStateBytes(dailyIoUsage->writtenBytes);
+ mLatestIoOveruseStats.emplace_back(std::move(stats));
+ }
}
-
if (!overusingNativeStats.empty()) {
notifyNativePackagesLocked(overusingNativeStats);
}
-
+ if (mLatestIoOveruseStats.empty()) {
+ return {};
+ }
if (const auto status = mWatchdogServiceHelper->latestIoOveruseStats(mLatestIoOveruseStats);
!status.isOk()) {
// Don't clear the cache as it can be pushed again on the next collection.
ALOGW("Failed to push the latest I/O overuse stats to watchdog service");
} else {
mLatestIoOveruseStats.clear();
+ if (DEBUG) {
+ ALOGD("Pushed latest I/O overuse stats to watchdog service");
+ }
}
return {};
@@ -328,12 +384,12 @@
}
Result<void> IoOveruseMonitor::onShutdownPrepareComplete() {
- // TODO(b/167240592): Flush in-memory stats to disk.
+ // TODO(b/185287136): Flush in-memory stats to disk.
return {};
}
Result<void> IoOveruseMonitor::onDump([[maybe_unused]] int fd) {
- // TODO(b/167240592): Dump the list of killed/disabled packages. Dump the list of packages that
+ // TODO(b/183436216): Dump the list of killed/disabled packages. Dump the list of packages that
// exceed xx% of their threshold.
return {};
}
@@ -351,7 +407,10 @@
stats.set<ResourceOveruseStats::ioOveruseStats>(ioOveruseStats);
listener->onOveruse(stats);
}
- // TODO(b/167240592): Upload I/O overuse metrics for native packages.
+ if (DEBUG) {
+ ALOGD("Notified native packages on I/O overuse");
+ }
+ // TODO(b/184310189): Upload I/O overuse metrics for native packages.
}
Result<void> IoOveruseMonitor::updateResourceOveruseConfigurations(
@@ -375,7 +434,10 @@
Result<void> IoOveruseMonitor::actionTakenOnIoOveruse(
[[maybe_unused]] const std::vector<PackageResourceOveruseAction>& actions) {
- // TODO(b/167240592): Upload metrics.
+ // TODO(b/184310189): Upload metrics.
+ if (DEBUG) {
+ ALOGD("Recorded action taken on I/O overuse");
+ }
return {};
}
@@ -395,6 +457,9 @@
<< "(pid " << callingPid << ", uid: " << callingUid << ") is dead";
}
mOveruseListenersByUid[callingUid] = listener;
+ if (DEBUG) {
+ ALOGD("Added I/O overuse listener for uid: %d", callingUid);
+ }
return {};
}
@@ -410,11 +475,14 @@
!findListenerAndProcessLocked(binder, processor)) {
return Error(Status::EX_ILLEGAL_ARGUMENT) << "Listener is not previously registered";
}
+ if (DEBUG) {
+ ALOGD("Removed I/O overuse listener for uid: %d", IPCThreadState::self()->getCallingUid());
+ }
return {};
}
Result<void> IoOveruseMonitor::getIoOveruseStats(IoOveruseStats* ioOveruseStats) {
- if (std::shared_lock readLock(mRwMutex); !isInitializedLocked()) {
+ if (!isInitialized()) {
return Error(Status::EX_ILLEGAL_STATE) << "I/O overuse monitor is not initialized";
}
uid_t callingUid = IPCThreadState::self()->getCallingUid();
@@ -447,6 +515,9 @@
calculateStartAndDuration(mLastUserPackageIoMonitorTime);
ioOveruseStats->startTime = startTime;
ioOveruseStats->durationInSeconds = durationInSeconds;
+ if (DEBUG) {
+ ALOGD("Returning I/O overuse listener for uid: %d", callingUid);
+ }
return {};
}
diff --git a/cpp/watchdog/server/src/IoOveruseMonitor.h b/cpp/watchdog/server/src/IoOveruseMonitor.h
index 966e7ec..7040353 100644
--- a/cpp/watchdog/server/src/IoOveruseMonitor.h
+++ b/cpp/watchdog/server/src/IoOveruseMonitor.h
@@ -66,6 +66,9 @@
*/
class IIoOveruseMonitor : virtual public IDataProcessorInterface {
public:
+ // Returns whether or not the monitor is initialized.
+ virtual bool isInitialized() = 0;
+
// Below API is from internal/ICarWatchdog.aidl. Please refer to the AIDL for description.
virtual android::base::Result<void> updateResourceOveruseConfigurations(
const std::vector<
@@ -92,19 +95,15 @@
class IoOveruseMonitor final : public IIoOveruseMonitor {
public:
explicit IoOveruseMonitor(
- const android::sp<IWatchdogServiceHelperInterface>& watchdogServiceHelper) :
- mWatchdogServiceHelper(watchdogServiceHelper),
- mSystemWideWrittenBytes({}),
- mPeriodicMonitorBufferSize(0),
- mLastSystemWideIoMonitorTime(0),
- mUserPackageDailyIoUsageById({}),
- mIoOveruseWarnPercentage(0),
- mLastUserPackageIoMonitorTime(0),
- mOveruseListenersByUid({}),
- mBinderDeathRecipient(new BinderDeathRecipient(this)) {}
+ const android::sp<IWatchdogServiceHelperInterface>& watchdogServiceHelper);
~IoOveruseMonitor() { terminate(); }
+ bool isInitialized() {
+ std::shared_lock readLock(mRwMutex);
+ return isInitializedLocked();
+ }
+
// Below methods implement IDataProcessorInterface.
std::string name() { return "IoOveruseMonitor"; }
friend std::ostream& operator<<(std::ostream& os, const IoOveruseMonitor& monitor);
@@ -116,7 +115,7 @@
return {};
}
- // TODO(b/167240592): Forward WatchdogBinderMediator's notifySystemStateChange call to
+ // TODO(b/185498771): Forward WatchdogBinderMediator's notifySystemStateChange call to
// WatchdogPerfService. On POWER_CYCLE_SHUTDOWN_PREPARE, switch to garage mode collection
// and pass collection flag as a param in this API to indicate garage mode collection.
android::base::Result<void> onPeriodicCollection(time_t time,
@@ -133,7 +132,7 @@
time_t time, const android::wp<IProcDiskStatsInterface>& procDiskStats,
const std::function<void()>& alertHandler);
- // TODO(b/167240592): Forward WatchdogBinderMediator's notifySystemStateChange call to
+ // TODO(b/185498771): Forward WatchdogBinderMediator's notifySystemStateChange call to
// WatchdogProcessService. On POWER_CYCLE_SHUTDOWN_PREPARE_COMPLETE, call this method via
// the IDataProcessorInterface. onShutdownPrepareComplete, IoOveruseMonitor will flush
// in-memory stats to disk.
@@ -187,6 +186,7 @@
PerStateBytes forgivenWriteBytes = {};
int totalOveruses = 0;
bool isPackageWarned = false;
+ uint64_t lastSyncedWrittenBytes = 0;
UserPackageIoUsage& operator+=(const UserPackageIoUsage& r);
@@ -219,12 +219,13 @@
// Local IPackageInfoResolver instance. Useful to mock in tests.
sp<IPackageInfoResolver> mPackageInfoResolver;
+ // Minimum written bytes to sync the stats with the Watchdog service.
+ double mMinSyncWrittenBytes;
+ android::sp<IWatchdogServiceHelperInterface> mWatchdogServiceHelper;
// Makes sure only one collection is running at any given time.
mutable std::shared_mutex mRwMutex;
- android::sp<IWatchdogServiceHelperInterface> mWatchdogServiceHelper GUARDED_BY(mRwMutex);
-
// Summary of configs available for all the components and system-wide overuse alert thresholds.
sp<IIoOveruseConfigs> mIoOveruseConfigs GUARDED_BY(mRwMutex);
diff --git a/cpp/watchdog/server/src/IoPerfCollection.cpp b/cpp/watchdog/server/src/IoPerfCollection.cpp
index 059059a..22dc45f 100644
--- a/cpp/watchdog/server/src/IoPerfCollection.cpp
+++ b/cpp/watchdog/server/src/IoPerfCollection.cpp
@@ -421,9 +421,6 @@
for (const auto& uIt : usages) {
const UidIoUsage& curUsage = uIt.second;
- if (curUsage.ios.isZero()) {
- continue;
- }
uids.push_back(curUsage.uid);
uidIoPerfData->total[READ_BYTES][FOREGROUND] +=
curUsage.ios.metrics[READ_BYTES][FOREGROUND];
diff --git a/cpp/watchdog/server/src/PackageInfoResolver.cpp b/cpp/watchdog/server/src/PackageInfoResolver.cpp
index f6b1dfd..1a4346a 100644
--- a/cpp/watchdog/server/src/PackageInfoResolver.cpp
+++ b/cpp/watchdog/server/src/PackageInfoResolver.cpp
@@ -23,7 +23,6 @@
#include <android/automotive/watchdog/internal/ComponentType.h>
#include <android/automotive/watchdog/internal/UidType.h>
#include <cutils/android_filesystem_config.h>
-#include <utils/String16.h>
#include <inttypes.h>
@@ -36,7 +35,6 @@
using ::android::IBinder;
using ::android::sp;
-using ::android::String16;
using ::android::automotive::watchdog::internal::ApplicationCategoryType;
using ::android::automotive::watchdog::internal::ComponentType;
using ::android::automotive::watchdog::internal::PackageInfo;
@@ -53,7 +51,7 @@
namespace {
-constexpr const char16_t* kSharedPackagePrefix = u"shared:";
+constexpr const char* kSharedPackagePrefix = "shared:";
ComponentType getComponentTypeForNativeUid(uid_t uid, std::string_view packageName,
const std::vector<std::string>& vendorPackagePrefixes) {
@@ -83,7 +81,7 @@
return Error() << "Failed to fetch package name";
}
const char* packageName = usrpwd->pw_name;
- packageInfo.packageIdentifier.name = String16(packageName);
+ packageInfo.packageIdentifier.name = packageName;
packageInfo.packageIdentifier.uid = uid;
packageInfo.uidType = UidType::NATIVE;
packageInfo.componentType =
@@ -153,7 +151,7 @@
continue;
}
mUidToPackageInfoMapping[uid] = *result;
- if (result->packageIdentifier.name.startsWith(kSharedPackagePrefix)) {
+ if (StartsWith(result->packageIdentifier.name, kSharedPackagePrefix)) {
// When the UID is shared, poll car watchdog service to fetch the shared packages info.
missingUids.emplace_back(static_cast<int32_t>(uid));
}
@@ -178,10 +176,10 @@
}
for (auto& packageInfo : packageInfos) {
const auto& id = packageInfo.packageIdentifier;
- if (id.name.size() == 0) {
+ if (id.name.empty()) {
continue;
}
- if (const auto it = mPackagesToAppCategories.find(String8(id.name).c_str());
+ if (const auto it = mPackagesToAppCategories.find(id.name);
packageInfo.uidType == UidType::APPLICATION && it != mPackagesToAppCategories.end()) {
packageInfo.appCategoryType = it->second;
}
@@ -200,8 +198,8 @@
std::shared_lock readLock(mRWMutex);
for (const auto& uid : uids) {
if (mUidToPackageInfoMapping.find(uid) != mUidToPackageInfoMapping.end()) {
- uidToPackageNameMapping[uid] = std::string(
- String8(mUidToPackageInfoMapping.at(uid).packageIdentifier.name));
+ uidToPackageNameMapping[uid] =
+ mUidToPackageInfoMapping.at(uid).packageIdentifier.name;
}
}
}
diff --git a/cpp/watchdog/server/src/ServiceManager.cpp b/cpp/watchdog/server/src/ServiceManager.cpp
index 978e83c..6bcba40 100644
--- a/cpp/watchdog/server/src/ServiceManager.cpp
+++ b/cpp/watchdog/server/src/ServiceManager.cpp
@@ -26,7 +26,6 @@
namespace watchdog {
using ::android::sp;
-using ::android::String16;
using ::android::automotive::watchdog::WatchdogPerfService;
using ::android::automotive::watchdog::WatchdogProcessService;
using ::android::base::Error;
diff --git a/cpp/watchdog/server/src/UidIoStats.cpp b/cpp/watchdog/server/src/UidIoStats.cpp
index 31cd0d9..ce7b351 100644
--- a/cpp/watchdog/server/src/UidIoStats.cpp
+++ b/cpp/watchdog/server/src/UidIoStats.cpp
@@ -113,11 +113,16 @@
mDeltaUidIoUsages.clear();
for (const auto& it : *uidIoUsages) {
- const UidIoUsage& curUsage = it.second;
- mDeltaUidIoUsages[it.first] = curUsage;
- if (mLatestUidIoUsages.find(it.first) != mLatestUidIoUsages.end()) {
- mDeltaUidIoUsages[it.first] -= mLatestUidIoUsages[it.first];
+ UidIoUsage curUsage = it.second;
+ if (curUsage.ios.isZero()) {
+ continue;
}
+ if (mLatestUidIoUsages.find(it.first) != mLatestUidIoUsages.end()) {
+ if (curUsage -= mLatestUidIoUsages[it.first]; curUsage.ios.isZero()) {
+ continue;
+ }
+ }
+ mDeltaUidIoUsages[it.first] = curUsage;
}
mLatestUidIoUsages = *uidIoUsages;
return {};
diff --git a/cpp/watchdog/server/src/UidIoStats.h b/cpp/watchdog/server/src/UidIoStats.h
index c6e87d1..3cc6a76 100644
--- a/cpp/watchdog/server/src/UidIoStats.h
+++ b/cpp/watchdog/server/src/UidIoStats.h
@@ -18,6 +18,7 @@
#define CPP_WATCHDOG_SERVER_SRC_UIDIOSTATS_H_
#include <android-base/result.h>
+#include <android-base/stringprintf.h>
#include <utils/Mutex.h>
#include <utils/RefBase.h>
@@ -87,6 +88,10 @@
ios -= rhs.ios;
return *this;
}
+ bool operator==(const UidIoUsage& rhs) const { return uid == rhs.uid && ios == rhs.ios; }
+ std::string toString() const {
+ return android::base::StringPrintf("Uid: %d, Usage: {%s}", uid, ios.toString().c_str());
+ }
};
class UidIoStats : public RefBase {
diff --git a/cpp/watchdog/server/src/WatchdogBinderMediator.cpp b/cpp/watchdog/server/src/WatchdogBinderMediator.cpp
index b27d9a9..0a4eb25 100644
--- a/cpp/watchdog/server/src/WatchdogBinderMediator.cpp
+++ b/cpp/watchdog/server/src/WatchdogBinderMediator.cpp
@@ -32,6 +32,7 @@
using ::android::defaultServiceManager;
using ::android::IBinder;
using ::android::sp;
+using ::android::String16;
using ::android::base::Error;
using ::android::base::Join;
using ::android::base::ParseUint;
@@ -170,7 +171,6 @@
ALOGW("Failed to dump I/O perf collection: %s", ret.error().message().c_str());
return ret.error().code();
}
- // TODO(b/167240592): Add a dump call to I/O overuse monitor and relevant tests.
return OK;
}
diff --git a/cpp/watchdog/server/src/WatchdogBinderMediator.h b/cpp/watchdog/server/src/WatchdogBinderMediator.h
index e470290..63b687b 100644
--- a/cpp/watchdog/server/src/WatchdogBinderMediator.h
+++ b/cpp/watchdog/server/src/WatchdogBinderMediator.h
@@ -66,7 +66,7 @@
~WatchdogBinderMediator() { terminate(); }
// Implements ICarWatchdog.aidl APIs.
- status_t dump(int fd, const Vector<String16>& args) override;
+ status_t dump(int fd, const Vector<android::String16>& args) override;
android::binder::Status registerClient(const android::sp<ICarWatchdogClient>& client,
TimeoutLength timeout) override;
android::binder::Status unregisterClient(
diff --git a/cpp/watchdog/server/src/WatchdogInternalHandler.cpp b/cpp/watchdog/server/src/WatchdogInternalHandler.cpp
index 31c40bf..556f9f1 100644
--- a/cpp/watchdog/server/src/WatchdogInternalHandler.cpp
+++ b/cpp/watchdog/server/src/WatchdogInternalHandler.cpp
@@ -37,6 +37,7 @@
using aawi::PackageResourceOveruseAction;
using aawi::ResourceOveruseConfiguration;
using ::android::sp;
+using ::android::String16;
using ::android::binder::Status;
namespace {
@@ -65,6 +66,18 @@
return mBinderMediator->dump(fd, args);
}
+void WatchdogInternalHandler::checkAndRegisterIoOveruseMonitor() {
+ if (mIoOveruseMonitor->isInitialized()) {
+ return;
+ }
+ if (const auto result = mWatchdogPerfService->registerDataProcessor(mIoOveruseMonitor);
+ !result.ok()) {
+ ALOGE("Failed to register I/O overuse monitor to watchdog performance service: %s",
+ result.error().message().c_str());
+ }
+ return;
+}
+
Status WatchdogInternalHandler::registerCarWatchdogService(
const sp<ICarWatchdogServiceForSystem>& service) {
Status status = checkSystemUser();
@@ -74,6 +87,12 @@
if (service == nullptr) {
return fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT, kNullCarWatchdogServiceError);
}
+ /*
+ * I/O overuse monitor reads from system, vendor, and data partitions during initialization.
+ * When CarService is running these partitions are available to read, thus register the I/O
+ * overuse monitor on processing the request to register CarService.
+ */
+ checkAndRegisterIoOveruseMonitor();
return mWatchdogServiceHelper->registerService(service);
}
@@ -167,19 +186,6 @@
if (const auto result = mWatchdogPerfService->onBootFinished(); !result.ok()) {
return fromExceptionCode(result.error().code(), result.error().message());
}
- /*
- * I/O overuse monitor reads from data partition on init so register the I/O
- * overuse monitor only on boot-complete.
- *
- * TODO(b/167240592): Uncomment the below code block after the I/O overuse monitor
- * is completely implemented.
- * if (const auto result
- * = mWatchdogPerfService->registerDataProcessor(mIoOveruseMonitor);
- * !result.ok()) {
- * ALOGW("Failed to register I/O overuse monitor to watchdog performance "
- * "service: %s", result.error().message().c_str());
- * }
- */
}
return Status::ok();
}
@@ -194,6 +200,8 @@
if (!status.isOk()) {
return status;
}
+ // Maybe retry registring I/O overuse monitor if failed to initialize previously.
+ checkAndRegisterIoOveruseMonitor();
if (const auto result = mIoOveruseMonitor->updateResourceOveruseConfigurations(configs);
!result.ok()) {
return fromExceptionCode(result.error().code(), result.error().message());
@@ -207,6 +215,8 @@
if (!status.isOk()) {
return status;
}
+ // Maybe retry registring I/O overuse monitor if failed to initialize previously.
+ checkAndRegisterIoOveruseMonitor();
if (const auto result = mIoOveruseMonitor->getResourceOveruseConfigurations(configs);
!result.ok()) {
return fromExceptionCode(result.error().code(), result.error().message());
diff --git a/cpp/watchdog/server/src/WatchdogInternalHandler.h b/cpp/watchdog/server/src/WatchdogInternalHandler.h
index c155523..098b0dd 100644
--- a/cpp/watchdog/server/src/WatchdogInternalHandler.h
+++ b/cpp/watchdog/server/src/WatchdogInternalHandler.h
@@ -32,6 +32,7 @@
#include <binder/Status.h>
#include <gtest/gtest_prod.h>
#include <utils/Errors.h>
+#include <utils/String16.h>
#include <utils/Vector.h>
namespace android {
@@ -55,7 +56,7 @@
mIoOveruseMonitor(ioOveruseMonitor) {}
~WatchdogInternalHandler() { terminate(); }
- status_t dump(int fd, const Vector<String16>& args) override;
+ status_t dump(int fd, const Vector<android::String16>& args) override;
android::binder::Status registerCarWatchdogService(
const android::sp<
android::automotive::watchdog::internal::ICarWatchdogServiceForSystem>& service)
@@ -103,6 +104,8 @@
}
private:
+ void checkAndRegisterIoOveruseMonitor();
+
android::sp<WatchdogBinderMediator> mBinderMediator;
android::sp<IWatchdogServiceHelperInterface> mWatchdogServiceHelper;
android::sp<WatchdogProcessService> mWatchdogProcessService;
diff --git a/cpp/watchdog/server/src/WatchdogPerfService.cpp b/cpp/watchdog/server/src/WatchdogPerfService.cpp
index 79177ad..214a16c 100644
--- a/cpp/watchdog/server/src/WatchdogPerfService.cpp
+++ b/cpp/watchdog/server/src/WatchdogPerfService.cpp
@@ -15,6 +15,7 @@
*/
#define LOG_TAG "carwatchdogd"
+#define DEBUG false // STOPSHIP if true.
#include "WatchdogPerfService.h"
@@ -36,6 +37,7 @@
namespace watchdog {
using ::android::String16;
+using ::android::String8;
using ::android::base::Error;
using ::android::base::Join;
using ::android::base::ParseUint;
@@ -140,6 +142,9 @@
}
Mutex::Autolock lock(mMutex);
mDataProcessors.push_back(processor);
+ if (DEBUG) {
+ ALOGD("Successfully registered %s to %s", processor->name().c_str(), kServiceName);
+ }
return {};
}
@@ -241,6 +246,9 @@
}
if (mCollectionThread.joinable()) {
mCollectionThread.join();
+ if (DEBUG) {
+ ALOGD("%s collection thread terminated", kServiceName);
+ }
}
}
@@ -260,6 +268,9 @@
mBoottimeCollection.lastUptime = mHandlerLooper->now();
mHandlerLooper->removeMessages(this);
mHandlerLooper->sendMessage(this, SwitchMessage::END_BOOTTIME_COLLECTION);
+ if (DEBUG) {
+ ALOGD("Boot-time event finished");
+ }
return {};
}
@@ -471,6 +482,9 @@
}
}
+ if (DEBUG) {
+ ALOGD("Custom event finished");
+ }
WriteStringToFd(kDumpMajorDelimiter, fd);
return {};
}
@@ -564,6 +578,9 @@
toString(mCurrCollectionEvent));
return {};
}
+ if (DEBUG) {
+ ALOGD("Processing %s collection event", toString(metadata->eventType));
+ }
if (metadata->interval < kMinEventInterval) {
return Error()
<< "Collection interval of "
@@ -636,6 +653,9 @@
if (metadata->eventType != static_cast<int>(EventType::PERIODIC_MONITOR)) {
return Error() << "Invalid monitor event " << toString(metadata->eventType);
}
+ if (DEBUG) {
+ ALOGD("Processing %s monitor event", toString(metadata->eventType));
+ }
if (metadata->interval < kMinEventInterval) {
return Error()
<< "Monitor interval of "
diff --git a/cpp/watchdog/server/src/WatchdogPerfService.h b/cpp/watchdog/server/src/WatchdogPerfService.h
index c44659b..0551d35 100644
--- a/cpp/watchdog/server/src/WatchdogPerfService.h
+++ b/cpp/watchdog/server/src/WatchdogPerfService.h
@@ -171,7 +171,8 @@
// 1. Starts a custom collection.
// 2. Or ends the current custom collection and dumps the collected data.
// Returns any error observed during the dump generation.
- virtual android::base::Result<void> onCustomCollection(int fd, const Vector<String16>& args);
+ virtual android::base::Result<void> onCustomCollection(int fd,
+ const Vector<android::String16>& args);
// Generates a dump from the boot-time and periodic collection events.
virtual android::base::Result<void> onDump(int fd);
diff --git a/cpp/watchdog/server/src/WatchdogProcessService.cpp b/cpp/watchdog/server/src/WatchdogProcessService.cpp
index 7af7ecc..952c83e 100644
--- a/cpp/watchdog/server/src/WatchdogProcessService.cpp
+++ b/cpp/watchdog/server/src/WatchdogProcessService.cpp
@@ -48,6 +48,7 @@
using aawi::ICarWatchdogServiceForSystem;
using ::android::IBinder;
using ::android::sp;
+using ::android::String16;
using ::android::base::Error;
using ::android::base::GetProperty;
using ::android::base::ReadFileToString;
diff --git a/cpp/watchdog/server/src/WatchdogProcessService.h b/cpp/watchdog/server/src/WatchdogProcessService.h
index c4ae2cb..288f99e 100644
--- a/cpp/watchdog/server/src/WatchdogProcessService.h
+++ b/cpp/watchdog/server/src/WatchdogProcessService.h
@@ -50,7 +50,8 @@
android::base::Result<void> start();
void terminate();
- virtual android::base::Result<void> dump(int fd, const android::Vector<String16>& args);
+ virtual android::base::Result<void> dump(int fd,
+ const android::Vector<android::String16>& args);
void doHealthCheck(int what);
virtual android::base::Result<void> registerWatchdogServiceHelper(
diff --git a/cpp/watchdog/server/src/WatchdogServiceHelper.cpp b/cpp/watchdog/server/src/WatchdogServiceHelper.cpp
index f89942e..03f3305 100644
--- a/cpp/watchdog/server/src/WatchdogServiceHelper.cpp
+++ b/cpp/watchdog/server/src/WatchdogServiceHelper.cpp
@@ -34,7 +34,6 @@
using aawi::PackageIoOveruseStats;
using ::android::IBinder;
using ::android::sp;
-using ::android::String16;
using ::android::wp;
using ::android::base::Error;
using ::android::base::Result;
@@ -180,21 +179,17 @@
Status WatchdogServiceHelper::getPackageInfosForUids(
const std::vector<int32_t>& uids, const std::vector<std::string>& vendorPackagePrefixes,
std::vector<PackageInfo>* packageInfos) {
- /*
- * The expected number of vendor package prefixes is in the order of 10s. Thus the overhead of
- * forwarding these in each get call is very low.
- */
- std::vector<String16> prefixes;
- for (const auto& prefix : vendorPackagePrefixes) {
- prefixes.push_back(String16(prefix.c_str()));
- }
sp<ICarWatchdogServiceForSystem> service;
if (std::shared_lock readLock(mRWMutex); mService == nullptr) {
return fromExceptionCode(Status::EX_ILLEGAL_STATE, "Watchdog service is not initialized");
} else {
service = mService;
}
- return service->getPackageInfosForUids(uids, prefixes, packageInfos);
+ /*
+ * The expected number of vendor package prefixes is in the order of 10s. Thus the overhead of
+ * forwarding these in each get call is very low.
+ */
+ return service->getPackageInfosForUids(uids, vendorPackagePrefixes, packageInfos);
}
Status WatchdogServiceHelper::latestIoOveruseStats(
diff --git a/cpp/watchdog/server/tests/IoOveruseConfigsTest.cpp b/cpp/watchdog/server/tests/IoOveruseConfigsTest.cpp
index f803566..433f027 100644
--- a/cpp/watchdog/server/tests/IoOveruseConfigsTest.cpp
+++ b/cpp/watchdog/server/tests/IoOveruseConfigsTest.cpp
@@ -18,7 +18,6 @@
#include <android-base/strings.h>
#include <gmock/gmock.h>
-#include <utils/String8.h>
#include <inttypes.h>
@@ -27,8 +26,6 @@
namespace watchdog {
using ::android::sp;
-using ::android::String16;
-using ::android::String8;
using ::android::automotive::watchdog::internal::ApplicationCategoryType;
using ::android::automotive::watchdog::internal::ComponentType;
using ::android::automotive::watchdog::internal::IoOveruseAlertThreshold;
@@ -86,7 +83,7 @@
PerStateIoOveruseThreshold toPerStateIoOveruseThreshold(const std::string& name,
const PerStateBytes& perStateBytes) {
PerStateIoOveruseThreshold threshold;
- threshold.name = String16(String8(name.c_str()));
+ threshold.name = name;
threshold.perStateWriteBytes = perStateBytes;
return threshold;
}
@@ -101,7 +98,7 @@
const int64_t bgBytes,
const int64_t garageModeBytes) {
PerStateIoOveruseThreshold threshold;
- threshold.name = String16(String8(name.c_str()));
+ threshold.name = name;
threshold.perStateWriteBytes = toPerStateBytes(fgBytes, bgBytes, garageModeBytes);
return threshold;
}
@@ -133,23 +130,13 @@
const char* packageName, const ComponentType componentType,
const ApplicationCategoryType appCategoryType = ApplicationCategoryType::OTHERS) {
PackageInfo packageInfo;
- packageInfo.packageIdentifier.name = String16(packageName);
+ packageInfo.packageIdentifier.name = packageName;
packageInfo.uidType = UidType::APPLICATION;
packageInfo.componentType = componentType;
packageInfo.appCategoryType = appCategoryType;
return packageInfo;
}
-std::vector<String16> toString16Vector(const std::vector<std::string>& values) {
- std::vector<String16> output;
- for (const auto& v : values) {
- if (!v.empty()) {
- output.emplace_back(String16(String8(v.c_str())));
- }
- }
- return output;
-}
-
ResourceOveruseConfiguration constructResourceOveruseConfig(
const ComponentType type, const std::vector<std::string>&& safeToKill,
const std::vector<std::string>&& vendorPrefixes,
@@ -157,8 +144,8 @@
const IoOveruseConfiguration& ioOveruseConfiguration) {
ResourceOveruseConfiguration resourceOveruseConfig;
resourceOveruseConfig.componentType = type;
- resourceOveruseConfig.safeToKillPackages = toString16Vector(safeToKill);
- resourceOveruseConfig.vendorPackagePrefixes = toString16Vector(vendorPrefixes);
+ resourceOveruseConfig.safeToKillPackages = safeToKill;
+ resourceOveruseConfig.vendorPackagePrefixes = vendorPrefixes;
resourceOveruseConfig.packageMetadata = packageMetadata;
ResourceSpecificConfiguration config;
config.set<ResourceSpecificConfiguration::ioOveruseConfiguration>(ioOveruseConfiguration);
@@ -318,7 +305,7 @@
ASSERT_RESULT_OK(ioOveruseConfigs.update(
{systemResourceConfig, vendorResourceConfig, thirdPartyResourceConfig}));
- vendorResourceConfig.vendorPackagePrefixes.push_back(String16(String8("vendorPkgB")));
+ vendorResourceConfig.vendorPackagePrefixes.push_back("vendorPkgB");
std::vector<ResourceOveruseConfiguration> expected = {systemResourceConfig,
vendorResourceConfig,
thirdPartyResourceConfig};
@@ -755,7 +742,7 @@
const auto ioOveruseConfigs = sampleIoOveruseConfigs();
PackageInfo packageInfo;
- packageInfo.packageIdentifier.name = String16("native package");
+ packageInfo.packageIdentifier.name = "native package";
packageInfo.uidType = UidType::NATIVE;
packageInfo.componentType = ComponentType::SYSTEM;
diff --git a/cpp/watchdog/server/tests/IoOveruseMonitorTest.cpp b/cpp/watchdog/server/tests/IoOveruseMonitorTest.cpp
index 38a0040..4ee94eb 100644
--- a/cpp/watchdog/server/tests/IoOveruseMonitorTest.cpp
+++ b/cpp/watchdog/server/tests/IoOveruseMonitorTest.cpp
@@ -31,12 +31,12 @@
namespace watchdog {
constexpr size_t kTestMonitorBufferSize = 3;
+constexpr uint64_t KTestMinSyncWrittenBytes = 5'000;
constexpr double kTestIoOveruseWarnPercentage = 80;
constexpr std::chrono::seconds kTestMonitorInterval = 5s;
using ::android::IPCThreadState;
using ::android::RefBase;
-using ::android::String16;
using ::android::automotive::watchdog::internal::ApplicationCategoryType;
using ::android::automotive::watchdog::internal::ComponentType;
using ::android::automotive::watchdog::internal::IoOveruseAlertThreshold;
@@ -69,7 +69,7 @@
PackageIdentifier constructPackageIdentifier(const char* packageName, const int32_t uid) {
PackageIdentifier packageIdentifier;
- packageIdentifier.name = String16(String8(packageName));
+ packageIdentifier.name = packageName;
packageIdentifier.uid = uid;
return packageIdentifier;
}
@@ -175,6 +175,7 @@
if (const auto result = mIoOveruseMonitor->init(); !result.ok()) {
return result;
}
+ mIoOveruseMonitor->mMinSyncWrittenBytes = KTestMinSyncWrittenBytes;
mIoOveruseMonitor->mPeriodicMonitorBufferSize = kTestMonitorBufferSize;
mIoOveruseMonitor->mIoOveruseWarnPercentage = kTestIoOveruseWarnPercentage;
mIoOveruseMonitor->mIoOveruseConfigs = ioOveruseConfigs;
@@ -220,23 +221,22 @@
};
TEST_F(IoOveruseMonitorTest, TestOnPeriodicCollection) {
- std::unordered_map<uid_t, PackageInfo> packageInfoMapping = {
- {1001000,
- constructPackageInfo(
- /*packageName=*/"system.daemon", /*uid=*/1001000, UidType::NATIVE)},
- {1112345,
- constructPackageInfo(
- /*packageName=*/"com.android.google.package", /*uid=*/1112345,
- UidType::APPLICATION)},
- {1212345,
- constructPackageInfo(
- /*packageName=*/"com.android.google.package", /*uid=*/1212345,
- UidType::APPLICATION)},
- {1113999,
- constructPackageInfo(
- /*packageName=*/"com.android.google.package", /*uid=*/1113999,
- UidType::APPLICATION)},
- };
+ std::unordered_map<uid_t, PackageInfo> packageInfoMapping =
+ {{1001000,
+ constructPackageInfo(
+ /*packageName=*/"system.daemon", /*uid=*/1001000, UidType::NATIVE)},
+ {1112345,
+ constructPackageInfo(
+ /*packageName=*/"com.android.google.package", /*uid=*/1112345,
+ UidType::APPLICATION)},
+ {1212345,
+ constructPackageInfo(
+ /*packageName=*/"com.android.google.package", /*uid=*/1212345,
+ UidType::APPLICATION)},
+ {1113999,
+ constructPackageInfo(
+ /*packageName=*/"com.android.google.package", /*uid=*/1113999,
+ UidType::APPLICATION)}};
ON_CALL(*mMockPackageInfoResolver, getPackageInfosForUids(_))
.WillByDefault(Return(packageInfoMapping));
mMockIoOveruseConfigs->injectPackageConfigs({
@@ -272,24 +272,25 @@
ASSERT_RESULT_OK(
mIoOveruseMonitor->onPeriodicCollection(currentTime, mockUidIoStats, nullptr, nullptr));
- std::vector<PackageIoOveruseStats> expectedIoOveruseStats = {
- constructPackageIoOveruseStats(/*uid*=*/1001000, /*shouldNotify=*/false,
- /*isKillable=*/false, /*remaining=*/
- constructPerStateBytes(10'000, 20'000, 100'000),
- /*written=*/constructPerStateBytes(70'000, 20'000, 0),
- /*totalOveruses=*/0, startTime, durationInSeconds),
- constructPackageIoOveruseStats(/*uid*=*/1112345, /*shouldNotify=*/false,
- /*isKillable=*/true, /*remaining=*/
- constructPerStateBytes(35'000, 15'000, 100'000),
- /*written=*/constructPerStateBytes(35'000, 15'000, 0),
- /*totalOveruses=*/0, startTime, durationInSeconds),
- // Exceeds threshold.
- constructPackageIoOveruseStats(/*uid*=*/1212345, /*shouldNotify=*/true,
- /*isKillable=*/true,
- /*remaining=*/constructPerStateBytes(0, 10'000, 100'000),
- /*written=*/constructPerStateBytes(70'000, 20'000, 0),
- /*totalOveruses=*/1, startTime, durationInSeconds),
- };
+
+ std::vector<PackageIoOveruseStats> expectedIoOveruseStats =
+ {constructPackageIoOveruseStats(/*uid*=*/1001000, /*shouldNotify=*/false,
+ /*isKillable=*/false, /*remaining=*/
+ constructPerStateBytes(10'000, 20'000, 100'000),
+ /*written=*/constructPerStateBytes(70'000, 20'000, 0),
+ /*totalOveruses=*/0, startTime, durationInSeconds),
+ constructPackageIoOveruseStats(/*uid*=*/1112345, /*shouldNotify=*/false,
+ /*isKillable=*/true, /*remaining=*/
+ constructPerStateBytes(35'000, 15'000, 100'000),
+ /*written=*/constructPerStateBytes(35'000, 15'000, 0),
+ /*totalOveruses=*/0, startTime, durationInSeconds),
+ // Exceeds threshold.
+ constructPackageIoOveruseStats(/*uid*=*/1212345, /*shouldNotify=*/true,
+ /*isKillable=*/true,
+ /*remaining=*/
+ constructPerStateBytes(0, 10'000, 100'000),
+ /*written=*/constructPerStateBytes(70'000, 20'000, 0),
+ /*totalOveruses=*/1, startTime, durationInSeconds)};
EXPECT_THAT(actualIoOveruseStats, UnorderedElementsAreArray(expectedIoOveruseStats))
<< "Expected: " << toString(expectedIoOveruseStats)
<< "\nActual: " << toString(actualIoOveruseStats);
@@ -384,15 +385,171 @@
<< "\nActual: " << toString(actualIoOveruseStats);
}
+TEST_F(IoOveruseMonitorTest, TestOnPeriodicCollectionWithZeroWriteBytes) {
+ sp<MockUidIoStats> mockUidIoStats = new MockUidIoStats();
+ mockUidIoStats->expectDeltaStats(
+ {{1001000, IoUsage(10, 0, /*fgWrBytes=*/0, /*bgWrBytes=*/0, 1, 0)},
+ {1112345, IoUsage(0, 20, /*fgWrBytes=*/0, /*bgWrBytes=*/0, 0, 0)},
+ {1212345, IoUsage(0, 00, /*fgWrBytes=*/0, /*bgWrBytes=*/0, 0, 1)}});
+
+ EXPECT_CALL(*mMockPackageInfoResolver, getPackageInfosForUids(_)).Times(0);
+ EXPECT_CALL(*mMockIoOveruseConfigs, fetchThreshold(_)).Times(0);
+ EXPECT_CALL(*mMockIoOveruseConfigs, isSafeToKill(_)).Times(0);
+ EXPECT_CALL(*mMockWatchdogServiceHelper, latestIoOveruseStats(_)).Times(0);
+
+ ASSERT_RESULT_OK(
+ mIoOveruseMonitor->onPeriodicCollection(std::chrono::system_clock::to_time_t(
+ std::chrono::system_clock::now()),
+ mockUidIoStats, nullptr, nullptr));
+}
+
+TEST_F(IoOveruseMonitorTest, TestOnPeriodicCollectionWithSmallWrittenBytes) {
+ std::unordered_map<uid_t, PackageInfo> packageInfoMapping =
+ {{1001000,
+ constructPackageInfo(
+ /*packageName=*/"system.daemon", /*uid=*/1001000, UidType::NATIVE)},
+ {1112345,
+ constructPackageInfo(
+ /*packageName=*/"com.android.google.package", /*uid=*/1112345,
+ UidType::APPLICATION)},
+ {1212345,
+ constructPackageInfo(
+ /*packageName=*/"com.android.google.package", /*uid=*/1212345,
+ UidType::APPLICATION)},
+ {1312345,
+ constructPackageInfo(
+ /*packageName=*/"com.android.google.package", /*uid=*/1312345,
+ UidType::APPLICATION)}};
+ EXPECT_CALL(*mMockPackageInfoResolver, getPackageInfosForUids(_))
+ .WillRepeatedly(Return(packageInfoMapping));
+ mMockIoOveruseConfigs->injectPackageConfigs(
+ {{"system.daemon",
+ {constructPerStateBytes(/*fgBytes=*/80'000, /*bgBytes=*/40'000, /*gmBytes=*/100'000),
+ /*isSafeToKill=*/false}},
+ {"com.android.google.package",
+ {constructPerStateBytes(/*fgBytes=*/70'000, /*bgBytes=*/30'000, /*gmBytes=*/100'000),
+ /*isSafeToKill=*/true}}});
+
+ sp<MockUidIoStats> mockUidIoStats = new MockUidIoStats();
+ /*
+ * UID 1212345 current written bytes < |KTestMinSyncWrittenBytes| so the UID's stats are not
+ * synced.
+ */
+ mockUidIoStats->expectDeltaStats(
+ {{1001000, IoUsage(10, 0, /*fgWrBytes=*/59'200, /*bgWrBytes=*/0, 1, 0)},
+ {1112345, IoUsage(0, 20, /*fgWrBytes=*/0, /*bgWrBytes=*/25'200, 0, 0)},
+ {1212345, IoUsage(0, 00, /*fgWrBytes=*/300, /*bgWrBytes=*/600, 0, 1)},
+ {1312345, IoUsage(0, 00, /*fgWrBytes=*/51'200, /*bgWrBytes=*/0, 0, 1)}});
+
+ std::vector<PackageIoOveruseStats> actualIoOveruseStats;
+ EXPECT_CALL(*mMockWatchdogServiceHelper, latestIoOveruseStats(_))
+ .WillOnce(DoAll(SaveArg<0>(&actualIoOveruseStats), Return(Status::ok())));
+
+ time_t currentTime = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
+ const auto [startTime, durationInSeconds] = calculateStartAndDuration(currentTime);
+
+ ASSERT_RESULT_OK(
+ mIoOveruseMonitor->onPeriodicCollection(currentTime, mockUidIoStats, nullptr, nullptr));
+
+ std::vector<PackageIoOveruseStats> expectedIoOveruseStats =
+ {constructPackageIoOveruseStats(/*uid*=*/1001000, /*shouldNotify=*/false,
+ /*isKillable=*/false, /*remaining=*/
+ constructPerStateBytes(20'800, 40'000, 100'000),
+ /*written=*/
+ constructPerStateBytes(59'200, 0, 0),
+ /*totalOveruses=*/0, startTime, durationInSeconds),
+ constructPackageIoOveruseStats(/*uid*=*/1112345, /*shouldNotify=*/true,
+ /*isKillable=*/true, /*remaining=*/
+ constructPerStateBytes(70'000, 4'800, 100'000),
+ /*written=*/constructPerStateBytes(0, 25'200, 0),
+ /*totalOveruses=*/0, startTime, durationInSeconds),
+ constructPackageIoOveruseStats(/*uid*=*/1312345, /*shouldNotify=*/false,
+ /*isKillable=*/true, /*remaining=*/
+ constructPerStateBytes(18'800, 30'000, 100'000),
+ /*written=*/constructPerStateBytes(51'200, 0, 0),
+ /*totalOveruses=*/0, startTime, durationInSeconds)};
+
+ EXPECT_THAT(actualIoOveruseStats, UnorderedElementsAreArray(expectedIoOveruseStats))
+ << "Expected: " << toString(expectedIoOveruseStats)
+ << "\nActual: " << toString(actualIoOveruseStats);
+
+ actualIoOveruseStats.clear();
+ EXPECT_CALL(*mMockWatchdogServiceHelper, latestIoOveruseStats(_))
+ .WillOnce(DoAll(SaveArg<0>(&actualIoOveruseStats), Return(Status::ok())));
+
+ /*
+ * UID 1001000 current written bytes is < |kTestMinSyncWrittenBytes| but exceeds warn threshold
+ * but not killable so the UID's stats are not synced.
+ * UID 1112345 current written bytes is < |kTestMinSyncWrittenBytes| but exceeds threshold so
+ * the UID's stats are synced.
+ * UID 1212345 current written bytes is < |kTestMinSyncWrittenBytes| but total written bytes
+ * since last synced > |kTestMinSyncWrittenBytes| so the UID's stats are synced.
+ * UID 1312345 current written bytes is < |kTestMinSyncWrittenBytes| but exceeds warn threshold
+ * and killable so the UID's stat are synced.
+ */
+ mockUidIoStats->expectDeltaStats(
+ {{1001000,
+ IoUsage(10, 0, /*fgWrBytes=*/KTestMinSyncWrittenBytes - 100, /*bgWrBytes=*/0, 1, 0)},
+ {1112345,
+ IoUsage(0, 20, /*fgWrBytes=*/0, /*bgWrBytes=*/KTestMinSyncWrittenBytes - 100, 0, 0)},
+ {1212345,
+ IoUsage(0, 00, /*fgWrBytes=*/KTestMinSyncWrittenBytes - 300, /*bgWrBytes=*/0, 0, 1)},
+ {1312345,
+ IoUsage(0, 00, /*fgWrBytes=*/KTestMinSyncWrittenBytes - 100, /*bgWrBytes=*/0, 0,
+ 1)}});
+
+ ASSERT_RESULT_OK(
+ mIoOveruseMonitor->onPeriodicCollection(currentTime, mockUidIoStats, nullptr, nullptr));
+
+ expectedIoOveruseStats =
+ {constructPackageIoOveruseStats(/*uid*=*/1112345, /*shouldNotify=*/true,
+ /*isKillable=*/true, /*remaining=*/
+ constructPerStateBytes(70'000, 0, 100'000),
+ /*written=*/constructPerStateBytes(0, 30'100, 0),
+ /*totalOveruses=*/1, startTime, durationInSeconds),
+ constructPackageIoOveruseStats(/*uid*=*/1212345, /*shouldNotify=*/false,
+ /*isKillable=*/true, /*remaining=*/
+ constructPerStateBytes(65'000, 29'400, 100'000),
+ /*written=*/constructPerStateBytes(5'000, 600, 0),
+ /*totalOveruses=*/0, startTime, durationInSeconds),
+ constructPackageIoOveruseStats(/*uid*=*/1312345, /*shouldNotify=*/true,
+ /*isKillable=*/true, /*remaining=*/
+ constructPerStateBytes(13'900, 30'000, 100'000),
+ /*written=*/constructPerStateBytes(56'100, 0, 0),
+ /*totalOveruses=*/0, startTime, durationInSeconds)};
+ EXPECT_THAT(actualIoOveruseStats, UnorderedElementsAreArray(expectedIoOveruseStats))
+ << "Expected: " << toString(expectedIoOveruseStats)
+ << "\nActual: " << toString(actualIoOveruseStats);
+}
+
+TEST_F(IoOveruseMonitorTest, TestOnPeriodicCollectionWithNoPackageInfo) {
+ sp<MockUidIoStats> mockUidIoStats = new MockUidIoStats();
+ mockUidIoStats->expectDeltaStats(
+ {{1001000, IoUsage(0, 0, /*fgWrBytes=*/70'000, /*bgWrBytes=*/20'000, 0, 0)},
+ {1112345, IoUsage(0, 0, /*fgWrBytes=*/35'000, /*bgWrBytes=*/15'000, 0, 0)},
+ {1212345, IoUsage(0, 0, /*fgWrBytes=*/70'000, /*bgWrBytes=*/20'000, 0, 0)}});
+
+ ON_CALL(*mMockPackageInfoResolver, getPackageInfosForUids(_))
+ .WillByDefault(Return(std::unordered_map<uid_t, PackageInfo>{}));
+
+ EXPECT_CALL(*mMockIoOveruseConfigs, fetchThreshold(_)).Times(0);
+ EXPECT_CALL(*mMockIoOveruseConfigs, isSafeToKill(_)).Times(0);
+ EXPECT_CALL(*mMockWatchdogServiceHelper, latestIoOveruseStats(_)).Times(0);
+
+ ASSERT_RESULT_OK(
+ mIoOveruseMonitor->onPeriodicCollection(std::chrono::system_clock::to_time_t(
+ std::chrono::system_clock::now()),
+ mockUidIoStats, nullptr, nullptr));
+}
+
TEST_F(IoOveruseMonitorTest, TestOnPeriodicMonitor) {
- IIoOveruseConfigs::IoOveruseAlertThresholdSet alertThresholds = {
- toIoOveruseAlertThreshold(
- /*durationInSeconds=*/10, /*writtenBytesPerSecond=*/15'360),
- toIoOveruseAlertThreshold(
- /*durationInSeconds=*/17, /*writtenBytesPerSecond=*/10'240),
- toIoOveruseAlertThreshold(
- /*durationInSeconds=*/23, /*writtenBytesPerSecond=*/7'168),
- };
+ IIoOveruseConfigs::IoOveruseAlertThresholdSet alertThresholds =
+ {toIoOveruseAlertThreshold(
+ /*durationInSeconds=*/10, /*writtenBytesPerSecond=*/15'360),
+ toIoOveruseAlertThreshold(
+ /*durationInSeconds=*/17, /*writtenBytesPerSecond=*/10'240),
+ toIoOveruseAlertThreshold(
+ /*durationInSeconds=*/23, /*writtenBytesPerSecond=*/7'168)};
ON_CALL(*mMockIoOveruseConfigs, systemWideAlertThresholds())
.WillByDefault(ReturnRef(alertThresholds));
diff --git a/cpp/watchdog/server/tests/MockCarWatchdogServiceForSystem.h b/cpp/watchdog/server/tests/MockCarWatchdogServiceForSystem.h
index 8ea4ba3..c8f1614 100644
--- a/cpp/watchdog/server/tests/MockCarWatchdogServiceForSystem.h
+++ b/cpp/watchdog/server/tests/MockCarWatchdogServiceForSystem.h
@@ -24,7 +24,6 @@
#include <binder/Status.h>
#include <gmock/gmock.h>
#include <utils/RefBase.h>
-#include <utils/String16.h>
#include <utils/StrongPointer.h>
namespace android {
@@ -45,7 +44,7 @@
(int32_t, android::automotive::watchdog::internal::TimeoutLength), (override));
MOCK_METHOD(android::binder::Status, prepareProcessTermination, (), (override));
MOCK_METHOD(android::binder::Status, getPackageInfosForUids,
- (const std::vector<int32_t>&, const std::vector<android::String16>&,
+ (const std::vector<int32_t>&, const std::vector<std::string>&,
std::vector<android::automotive::watchdog::internal::PackageInfo>*),
(override));
MOCK_METHOD(
diff --git a/cpp/watchdog/server/tests/MockDataProcessor.h b/cpp/watchdog/server/tests/MockDataProcessor.h
index e120ff7..34a572e 100644
--- a/cpp/watchdog/server/tests/MockDataProcessor.h
+++ b/cpp/watchdog/server/tests/MockDataProcessor.h
@@ -28,7 +28,7 @@
class MockDataProcessor : virtual public IDataProcessorInterface {
public:
MockDataProcessor() {
- ON_CALL(*this, name()).WillByDefault(::testing::Return("MockedDataProcessor"));
+ EXPECT_CALL(*this, name()).WillRepeatedly(::testing::Return("MockedDataProcessor"));
}
MOCK_METHOD(std::string, name, (), (override));
MOCK_METHOD(android::base::Result<void>, init, (), (override));
diff --git a/cpp/watchdog/server/tests/MockIoOveruseConfigs.h b/cpp/watchdog/server/tests/MockIoOveruseConfigs.h
index 67c1edf..cc88c1d 100644
--- a/cpp/watchdog/server/tests/MockIoOveruseConfigs.h
+++ b/cpp/watchdog/server/tests/MockIoOveruseConfigs.h
@@ -71,8 +71,7 @@
.WillByDefault([perPackageConfig = perPackageConfig](
const android::automotive::watchdog::internal::PackageInfo&
packageInfo) {
- const std::string packageName =
- std::string(String8(packageInfo.packageIdentifier.name));
+ const std::string packageName = packageInfo.packageIdentifier.name;
if (const auto it = perPackageConfig.find(packageName);
it != perPackageConfig.end()) {
return it->second.threshold;
@@ -83,8 +82,7 @@
.WillByDefault([perPackageConfig = perPackageConfig](
const android::automotive::watchdog::internal::PackageInfo&
packageInfo) {
- const std::string packageName =
- std::string(String8(packageInfo.packageIdentifier.name));
+ const std::string packageName = packageInfo.packageIdentifier.name;
if (const auto it = perPackageConfig.find(packageName);
it != perPackageConfig.end()) {
return it->second.isSafeToKill;
diff --git a/cpp/watchdog/server/tests/MockIoOveruseMonitor.h b/cpp/watchdog/server/tests/MockIoOveruseMonitor.h
index 221b1a1..cb7d0c5 100644
--- a/cpp/watchdog/server/tests/MockIoOveruseMonitor.h
+++ b/cpp/watchdog/server/tests/MockIoOveruseMonitor.h
@@ -35,6 +35,7 @@
ON_CALL(*this, name()).WillByDefault(::testing::Return("MockIoOveruseMonitor"));
}
~MockIoOveruseMonitor() {}
+ MOCK_METHOD(bool, isInitialized, (), (override));
MOCK_METHOD(android::base::Result<void>, updateResourceOveruseConfigurations,
(const std::vector<
android::automotive::watchdog::internal::ResourceOveruseConfiguration>&),
diff --git a/cpp/watchdog/server/tests/MockWatchdogPerfService.h b/cpp/watchdog/server/tests/MockWatchdogPerfService.h
index 5d14837..60d165b 100644
--- a/cpp/watchdog/server/tests/MockWatchdogPerfService.h
+++ b/cpp/watchdog/server/tests/MockWatchdogPerfService.h
@@ -36,7 +36,7 @@
MOCK_METHOD(void, terminate, (), (override));
MOCK_METHOD(android::base::Result<void>, onBootFinished, (), (override));
MOCK_METHOD(android::base::Result<void>, onCustomCollection,
- (int fd, const Vector<String16>& args), (override));
+ (int fd, const Vector<android::String16>& args), (override));
MOCK_METHOD(android::base::Result<void>, onDump, (int fd), (override));
};
diff --git a/cpp/watchdog/server/tests/MockWatchdogProcessService.h b/cpp/watchdog/server/tests/MockWatchdogProcessService.h
index 8c35f10..b720d91 100644
--- a/cpp/watchdog/server/tests/MockWatchdogProcessService.h
+++ b/cpp/watchdog/server/tests/MockWatchdogProcessService.h
@@ -40,7 +40,7 @@
class MockWatchdogProcessService : public WatchdogProcessService {
public:
MockWatchdogProcessService() : WatchdogProcessService(nullptr) {}
- MOCK_METHOD(android::base::Result<void>, dump, (int fd, const Vector<String16>& args),
+ MOCK_METHOD(android::base::Result<void>, dump, (int fd, const Vector<android::String16>& args),
(override));
MOCK_METHOD(android::base::Result<void>, registerWatchdogServiceHelper,
(const android::sp<IWatchdogServiceHelperInterface>& helper), (override));
diff --git a/cpp/watchdog/server/tests/PackageInfoResolverTest.cpp b/cpp/watchdog/server/tests/PackageInfoResolverTest.cpp
index 92fc5b0..f2fe3a6 100644
--- a/cpp/watchdog/server/tests/PackageInfoResolverTest.cpp
+++ b/cpp/watchdog/server/tests/PackageInfoResolverTest.cpp
@@ -23,13 +23,11 @@
#include <android/automotive/watchdog/internal/UidType.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
-#include <utils/String16.h>
namespace android {
namespace automotive {
namespace watchdog {
-using ::android::String16;
using ::android::automotive::watchdog::internal::ApplicationCategoryType;
using ::android::automotive::watchdog::internal::ComponentType;
using ::android::automotive::watchdog::internal::PackageInfo;
@@ -53,9 +51,9 @@
PackageInfo constructPackageInfo(const char* packageName, int32_t uid, UidType uidType,
ComponentType componentType,
ApplicationCategoryType appCategoryType,
- std::vector<String16> sharedUidPackages = {}) {
+ std::vector<std::string> sharedUidPackages = {}) {
PackageInfo packageInfo;
- packageInfo.packageIdentifier.name = String16(packageName);
+ packageInfo.packageIdentifier.name = packageName;
packageInfo.packageIdentifier.uid = uid;
packageInfo.uidType = uidType;
packageInfo.componentType = componentType;
@@ -209,7 +207,7 @@
{6100,
constructPackageInfo("shared:system.package.A", 6100, UidType::NATIVE,
ComponentType::SYSTEM, ApplicationCategoryType::OTHERS,
- {String16("system.pkg.1"), String16("system.pkg.2")})},
+ {"system.pkg.1", "system.pkg.2"})},
{7700,
constructPackageInfo("system.package.B", 7700, UidType::NATIVE, ComponentType::SYSTEM,
ApplicationCategoryType::OTHERS)},
diff --git a/cpp/watchdog/server/tests/UidIoStatsTest.cpp b/cpp/watchdog/server/tests/UidIoStatsTest.cpp
index 2326381..6e88ed5 100644
--- a/cpp/watchdog/server/tests/UidIoStatsTest.cpp
+++ b/cpp/watchdog/server/tests/UidIoStatsTest.cpp
@@ -17,6 +17,7 @@
#include "UidIoStats.h"
#include <android-base/file.h>
+#include <android-base/stringprintf.h>
#include <gmock/gmock.h>
#include <unordered_map>
@@ -25,25 +26,37 @@
namespace automotive {
namespace watchdog {
+using ::android::base::StringAppendF;
using ::android::base::WriteStringToFile;
+using ::testing::UnorderedElementsAreArray;
+
+namespace {
+
+std::string toString(std::unordered_map<uid_t, UidIoUsage> usages) {
+ std::string buffer;
+ for (const auto& [uid, usage] : usages) {
+ StringAppendF(&buffer, "{%s}\n", usage.toString().c_str());
+ }
+ return buffer;
+}
+
+} // namespace
TEST(UidIoStatsTest, TestValidStatFile) {
// Format: uid fgRdChar fgWrChar fgRdBytes fgWrBytes bgRdChar bgWrChar bgRdBytes bgWrBytes
// fgFsync bgFsync
- constexpr char firstSnapshot[] =
- "1001234 5000 1000 3000 500 0 0 0 0 20 0\n"
- "1005678 500 100 30 50 300 400 100 200 45 60\n"
- "1009 0 0 0 0 40000 50000 20000 30000 0 300\n"
- "1001000 4000 3000 2000 1000 400 300 200 100 50 10\n";
- std::unordered_map<uid_t, UidIoUsage> expectedFirstUsage = {
- {1001234,
- {.uid = 1001234,
- .ios = {/*fgRdBytes=*/3000, /*bgRdBytes=*/0, /*fgWrBytes=*/500,
- /*bgWrBytes=*/0, /*fgFsync=*/20, /*bgFsync=*/0}}},
- {1005678, {.uid = 1005678, .ios = {30, 100, 50, 200, 45, 60}}},
- {1009, {.uid = 1009, .ios = {0, 20000, 0, 30000, 0, 300}}},
- {1001000, {.uid = 1001000, .ios = {2000, 200, 1000, 100, 50, 10}}},
- };
+ constexpr char firstSnapshot[] = "1001234 5000 1000 3000 500 0 0 0 0 20 0\n"
+ "1005678 500 100 30 50 300 400 100 200 45 60\n"
+ "1009 0 0 0 0 40000 50000 20000 30000 0 300\n"
+ "1001000 4000 3000 2000 1000 400 300 200 100 50 10\n";
+ std::unordered_map<uid_t, UidIoUsage> expectedFirstUsage =
+ {{1001234,
+ {.uid = 1001234,
+ .ios = {/*fgRdBytes=*/3000, /*bgRdBytes=*/0, /*fgWrBytes=*/500,
+ /*bgWrBytes=*/0, /*fgFsync=*/20, /*bgFsync=*/0}}},
+ {1005678, {.uid = 1005678, .ios = {30, 100, 50, 200, 45, 60}}},
+ {1009, {.uid = 1009, .ios = {0, 20000, 0, 30000, 0, 300}}},
+ {1001000, {.uid = 1001000, .ios = {2000, 200, 1000, 100, 50, 10}}}};
TemporaryFile tf;
ASSERT_NE(tf.fd, -1);
ASSERT_TRUE(WriteStringToFile(firstSnapshot, tf.path));
@@ -53,62 +66,38 @@
ASSERT_RESULT_OK(uidIoStats.collect());
const auto& actualFirstUsage = uidIoStats.deltaStats();
- EXPECT_EQ(expectedFirstUsage.size(), actualFirstUsage.size());
+ EXPECT_THAT(actualFirstUsage, UnorderedElementsAreArray(expectedFirstUsage))
+ << "Expected: " << toString(expectedFirstUsage)
+ << "Actual: " << toString(actualFirstUsage);
- for (const auto& it : expectedFirstUsage) {
- if (actualFirstUsage.find(it.first) == actualFirstUsage.end()) {
- ADD_FAILURE() << "Expected uid " << it.first << " not found in the first snapshot";
- }
- const UidIoUsage& expected = it.second;
- const UidIoUsage& actual = actualFirstUsage.at(it.first);
- EXPECT_EQ(expected.uid, actual.uid);
- EXPECT_EQ(expected.ios, actual.ios)
- << "Unexpected I/O usage for uid " << it.first << " in first snapshot.\nExpected:\n"
- << expected.ios.toString() << "\nActual:\n"<< actual.ios.toString();
- }
-
- constexpr char secondSnapshot[] =
- "1001234 10000 2000 7000 950 0 0 0 0 45 0\n"
- "1005678 600 100 40 50 1000 1000 1000 600 50 70\n"
- "1003456 300 500 200 300 0 0 0 0 50 0\n"
- "1001000 400 300 200 100 40 30 20 10 5 1\n";
- std::unordered_map<uid_t, UidIoUsage> expectedSecondUsage = {
- {1001234,
- {.uid = 1001234,
- .ios = {/*fgRdBytes=*/4000, /*bgRdBytes=*/0,
- /*fgWrBytes=*/450, /*bgWrBytes=*/0, /*fgFsync=*/25,
- /*bgFsync=*/0}}},
- {1005678, {.uid = 1005678, .ios = {10, 900, 0, 400, 5, 10}}},
- {1003456, {.uid = 1003456, .ios = {200, 0, 300, 0, 50, 0}}},
- {1001000, {.uid = 1001000, .ios = {0, 0, 0, 0, 0, 0}}},
- };
+ constexpr char secondSnapshot[] = "1001234 10000 2000 7000 950 0 0 0 0 45 0\n"
+ "1005678 600 100 40 50 1000 1000 1000 600 50 70\n"
+ "1003456 300 500 200 300 0 0 0 0 50 0\n"
+ "1001000 400 300 200 100 40 30 20 10 5 1\n";
+ std::unordered_map<uid_t, UidIoUsage> expectedSecondUsage =
+ {{1001234,
+ {.uid = 1001234,
+ .ios = {/*fgRdBytes=*/4000, /*bgRdBytes=*/0,
+ /*fgWrBytes=*/450, /*bgWrBytes=*/0, /*fgFsync=*/25,
+ /*bgFsync=*/0}}},
+ {1005678, {.uid = 1005678, .ios = {10, 900, 0, 400, 5, 10}}},
+ {1003456, {.uid = 1003456, .ios = {200, 0, 300, 0, 50, 0}}}};
ASSERT_TRUE(WriteStringToFile(secondSnapshot, tf.path));
ASSERT_RESULT_OK(uidIoStats.collect());
const auto& actualSecondUsage = uidIoStats.deltaStats();
- EXPECT_EQ(expectedSecondUsage.size(), actualSecondUsage.size());
-
- for (const auto& it : expectedSecondUsage) {
- if (actualSecondUsage.find(it.first) == actualSecondUsage.end()) {
- ADD_FAILURE() << "Expected uid " << it.first << " not found in the second snapshot";
- }
- const UidIoUsage& expected = it.second;
- const UidIoUsage& actual = actualSecondUsage.at(it.first);
- EXPECT_EQ(expected.uid, actual.uid);
- EXPECT_EQ(expected.ios, actual.ios)
- << "Unexpected I/O usage for uid " << it.first << " in second snapshot:.\nExpected:\n"
- << expected.ios.toString() << "\nActual:\n"<< actual.ios.toString();
- }
+ EXPECT_THAT(actualSecondUsage, UnorderedElementsAreArray(expectedSecondUsage))
+ << "Expected: " << toString(expectedSecondUsage)
+ << "Actual: " << toString(actualSecondUsage);
}
TEST(UidIoStatsTest, TestErrorOnInvalidStatFile) {
// Format: uid fgRdChar fgWrChar fgRdBytes fgWrBytes bgRdChar bgWrChar bgRdBytes bgWrBytes
// fgFsync bgFsync
- constexpr char contents[] =
- "1001234 5000 1000 3000 500 0 0 0 0 20 0\n"
- "1005678 500 100 30 50 300 400 100 200 45 60\n"
- "1009012 0 0 0 0 40000 50000 20000 30000 0 300\n"
- "1001000 4000 3000 2000 1000 CORRUPTED DATA\n";
+ constexpr char contents[] = "1001234 5000 1000 3000 500 0 0 0 0 20 0\n"
+ "1005678 500 100 30 50 300 400 100 200 45 60\n"
+ "1009012 0 0 0 0 40000 50000 20000 30000 0 300\n"
+ "1001000 4000 3000 2000 1000 CORRUPTED DATA\n";
TemporaryFile tf;
ASSERT_NE(tf.fd, -1);
ASSERT_TRUE(WriteStringToFile(contents, tf.path));
diff --git a/cpp/watchdog/server/tests/WatchdogInternalHandlerTest.cpp b/cpp/watchdog/server/tests/WatchdogInternalHandlerTest.cpp
index 3db7e29..f5dd7dc 100644
--- a/cpp/watchdog/server/tests/WatchdogInternalHandlerTest.cpp
+++ b/cpp/watchdog/server/tests/WatchdogInternalHandlerTest.cpp
@@ -48,6 +48,7 @@
using aawi::PackageResourceOveruseAction;
using aawi::ResourceOveruseConfiguration;
using ::android::sp;
+using ::android::String16;
using ::android::base::Result;
using ::android::binder::Status;
using ::testing::_;
@@ -67,7 +68,7 @@
-> Result<void> { return Result<void>{}; }) {}
~MockWatchdogBinderMediator() {}
- MOCK_METHOD(status_t, dump, (int fd, const Vector<String16>& args), (override));
+ MOCK_METHOD(status_t, dump, (int fd, const Vector<android::String16>& args), (override));
};
class ScopedChangeCallingUid : public RefBase {
diff --git a/cpp/watchdog/server/tests/WatchdogPerfServiceTest.cpp b/cpp/watchdog/server/tests/WatchdogPerfServiceTest.cpp
index c7de12b..3873195 100644
--- a/cpp/watchdog/server/tests/WatchdogPerfServiceTest.cpp
+++ b/cpp/watchdog/server/tests/WatchdogPerfServiceTest.cpp
@@ -39,6 +39,7 @@
namespace automotive {
namespace watchdog {
+using ::android::String16;
using ::android::wp;
using ::android::automotive::watchdog::testing::LooperStub;
using ::android::base::Error;
diff --git a/cpp/watchdog/server/tests/WatchdogServiceHelperTest.cpp b/cpp/watchdog/server/tests/WatchdogServiceHelperTest.cpp
index 681d900..17edadf 100644
--- a/cpp/watchdog/server/tests/WatchdogServiceHelperTest.cpp
+++ b/cpp/watchdog/server/tests/WatchdogServiceHelperTest.cpp
@@ -22,7 +22,6 @@
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <utils/RefBase.h>
-#include <utils/String16.h>
namespace android {
namespace automotive {
@@ -41,7 +40,6 @@
using ::android::IBinder;
using ::android::RefBase;
using ::android::sp;
-using ::android::String16;
using ::android::base::Error;
using ::android::base::Result;
using ::android::binder::Status;
@@ -80,7 +78,7 @@
ComponentType componentType,
ApplicationCategoryType appCategoryType) {
PackageInfo packageInfo;
- packageInfo.packageIdentifier.name = String16(packageName);
+ packageInfo.packageIdentifier.name = packageName;
packageInfo.packageIdentifier.uid = uid;
packageInfo.uidType = uidType;
packageInfo.componentType = componentType;
@@ -329,7 +327,6 @@
TEST_F(WatchdogServiceHelperTest, TestGetPackageInfosForUids) {
std::vector<int32_t> uids = {1000};
std::vector<std::string> prefixesStr = {"vendor.package"};
- std::vector<String16> prefixesStr16 = {String16("vendor.package")};
std::vector<PackageInfo> expectedPackageInfo{
constructPackageInfo("vendor.package.A", 120000, UidType::NATIVE, ComponentType::VENDOR,
ApplicationCategoryType::OTHERS),
@@ -340,7 +337,7 @@
registerCarWatchdogService();
- EXPECT_CALL(*mMockCarWatchdogServiceForSystem, getPackageInfosForUids(uids, prefixesStr16, _))
+ EXPECT_CALL(*mMockCarWatchdogServiceForSystem, getPackageInfosForUids(uids, prefixesStr, _))
.WillOnce(DoAll(SetArgPointee<2>(expectedPackageInfo), Return(Status::ok())));
Status status =
diff --git a/service/src/com/android/car/CarShellCommand.java b/service/src/com/android/car/CarShellCommand.java
index ffa071e..6867ca5 100644
--- a/service/src/com/android/car/CarShellCommand.java
+++ b/service/src/com/android/car/CarShellCommand.java
@@ -236,6 +236,8 @@
PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
USER_BUILD_COMMAND_TO_PERMISSION_MAP.put(COMMAND_SET_GROUP_VOLUME,
PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
+ USER_BUILD_COMMAND_TO_PERMISSION_MAP.put(COMMAND_INJECT_KEY,
+ android.Manifest.permission.INJECT_EVENTS);
}
private static final String PARAM_DAY_MODE = "day";
@@ -1030,9 +1032,14 @@
private void injectKeyEvent(int action, int keyCode, int display) {
long currentTime = SystemClock.uptimeMillis();
if (action == KeyEvent.ACTION_DOWN) mKeyDownTime = currentTime;
- mCarInputService.onKeyEvent(
- new KeyEvent(/* downTime= */ mKeyDownTime, /* eventTime= */ currentTime,
- action, keyCode, /* repeat= */ 0), display);
+ long token = Binder.clearCallingIdentity();
+ try {
+ mCarInputService.injectKeyEvent(
+ new KeyEvent(/* downTime= */ mKeyDownTime, /* eventTime= */ currentTime,
+ action, keyCode, /* repeat= */ 0), display);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
private void injectRotary(String[] args, IndentingPrintWriter writer) {
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/service/src/com/android/car/watchdog/CarWatchdogService.java b/service/src/com/android/car/watchdog/CarWatchdogService.java
index 8b650ed..0b26e0a 100644
--- a/service/src/com/android/car/watchdog/CarWatchdogService.java
+++ b/service/src/com/android/car/watchdog/CarWatchdogService.java
@@ -63,8 +63,8 @@
* Service to implement CarWatchdogManager API.
*/
public final class CarWatchdogService extends ICarWatchdogService.Stub implements CarServiceBase {
- private static final boolean DEBUG = false; // STOPSHIP if true
- private static final String TAG = CarLog.tagFor(CarWatchdogService.class);
+ static final boolean DEBUG = false; // STOPSHIP if true
+ static final String TAG = CarLog.tagFor(CarWatchdogService.class);
private final Context mContext;
private final ICarWatchdogServiceForSystemImpl mWatchdogServiceForSystem;
@@ -85,9 +85,9 @@
mCarWatchdogDaemonHelper = new CarWatchdogDaemonHelper(TAG_WATCHDOG);
mWatchdogServiceForSystem = new ICarWatchdogServiceForSystemImpl(this);
mWatchdogProcessHandler = new WatchdogProcessHandler(mWatchdogServiceForSystem,
- mCarWatchdogDaemonHelper, DEBUG);
+ mCarWatchdogDaemonHelper);
mWatchdogPerfHandler = new WatchdogPerfHandler(mContext, mCarWatchdogDaemonHelper,
- mPackageInfoHandler, DEBUG);
+ mPackageInfoHandler);
}
@Override
diff --git a/service/src/com/android/car/watchdog/PackageInfoHandler.java b/service/src/com/android/car/watchdog/PackageInfoHandler.java
index c2b3323..f04f2b9 100644
--- a/service/src/com/android/car/watchdog/PackageInfoHandler.java
+++ b/service/src/com/android/car/watchdog/PackageInfoHandler.java
@@ -45,6 +45,8 @@
/* Cache of uid to package name mapping. */
@GuardedBy("mLock")
private final SparseArray<String> mPackageNamesByUid = new SparseArray<>();
+ @GuardedBy("mLock")
+ private List<String> mVendorPackagePrefixes = new ArrayList<>();
public PackageInfoHandler(PackageManager packageManager) {
mPackageManager = packageManager;
@@ -93,25 +95,31 @@
*/
public List<PackageInfo> getPackageInfosForUids(int[] uids,
List<String> vendorPackagePrefixes) {
+ synchronized (mLock) {
+ /*
+ * Vendor package prefixes don't change frequently because it changes only when the
+ * vendor configuration is updated. Thus caching this locally during this call should
+ * keep the cache up-to-date because the daemon issues this call frequently.
+ */
+ mVendorPackagePrefixes = vendorPackagePrefixes;
+ }
SparseArray<String> packageNamesByUid = getPackageNamesForUids(uids);
ArrayList<PackageInfo> packageInfos = new ArrayList<>(packageNamesByUid.size());
for (int i = 0; i < packageNamesByUid.size(); ++i) {
packageInfos.add(getPackageInfo(packageNamesByUid.keyAt(i),
- packageNamesByUid.valueAt(i), vendorPackagePrefixes));
+ packageNamesByUid.valueAt(i)));
}
return packageInfos;
}
- private PackageInfo getPackageInfo(
- int uid, String packageName, List<String> vendorPackagePrefixes) {
+ private PackageInfo getPackageInfo(int uid, String packageName) {
PackageInfo packageInfo = new PackageInfo();
packageInfo.packageIdentifier = new PackageIdentifier();
packageInfo.packageIdentifier.uid = uid;
packageInfo.packageIdentifier.name = packageName;
packageInfo.sharedUidPackages = new ArrayList<>();
packageInfo.componentType = ComponentType.UNKNOWN;
- // TODO(b/170741935): Identify application category type using the package names. Vendor
- // should define the mappings from package name to the application category type.
+ /* Application category type mapping is handled on the daemon side. */
packageInfo.appCategoryType = ApplicationCategoryType.OTHERS;
int userId = UserHandle.getUserId(uid);
int appId = UserHandle.getAppId(uid);
@@ -126,14 +134,13 @@
boolean seenVendor = false;
boolean seenSystem = false;
boolean seenThirdParty = false;
- /**
+ /*
* A shared UID has multiple packages associated with it and these packages may be
* mapped to different component types. Thus map the shared UID to the most restrictive
* component type.
*/
for (int i = 0; i < sharedUidPackages.length; ++i) {
- int componentType = getPackageComponentType(userId, sharedUidPackages[i],
- vendorPackagePrefixes);
+ int componentType = getPackageComponentType(userId, sharedUidPackages[i]);
switch(componentType) {
case ComponentType.VENDOR:
seenVendor = true;
@@ -159,35 +166,41 @@
}
} else {
packageInfo.componentType = getPackageComponentType(
- userId, packageName, vendorPackagePrefixes);
+ userId, packageName);
}
return packageInfo;
}
- private int getPackageComponentType(
- int userId, String packageName, List<String> vendorPackagePrefixes) {
+ private int getPackageComponentType(int userId, String packageName) {
try {
ApplicationInfo info = mPackageManager.getApplicationInfoAsUser(packageName,
/* flags= */ 0, userId);
- if ((info.privateFlags & ApplicationInfo.PRIVATE_FLAG_OEM) != 0
- || (info.privateFlags & ApplicationInfo.PRIVATE_FLAG_VENDOR) != 0
- || (info.privateFlags & ApplicationInfo.PRIVATE_FLAG_ODM) != 0) {
- return ComponentType.VENDOR;
- }
- if ((info.flags & ApplicationInfo.FLAG_SYSTEM) != 0
- || (info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0
- || (info.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRODUCT) != 0
- || (info.privateFlags & ApplicationInfo.PRIVATE_FLAG_SYSTEM_EXT) != 0) {
- for (String prefix : vendorPackagePrefixes) {
+ return getComponentType(packageName, info);
+ } catch (PackageManager.NameNotFoundException e) {
+ Slogf.e(TAG, "Package '%s' not found for user %d: %s", packageName, userId, e);
+ }
+ return ComponentType.UNKNOWN;
+ }
+
+ /** Returns the component type for the given package and its application info. */
+ public int getComponentType(String packageName, ApplicationInfo info) {
+ if ((info.privateFlags & ApplicationInfo.PRIVATE_FLAG_OEM) != 0
+ || (info.privateFlags & ApplicationInfo.PRIVATE_FLAG_VENDOR) != 0
+ || (info.privateFlags & ApplicationInfo.PRIVATE_FLAG_ODM) != 0) {
+ return ComponentType.VENDOR;
+ }
+ if ((info.flags & ApplicationInfo.FLAG_SYSTEM) != 0
+ || (info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0
+ || (info.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRODUCT) != 0
+ || (info.privateFlags & ApplicationInfo.PRIVATE_FLAG_SYSTEM_EXT) != 0) {
+ synchronized (mLock) {
+ for (String prefix : mVendorPackagePrefixes) {
if (packageName.startsWith(prefix)) {
return ComponentType.VENDOR;
}
}
- return ComponentType.SYSTEM;
}
- } catch (PackageManager.NameNotFoundException e) {
- Slogf.e(TAG, "Package '%s' not found for user %d: %s", packageName, userId, e);
- return ComponentType.UNKNOWN;
+ return ComponentType.SYSTEM;
}
return ComponentType.THIRD_PARTY;
}
diff --git a/service/src/com/android/car/watchdog/WatchdogPerfHandler.java b/service/src/com/android/car/watchdog/WatchdogPerfHandler.java
index 37fbbb3..80dcb82 100644
--- a/service/src/com/android/car/watchdog/WatchdogPerfHandler.java
+++ b/service/src/com/android/car/watchdog/WatchdogPerfHandler.java
@@ -34,11 +34,18 @@
import android.annotation.UserIdInt;
import android.app.ActivityThread;
import android.automotive.watchdog.ResourceType;
+import android.automotive.watchdog.internal.ApplicationCategoryType;
+import android.automotive.watchdog.internal.ComponentType;
import android.automotive.watchdog.internal.PackageIdentifier;
import android.automotive.watchdog.internal.PackageIoOveruseStats;
+import android.automotive.watchdog.internal.PackageMetadata;
import android.automotive.watchdog.internal.PackageResourceOveruseAction;
+import android.automotive.watchdog.internal.PerStateIoOveruseThreshold;
+import android.automotive.watchdog.internal.ResourceSpecificConfiguration;
import android.car.watchdog.CarWatchdogManager;
import android.car.watchdog.IResourceOveruseListener;
+import android.car.watchdog.IoOveruseAlertThreshold;
+import android.car.watchdog.IoOveruseConfiguration;
import android.car.watchdog.IoOveruseStats;
import android.car.watchdog.PackageKillableState;
import android.car.watchdog.PackageKillableState.KillableState;
@@ -48,18 +55,21 @@
import android.car.watchdoglib.CarWatchdogDaemonHelper;
import android.content.Context;
import android.content.pm.IPackageManager;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.os.UserManager;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
import android.util.SparseArray;
-import com.android.car.CarLog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
@@ -78,9 +88,10 @@
* Handles system resource performance monitoring module.
*/
public final class WatchdogPerfHandler {
- private static final String TAG = CarLog.tagFor(CarWatchdogService.class);
+ public static final String INTERNAL_APPLICATION_CATEGORY_TYPE_MAPS = "MAPS";
+ public static final String INTERNAL_APPLICATION_CATEGORY_TYPE_MEDIA = "MEDIA";
+ public static final String INTERNAL_APPLICATION_CATEGORY_TYPE_UNKNOWN = "UNKNOWN";
- private final boolean mIsDebugEnabled;
private final Context mContext;
private final CarWatchdogDaemonHelper mCarWatchdogDaemonHelper;
private final PackageInfoHandler mPackageInfoHandler;
@@ -102,10 +113,15 @@
new SparseArray<>();
@GuardedBy("mLock")
private ZonedDateTime mLastStatsReportUTC;
+ /* Set of safe-to-kill system and vendor packages. */
+ @GuardedBy("mLock")
+ public final Set<String> mSafeToKillPackages = new ArraySet<>();
+ /* Default killable state for packages when not updated by the user. */
+ @GuardedBy("mLock")
+ public final Set<String> mDefaultNotKillablePackages = new ArraySet<>();
public WatchdogPerfHandler(Context context, CarWatchdogDaemonHelper daemonHelper,
- PackageInfoHandler packageInfoHandler, boolean isDebugEnabled) {
- mIsDebugEnabled = isDebugEnabled;
+ PackageInfoHandler packageInfoHandler) {
mContext = context;
mCarWatchdogDaemonHelper = daemonHelper;
mPackageInfoHandler = packageInfoHandler;
@@ -119,31 +135,31 @@
* TODO(b/183947162): Opt-in to receive package change broadcast and handle package enabled
* state changes.
*
- * TODO(b/170741935): Read the current day's I/O overuse stats from database and push them
+ * TODO(b/185287136): Persist in-memory data:
+ * 1. Read the current day's I/O overuse stats from database and push them
* to the daemon.
+ * 2. Fetch the safe-to-kill from daemon on initialization and update mSafeToKillPackages.
*/
synchronized (mLock) {
checkAndHandleDateChangeLocked();
}
- if (mIsDebugEnabled) {
- Slogf.d(TAG, "WatchdogPerfHandler is initialized");
+ if (CarWatchdogService.DEBUG) {
+ Slogf.d(CarWatchdogService.TAG, "WatchdogPerfHandler is initialized");
}
}
/** Releases the handler */
public void release() {
- /*
- * TODO(b/170741935): Write daily usage to SQLite DB storage.
- */
- if (mIsDebugEnabled) {
- Slogf.d(TAG, "WatchdogPerfHandler is released");
+ /* TODO(b/185287136): Write daily usage to SQLite DB storage. */
+ if (CarWatchdogService.DEBUG) {
+ Slogf.d(CarWatchdogService.TAG, "WatchdogPerfHandler is released");
}
}
/** Dumps its state. */
public void dump(IndentingPrintWriter writer) {
- /**
- * TODO(b/170741935): Implement this method.
+ /*
+ * TODO(b/183436216): Implement this method.
*/
}
@@ -156,9 +172,29 @@
"Must provide valid resource overuse flag");
Preconditions.checkArgument((maxStatsPeriod > 0),
"Must provide valid maximum stats period");
- // TODO(b/170741935): Implement this method.
- return new ResourceOveruseStats.Builder("",
- UserHandle.getUserHandleForUid(Binder.getCallingUid())).build();
+ // When more resource stats are added, make this as optional.
+ Preconditions.checkArgument((resourceOveruseFlag & FLAG_RESOURCE_OVERUSE_IO) != 0,
+ "Must provide resource I/O overuse flag");
+ int callingUid = Binder.getCallingUid();
+ int callingUserId = UserHandle.getUserId(callingUid);
+ UserHandle callingUserHandle = UserHandle.of(callingUserId);
+ String callingPackageName =
+ mPackageInfoHandler.getPackageNamesForUids(new int[]{callingUid})
+ .get(callingUid, null);
+ if (callingPackageName == null) {
+ Slogf.w(CarWatchdogService.TAG, "Failed to fetch package info for uid %d", callingUid);
+ return new ResourceOveruseStats.Builder("", callingUserHandle).build();
+ }
+ ResourceOveruseStats.Builder statsBuilder =
+ new ResourceOveruseStats.Builder(callingPackageName, callingUserHandle);
+ statsBuilder.setIoOveruseStats(getIoOveruseStats(callingUserId, callingPackageName,
+ /* minimumBytesWritten= */ 0, maxStatsPeriod));
+ if (CarWatchdogService.DEBUG) {
+ Slogf.d(CarWatchdogService.TAG, "Returning all resource overuse stats for calling uid "
+ + "%d [user %d and package '%s']", callingUid, callingUserId,
+ callingPackageName);
+ }
+ return statsBuilder.build();
}
/** Returns resource overuse stats for all packages. */
@@ -171,8 +207,24 @@
"Must provide valid resource overuse flag");
Preconditions.checkArgument((maxStatsPeriod > 0),
"Must provide valid maximum stats period");
- // TODO(b/170741935): Implement this method.
- return new ArrayList<>();
+ // When more resource types are added, make this as optional.
+ Preconditions.checkArgument((resourceOveruseFlag & FLAG_RESOURCE_OVERUSE_IO) != 0,
+ "Must provide resource I/O overuse flag");
+ long minimumBytesWritten = getMinimumBytesWritten(minimumStatsFlag);
+ List<ResourceOveruseStats> allStats = new ArrayList<>();
+ for (PackageResourceUsage usage : mUsageByUserPackage.values()) {
+ ResourceOveruseStats.Builder statsBuilder = usage.getResourceOveruseStatsBuilder();
+ IoOveruseStats ioOveruseStats = getIoOveruseStats(usage.userId, usage.packageName,
+ minimumBytesWritten, maxStatsPeriod);
+ if (ioOveruseStats == null) {
+ continue;
+ }
+ allStats.add(statsBuilder.setIoOveruseStats(ioOveruseStats).build());
+ }
+ if (CarWatchdogService.DEBUG) {
+ Slogf.d(CarWatchdogService.TAG, "Returning all resource overuse stats");
+ }
+ return allStats;
}
/** Returns resource overuse stats for the specified user package. */
@@ -183,12 +235,25 @@
@CarWatchdogManager.StatsPeriod int maxStatsPeriod) {
Objects.requireNonNull(packageName, "Package name must be non-null");
Objects.requireNonNull(userHandle, "User handle must be non-null");
+ Preconditions.checkArgument((userHandle != UserHandle.ALL),
+ "Must provide the user handle for a specific user");
Preconditions.checkArgument((resourceOveruseFlag > 0),
"Must provide valid resource overuse flag");
Preconditions.checkArgument((maxStatsPeriod > 0),
"Must provide valid maximum stats period");
- // TODO(b/170741935): Implement this method.
- return new ResourceOveruseStats.Builder("", userHandle).build();
+ // When more resource types are added, make this as optional.
+ Preconditions.checkArgument((resourceOveruseFlag & FLAG_RESOURCE_OVERUSE_IO) != 0,
+ "Must provide resource I/O overuse flag");
+ ResourceOveruseStats.Builder statsBuilder =
+ new ResourceOveruseStats.Builder(packageName, userHandle);
+ statsBuilder.setIoOveruseStats(getIoOveruseStats(userHandle.getIdentifier(), packageName,
+ /* minimumBytesWritten= */ 0, maxStatsPeriod));
+ if (CarWatchdogService.DEBUG) {
+ Slogf.d(CarWatchdogService.TAG,
+ "Returning resource overuse stats for user %d, package '%s'",
+ userHandle.getIdentifier(), packageName);
+ }
+ return statsBuilder.build();
}
/** Adds the resource overuse listener. */
@@ -238,18 +303,107 @@
boolean isKillable) {
Objects.requireNonNull(packageName, "Package name must be non-null");
Objects.requireNonNull(userHandle, "User handle must be non-null");
- /*
- * TODO(b/170741935): Add/remove the package from the user do-no-kill list.
- * If the {@code userHandle == UserHandle.ALL}, update the settings for all users.
- */
+ if (userHandle == UserHandle.ALL) {
+ synchronized (mLock) {
+ for (PackageResourceUsage usage : mUsageByUserPackage.values()) {
+ if (!usage.packageName.equals(packageName)) {
+ continue;
+ }
+ if (!usage.setKillableState(isKillable)) {
+ Slogf.e(CarWatchdogService.TAG,
+ "Cannot set killable state for package '%s'", packageName);
+ throw new IllegalArgumentException(
+ "Package killable state is not updatable");
+ }
+ }
+ if (!isKillable) {
+ mDefaultNotKillablePackages.add(packageName);
+ } else {
+ mDefaultNotKillablePackages.remove(packageName);
+ }
+ }
+ if (CarWatchdogService.DEBUG) {
+ Slogf.d(CarWatchdogService.TAG,
+ "Successfully set killable package state for all users");
+ }
+ return;
+ }
+ int userId = userHandle.getIdentifier();
+ String key = getUserPackageUniqueId(userId, packageName);
+ synchronized (mLock) {
+ /*
+ * When the queried package is not cached in {@link mUsageByUserPackage}, the set API
+ * will update the killable state even when the package should never be killed.
+ * But the get API will return the correct killable state. This behavior is tolerable
+ * because in production the set API should be called only after the get API.
+ * For instance, when this case happens by mistake and the package overuses resource
+ * between the set and the get API calls, the daemon will provide correct killable
+ * state when pushing the latest stats. Ergo, the invalid killable state doesn't have
+ * any effect.
+ */
+ PackageResourceUsage usage = mUsageByUserPackage.getOrDefault(key,
+ new PackageResourceUsage(userId, packageName));
+ if (!usage.setKillableState(isKillable)) {
+ Slogf.e(CarWatchdogService.TAG,
+ "User %d cannot set killable state for package '%s'",
+ userHandle.getIdentifier(), packageName);
+ throw new IllegalArgumentException("Package killable state is not updatable");
+ }
+ mUsageByUserPackage.put(key, usage);
+ }
+ if (CarWatchdogService.DEBUG) {
+ Slogf.d(CarWatchdogService.TAG, "Successfully set killable package state for user %d",
+ userId);
+ }
}
/** Returns the list of package killable states on resource overuse for the user. */
@NonNull
public List<PackageKillableState> getPackageKillableStatesAsUser(UserHandle userHandle) {
Objects.requireNonNull(userHandle, "User handle must be non-null");
- // TODO(b/170741935): Implement this method.
- return new ArrayList<>();
+ PackageManager pm = mContext.getPackageManager();
+ if (userHandle != UserHandle.ALL) {
+ if (CarWatchdogService.DEBUG) {
+ Slogf.d(CarWatchdogService.TAG, "Returning all package killable states for user %d",
+ userHandle.getIdentifier());
+ }
+ return getPackageKillableStatesForUserId(userHandle.getIdentifier(), pm);
+ }
+ List<PackageKillableState> packageKillableStates = new ArrayList<>();
+ UserManager userManager = UserManager.get(mContext);
+ List<UserInfo> userInfos = userManager.getAliveUsers();
+ for (UserInfo userInfo : userInfos) {
+ packageKillableStates.addAll(getPackageKillableStatesForUserId(userInfo.id, pm));
+ }
+ if (CarWatchdogService.DEBUG) {
+ Slogf.d(CarWatchdogService.TAG, "Returning all package killable states for all users");
+ }
+ return packageKillableStates;
+ }
+
+ private List<PackageKillableState> getPackageKillableStatesForUserId(int userId,
+ PackageManager pm) {
+ List<PackageInfo> packageInfos = pm.getInstalledPackagesAsUser(/* flags= */0, userId);
+ List<PackageKillableState> states = new ArrayList<>();
+ synchronized (mLock) {
+ for (int i = 0; i < packageInfos.size(); ++i) {
+ PackageInfo packageInfo = packageInfos.get(i);
+ String key = getUserPackageUniqueId(userId, packageInfo.packageName);
+ PackageResourceUsage usage = mUsageByUserPackage.getOrDefault(key,
+ new PackageResourceUsage(userId, packageInfo.packageName));
+ int killableState = usage.syncAndFetchKillableStateLocked(
+ mPackageInfoHandler.getComponentType(packageInfo.packageName,
+ packageInfo.applicationInfo));
+ mUsageByUserPackage.put(key, usage);
+ states.add(
+ new PackageKillableState(packageInfo.packageName, userId, killableState));
+ }
+ }
+ if (CarWatchdogService.DEBUG) {
+ Slogf.d(CarWatchdogService.TAG,
+ "Returning the package killable states for a user package");
+ }
+ return states;
}
/** Sets the given resource overuse configurations. */
@@ -257,11 +411,18 @@
List<ResourceOveruseConfiguration> configurations,
@CarWatchdogManager.ResourceOveruseFlag int resourceOveruseFlag) {
Objects.requireNonNull(configurations, "Configurations must be non-null");
+ Preconditions.checkArgument((configurations.size() > 0),
+ "Must provide at least one configuration");
Preconditions.checkArgument((resourceOveruseFlag > 0),
"Must provide valid resource overuse flag");
Set<Integer> seenComponentTypes = new ArraySet<>();
+ List<android.automotive.watchdog.internal.ResourceOveruseConfiguration> internalConfigs =
+ new ArrayList<>();
for (ResourceOveruseConfiguration config : configurations) {
int componentType = config.getComponentType();
+ if (toComponentTypeStr(componentType).equals("UNKNOWN")) {
+ throw new IllegalArgumentException("Invalid component type in the configuration");
+ }
if (seenComponentTypes.contains(componentType)) {
throw new IllegalArgumentException(
"Cannot provide duplicate configurations for the same component type");
@@ -271,8 +432,24 @@
throw new IllegalArgumentException("Must provide I/O overuse configuration");
}
seenComponentTypes.add(config.getComponentType());
+ internalConfigs.add(toInternalResourceOveruseConfiguration(config,
+ resourceOveruseFlag));
}
- // TODO(b/170741935): Implement this method.
+
+ // TODO(b/186119640): Add retry logic when daemon is not available.
+ try {
+ mCarWatchdogDaemonHelper.updateResourceOveruseConfigurations(internalConfigs);
+ } catch (IllegalArgumentException e) {
+ Slogf.w(CarWatchdogService.TAG, "Failed to set resource overuse configurations: %s", e);
+ throw e;
+ } catch (RemoteException | RuntimeException e) {
+ Slogf.w(CarWatchdogService.TAG, "Failed to set resource overuse configurations: %s", e);
+ throw new IllegalStateException(e);
+ }
+ /* TODO(b/185287136): Fetch safe-to-kill list from daemon and update mSafeToKillPackages. */
+ if (CarWatchdogService.DEBUG) {
+ Slogf.d(CarWatchdogService.TAG, "Set the resource overuse configuration successfully");
+ }
}
/** Returns the available resource overuse configurations. */
@@ -281,8 +458,25 @@
@CarWatchdogManager.ResourceOveruseFlag int resourceOveruseFlag) {
Preconditions.checkArgument((resourceOveruseFlag > 0),
"Must provide valid resource overuse flag");
- // TODO(b/170741935): Implement this method.
- return new ArrayList<>();
+ List<android.automotive.watchdog.internal.ResourceOveruseConfiguration> internalConfigs =
+ new ArrayList<>();
+ // TODO(b/186119640): Add retry logic when daemon is not available.
+ try {
+ internalConfigs = mCarWatchdogDaemonHelper.getResourceOveruseConfigurations();
+ } catch (RemoteException | RuntimeException e) {
+ Slogf.w(CarWatchdogService.TAG, "Failed to fetch resource overuse configurations: %s",
+ e);
+ throw new IllegalStateException(e);
+ }
+ List<ResourceOveruseConfiguration> configs = new ArrayList<>();
+ for (android.automotive.watchdog.internal.ResourceOveruseConfiguration internalConfig
+ : internalConfigs) {
+ configs.add(toResourceOveruseConfiguration(internalConfig, resourceOveruseFlag));
+ }
+ if (CarWatchdogService.DEBUG) {
+ Slogf.d(CarWatchdogService.TAG, "Returning the resource overuse configuration");
+ }
+ return configs;
}
/** Processes the latest I/O overuse stats */
@@ -308,9 +502,12 @@
* and only the daemon is aware of such packages. Thus the flag is used to
* indicate which packages should be notified.
*/
- notifyResourceOveruseStatsLocked(stats.uid,
- usage.getResourceOveruseStatsWithIo());
+ ResourceOveruseStats resourceOveruseStats =
+ usage.getResourceOveruseStatsBuilder().setIoOveruseStats(
+ usage.getIoOveruseStats()).build();
+ notifyResourceOveruseStatsLocked(stats.uid, resourceOveruseStats);
}
+
if (!usage.ioUsage.exceedsThreshold()) {
continue;
}
@@ -326,7 +523,6 @@
* #2 The package has no recurring overuse behavior and the user opted to not
* kill the package so honor the user's decision.
*/
- String userPackageId = getUserPackageUniqueId(userId, packageName);
int killableState = usage.getKillableState();
if (killableState == KILLABLE_STATE_NEVER) {
mOveruseActionsByUserPackage.add(overuseAction);
@@ -363,8 +559,8 @@
usage.oldEnabledState = oldEnabledState;
}
} catch (RemoteException e) {
- Slogf.e(TAG, "Failed to disable application enabled setting for user %d, "
- + "package '%s'", userId, packageName);
+ Slogf.e(CarWatchdogService.TAG, "Failed to disable application enabled setting "
+ + "for user %d, package '%s'", userId, packageName);
}
mOveruseActionsByUserPackage.add(overuseAction);
}
@@ -373,6 +569,9 @@
WatchdogPerfHandler::notifyActionsTakenOnOveruse, this));
}
}
+ if (CarWatchdogService.DEBUG) {
+ Slogf.d(CarWatchdogService.TAG, "Processed latest I/O overuse stats");
+ }
}
/** Notify daemon about the actions take on resource overuse */
@@ -387,9 +586,13 @@
}
try {
mCarWatchdogDaemonHelper.actionTakenOnResourceOveruse(actions);
- } catch (RemoteException e) {
- Slogf.w(TAG, "Failed to notify car watchdog daemon of actions taken on "
- + "resource overuse: %s", e);
+ } catch (RemoteException | RuntimeException e) {
+ Slogf.w(CarWatchdogService.TAG, "Failed to notify car watchdog daemon of actions taken "
+ + "on resource overuse: %s", e);
+ }
+ if (CarWatchdogService.DEBUG) {
+ Slogf.d(CarWatchdogService.TAG,
+ "Notified car watchdog daemon of actions taken on resource overuse");
}
}
@@ -401,7 +604,7 @@
try {
listenerInfo.listener.onOveruse(resourceOveruseStats);
} catch (RemoteException e) {
- Slogf.e(TAG,
+ Slogf.e(CarWatchdogService.TAG,
"Failed to notify listener(uid %d, package '%s') on resource overuse: %s",
uid, resourceOveruseStats, e);
}
@@ -415,11 +618,15 @@
try {
systemListenerInfo.listener.onOveruse(resourceOveruseStats);
} catch (RemoteException e) {
- Slogf.e(TAG, "Failed to notify system listener(uid %d, pid: %d) of resource "
- + "overuse by package(uid %d, package '%s'): %s",
+ Slogf.e(CarWatchdogService.TAG, "Failed to notify system listener(uid %d, pid: %d) "
+ + "of resource overuse by package(uid %d, package '%s'): %s",
systemListenerInfo.uid, systemListenerInfo.pid, uid, packageName, e);
}
}
+ if (CarWatchdogService.DEBUG) {
+ Slogf.d(CarWatchdogService.TAG,
+ "Notified resource overuse stats to listening applications");
+ }
}
private void checkAndHandleDateChangeLocked() {
@@ -443,7 +650,7 @@
usage.oldEnabledState,
/* flags= */ 0, usage.userId, mContext.getPackageName());
} catch (RemoteException e) {
- Slogf.e(TAG,
+ Slogf.e(CarWatchdogService.TAG,
"Failed to reset enabled setting for disabled package '%s', user %d",
usage.packageName, usage.userId);
}
@@ -451,6 +658,9 @@
/* TODO(b/170741935): Stash the old usage into SQLite DB storage. */
usage.ioUsage.clear();
}
+ if (CarWatchdogService.DEBUG) {
+ Slogf.d(CarWatchdogService.TAG, "Handled date change successfully");
+ }
}
private PackageResourceUsage cacheAndFetchUsageLocked(@UserIdInt int userId, String packageName,
@@ -458,19 +668,39 @@
String key = getUserPackageUniqueId(userId, packageName);
PackageResourceUsage usage = mUsageByUserPackage.getOrDefault(key,
new PackageResourceUsage(userId, packageName));
- usage.update(internalStats);
+ usage.updateLocked(internalStats);
mUsageByUserPackage.put(key, usage);
return usage;
}
private boolean isRecurringOveruseLocked(PackageResourceUsage ioUsage) {
/*
- * TODO(b/170741935): Look up I/O overuse history and determine whether or not the package
+ * TODO(b/185287136): Look up I/O overuse history and determine whether or not the package
* has recurring I/O overuse behavior.
*/
return false;
}
+ private IoOveruseStats getIoOveruseStats(int userId, String packageName,
+ long minimumBytesWritten, @CarWatchdogManager.StatsPeriod int maxStatsPeriod) {
+ String key = getUserPackageUniqueId(userId, packageName);
+ PackageResourceUsage usage = mUsageByUserPackage.get(key);
+ if (usage == null) {
+ return null;
+ }
+ IoOveruseStats stats = usage.getIoOveruseStats();
+ long totalBytesWritten = stats != null ? stats.getTotalBytesWritten() : 0;
+ /*
+ * TODO(b/185431129): When maxStatsPeriod > current day, populate the historical stats
+ * from the local database. Also handle the case where the package doesn't have current
+ * day stats but has historical stats.
+ */
+ if (totalBytesWritten < minimumBytesWritten) {
+ return null;
+ }
+ return stats;
+ }
+
private void addResourceOveruseListenerLocked(
@CarWatchdogManager.ResourceOveruseFlag int resourceOveruseFlag,
@NonNull IResourceOveruseListener listener,
@@ -495,20 +725,21 @@
try {
listenerInfo.linkToDeath();
} catch (RemoteException e) {
- Slogf.w(TAG, "Cannot add %s: linkToDeath to listener failed", listenerType);
+ Slogf.w(CarWatchdogService.TAG, "Cannot add %s: linkToDeath to listener failed",
+ listenerType);
return;
}
if (existingListenerInfo != null) {
- Slogf.w(TAG, "Overwriting existing %s: pid %d, uid: %d", listenerType,
- existingListenerInfo.pid, existingListenerInfo.uid);
+ Slogf.w(CarWatchdogService.TAG, "Overwriting existing %s: pid %d, uid: %d",
+ listenerType, existingListenerInfo.pid, existingListenerInfo.uid);
existingListenerInfo.unlinkToDeath();
}
listenerInfosByUid.put(callingUid, listenerInfo);
- if (mIsDebugEnabled) {
- Slogf.d(TAG, "The %s (pid: %d, uid: %d) is added", listenerType,
+ if (CarWatchdogService.DEBUG) {
+ Slogf.d(CarWatchdogService.TAG, "The %s (pid: %d, uid: %d) is added", listenerType,
callingPid, callingUid);
}
}
@@ -522,14 +753,15 @@
ResourceOveruseListenerInfo listenerInfo = listenerInfosByUid.get(callingUid, null);
if (listenerInfo == null || listenerInfo.listener != listener) {
- Slogf.w(TAG, "Cannot remove the %s: it has not been registered before", listenerType);
+ Slogf.w(CarWatchdogService.TAG,
+ "Cannot remove the %s: it has not been registered before", listenerType);
return;
}
listenerInfo.unlinkToDeath();
listenerInfosByUid.remove(callingUid);
- if (mIsDebugEnabled) {
- Slogf.d(TAG, "The %s (pid: %d, uid: %d) is removed", listenerType, listenerInfo.pid,
- listenerInfo.uid);
+ if (CarWatchdogService.DEBUG) {
+ Slogf.d(CarWatchdogService.TAG, "The %s (pid: %d, uid: %d) is removed", listenerType,
+ listenerInfo.pid, listenerInfo.uid);
}
}
@@ -561,9 +793,8 @@
private static PerStateBytes toPerStateBytes(
android.automotive.watchdog.PerStateBytes internalPerStateBytes) {
- PerStateBytes perStateBytes = new PerStateBytes(internalPerStateBytes.foregroundBytes,
+ return new PerStateBytes(internalPerStateBytes.foregroundBytes,
internalPerStateBytes.backgroundBytes, internalPerStateBytes.garageModeBytes);
- return perStateBytes;
}
private static long totalPerStateBytes(
@@ -575,7 +806,231 @@
internalPerStateBytes.backgroundBytes), internalPerStateBytes.garageModeBytes);
}
- private static final class PackageResourceUsage {
+ private static long getMinimumBytesWritten(
+ @CarWatchdogManager.MinimumStatsFlag int minimumStatsIoFlag) {
+ switch (minimumStatsIoFlag) {
+ case 0:
+ return 0;
+ case CarWatchdogManager.FLAG_MINIMUM_STATS_IO_1_MB:
+ return 1024 * 1024;
+ case CarWatchdogManager.FLAG_MINIMUM_STATS_IO_100_MB:
+ return 100 * 1024 * 1024;
+ case CarWatchdogManager.FLAG_MINIMUM_STATS_IO_1_GB:
+ return 1024 * 1024 * 1024;
+ default:
+ throw new IllegalArgumentException(
+ "Must provide valid minimum stats flag for I/O resource");
+ }
+ }
+
+ private static android.automotive.watchdog.internal.ResourceOveruseConfiguration
+ toInternalResourceOveruseConfiguration(ResourceOveruseConfiguration config,
+ @CarWatchdogManager.ResourceOveruseFlag int resourceOveruseFlag) {
+ android.automotive.watchdog.internal.ResourceOveruseConfiguration internalConfig =
+ new android.automotive.watchdog.internal.ResourceOveruseConfiguration();
+ internalConfig.componentType = config.getComponentType();
+ internalConfig.safeToKillPackages = config.getSafeToKillPackages();
+ internalConfig.vendorPackagePrefixes = config.getVendorPackagePrefixes();
+ internalConfig.packageMetadata = new ArrayList<>();
+ for (Map.Entry<String, String> entry : config.getPackagesToAppCategoryTypes().entrySet()) {
+ if (entry.getKey().isEmpty()) {
+ continue;
+ }
+ PackageMetadata metadata = new PackageMetadata();
+ metadata.packageName = entry.getKey();
+ switch(entry.getValue()) {
+ case ResourceOveruseConfiguration.APPLICATION_CATEGORY_TYPE_MAPS:
+ metadata.appCategoryType = ApplicationCategoryType.MAPS;
+ break;
+ case ResourceOveruseConfiguration.APPLICATION_CATEGORY_TYPE_MEDIA:
+ metadata.appCategoryType = ApplicationCategoryType.MEDIA;
+ break;
+ default:
+ continue;
+ }
+ internalConfig.packageMetadata.add(metadata);
+ }
+ internalConfig.resourceSpecificConfigurations = new ArrayList<>();
+ if ((resourceOveruseFlag & FLAG_RESOURCE_OVERUSE_IO) != 0
+ && config.getIoOveruseConfiguration() != null) {
+ internalConfig.resourceSpecificConfigurations.add(
+ toResourceSpecificConfiguration(config.getComponentType(),
+ config.getIoOveruseConfiguration()));
+ }
+ return internalConfig;
+ }
+
+ private static ResourceSpecificConfiguration
+ toResourceSpecificConfiguration(int componentType, IoOveruseConfiguration config) {
+ android.automotive.watchdog.internal.IoOveruseConfiguration internalConfig =
+ new android.automotive.watchdog.internal.IoOveruseConfiguration();
+ internalConfig.componentLevelThresholds = toPerStateIoOveruseThreshold(
+ toComponentTypeStr(componentType), config.getComponentLevelThresholds());
+ internalConfig.packageSpecificThresholds = toPerStateIoOveruseThresholds(
+ config.getPackageSpecificThresholds());
+ internalConfig.categorySpecificThresholds = toPerStateIoOveruseThresholds(
+ config.getAppCategorySpecificThresholds());
+ for (PerStateIoOveruseThreshold threshold : internalConfig.categorySpecificThresholds) {
+ switch(threshold.name) {
+ case ResourceOveruseConfiguration.APPLICATION_CATEGORY_TYPE_MAPS:
+ threshold.name = INTERNAL_APPLICATION_CATEGORY_TYPE_MAPS;
+ break;
+ case ResourceOveruseConfiguration.APPLICATION_CATEGORY_TYPE_MEDIA:
+ threshold.name = INTERNAL_APPLICATION_CATEGORY_TYPE_MEDIA;
+ break;
+ default:
+ threshold.name = INTERNAL_APPLICATION_CATEGORY_TYPE_UNKNOWN;
+ }
+ }
+ internalConfig.systemWideThresholds = toInternalIoOveruseAlertThresholds(
+ config.getSystemWideThresholds());
+
+ ResourceSpecificConfiguration resourceSpecificConfig = new ResourceSpecificConfiguration();
+ resourceSpecificConfig.setIoOveruseConfiguration(internalConfig);
+ return resourceSpecificConfig;
+ }
+
+ @VisibleForTesting
+ static String toComponentTypeStr(int componentType) {
+ switch(componentType) {
+ case ComponentType.SYSTEM:
+ return "SYSTEM";
+ case ComponentType.VENDOR:
+ return "VENDOR";
+ case ComponentType.THIRD_PARTY:
+ return "THIRD_PARTY";
+ default:
+ return "UNKNOWN";
+ }
+ }
+
+ private static List<PerStateIoOveruseThreshold> toPerStateIoOveruseThresholds(
+ Map<String, PerStateBytes> thresholds) {
+ List<PerStateIoOveruseThreshold> internalThresholds = new ArrayList<>();
+ for (Map.Entry<String, PerStateBytes> entry : thresholds.entrySet()) {
+ if (!entry.getKey().isEmpty()) {
+ internalThresholds.add(toPerStateIoOveruseThreshold(entry.getKey(),
+ entry.getValue()));
+ }
+ }
+ return internalThresholds;
+ }
+
+ private static PerStateIoOveruseThreshold toPerStateIoOveruseThreshold(String name,
+ PerStateBytes perStateBytes) {
+ PerStateIoOveruseThreshold threshold = new PerStateIoOveruseThreshold();
+ threshold.name = name;
+ threshold.perStateWriteBytes = new android.automotive.watchdog.PerStateBytes();
+ threshold.perStateWriteBytes.foregroundBytes = perStateBytes.getForegroundModeBytes();
+ threshold.perStateWriteBytes.backgroundBytes = perStateBytes.getBackgroundModeBytes();
+ threshold.perStateWriteBytes.garageModeBytes = perStateBytes.getGarageModeBytes();
+ return threshold;
+ }
+
+ private static List<android.automotive.watchdog.internal.IoOveruseAlertThreshold>
+ toInternalIoOveruseAlertThresholds(List<IoOveruseAlertThreshold> thresholds) {
+ List<android.automotive.watchdog.internal.IoOveruseAlertThreshold> internalThresholds =
+ new ArrayList<>();
+ for (IoOveruseAlertThreshold threshold : thresholds) {
+ if (threshold.getDurationInSeconds() == 0
+ || threshold.getWrittenBytesPerSecond() == 0) {
+ continue;
+ }
+ android.automotive.watchdog.internal.IoOveruseAlertThreshold internalThreshold =
+ new android.automotive.watchdog.internal.IoOveruseAlertThreshold();
+ internalThreshold.durationInSeconds = threshold.getDurationInSeconds();
+ internalThreshold.writtenBytesPerSecond = threshold.getWrittenBytesPerSecond();
+ internalThresholds.add(internalThreshold);
+ }
+ return internalThresholds;
+ }
+
+ private static ResourceOveruseConfiguration toResourceOveruseConfiguration(
+ android.automotive.watchdog.internal.ResourceOveruseConfiguration internalConfig,
+ @CarWatchdogManager.ResourceOveruseFlag int resourceOveruseFlag) {
+ Map<String, String> packagesToAppCategoryTypes = new ArrayMap<>();
+ for (PackageMetadata metadata : internalConfig.packageMetadata) {
+ String categoryTypeStr;
+ switch (metadata.appCategoryType) {
+ case ApplicationCategoryType.MAPS:
+ categoryTypeStr = ResourceOveruseConfiguration.APPLICATION_CATEGORY_TYPE_MAPS;
+ break;
+ case ApplicationCategoryType.MEDIA:
+ categoryTypeStr = ResourceOveruseConfiguration.APPLICATION_CATEGORY_TYPE_MEDIA;
+ break;
+ default:
+ continue;
+ }
+ packagesToAppCategoryTypes.put(metadata.packageName, categoryTypeStr);
+ }
+ ResourceOveruseConfiguration.Builder configBuilder =
+ new ResourceOveruseConfiguration.Builder(
+ internalConfig.componentType,
+ internalConfig.safeToKillPackages,
+ internalConfig.vendorPackagePrefixes,
+ packagesToAppCategoryTypes);
+ for (ResourceSpecificConfiguration resourceSpecificConfig :
+ internalConfig.resourceSpecificConfigurations) {
+ if (resourceSpecificConfig.getTag()
+ == ResourceSpecificConfiguration.ioOveruseConfiguration
+ && (resourceOveruseFlag & FLAG_RESOURCE_OVERUSE_IO) != 0) {
+ configBuilder.setIoOveruseConfiguration(toIoOveruseConfiguration(
+ resourceSpecificConfig.getIoOveruseConfiguration()));
+ }
+ }
+ return configBuilder.build();
+ }
+
+ private static IoOveruseConfiguration toIoOveruseConfiguration(
+ android.automotive.watchdog.internal.IoOveruseConfiguration internalConfig) {
+ PerStateBytes componentLevelThresholds =
+ toPerStateBytes(internalConfig.componentLevelThresholds.perStateWriteBytes);
+ Map<String, PerStateBytes> packageSpecificThresholds =
+ toPerStateBytesMap(internalConfig.packageSpecificThresholds);
+ Map<String, PerStateBytes> appCategorySpecificThresholds =
+ toPerStateBytesMap(internalConfig.categorySpecificThresholds);
+ replaceKey(appCategorySpecificThresholds, INTERNAL_APPLICATION_CATEGORY_TYPE_MAPS,
+ ResourceOveruseConfiguration.APPLICATION_CATEGORY_TYPE_MAPS);
+ replaceKey(appCategorySpecificThresholds, INTERNAL_APPLICATION_CATEGORY_TYPE_MEDIA,
+ ResourceOveruseConfiguration.APPLICATION_CATEGORY_TYPE_MEDIA);
+ List<IoOveruseAlertThreshold> systemWideThresholds =
+ toIoOveruseAlertThresholds(internalConfig.systemWideThresholds);
+
+ IoOveruseConfiguration.Builder configBuilder = new IoOveruseConfiguration.Builder(
+ componentLevelThresholds, packageSpecificThresholds, appCategorySpecificThresholds,
+ systemWideThresholds);
+ return configBuilder.build();
+ }
+
+ private static Map<String, PerStateBytes> toPerStateBytesMap(
+ List<PerStateIoOveruseThreshold> thresholds) {
+ Map<String, PerStateBytes> thresholdsMap = new ArrayMap<>();
+ for (PerStateIoOveruseThreshold threshold : thresholds) {
+ thresholdsMap.put(threshold.name, toPerStateBytes(threshold.perStateWriteBytes));
+ }
+ return thresholdsMap;
+ }
+
+ private static List<IoOveruseAlertThreshold> toIoOveruseAlertThresholds(
+ List<android.automotive.watchdog.internal.IoOveruseAlertThreshold> internalThresholds) {
+ List<IoOveruseAlertThreshold> thresholds = new ArrayList<>();
+ for (android.automotive.watchdog.internal.IoOveruseAlertThreshold internalThreshold
+ : internalThresholds) {
+ thresholds.add(new IoOveruseAlertThreshold(internalThreshold.durationInSeconds,
+ internalThreshold.writtenBytesPerSecond));
+ }
+ return thresholds;
+ }
+
+ private static void replaceKey(Map<String, PerStateBytes> map, String oldKey, String newKey) {
+ PerStateBytes perStateBytes = map.get(oldKey);
+ if (perStateBytes != null) {
+ map.put(newKey, perStateBytes);
+ map.remove(oldKey);
+ }
+ }
+
+ private final class PackageResourceUsage {
public final String packageName;
public @UserIdInt final int userId;
public final PackageIoUsage ioUsage;
@@ -583,15 +1038,17 @@
private @KillableState int mKillableState;
+ /** Must be called only after acquiring {@link mLock} */
PackageResourceUsage(@UserIdInt int userId, String packageName) {
this.packageName = packageName;
this.userId = userId;
this.ioUsage = new PackageIoUsage();
this.oldEnabledState = -1;
- this.mKillableState = KILLABLE_STATE_YES;
+ this.mKillableState = mDefaultNotKillablePackages.contains(packageName)
+ ? KILLABLE_STATE_NO : KILLABLE_STATE_YES;
}
- public void update(android.automotive.watchdog.IoOveruseStats internalStats) {
+ public void updateLocked(android.automotive.watchdog.IoOveruseStats internalStats) {
if (!internalStats.killableOnOveruse) {
/*
* Killable value specified in the internal stats is provided by the native daemon.
@@ -600,20 +1057,28 @@
* vendor services and doesn't reflect the user choices. Thus if the internal stats
* specify the application is not killable, the application is not safe-to-kill.
*/
- this.mKillableState = KILLABLE_STATE_NEVER;
+ mKillableState = KILLABLE_STATE_NEVER;
+ } else if (mKillableState == KILLABLE_STATE_NEVER) {
+ /*
+ * This case happens when a previously unsafe to kill system/vendor package was
+ * recently marked as safe-to-kill so update the old state to the default value.
+ */
+ mKillableState = mDefaultNotKillablePackages.contains(packageName)
+ ? KILLABLE_STATE_NO : KILLABLE_STATE_YES;
}
ioUsage.update(internalStats);
}
- public ResourceOveruseStats getResourceOveruseStatsWithIo() {
- IoOveruseStats ioOveruseStats = null;
- if (ioUsage.hasUsage()) {
- ioOveruseStats = ioUsage.getStatsBuilder().setKillableOnOveruse(
- mKillableState != PackageKillableState.KILLABLE_STATE_NEVER).build();
- }
+ public ResourceOveruseStats.Builder getResourceOveruseStatsBuilder() {
+ return new ResourceOveruseStats.Builder(packageName, UserHandle.of(userId));
+ }
- return new ResourceOveruseStats.Builder(packageName, UserHandle.of(userId))
- .setIoOveruseStats(ioOveruseStats).build();
+ public IoOveruseStats getIoOveruseStats() {
+ if (!ioUsage.hasUsage()) {
+ return null;
+ }
+ return ioUsage.getStatsBuilder().setKillableOnOveruse(
+ mKillableState != KILLABLE_STATE_NEVER).build();
}
public @KillableState int getKillableState() {
@@ -621,12 +1086,30 @@
}
public boolean setKillableState(boolean isKillable) {
- if (mKillableState == PackageKillableState.KILLABLE_STATE_NEVER) {
+ if (mKillableState == KILLABLE_STATE_NEVER) {
return false;
}
mKillableState = isKillable ? KILLABLE_STATE_YES : KILLABLE_STATE_NO;
return true;
}
+
+ public int syncAndFetchKillableStateLocked(int myComponentType) {
+ /*
+ * The killable state goes out-of-sync:
+ * 1. When the on-device safe-to-kill list is recently updated and the user package
+ * didn't have any resource usage so the native daemon didn't update the killable state.
+ * 2. When a package has no resource usage and is initialized outside of processing the
+ * latest resource usage stats.
+ */
+ if (myComponentType != ComponentType.THIRD_PARTY
+ && !mSafeToKillPackages.contains(packageName)) {
+ mKillableState = KILLABLE_STATE_NEVER;
+ } else if (mKillableState == KILLABLE_STATE_NEVER) {
+ mKillableState = mDefaultNotKillablePackages.contains(packageName)
+ ? KILLABLE_STATE_NO : KILLABLE_STATE_YES;
+ }
+ return mKillableState;
+ }
}
private static final class PackageIoUsage {
@@ -697,7 +1180,7 @@
@Override
public void binderDied() {
- Slogf.w(TAG, "Resource overuse listener%s (pid: %d) died",
+ Slogf.w(CarWatchdogService.TAG, "Resource overuse listener%s (pid: %d) died",
isListenerForSystem ? " for system" : "", pid);
onResourceOveruseListenerDeath(uid, isListenerForSystem);
unlinkToDeath();
diff --git a/service/src/com/android/car/watchdog/WatchdogProcessHandler.java b/service/src/com/android/car/watchdog/WatchdogProcessHandler.java
index 59e76c7..2754755 100644
--- a/service/src/com/android/car/watchdog/WatchdogProcessHandler.java
+++ b/service/src/com/android/car/watchdog/WatchdogProcessHandler.java
@@ -37,7 +37,6 @@
import android.util.SparseArray;
import android.util.SparseBooleanArray;
-import com.android.car.CarLog;
import com.android.internal.annotations.GuardedBy;
import com.android.server.utils.Slogf;
@@ -47,11 +46,9 @@
* Handles clients' health status checking and reporting the statuses to the watchdog daemon.
*/
public final class WatchdogProcessHandler {
- private static final String TAG = CarLog.tagFor(CarWatchdogService.class);
private static final int[] ALL_TIMEOUTS =
{ TIMEOUT_CRITICAL, TIMEOUT_MODERATE, TIMEOUT_NORMAL };
- private final boolean mIsDebugEnabled;
private final ICarWatchdogServiceForSystem mWatchdogServiceForSystem;
private final CarWatchdogDaemonHelper mCarWatchdogDaemonHelper;
private final Handler mMainHandler = new Handler(Looper.getMainLooper());
@@ -85,8 +82,7 @@
private final SparseBooleanArray mStoppedUser = new SparseBooleanArray();
public WatchdogProcessHandler(ICarWatchdogServiceForSystem serviceImpl,
- CarWatchdogDaemonHelper daemonHelper, boolean isDebugEnabled) {
- mIsDebugEnabled = isDebugEnabled;
+ CarWatchdogDaemonHelper daemonHelper) {
mWatchdogServiceForSystem = serviceImpl;
mCarWatchdogDaemonHelper = daemonHelper;
}
@@ -98,8 +94,8 @@
mPingedClientMap.put(timeout, new SparseArray<ClientInfo>());
mClientCheckInProgress.put(timeout, false);
}
- if (mIsDebugEnabled) {
- Slogf.d(TAG, "WatchdogProcessHandler is initialized");
+ if (CarWatchdogService.DEBUG) {
+ Slogf.d(CarWatchdogService.TAG, "WatchdogProcessHandler is initialized");
}
}
@@ -136,14 +132,15 @@
synchronized (mLock) {
ArrayList<ClientInfo> clients = mClientMap.get(timeout);
if (clients == null) {
- Slogf.w(TAG, "Cannot register the client: invalid timeout");
+ Slogf.w(CarWatchdogService.TAG, "Cannot register the client: invalid timeout");
return;
}
IBinder binder = client.asBinder();
for (int i = 0; i < clients.size(); i++) {
ClientInfo clientInfo = clients.get(i);
if (binder == clientInfo.client.asBinder()) {
- Slogf.w(TAG, "Cannot register the client: the client(pid: %d) has been already "
+ Slogf.w(CarWatchdogService.TAG,
+ "Cannot register the client: the client(pid: %d) has been already "
+ "registered", clientInfo.pid);
return;
}
@@ -154,12 +151,13 @@
try {
clientInfo.linkToDeath();
} catch (RemoteException e) {
- Slogf.w(TAG, "Cannot register the client: linkToDeath to the client failed");
+ Slogf.w(CarWatchdogService.TAG,
+ "Cannot register the client: linkToDeath to the client failed");
return;
}
clients.add(clientInfo);
- if (mIsDebugEnabled) {
- Slogf.d(TAG, "Client(pid: %d) is registered", pid);
+ if (CarWatchdogService.DEBUG) {
+ Slogf.d(CarWatchdogService.TAG, "Client(pid: %d) is registered", pid);
}
}
}
@@ -177,14 +175,16 @@
}
clientInfo.unlinkToDeath();
clients.remove(i);
- if (mIsDebugEnabled) {
- Slogf.d(TAG, "Client(pid: %d) is unregistered", clientInfo.pid);
+ if (CarWatchdogService.DEBUG) {
+ Slogf.d(CarWatchdogService.TAG, "Client(pid: %d) is unregistered",
+ clientInfo.pid);
}
return;
}
}
}
- Slogf.w(TAG, "Cannot unregister the client: the client has not been registered before");
+ Slogf.w(CarWatchdogService.TAG,
+ "Cannot unregister the client: the client has not been registered before");
return;
}
@@ -297,8 +297,8 @@
try {
clientInfo.client.onCheckHealthStatus(clientInfo.sessionId, timeout);
} catch (RemoteException e) {
- Slogf.w(TAG, "Sending a ping message to client(pid: %d) failed: %s", clientInfo.pid,
- e);
+ Slogf.w(CarWatchdogService.TAG,
+ "Sending a ping message to client(pid: %d) failed: %s", clientInfo.pid, e);
synchronized (mLock) {
pingedClients.remove(clientInfo.sessionId);
}
@@ -348,7 +348,8 @@
try {
clientInfo.client.onPrepareProcessTermination();
} catch (RemoteException e) {
- Slogf.w(TAG, "Notifying onPrepareProcessTermination to client(pid: %d) failed: %s",
+ Slogf.w(CarWatchdogService.TAG,
+ "Notifying onPrepareProcessTermination to client(pid: %d) failed: %s",
clientInfo.pid, e);
}
}
@@ -357,7 +358,8 @@
mCarWatchdogDaemonHelper.tellCarWatchdogServiceAlive(
mWatchdogServiceForSystem, clientsNotResponding, sessionId);
} catch (RemoteException | RuntimeException e) {
- Slogf.w(TAG, "Cannot respond to car watchdog daemon (sessionId=%d): %s", sessionId, e);
+ Slogf.w(CarWatchdogService.TAG,
+ "Cannot respond to car watchdog daemon (sessionId=%d): %s", sessionId, e);
}
}
@@ -380,7 +382,7 @@
case TIMEOUT_NORMAL:
return "normal";
default:
- Slogf.w(TAG, "Unknown timeout value");
+ Slogf.w(CarWatchdogService.TAG, "Unknown timeout value");
return "unknown";
}
}
@@ -394,7 +396,7 @@
case TIMEOUT_NORMAL:
return 10000L;
default:
- Slogf.w(TAG, "Unknown timeout value");
+ Slogf.w(CarWatchdogService.TAG, "Unknown timeout value");
return 10000L;
}
}
@@ -416,7 +418,7 @@
@Override
public void binderDied() {
- Slogf.w(TAG, "Client(pid: %d) died", pid);
+ Slogf.w(CarWatchdogService.TAG, "Client(pid: %d) died", pid);
onClientDeath(client, timeout);
}
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/bluetooth_devices.xml b/tests/EmbeddedKitchenSinkApp/res/layout/bluetooth_devices.xml
new file mode 100644
index 0000000..c856233
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/bluetooth_devices.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:columnCount = "2">
+ <TextView
+ android:id="@+id/bluetooth_device"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"/>
+ <Button
+ android:id="@+id/bluetooth_pick_device"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/bluetooth_pick_device"/>
+ <TableLayout
+ android:id="@+id/PairedDeviceTable"
+ android:layout_width="409dp"
+ android:layout_height="190dp"
+ tools:layout_editor_absoluteX="1dp"
+ tools:layout_editor_absoluteY="1dp"/>
+</GridLayout>
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java
index acbc2ad..01748a6 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java
@@ -47,6 +47,7 @@
import com.google.android.car.kitchensink.assistant.CarAssistantFragment;
import com.google.android.car.kitchensink.audio.AudioTestFragment;
import com.google.android.car.kitchensink.audio.CarAudioInputTestFragment;
+import com.google.android.car.kitchensink.bluetooth.BluetoothDeviceFragment;
import com.google.android.car.kitchensink.bluetooth.BluetoothHeadsetFragment;
import com.google.android.car.kitchensink.bluetooth.MapMceTestFragment;
import com.google.android.car.kitchensink.carboard.KeyboardTestFragment;
@@ -173,6 +174,7 @@
new FragmentMenuEntry("assistant", CarAssistantFragment.class),
new FragmentMenuEntry("audio", AudioTestFragment.class),
new FragmentMenuEntry("Audio Input", CarAudioInputTestFragment.class),
+ new FragmentMenuEntry("BT device", BluetoothDeviceFragment.class),
new FragmentMenuEntry("BT headset", BluetoothHeadsetFragment.class),
new FragmentMenuEntry("BT messaging", MapMceTestFragment.class),
new FragmentMenuEntry("carapi", CarApiTestFragment.class),
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/bluetooth/BluetoothDeviceFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/bluetooth/BluetoothDeviceFragment.java
new file mode 100644
index 0000000..c1de700
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/bluetooth/BluetoothDeviceFragment.java
@@ -0,0 +1,162 @@
+/*
+ * 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.google.android.car.kitchensink.bluetooth;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothDevicePicker;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.TableLayout;
+import android.widget.TableRow;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+
+import com.google.android.car.kitchensink.R;
+
+import java.util.Set;
+import java.util.concurrent.Executor;
+
+public class BluetoothDeviceFragment extends Fragment {
+ private static final String TAG = "CAR.BLUETOOTH.KS";
+ BluetoothAdapter mBluetoothAdapter;
+ BluetoothDevice mPickedDevice;
+ Executor mExecutor;
+ BluetoothDeviceTypeChecker mDeviceTypeChecker;
+
+ TextView mPickedDeviceText;
+ Button mDevicePicker;
+ TableLayout mTableLayout;
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ View v = inflater.inflate(R.layout.bluetooth_devices, container, false);
+ mDeviceTypeChecker = new BluetoothDeviceTypeChecker(getContext(), true);
+ mDevicePicker = v.findViewById(R.id.bluetooth_pick_device);
+ mTableLayout = v.findViewById(R.id.PairedDeviceTable);
+ mExecutor = new ThreadPerTaskExecutor();
+
+ // Pick a bluetooth device
+ mDevicePicker.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ launchDevicePicker();
+ }
+ });
+
+ return v;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ checkAllDevices();
+ }
+
+ void launchDevicePicker() {
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(BluetoothDevicePicker.ACTION_DEVICE_SELECTED);
+ getContext().registerReceiver(mPickerReceiver, filter);
+
+ Intent intent = new Intent(BluetoothDevicePicker.ACTION_LAUNCH);
+ intent.setFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ getContext().startActivity(intent);
+ }
+
+ void checkAllDevices() {
+ BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+ if (bluetoothAdapter == null) {
+ Log.w(TAG, "Bluetooth Adapter not available");
+ return;
+ }
+ mTableLayout.removeAllViews();
+ Set<BluetoothDevice> bondedDevices = bluetoothAdapter.getBondedDevices();
+ Context context = getContext();
+ for (BluetoothDevice device : bondedDevices) {
+ TableRow row = new TableRow(context);
+ TextView deviceName = new TextView(context);
+ deviceName.setText(device.getName());
+ TextView deviceType = new TextView(context);
+ deviceType.setText(Boolean.toString(mDeviceTypeChecker.isIapDevice(device)));
+ row.addView(deviceName);
+ row.addView(deviceType);
+ mTableLayout.addView(row);
+ }
+ }
+
+ private void addDeviceToTable(BluetoothDevice device, String value) {
+ getActivity().runOnUiThread(new Runnable() {
+ public void run() {
+ Context context = getContext();
+ TableRow row = new TableRow(context);
+ TextView deviceName = new TextView(context);
+ TextView deviceValue = new TextView(context);
+ deviceName.setText(device.getName());
+ deviceValue.setText(value);
+ row.addView(deviceName);
+ row.addView(deviceValue);
+ mTableLayout.addView(row);
+ }
+ });
+ }
+
+ private final BroadcastReceiver mPickerReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ Log.v(TAG, "mPickerReceiver got " + action);
+
+ if (BluetoothDevicePicker.ACTION_DEVICE_SELECTED.equals(action)) {
+ final BluetoothDevice device =
+ intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ Log.v(TAG, "mPickerReceiver got " + device);
+ if (device == null) {
+ Toast.makeText(getContext(), "No device selected", Toast.LENGTH_SHORT).show();
+ return;
+ }
+ mExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ addDeviceToTable(device,
+ Boolean.toString(mDeviceTypeChecker.isIapDevice(device)));
+ Log.w(TAG, "Is iAP" + mDeviceTypeChecker.isIapDevice(device));
+ }
+ });
+ Log.w(TAG, "Dispatched");
+ getContext().unregisterReceiver(mPickerReceiver);
+ }
+ }
+ };
+
+ private class ThreadPerTaskExecutor implements Executor {
+ public void execute(Runnable r) {
+ new Thread(r).start();
+ }
+ }
+}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/bluetooth/BluetoothDeviceTypeChecker.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/bluetooth/BluetoothDeviceTypeChecker.java
new file mode 100644
index 0000000..1523634
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/bluetooth/BluetoothDeviceTypeChecker.java
@@ -0,0 +1,137 @@
+/*
+ * 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.google.android.car.kitchensink.bluetooth;
+
+import android.bluetooth.BluetoothDevice;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.ParcelUuid;
+import android.util.Log;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
+public class BluetoothDeviceTypeChecker {
+ private static final ParcelUuid IAP_UUID =
+ ParcelUuid.fromString("00000000-deca-fade-deca-deafdecacafe");
+ private static final int MAX_SECONDS_TO_BLOCK = 10;
+ private static final String TAG = "BluetoothDeviceTypeChecker";
+
+ private final Context mContext;
+ private final boolean mAllowBlocking;
+ private final Object mLock = new Object();
+
+ CompletableFuture<ParcelUuid[]> mDeviceUUidsFuture;
+ BluetoothDevice mBluetoothDevice;
+
+ /**
+ * BluetoothDeviceTypeChecker
+ * Class designed to fetch and check UUID records for matches based on either cached or live
+ * records. Live records are fetched if allowBlocking is enabled and there is nothing in the
+ * cache. Paired devices should always have records in the cache if the BluetoothAdapter is on.
+ *
+ * @param context The context on which to receive updates if live records are necessary
+ * @param allowBlocking If cached SDP records are not available allow methods to block in a
+ * best effort of acquiring them.
+ */
+ public BluetoothDeviceTypeChecker(Context context, boolean allowBlocking) {
+ mContext = context;
+ if (mContext != null) {
+ mAllowBlocking = allowBlocking;
+ } else {
+ mAllowBlocking = false;
+ }
+ }
+
+ /**
+ * isIapDevice
+ * Check if device is indicating support for iAP
+ * @param device
+ * @return
+ */
+ public boolean isIapDevice(BluetoothDevice device) {
+ return deviceContainsUuid(device, IAP_UUID);
+ }
+
+ /**
+ * deviceContainsUuid
+ * Check if device contains a specific UUID record
+ * @param device to perform a lookup on
+ * @param uuid to check in the records
+ * @return
+ */
+ public boolean deviceContainsUuid(BluetoothDevice device, ParcelUuid uuid) {
+ if (device == null) return false;
+ if (uuid == null) return false;
+
+ synchronized (mLock) {
+ mBluetoothDevice = device;
+ ParcelUuid[] uuidsArray = device.getUuids();
+ if (mAllowBlocking && (uuidsArray == null || uuidsArray.length == 0)) {
+ uuidsArray = blockingFetchUuids(device);
+ }
+ if (uuidsArray == null || uuidsArray.length == 0) {
+ return false;
+ }
+ for (int i = 0; i < uuidsArray.length; i++) {
+ if (uuid.equals(uuidsArray[i])) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ /*
+ Perform a blocking fetch of the UUIDs on specified BluetoothDevice
+ */
+ private ParcelUuid[] blockingFetchUuids(BluetoothDevice device) {
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(BluetoothDevice.ACTION_UUID);
+ mContext.registerReceiver(mUuidReceiver, filter);
+ mDeviceUUidsFuture = new CompletableFuture<>();
+ if (!device.fetchUuidsWithSdp()) {
+ Log.w(TAG, "fetching UUIDs failed.");
+ mContext.unregisterReceiver(mUuidReceiver);
+ return new ParcelUuid[0];
+ }
+ try {
+ return mDeviceUUidsFuture.get(MAX_SECONDS_TO_BLOCK, TimeUnit.SECONDS);
+ } catch (Exception e) {
+ mContext.unregisterReceiver(mUuidReceiver);
+ return new ParcelUuid[0];
+ }
+ }
+
+ /*
+ Broadcast receiver on which to receive updates to Bluetooth UUID records.
+ */
+ private BroadcastReceiver mUuidReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ BluetoothDevice device =
+ intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ if (mBluetoothDevice.equals(device)
+ && BluetoothDevice.ACTION_UUID.equals(intent.getAction())) {
+ mDeviceUUidsFuture.complete(device.getUuids());
+ mContext.unregisterReceiver(this);
+ }
+ }
+ };
+}
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/android/car/test/mocks/JavaMockitoHelperTest.java b/tests/carservice_unit_test/src/android/car/test/mocks/JavaMockitoHelperTest.java
new file mode 100644
index 0000000..f83deee
--- /dev/null
+++ b/tests/carservice_unit_test/src/android/car/test/mocks/JavaMockitoHelperTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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.car.test.mocks;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import org.junit.Test;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+public final class JavaMockitoHelperTest {
+
+ private static final long TIMEOUT_MS = 1_000L;
+
+ @Test
+ public void testAwait_Semaphore() throws InterruptedException {
+ Semaphore semaphore = new Semaphore(1);
+
+ JavaMockitoHelper.await(semaphore, TIMEOUT_MS);
+
+ assertThat(semaphore.availablePermits()).isEqualTo(0);
+ }
+
+ @Test
+ public void testAwait_CountDownLatch() throws InterruptedException {
+ CountDownLatch latch = new CountDownLatch(1);
+ new Thread(() -> latch.countDown(), "testAwait_CountDownLatch").start();
+
+ JavaMockitoHelper.await(latch, TIMEOUT_MS);
+
+ assertThat(latch.getCount()).isEqualTo(0);
+ }
+
+ @Test
+ public void testSilentAwait_notCalled() {
+ CountDownLatch latch = new CountDownLatch(1);
+
+ assertThat(JavaMockitoHelper.silentAwait(latch, 5L)).isFalse();
+ assertThat(latch.getCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void testSilentAwait_called() {
+ CountDownLatch latch = new CountDownLatch(1);
+ new Thread(() -> latch.countDown(), "testSilentAwait_called").start();
+
+ assertThat(JavaMockitoHelper.silentAwait(latch, TIMEOUT_MS)).isTrue();
+ assertThat(latch.getCount()).isEqualTo(0);
+ }
+
+ @Test
+ public void testGetResult() throws InterruptedException, ExecutionException, TimeoutException {
+ Future<String> future = mock(Future.class);
+ when(future.get(anyLong(), any())).thenReturn("done");
+
+ assertThat(JavaMockitoHelper.getResult(future)).isEqualTo("done");
+ }
+
+ @Test
+ public void testGetResult_withCustomTimeout()
+ throws InterruptedException, ExecutionException, TimeoutException {
+ Future<String> future = mock(Future.class);
+ when(future.get(anyLong(), any(TimeUnit.class))).thenReturn("done");
+
+ assertThat(JavaMockitoHelper.getResult(future, TIMEOUT_MS)).isEqualTo("done");
+ verify(future).get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ }
+}
diff --git a/tests/carservice_unit_test/src/android/car/test/util/SyncAnswerTest.java b/tests/carservice_unit_test/src/android/car/test/mocks/SyncAnswerTest.java
similarity index 97%
rename from tests/carservice_unit_test/src/android/car/test/util/SyncAnswerTest.java
rename to tests/carservice_unit_test/src/android/car/test/mocks/SyncAnswerTest.java
index 2e26e7e..4847588 100644
--- a/tests/carservice_unit_test/src/android/car/test/util/SyncAnswerTest.java
+++ b/tests/carservice_unit_test/src/android/car/test/mocks/SyncAnswerTest.java
@@ -14,15 +14,13 @@
* limitations under the License.
*/
-package android.car.test.util;
+package android.car.test.mocks;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
-import android.car.test.mocks.SyncAnswer;
-
import org.junit.Before;
import org.junit.Test;
diff --git a/tests/carservice_unit_test/src/android/car/watchdoglib/CarWatchdogDaemonHelperTest.java b/tests/carservice_unit_test/src/android/car/watchdoglib/CarWatchdogDaemonHelperTest.java
index d682969..92dfb9a 100644
--- a/tests/carservice_unit_test/src/android/car/watchdoglib/CarWatchdogDaemonHelperTest.java
+++ b/tests/carservice_unit_test/src/android/car/watchdoglib/CarWatchdogDaemonHelperTest.java
@@ -20,11 +20,15 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import static org.testng.Assert.assertThrows;
+import android.automotive.watchdog.internal.ComponentType;
import android.automotive.watchdog.internal.ICarWatchdog;
import android.automotive.watchdog.internal.ICarWatchdogMonitor;
import android.automotive.watchdog.internal.ICarWatchdogServiceForSystem;
@@ -32,6 +36,7 @@
import android.automotive.watchdog.internal.PackageIoOveruseStats;
import android.automotive.watchdog.internal.PackageResourceOveruseAction;
import android.automotive.watchdog.internal.PowerCycle;
+import android.automotive.watchdog.internal.ResourceOveruseConfiguration;
import android.automotive.watchdog.internal.StateType;
import android.os.Binder;
import android.os.IBinder;
@@ -47,6 +52,7 @@
import org.mockito.quality.Strictness;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
/**
@@ -154,6 +160,31 @@
}
@Test
+ public void testIndirectCall_updateResourceOveruseConfigurations() throws Exception {
+ ResourceOveruseConfiguration config = new ResourceOveruseConfiguration();
+ config.componentType = ComponentType.SYSTEM;
+ List<ResourceOveruseConfiguration> configs = new ArrayList<>(Collections.singleton(config));
+
+ mCarWatchdogDaemonHelper.updateResourceOveruseConfigurations(configs);
+
+ verify(mFakeCarWatchdog).updateResourceOveruseConfigurations(eq(configs));
+ }
+
+ @Test
+ public void testIndirectCall_getResourceOveruseConfigurations() throws Exception {
+ ResourceOveruseConfiguration config = new ResourceOveruseConfiguration();
+ config.componentType = ComponentType.SYSTEM;
+ List<ResourceOveruseConfiguration> expected =
+ new ArrayList<>(Collections.singleton(config));
+ when(mFakeCarWatchdog.getResourceOveruseConfigurations()).thenReturn(expected);
+
+ List<ResourceOveruseConfiguration> actual =
+ mCarWatchdogDaemonHelper.getResourceOveruseConfigurations();
+
+ assertThat(actual).isEqualTo(expected);
+ }
+
+ @Test
public void testIndirectCall_actionTakenOnResourceOveruse() throws Exception {
List<PackageResourceOveruseAction> actions = new ArrayList<>();
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;
+ }
+ }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/watchdog/CarWatchdogServiceUnitTest.java b/tests/carservice_unit_test/src/com/android/car/watchdog/CarWatchdogServiceUnitTest.java
index fa4c30f..cdb3452 100644
--- a/tests/carservice_unit_test/src/com/android/car/watchdog/CarWatchdogServiceUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/watchdog/CarWatchdogServiceUnitTest.java
@@ -18,6 +18,7 @@
import static android.automotive.watchdog.internal.ResourceOveruseActionType.KILLED;
import static android.automotive.watchdog.internal.ResourceOveruseActionType.NOT_KILLED;
+import static android.car.test.mocks.AndroidMockitoHelper.mockUmGetAliveUsers;
import static android.car.test.mocks.AndroidMockitoHelper.mockUmGetAllUsers;
import static android.car.watchdog.CarWatchdogManager.TIMEOUT_CRITICAL;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
@@ -51,13 +52,21 @@
import android.automotive.watchdog.internal.PackageIdentifier;
import android.automotive.watchdog.internal.PackageInfo;
import android.automotive.watchdog.internal.PackageIoOveruseStats;
+import android.automotive.watchdog.internal.PackageMetadata;
import android.automotive.watchdog.internal.PackageResourceOveruseAction;
+import android.automotive.watchdog.internal.PerStateIoOveruseThreshold;
+import android.automotive.watchdog.internal.ResourceSpecificConfiguration;
import android.automotive.watchdog.internal.UidType;
import android.car.test.mocks.AbstractExtendedMockitoTestCase;
import android.car.watchdog.CarWatchdogManager;
import android.car.watchdog.ICarWatchdogServiceCallback;
import android.car.watchdog.IResourceOveruseListener;
+import android.car.watchdog.IoOveruseAlertThreshold;
+import android.car.watchdog.IoOveruseConfiguration;
import android.car.watchdog.IoOveruseStats;
+import android.car.watchdog.PackageKillableState;
+import android.car.watchdog.PerStateBytes;
+import android.car.watchdog.ResourceOveruseConfiguration;
import android.car.watchdog.ResourceOveruseStats;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -66,13 +75,16 @@
import android.content.pm.UserInfo;
import android.os.Binder;
import android.os.IBinder;
+import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.SparseArray;
-import android.util.SparseBooleanArray;
+
+import com.android.internal.util.function.TriConsumer;
import com.google.common.truth.Correspondence;
@@ -89,6 +101,8 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
/**
* <p>This class contains unit tests for the {@link CarWatchdogService}.
@@ -102,9 +116,9 @@
@Mock private Context mMockContext;
@Mock private PackageManager mMockPackageManager;
- @Mock private UserManager mUserManager;
- @Mock private IBinder mBinder;
- @Mock private ICarWatchdog mCarWatchdogDaemon;
+ @Mock private UserManager mMockUserManager;
+ @Mock private IBinder mMockBinder;
+ @Mock private ICarWatchdog mMockCarWatchdogDaemon;
private CarWatchdogService mCarWatchdogService;
private ICarWatchdogServiceForSystem mWatchdogServiceForSystemImpl;
@@ -135,7 +149,7 @@
@Test
public void testCarWatchdogServiceHealthCheck() throws Exception {
mWatchdogServiceForSystemImpl.checkIfAlive(123456, TIMEOUT_CRITICAL);
- verify(mCarWatchdogDaemon,
+ verify(mMockCarWatchdogDaemon,
timeout(MAX_WAIT_TIME_MS)).tellCarWatchdogServiceAlive(
eq(mWatchdogServiceForSystemImpl), any(int[].class), eq(123456));
}
@@ -178,6 +192,204 @@
}
@Test
+ public void testGetResourceOveruseStats() throws Exception {
+ SparseArray<String> packageNamesByUid = new SparseArray<>();
+ packageNamesByUid.put(Binder.getCallingUid(), mMockContext.getPackageName());
+ injectUidToPackageNameMapping(packageNamesByUid);
+
+ List<PackageIoOveruseStats> packageIoOveruseStats = new ArrayList<>(
+ Collections.singletonList(
+ constructPackageIoOveruseStats(
+ Binder.getCallingUid(), /* shouldNotify= */false,
+ constructInternalIoOveruseStats(/* killableOnOveruse= */false,
+ /* remainingWriteBytes= */constructPerStateBytes(20, 20, 20),
+ /* writtenBytes= */constructPerStateBytes(100, 200, 300),
+ /* totalOveruses= */2)))
+ );
+ mWatchdogServiceForSystemImpl.latestIoOveruseStats(packageIoOveruseStats);
+
+ ResourceOveruseStats expectedStats =
+ constructResourceOveruseStats(packageNamesByUid.keyAt(0),
+ packageNamesByUid.valueAt(0), packageIoOveruseStats.get(0).ioOveruseStats);
+
+ ResourceOveruseStats actualStats = mCarWatchdogService.getResourceOveruseStats(
+ CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO,
+ CarWatchdogManager.STATS_PERIOD_CURRENT_DAY);
+
+ assertWithMessage("Expected: " + expectedStats.toString() + "\nActual: "
+ + actualStats.toString())
+ .that(ResourceOveruseStatsSubject.isEquals(actualStats, expectedStats)).isTrue();
+ }
+
+ @Test
+ public void testFailsGetResourceOveruseStatsWithInvalidArgs() throws Exception {
+ assertThrows(IllegalArgumentException.class,
+ () -> mCarWatchdogService.getResourceOveruseStats(/* resourceOveruseFlag= */0,
+ CarWatchdogManager.STATS_PERIOD_CURRENT_DAY));
+
+ assertThrows(IllegalArgumentException.class,
+ () -> mCarWatchdogService.getResourceOveruseStats(
+ CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO, /* maxStatsPeriod= */0));
+ }
+
+ @Test
+ public void testGetAllResourceOveruseStatsWithNoMinimum() throws Exception {
+ SparseArray<String> packageNamesByUid = new SparseArray<>();
+ packageNamesByUid.put(1103456, "third_party_package");
+ packageNamesByUid.put(1201278, "vendor_package.critical");
+ injectUidToPackageNameMapping(packageNamesByUid);
+
+ List<PackageIoOveruseStats> packageIoOveruseStats = new ArrayList<>(Arrays.asList(
+ constructPackageIoOveruseStats(packageNamesByUid.keyAt(0), /* shouldNotify= */false,
+ constructInternalIoOveruseStats(/* killableOnOveruse= */true,
+ /* remainingWriteBytes= */constructPerStateBytes(20, 20, 20),
+ /* writtenBytes= */constructPerStateBytes(100, 200, 300),
+ /* totalOveruses= */2)),
+ constructPackageIoOveruseStats(packageNamesByUid.keyAt(1), /* shouldNotify= */false,
+ constructInternalIoOveruseStats(/* killableOnOveruse= */false,
+ /* remainingWriteBytes= */constructPerStateBytes(450, 120, 340),
+ /* writtenBytes= */constructPerStateBytes(5000, 6000, 9000),
+ /* totalOveruses= */2))));
+ mWatchdogServiceForSystemImpl.latestIoOveruseStats(packageIoOveruseStats);
+
+ List<ResourceOveruseStats> expectedStats = new ArrayList<>(Arrays.asList(
+ constructResourceOveruseStats(packageNamesByUid.keyAt(0),
+ packageNamesByUid.valueAt(0), packageIoOveruseStats.get(0).ioOveruseStats),
+ constructResourceOveruseStats(packageNamesByUid.keyAt(1),
+ packageNamesByUid.valueAt(1), packageIoOveruseStats.get(1).ioOveruseStats))
+ );
+
+ List<ResourceOveruseStats> actualStats = mCarWatchdogService.getAllResourceOveruseStats(
+ CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO, /* minimumStatsFlag= */0,
+ CarWatchdogManager.STATS_PERIOD_CURRENT_DAY);
+
+ ResourceOveruseStatsSubject.assertThat(actualStats)
+ .containsExactlyElementsIn(expectedStats);
+ }
+
+ @Test
+ public void testFailsGetAllResourceOveruseStatsWithInvalidArgs() throws Exception {
+ assertThrows(IllegalArgumentException.class,
+ () -> mCarWatchdogService.getAllResourceOveruseStats(0, /* minimumStatsFlag= */0,
+ CarWatchdogManager.STATS_PERIOD_CURRENT_DAY));
+
+ assertThrows(IllegalArgumentException.class,
+ () -> mCarWatchdogService.getAllResourceOveruseStats(
+ CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO,
+ CarWatchdogManager.FLAG_MINIMUM_STATS_IO_1_MB
+ | CarWatchdogManager.FLAG_MINIMUM_STATS_IO_100_MB,
+ CarWatchdogManager.STATS_PERIOD_CURRENT_DAY));
+
+ assertThrows(IllegalArgumentException.class,
+ () -> mCarWatchdogService.getAllResourceOveruseStats(
+ CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO, /* minimumStatsFlag= */1 << 5,
+ CarWatchdogManager.STATS_PERIOD_CURRENT_DAY));
+
+ assertThrows(IllegalArgumentException.class,
+ () -> mCarWatchdogService.getAllResourceOveruseStats(
+ CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO, /* minimumStatsFlag= */0,
+ /* maxStatsPeriod= */0));
+ }
+
+ @Test
+ public void testGetAllResourceOveruseStatsWithMinimum() throws Exception {
+ SparseArray<String> packageNamesByUid = new SparseArray<>();
+ packageNamesByUid.put(1103456, "third_party_package");
+ packageNamesByUid.put(1201278, "vendor_package.critical");
+ injectUidToPackageNameMapping(packageNamesByUid);
+
+ List<PackageIoOveruseStats> packageIoOveruseStats = new ArrayList<>(Arrays.asList(
+ constructPackageIoOveruseStats(packageNamesByUid.keyAt(0), /* shouldNotify= */false,
+ constructInternalIoOveruseStats(/* killableOnOveruse= */true,
+ /* remainingWriteBytes= */constructPerStateBytes(20, 20, 20),
+ /* writtenBytes= */constructPerStateBytes(100, 200, 300),
+ /* totalOveruses= */2)),
+ constructPackageIoOveruseStats(packageNamesByUid.keyAt(1), /* shouldNotify= */false,
+ constructInternalIoOveruseStats(/* killableOnOveruse= */false,
+ /* remainingWriteBytes= */constructPerStateBytes(450, 120, 340),
+ /* writtenBytes= */constructPerStateBytes(7000000, 6000, 9000),
+ /* totalOveruses= */2))));
+ mWatchdogServiceForSystemImpl.latestIoOveruseStats(packageIoOveruseStats);
+
+ List<ResourceOveruseStats> expectedStats = new ArrayList<>(Arrays.asList(
+ constructResourceOveruseStats(packageNamesByUid.keyAt(1),
+ packageNamesByUid.valueAt(1), packageIoOveruseStats.get(1).ioOveruseStats))
+ );
+
+ List<ResourceOveruseStats> actualStats = mCarWatchdogService.getAllResourceOveruseStats(
+ CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO,
+ CarWatchdogManager.FLAG_MINIMUM_STATS_IO_1_MB,
+ CarWatchdogManager.STATS_PERIOD_CURRENT_DAY);
+
+ ResourceOveruseStatsSubject.assertThat(actualStats)
+ .containsExactlyElementsIn(expectedStats);
+ }
+
+ @Test
+ public void testGetResourceOveruseStatsForUserPackage() throws Exception {
+ SparseArray<String> packageNamesByUid = new SparseArray<>();
+ packageNamesByUid.put(1103456, "third_party_package");
+ packageNamesByUid.put(1201278, "vendor_package.critical");
+ injectUidToPackageNameMapping(packageNamesByUid);
+
+ List<PackageIoOveruseStats> packageIoOveruseStats = new ArrayList<>(Arrays.asList(
+ constructPackageIoOveruseStats(packageNamesByUid.keyAt(0), /* shouldNotify= */false,
+ constructInternalIoOveruseStats(/* killableOnOveruse= */true,
+ /* remainingWriteBytes= */constructPerStateBytes(20, 20, 20),
+ /* writtenBytes= */constructPerStateBytes(100, 200, 300),
+ /* totalOveruses= */2)),
+ constructPackageIoOveruseStats(packageNamesByUid.keyAt(1), /* shouldNotify= */false,
+ constructInternalIoOveruseStats(/* killableOnOveruse= */false,
+ /* remainingWriteBytes= */constructPerStateBytes(450, 120, 340),
+ /* writtenBytes= */constructPerStateBytes(500, 600, 900),
+ /* totalOveruses= */2))));
+ mWatchdogServiceForSystemImpl.latestIoOveruseStats(packageIoOveruseStats);
+
+ ResourceOveruseStats expectedStats =
+ constructResourceOveruseStats(packageNamesByUid.keyAt(1),
+ packageNamesByUid.valueAt(1), packageIoOveruseStats.get(1).ioOveruseStats);
+
+ ResourceOveruseStats actualStats =
+ mCarWatchdogService.getResourceOveruseStatsForUserPackage(
+ "vendor_package.critical", new UserHandle(12),
+ CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO,
+ CarWatchdogManager.STATS_PERIOD_CURRENT_DAY);
+
+ assertWithMessage("Expected: " + expectedStats.toString() + "\nActual: "
+ + actualStats.toString())
+ .that(ResourceOveruseStatsSubject.isEquals(actualStats, expectedStats)).isTrue();
+ }
+
+ @Test
+ public void testFailsGetResourceOveruseStatsForUserPackageWithInvalidArgs() throws Exception {
+ assertThrows(NullPointerException.class,
+ () -> mCarWatchdogService.getResourceOveruseStatsForUserPackage(
+ /* packageName= */null, new UserHandle(10),
+ CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO,
+ CarWatchdogManager.STATS_PERIOD_CURRENT_DAY));
+
+ assertThrows(NullPointerException.class,
+ () -> mCarWatchdogService.getResourceOveruseStatsForUserPackage("some.package",
+ /* userHandle= */null, CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO,
+ CarWatchdogManager.STATS_PERIOD_CURRENT_DAY));
+
+ assertThrows(IllegalArgumentException.class,
+ () -> mCarWatchdogService.getResourceOveruseStatsForUserPackage("some.package",
+ UserHandle.ALL, CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO,
+ CarWatchdogManager.STATS_PERIOD_CURRENT_DAY));
+
+ assertThrows(IllegalArgumentException.class,
+ () -> mCarWatchdogService.getResourceOveruseStatsForUserPackage("some.package",
+ new UserHandle(10), /* resourceOveruseFlag= */0,
+ CarWatchdogManager.STATS_PERIOD_CURRENT_DAY));
+
+ assertThrows(IllegalArgumentException.class,
+ () -> mCarWatchdogService.getResourceOveruseStatsForUserPackage("some.package",
+ new UserHandle(10), CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO,
+ /* maxStatsPeriod= */0));
+ }
+
+ @Test
public void testAddResourceOveruseListenerThrowsWithInvalidFlag() throws Exception {
IResourceOveruseListener mockListener = createMockResourceOveruseListener();
assertThrows(IllegalArgumentException.class, () -> {
@@ -203,8 +415,9 @@
Collections.singletonList(constructPackageIoOveruseStats(
callingUid, /* shouldNotify= */true,
constructInternalIoOveruseStats(/* killableOnOveruse= */true,
- constructPerStateBytes(20, 20, 20),
- constructPerStateBytes(100, 200, 300), /* totalOveruses= */2))));
+ /* remainingWriteBytes= */constructPerStateBytes(20, 20, 20),
+ /* writtenBytes= */constructPerStateBytes(100, 200, 300),
+ /* totalOveruses= */2))));
mWatchdogServiceForSystemImpl.latestIoOveruseStats(packageIoOveruseStats);
@@ -246,8 +459,9 @@
Collections.singletonList(constructPackageIoOveruseStats(
callingUid, /* shouldNotify= */true,
constructInternalIoOveruseStats(/* killableOnOveruse= */true,
- constructPerStateBytes(20, 20, 20),
- constructPerStateBytes(100, 200, 300), /* totalOveruses= */2))));
+ /* remainingWriteBytes= */constructPerStateBytes(20, 20, 20),
+ /* writtenBytes= */constructPerStateBytes(100, 200, 300),
+ /* totalOveruses= */2))));
mWatchdogServiceForSystemImpl.latestIoOveruseStats(packageIoOveruseStats);
@@ -264,6 +478,308 @@
}
@Test
+ public void testSetKillablePackageAsUserWithPackageStats() throws Exception {
+ mockUmGetAliveUsers(mMockUserManager, 11, 12);
+ injectPackageInfos(new ArrayList<>(Arrays.asList("third_party_package",
+ "vendor_package.critical")));
+
+ SparseArray<String> packageNamesByUid = new SparseArray<>();
+ packageNamesByUid.put(1103456, "third_party_package");
+ packageNamesByUid.put(1101278, "vendor_package.critical");
+ injectIoOveruseStatsForPackages(packageNamesByUid,
+ new ArraySet<>(Collections.singletonList("third_party_package")));
+
+ UserHandle userHandle = new UserHandle(11);
+
+ mCarWatchdogService.setKillablePackageAsUser("third_party_package", userHandle,
+ /* isKillable= */ true);
+ assertThrows(IllegalArgumentException.class,
+ () -> mCarWatchdogService.setKillablePackageAsUser("vendor_package.critical",
+ userHandle, /* isKillable= */ true));
+
+ PackageKillableStateSubject.assertThat(
+ mCarWatchdogService.getPackageKillableStatesAsUser(userHandle)).containsExactly(
+ new PackageKillableState("third_party_package", 11,
+ PackageKillableState.KILLABLE_STATE_YES),
+ new PackageKillableState("vendor_package.critical", 11,
+ PackageKillableState.KILLABLE_STATE_NEVER));
+
+ mCarWatchdogService.setKillablePackageAsUser("third_party_package", userHandle,
+ /* isKillable= */ false);
+ assertThrows(IllegalArgumentException.class,
+ () -> mCarWatchdogService.setKillablePackageAsUser("vendor_package.critical",
+ userHandle, /* isKillable= */ false));
+
+ PackageKillableStateSubject.assertThat(
+ mCarWatchdogService.getPackageKillableStatesAsUser(userHandle)).containsExactly(
+ new PackageKillableState("third_party_package", 11,
+ PackageKillableState.KILLABLE_STATE_NO),
+ new PackageKillableState("vendor_package.critical", 11,
+ PackageKillableState.KILLABLE_STATE_NEVER));
+ }
+
+ @Test
+ public void testSetKillablePackageAsUserWithNoPackageStats() throws Exception {
+ mockUmGetAliveUsers(mMockUserManager, 11, 12);
+ injectPackageInfos(new ArrayList<>(Arrays.asList("third_party_package",
+ "vendor_package.critical")));
+
+ UserHandle userHandle = new UserHandle(11);
+ mCarWatchdogService.setKillablePackageAsUser("third_party_package", userHandle,
+ /* isKillable= */ true);
+ mCarWatchdogService.setKillablePackageAsUser("vendor_package.critical",
+ userHandle, /* isKillable= */ true);
+
+ PackageKillableStateSubject.assertThat(
+ mCarWatchdogService.getPackageKillableStatesAsUser(userHandle)).containsExactly(
+ new PackageKillableState("third_party_package", 11,
+ PackageKillableState.KILLABLE_STATE_YES),
+ new PackageKillableState("vendor_package.critical", 11,
+ PackageKillableState.KILLABLE_STATE_NEVER));
+
+ mCarWatchdogService.setKillablePackageAsUser("third_party_package", userHandle,
+ /* isKillable= */ false);
+ assertThrows(IllegalArgumentException.class,
+ () -> mCarWatchdogService.setKillablePackageAsUser("vendor_package.critical",
+ userHandle, /* isKillable= */ false));
+
+ PackageKillableStateSubject.assertThat(
+ mCarWatchdogService.getPackageKillableStatesAsUser(userHandle)).containsExactly(
+ new PackageKillableState("third_party_package", 11,
+ PackageKillableState.KILLABLE_STATE_NO),
+ new PackageKillableState("vendor_package.critical", 11,
+ PackageKillableState.KILLABLE_STATE_NEVER));
+ }
+
+ @Test
+ public void testSetKillablePackageAsUserForAllUsersWithPackageStats() throws Exception {
+ mockUmGetAliveUsers(mMockUserManager, 11, 12);
+ injectPackageInfos(new ArrayList<>(Arrays.asList("third_party_package",
+ "vendor_package.critical")));
+
+ SparseArray<String> packageNamesByUid = new SparseArray<>();
+ packageNamesByUid.put(1103456, "third_party_package");
+ packageNamesByUid.put(1101278, "vendor_package.critical");
+ injectIoOveruseStatsForPackages(packageNamesByUid,
+ new ArraySet<>(Collections.singletonList("third_party_package")));
+
+ mCarWatchdogService.setKillablePackageAsUser("third_party_package", UserHandle.ALL,
+ /* isKillable= */ true);
+ assertThrows(IllegalArgumentException.class,
+ () -> mCarWatchdogService.setKillablePackageAsUser("vendor_package.critical",
+ UserHandle.ALL, /* isKillable= */ true));
+
+ PackageKillableStateSubject.assertThat(
+ mCarWatchdogService.getPackageKillableStatesAsUser(UserHandle.ALL)).containsExactly(
+ new PackageKillableState("third_party_package", 11,
+ PackageKillableState.KILLABLE_STATE_YES),
+ new PackageKillableState("vendor_package.critical", 11,
+ PackageKillableState.KILLABLE_STATE_NEVER),
+ new PackageKillableState("third_party_package", 12,
+ PackageKillableState.KILLABLE_STATE_YES),
+ new PackageKillableState("vendor_package.critical", 12,
+ PackageKillableState.KILLABLE_STATE_NEVER));
+
+ mCarWatchdogService.setKillablePackageAsUser("third_party_package", UserHandle.ALL,
+ /* isKillable= */ false);
+ assertThrows(IllegalArgumentException.class,
+ () -> mCarWatchdogService.setKillablePackageAsUser("vendor_package.critical",
+ UserHandle.ALL, /* isKillable= */ false));
+
+ PackageKillableStateSubject.assertThat(
+ mCarWatchdogService.getPackageKillableStatesAsUser(UserHandle.ALL)).containsExactly(
+ new PackageKillableState("third_party_package", 11,
+ PackageKillableState.KILLABLE_STATE_NO),
+ new PackageKillableState("vendor_package.critical", 11,
+ PackageKillableState.KILLABLE_STATE_NEVER),
+ new PackageKillableState("third_party_package", 12,
+ PackageKillableState.KILLABLE_STATE_NO),
+ new PackageKillableState("vendor_package.critical", 12,
+ PackageKillableState.KILLABLE_STATE_NEVER));
+ }
+
+ @Test
+ public void testSetKillablePackageAsUserForAllUsersWithNoPackageStats() throws Exception {
+ mockUmGetAliveUsers(mMockUserManager, 11, 12);
+ injectPackageInfos(new ArrayList<>(Arrays.asList("third_party_package",
+ "vendor_package.critical")));
+
+ mCarWatchdogService.setKillablePackageAsUser("third_party_package", UserHandle.ALL,
+ /* isKillable= */ true);
+ mCarWatchdogService.setKillablePackageAsUser("vendor_package.critical",
+ UserHandle.ALL, /* isKillable= */ true);
+
+ PackageKillableStateSubject.assertThat(
+ mCarWatchdogService.getPackageKillableStatesAsUser(UserHandle.ALL)).containsExactly(
+ new PackageKillableState("third_party_package", 11,
+ PackageKillableState.KILLABLE_STATE_YES),
+ new PackageKillableState("vendor_package.critical", 11,
+ PackageKillableState.KILLABLE_STATE_NEVER),
+ new PackageKillableState("third_party_package", 12,
+ PackageKillableState.KILLABLE_STATE_YES),
+ new PackageKillableState("vendor_package.critical", 12,
+ PackageKillableState.KILLABLE_STATE_NEVER));
+
+ mCarWatchdogService.setKillablePackageAsUser("third_party_package", UserHandle.ALL,
+ /* isKillable= */ false);
+ assertThrows(IllegalArgumentException.class,
+ () -> mCarWatchdogService.setKillablePackageAsUser("vendor_package.critical",
+ UserHandle.ALL, /* isKillable= */ false));
+
+ PackageKillableStateSubject.assertThat(
+ mCarWatchdogService.getPackageKillableStatesAsUser(UserHandle.ALL)).containsExactly(
+ new PackageKillableState("third_party_package", 11,
+ PackageKillableState.KILLABLE_STATE_NO),
+ new PackageKillableState("vendor_package.critical", 11,
+ PackageKillableState.KILLABLE_STATE_NEVER),
+ new PackageKillableState("third_party_package", 12,
+ PackageKillableState.KILLABLE_STATE_NO),
+ new PackageKillableState("vendor_package.critical", 12,
+ PackageKillableState.KILLABLE_STATE_NEVER));
+ }
+
+ @Test
+ public void testGetPackageKillableStatesAsUser() throws Exception {
+ mockUmGetAliveUsers(mMockUserManager, 11, 12);
+ injectPackageInfos(new ArrayList<>(Arrays.asList("third_party_package",
+ "vendor_package.critical")));
+ PackageKillableStateSubject.assertThat(
+ mCarWatchdogService.getPackageKillableStatesAsUser(new UserHandle(11)))
+ .containsExactly(
+ new PackageKillableState("third_party_package", 11,
+ PackageKillableState.KILLABLE_STATE_YES),
+ new PackageKillableState("vendor_package.critical", 11,
+ PackageKillableState.KILLABLE_STATE_NEVER));
+ }
+
+ @Test
+ public void testGetPackageKillableStatesAsUserForAllUsers() throws Exception {
+ mockUmGetAliveUsers(mMockUserManager, 11, 12);
+ injectPackageInfos(new ArrayList<>(Arrays.asList("third_party_package",
+ "vendor_package.critical")));
+ PackageKillableStateSubject.assertThat(
+ mCarWatchdogService.getPackageKillableStatesAsUser(UserHandle.ALL)).containsExactly(
+ new PackageKillableState("third_party_package", 11,
+ PackageKillableState.KILLABLE_STATE_YES),
+ new PackageKillableState("vendor_package.critical", 11,
+ PackageKillableState.KILLABLE_STATE_NEVER),
+ new PackageKillableState("third_party_package", 12,
+ PackageKillableState.KILLABLE_STATE_YES),
+ new PackageKillableState("vendor_package.critical", 12,
+ PackageKillableState.KILLABLE_STATE_NEVER));
+ }
+
+ @Test
+ public void testSetResourceOveruseConfigurations() throws Exception {
+ List<ResourceOveruseConfiguration> resourceOveruseConfigs = new ArrayList<>(Arrays.asList(
+ sampleResourceOveruseConfigurationBuilder(ComponentType.SYSTEM,
+ sampleIoOveruseConfigurationBuilder(ComponentType.SYSTEM).build()).build(),
+ sampleResourceOveruseConfigurationBuilder(ComponentType.VENDOR,
+ sampleIoOveruseConfigurationBuilder(ComponentType.VENDOR).build()).build(),
+ sampleResourceOveruseConfigurationBuilder(ComponentType.THIRD_PARTY,
+ sampleIoOveruseConfigurationBuilder(ComponentType.THIRD_PARTY).build())
+ .build()));
+
+ mCarWatchdogService.setResourceOveruseConfigurations(resourceOveruseConfigs,
+ CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO);
+
+ List<android.automotive.watchdog.internal.ResourceOveruseConfiguration>
+ actualConfigs = captureOnSetResourceOveruseConfigurations();
+
+ List<android.automotive.watchdog.internal.ResourceOveruseConfiguration>
+ expectedConfigs = new ArrayList<>(Arrays.asList(
+ sampleInternalResourceOveruseConfiguration(ComponentType.SYSTEM,
+ sampleInternalIoOveruseConfiguration(ComponentType.SYSTEM)),
+ sampleInternalResourceOveruseConfiguration(ComponentType.VENDOR,
+ sampleInternalIoOveruseConfiguration(ComponentType.VENDOR)),
+ sampleInternalResourceOveruseConfiguration(ComponentType.THIRD_PARTY,
+ sampleInternalIoOveruseConfiguration(ComponentType.THIRD_PARTY))));
+
+ InternalResourceOveruseConfigurationSubject.assertThat(actualConfigs)
+ .containsExactlyElementsIn(expectedConfigs);
+ }
+
+ @Test
+ public void testFailsSetResourceOveruseConfigurationsOnInvalidArgs() throws Exception {
+ assertThrows(NullPointerException.class,
+ () -> mCarWatchdogService.setResourceOveruseConfigurations(null,
+ CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO));
+
+ assertThrows(IllegalArgumentException.class,
+ () -> mCarWatchdogService.setResourceOveruseConfigurations(new ArrayList<>(),
+ CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO));
+
+ List<ResourceOveruseConfiguration> resourceOveruseConfigs = new ArrayList<>(
+ Collections.singletonList(
+ sampleResourceOveruseConfigurationBuilder(ComponentType.SYSTEM,
+ sampleIoOveruseConfigurationBuilder(ComponentType.SYSTEM).build())
+ .build()));
+ assertThrows(IllegalArgumentException.class,
+ () -> mCarWatchdogService.setResourceOveruseConfigurations(resourceOveruseConfigs,
+ 0));
+ }
+
+ @Test
+ public void testFailsSetResourceOveruseConfigurationsOnDuplicateComponents() throws Exception {
+ ResourceOveruseConfiguration config =
+ sampleResourceOveruseConfigurationBuilder(ComponentType.SYSTEM,
+ sampleIoOveruseConfigurationBuilder(ComponentType.SYSTEM).build()).build();
+ List<ResourceOveruseConfiguration> resourceOveruseConfigs = new ArrayList<>(Arrays.asList(
+ config, config));
+ assertThrows(IllegalArgumentException.class,
+ () -> mCarWatchdogService.setResourceOveruseConfigurations(resourceOveruseConfigs,
+ CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO));
+ }
+
+ @Test
+ public void testFailsSetResourceOveruseConfigurationsOnNullIoOveruseConfiguration()
+ throws Exception {
+ List<ResourceOveruseConfiguration> resourceOveruseConfigs = new ArrayList<>(
+ Collections.singletonList(
+ sampleResourceOveruseConfigurationBuilder(ComponentType.SYSTEM,
+ null).build()));
+ assertThrows(IllegalArgumentException.class,
+ () -> mCarWatchdogService.setResourceOveruseConfigurations(resourceOveruseConfigs,
+ CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO));
+ }
+
+ @Test
+ public void testGetResourceOveruseConfigurations() throws Exception {
+ List<android.automotive.watchdog.internal.ResourceOveruseConfiguration>
+ internalResourceOveruseConfigs = new ArrayList<>(Arrays.asList(
+ sampleInternalResourceOveruseConfiguration(ComponentType.SYSTEM,
+ sampleInternalIoOveruseConfiguration(ComponentType.SYSTEM)),
+ sampleInternalResourceOveruseConfiguration(ComponentType.VENDOR,
+ sampleInternalIoOveruseConfiguration(ComponentType.VENDOR)),
+ sampleInternalResourceOveruseConfiguration(ComponentType.THIRD_PARTY,
+ sampleInternalIoOveruseConfiguration(ComponentType.THIRD_PARTY))));
+ doReturn(internalResourceOveruseConfigs).when(mMockCarWatchdogDaemon)
+ .getResourceOveruseConfigurations();
+
+ List<ResourceOveruseConfiguration> actualConfigs =
+ mCarWatchdogService.getResourceOveruseConfigurations(
+ CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO);
+
+ List<ResourceOveruseConfiguration> expectedConfigs = new ArrayList<>(Arrays.asList(
+ sampleResourceOveruseConfigurationBuilder(ComponentType.SYSTEM,
+ sampleIoOveruseConfigurationBuilder(ComponentType.SYSTEM).build()).build(),
+ sampleResourceOveruseConfigurationBuilder(ComponentType.VENDOR,
+ sampleIoOveruseConfigurationBuilder(ComponentType.VENDOR).build()).build(),
+ sampleResourceOveruseConfigurationBuilder(ComponentType.THIRD_PARTY,
+ sampleIoOveruseConfigurationBuilder(ComponentType.THIRD_PARTY).build())
+ .build()));
+
+ ResourceOveruseConfigurationSubject.assertThat(actualConfigs)
+ .containsExactlyElementsIn(expectedConfigs);
+ }
+
+ @Test
+ public void testFailsGetResourceOveruseConfigurationsOnInvalidArgs() throws Exception {
+ assertThrows(IllegalArgumentException.class,
+ () -> mCarWatchdogService.getResourceOveruseConfigurations(0));
+ }
+
+ @Test
public void testLatestIoOveruseStats() throws Exception {
int criticalSysPkgUid = Binder.getCallingUid();
int nonCriticalSysPkgUid = getUid(1056);
@@ -275,12 +791,6 @@
packageNamesByUid.put(nonCriticalSysPkgUid, "non_critical.system.package");
packageNamesByUid.put(nonCriticalVndrPkgUid, "non_critical.vendor.package");
packageNamesByUid.put(thirdPartyPkgUid, "third_party.package");
-
- SparseBooleanArray killableOnOveruseByUid = new SparseBooleanArray();
- killableOnOveruseByUid.put(criticalSysPkgUid, false);
- killableOnOveruseByUid.put(nonCriticalSysPkgUid, true);
- killableOnOveruseByUid.put(nonCriticalVndrPkgUid, true);
- killableOnOveruseByUid.put(thirdPartyPkgUid, true);
injectUidToPackageNameMapping(packageNamesByUid);
IResourceOveruseListener mockSystemListener = createMockResourceOveruseListener();
@@ -298,28 +808,28 @@
List<PackageIoOveruseStats> packageIoOveruseStats = new ArrayList<>(Arrays.asList(
/* Overuse occurred but cannot be killed/disabled. */
constructPackageIoOveruseStats(criticalSysPkgUid, /* shouldNotify= */true,
- constructInternalIoOveruseStats(
- killableOnOveruseByUid.get(criticalSysPkgUid),
- constructPerStateBytes(0, 0, 0),
- constructPerStateBytes(100, 200, 300), /* totalOveruses= */2)),
+ constructInternalIoOveruseStats(/* killableOnOveruse= */false,
+ /* remainingWriteBytes= */constructPerStateBytes(0, 0, 0),
+ /* writtenBytes= */constructPerStateBytes(100, 200, 300),
+ /* totalOveruses= */2)),
/* No overuse occurred but should be notified. */
constructPackageIoOveruseStats(nonCriticalSysPkgUid, /* shouldNotify= */true,
- constructInternalIoOveruseStats(
- killableOnOveruseByUid.get(nonCriticalSysPkgUid),
- constructPerStateBytes(20, 30, 40),
- constructPerStateBytes(100, 200, 300), /* totalOveruses= */2)),
+ constructInternalIoOveruseStats(/* killableOnOveruse= */true,
+ /* remainingWriteBytes= */constructPerStateBytes(20, 30, 40),
+ /* writtenBytes= */constructPerStateBytes(100, 200, 300),
+ /* totalOveruses= */2)),
/* Neither overuse occurred nor be notified. */
constructPackageIoOveruseStats(nonCriticalVndrPkgUid, /* shouldNotify= */false,
- constructInternalIoOveruseStats(
- killableOnOveruseByUid.get(nonCriticalVndrPkgUid),
- constructPerStateBytes(200, 300, 400),
- constructPerStateBytes(100, 200, 300), /* totalOveruses= */2)),
+ constructInternalIoOveruseStats(/* killableOnOveruse= */true,
+ /* remainingWriteBytes= */constructPerStateBytes(200, 300, 400),
+ /* writtenBytes= */constructPerStateBytes(100, 200, 300),
+ /* totalOveruses= */2)),
/* Overuse occurred and can be killed/disabled. */
constructPackageIoOveruseStats(thirdPartyPkgUid, /* shouldNotify= */true,
- constructInternalIoOveruseStats(
- killableOnOveruseByUid.get(thirdPartyPkgUid),
- constructPerStateBytes(0, 0, 0),
- constructPerStateBytes(100, 200, 300), /* totalOveruses= */2))));
+ constructInternalIoOveruseStats(/* killableOnOveruse= */true,
+ /* remainingWriteBytes= */constructPerStateBytes(0, 0, 0),
+ /* writtenBytes= */constructPerStateBytes(100, 200, 300),
+ /* totalOveruses= */2))));
mWatchdogServiceForSystemImpl.latestIoOveruseStats(packageIoOveruseStats);
@@ -327,19 +837,16 @@
expectedStats.add(constructResourceOveruseStats(criticalSysPkgUid,
packageNamesByUid.get(criticalSysPkgUid),
- killableOnOveruseByUid.get(criticalSysPkgUid),
packageIoOveruseStats.get(0).ioOveruseStats));
verifyOnOveruseCalled(expectedStats, mockListener);
expectedStats.add(constructResourceOveruseStats(nonCriticalSysPkgUid,
packageNamesByUid.get(nonCriticalSysPkgUid),
- killableOnOveruseByUid.get(nonCriticalSysPkgUid),
packageIoOveruseStats.get(1).ioOveruseStats));
expectedStats.add(constructResourceOveruseStats(thirdPartyPkgUid,
packageNamesByUid.get(thirdPartyPkgUid),
- killableOnOveruseByUid.get(thirdPartyPkgUid),
packageIoOveruseStats.get(3).ioOveruseStats));
verifyOnOveruseCalled(expectedStats, mockSystemListener);
@@ -499,19 +1006,19 @@
}
private void mockWatchdogDaemon() {
- doReturn(mBinder).when(() -> ServiceManager.getService(CAR_WATCHDOG_DAEMON_INTERFACE));
- when(mBinder.queryLocalInterface(anyString())).thenReturn(mCarWatchdogDaemon);
+ doReturn(mMockBinder).when(() -> ServiceManager.getService(CAR_WATCHDOG_DAEMON_INTERFACE));
+ when(mMockBinder.queryLocalInterface(anyString())).thenReturn(mMockCarWatchdogDaemon);
}
private void setupUsers() {
- when(mMockContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
- mockUmGetAllUsers(mUserManager, new UserInfo[0]);
+ when(mMockContext.getSystemService(Context.USER_SERVICE)).thenReturn(mMockUserManager);
+ mockUmGetAllUsers(mMockUserManager, new UserInfo[0]);
}
private ICarWatchdogServiceForSystem registerCarWatchdogService() throws Exception {
ArgumentCaptor<ICarWatchdogServiceForSystem> watchdogServiceForSystemImplCaptor =
ArgumentCaptor.forClass(ICarWatchdogServiceForSystem.class);
- verify(mCarWatchdogDaemon).registerCarWatchdogService(
+ verify(mMockCarWatchdogDaemon).registerCarWatchdogService(
watchdogServiceForSystemImplCaptor.capture());
return watchdogServiceForSystemImplCaptor.getValue();
}
@@ -520,15 +1027,24 @@
mCarWatchdogService.registerClient(client, TIMEOUT_CRITICAL);
mWatchdogServiceForSystemImpl.checkIfAlive(123456, TIMEOUT_CRITICAL);
ArgumentCaptor<int[]> notRespondingClients = ArgumentCaptor.forClass(int[].class);
- verify(mCarWatchdogDaemon, timeout(MAX_WAIT_TIME_MS)).tellCarWatchdogServiceAlive(
+ verify(mMockCarWatchdogDaemon, timeout(MAX_WAIT_TIME_MS)).tellCarWatchdogServiceAlive(
eq(mWatchdogServiceForSystemImpl), notRespondingClients.capture(), eq(123456));
assertThat(notRespondingClients.getValue().length).isEqualTo(0);
mWatchdogServiceForSystemImpl.checkIfAlive(987654, TIMEOUT_CRITICAL);
- verify(mCarWatchdogDaemon, timeout(MAX_WAIT_TIME_MS)).tellCarWatchdogServiceAlive(
+ verify(mMockCarWatchdogDaemon, timeout(MAX_WAIT_TIME_MS)).tellCarWatchdogServiceAlive(
eq(mWatchdogServiceForSystemImpl), notRespondingClients.capture(), eq(987654));
assertThat(notRespondingClients.getValue().length).isEqualTo(badClientCount);
}
+ private List<android.automotive.watchdog.internal.ResourceOveruseConfiguration>
+ captureOnSetResourceOveruseConfigurations() throws Exception {
+ ArgumentCaptor<List<android.automotive.watchdog.internal.ResourceOveruseConfiguration>>
+ resourceOveruseConfigurationsCaptor = ArgumentCaptor.forClass(List.class);
+ verify(mMockCarWatchdogDaemon).updateResourceOveruseConfigurations(
+ resourceOveruseConfigurationsCaptor.capture());
+ return resourceOveruseConfigurationsCaptor.getValue();
+ }
+
private void injectUidToPackageNameMapping(SparseArray<String> packageNamesByUid) {
doAnswer(args -> {
int[] uids = args.getArgument(0);
@@ -540,6 +1056,49 @@
}).when(mMockPackageManager).getNamesForUids(any());
}
+ private void injectIoOveruseStatsForPackages(SparseArray<String> packageNamesByUid,
+ Set<String> killablePackages) throws RemoteException {
+ injectUidToPackageNameMapping(packageNamesByUid);
+ List<PackageIoOveruseStats> packageIoOveruseStats = new ArrayList<>();
+ for (int i = 0; i < packageNamesByUid.size(); ++i) {
+ String packageName = packageNamesByUid.valueAt(i);
+ int uid = packageNamesByUid.keyAt(i);
+ packageIoOveruseStats.add(constructPackageIoOveruseStats(uid,
+ false,
+ constructInternalIoOveruseStats(killablePackages.contains(packageName),
+ /* remainingWriteBytes= */constructPerStateBytes(20, 20, 20),
+ /* writtenBytes= */constructPerStateBytes(100, 200, 300),
+ /* totalOveruses= */2)));
+ }
+ mWatchdogServiceForSystemImpl.latestIoOveruseStats(packageIoOveruseStats);
+ }
+
+ private void injectPackageInfos(List<String> packageNames) {
+ List<android.content.pm.PackageInfo> packageInfos = new ArrayList<>();
+ TriConsumer<String, Integer, Integer> addPackageInfo =
+ (packageName, flags, privateFlags) -> {
+ android.content.pm.PackageInfo packageInfo =
+ new android.content.pm.PackageInfo();
+ packageInfo.packageName = packageName;
+ packageInfo.applicationInfo = new ApplicationInfo();
+ packageInfo.applicationInfo.flags = flags;
+ packageInfo.applicationInfo.privateFlags = privateFlags;
+ packageInfos.add(packageInfo);
+ };
+ for (String packageName : packageNames) {
+ if (packageName.startsWith("system")) {
+ addPackageInfo.accept(packageName, ApplicationInfo.FLAG_SYSTEM, 0);
+ } else if (packageName.startsWith("vendor")) {
+ addPackageInfo.accept(packageName, ApplicationInfo.FLAG_SYSTEM,
+ ApplicationInfo.PRIVATE_FLAG_OEM);
+ } else {
+ addPackageInfo.accept(packageName, 0, 0);
+ }
+ }
+ doReturn(packageInfos).when(mMockPackageManager).getInstalledPackagesAsUser(
+ eq(0), anyInt());
+ }
+
private void mockApplicationEnabledSettingAccessors(IPackageManager pm) throws Exception {
doReturn(COMPONENT_ENABLED_STATE_ENABLED).when(pm)
.getApplicationEnabledSetting(anyString(), eq(UserHandle.myUserId()));
@@ -553,7 +1112,7 @@
ArgumentCaptor<List<PackageResourceOveruseAction>> resourceOveruseActionsCaptor =
ArgumentCaptor.forClass((Class) List.class);
- verify(mCarWatchdogDaemon, timeout(MAX_WAIT_TIME_MS)).actionTakenOnResourceOveruse(
+ verify(mMockCarWatchdogDaemon, timeout(MAX_WAIT_TIME_MS)).actionTakenOnResourceOveruse(
resourceOveruseActionsCaptor.capture());
List<PackageResourceOveruseAction> actual = resourceOveruseActionsCaptor.getValue();
@@ -586,6 +1145,118 @@
return UserHandle.getUid(UserHandle.myUserId(), appId);
}
+ private static ResourceOveruseConfiguration.Builder sampleResourceOveruseConfigurationBuilder(
+ int componentType, IoOveruseConfiguration ioOveruseConfig) {
+ String prefix = WatchdogPerfHandler.toComponentTypeStr(componentType);
+ List<String> safeToKill = new ArrayList<>(Arrays.asList(
+ prefix + "_package.A", prefix + "_pkg.B"));
+ List<String> vendorPrefixes = new ArrayList<>(Arrays.asList(
+ prefix + "_package", prefix + "_pkg"));
+ Map<String, String> pkgToAppCategory = new ArrayMap<>();
+ pkgToAppCategory.put(prefix + "_package.A", "android.car.watchdog.app.category.MEDIA");
+ ResourceOveruseConfiguration.Builder configBuilder =
+ new ResourceOveruseConfiguration.Builder(componentType, safeToKill,
+ vendorPrefixes, pkgToAppCategory);
+ configBuilder.setIoOveruseConfiguration(ioOveruseConfig);
+ return configBuilder;
+ }
+
+ private static IoOveruseConfiguration.Builder sampleIoOveruseConfigurationBuilder(
+ int componentType) {
+ String prefix = WatchdogPerfHandler.toComponentTypeStr(componentType);
+ PerStateBytes componentLevelThresholds = new PerStateBytes(
+ /* foregroundModeBytes= */10, /* backgroundModeBytes= */20,
+ /* garageModeBytes= */30);
+ Map<String, PerStateBytes> packageSpecificThresholds = new ArrayMap<>();
+ packageSpecificThresholds.put(prefix + "_package.A", new PerStateBytes(
+ /* foregroundModeBytes= */40, /* backgroundModeBytes= */50,
+ /* garageModeBytes= */60));
+
+ Map<String, PerStateBytes> appCategorySpecificThresholds = new ArrayMap<>();
+ appCategorySpecificThresholds.put(
+ ResourceOveruseConfiguration.APPLICATION_CATEGORY_TYPE_MEDIA,
+ new PerStateBytes(/* foregroundModeBytes= */100, /* backgroundModeBytes= */200,
+ /* garageModeBytes= */300));
+ appCategorySpecificThresholds.put(
+ ResourceOveruseConfiguration.APPLICATION_CATEGORY_TYPE_MAPS,
+ new PerStateBytes(/* foregroundModeBytes= */1100, /* backgroundModeBytes= */2200,
+ /* garageModeBytes= */3300));
+
+ List<IoOveruseAlertThreshold> systemWideThresholds = new ArrayList<>(
+ Collections.singletonList(new IoOveruseAlertThreshold(/* durationInSeconds= */10,
+ /* writtenBytesPerSecond= */200)));
+
+ return new IoOveruseConfiguration.Builder(componentLevelThresholds,
+ packageSpecificThresholds, appCategorySpecificThresholds, systemWideThresholds);
+ }
+
+ private static android.automotive.watchdog.internal.ResourceOveruseConfiguration
+ sampleInternalResourceOveruseConfiguration(int componentType,
+ android.automotive.watchdog.internal.IoOveruseConfiguration ioOveruseConfig) {
+ String prefix = WatchdogPerfHandler.toComponentTypeStr(componentType);
+ android.automotive.watchdog.internal.ResourceOveruseConfiguration config =
+ new android.automotive.watchdog.internal.ResourceOveruseConfiguration();
+ config.componentType = componentType;
+ config.safeToKillPackages = new ArrayList<>(Arrays.asList(
+ prefix + "_package.A", prefix + "_pkg.B"));
+ config.vendorPackagePrefixes = new ArrayList<>(Arrays.asList(
+ prefix + "_package", prefix + "_pkg"));
+
+ PackageMetadata metadata = new PackageMetadata();
+ metadata.packageName = prefix + "_package.A";
+ metadata.appCategoryType = ApplicationCategoryType.MEDIA;
+ config.packageMetadata = new ArrayList<>(Collections.singletonList(metadata));
+
+ ResourceSpecificConfiguration resourceSpecificConfig = new ResourceSpecificConfiguration();
+ resourceSpecificConfig.setIoOveruseConfiguration(ioOveruseConfig);
+ config.resourceSpecificConfigurations = new ArrayList<>(
+ Collections.singletonList(resourceSpecificConfig));
+
+ return config;
+ }
+
+ private static android.automotive.watchdog.internal.IoOveruseConfiguration
+ sampleInternalIoOveruseConfiguration(int componentType) {
+ String prefix = WatchdogPerfHandler.toComponentTypeStr(componentType);
+ android.automotive.watchdog.internal.IoOveruseConfiguration config =
+ new android.automotive.watchdog.internal.IoOveruseConfiguration();
+ config.componentLevelThresholds = constructPerStateIoOveruseThreshold(prefix,
+ /* fgBytes= */10, /* bgBytes= */20, /* gmBytes= */30);
+ config.packageSpecificThresholds = new ArrayList<>(Collections.singletonList(
+ constructPerStateIoOveruseThreshold(prefix + "_package.A", /* fgBytes= */40,
+ /* bgBytes= */50, /* gmBytes= */60)));
+ config.categorySpecificThresholds = new ArrayList<>(Arrays.asList(
+ constructPerStateIoOveruseThreshold(
+ WatchdogPerfHandler.INTERNAL_APPLICATION_CATEGORY_TYPE_MEDIA,
+ /* fgBytes= */100, /* bgBytes= */200, /* gmBytes= */300),
+ constructPerStateIoOveruseThreshold(
+ WatchdogPerfHandler.INTERNAL_APPLICATION_CATEGORY_TYPE_MAPS,
+ /* fgBytes= */1100, /* bgBytes= */2200, /* gmBytes= */3300)));
+ config.systemWideThresholds = new ArrayList<>(Collections.singletonList(
+ constructInternalIoOveruseAlertThreshold(/* duration= */10, /* writeBPS= */200)));
+ return config;
+ }
+
+ private static PerStateIoOveruseThreshold constructPerStateIoOveruseThreshold(String name,
+ long fgBytes, long bgBytes, long gmBytes) {
+ PerStateIoOveruseThreshold threshold = new PerStateIoOveruseThreshold();
+ threshold.name = name;
+ threshold.perStateWriteBytes = new android.automotive.watchdog.PerStateBytes();
+ threshold.perStateWriteBytes.foregroundBytes = fgBytes;
+ threshold.perStateWriteBytes.backgroundBytes = bgBytes;
+ threshold.perStateWriteBytes.garageModeBytes = gmBytes;
+ return threshold;
+ }
+
+ private static android.automotive.watchdog.internal.IoOveruseAlertThreshold
+ constructInternalIoOveruseAlertThreshold(long duration, long writeBPS) {
+ android.automotive.watchdog.internal.IoOveruseAlertThreshold threshold =
+ new android.automotive.watchdog.internal.IoOveruseAlertThreshold();
+ threshold.durationInSeconds = duration;
+ threshold.writtenBytesPerSecond = writeBPS;
+ return threshold;
+ }
+
private static PackageIoOveruseStats constructPackageIoOveruseStats(int uid,
boolean shouldNotify, android.automotive.watchdog.IoOveruseStats ioOveruseStats) {
PackageIoOveruseStats stats = new PackageIoOveruseStats();
@@ -596,11 +1267,10 @@
}
private static ResourceOveruseStats constructResourceOveruseStats(int uid, String packageName,
- boolean killableOnOveruse,
android.automotive.watchdog.IoOveruseStats internalIoOveruseStats) {
IoOveruseStats ioOveruseStats =
WatchdogPerfHandler.toIoOveruseStatsBuilder(internalIoOveruseStats)
- .setKillableOnOveruse(killableOnOveruse).build();
+ .setKillableOnOveruse(internalIoOveruseStats.killableOnOveruse).build();
return new ResourceOveruseStats.Builder(packageName, UserHandle.getUserHandleForUid(uid))
.setIoOveruseStats(ioOveruseStats).build();
@@ -629,7 +1299,7 @@
return perStateBytes;
}
- public static PackageResourceOveruseAction constructPackageResourceOveruseAction(
+ private static PackageResourceOveruseAction constructPackageResourceOveruseAction(
String packageName, int uid, int[] resourceTypes, int resourceOveruseActionType) {
PackageResourceOveruseAction action = new PackageResourceOveruseAction();
action.packageIdentifier = new PackageIdentifier();
diff --git a/tests/carservice_unit_test/src/com/android/car/watchdog/InternalIoOveruseConfigurationSubject.java b/tests/carservice_unit_test/src/com/android/car/watchdog/InternalIoOveruseConfigurationSubject.java
new file mode 100644
index 0000000..47ad645
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/watchdog/InternalIoOveruseConfigurationSubject.java
@@ -0,0 +1,161 @@
+/*
+ * 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.watchdog;
+
+import static com.google.common.truth.Truth.assertAbout;
+
+import android.annotation.Nullable;
+import android.automotive.watchdog.PerStateBytes;
+import android.automotive.watchdog.internal.IoOveruseAlertThreshold;
+import android.automotive.watchdog.internal.IoOveruseConfiguration;
+import android.automotive.watchdog.internal.PerStateIoOveruseThreshold;
+
+import com.google.common.truth.Correspondence;
+import com.google.common.truth.FailureMetadata;
+import com.google.common.truth.Subject;
+import com.google.common.truth.Truth;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public final class InternalIoOveruseConfigurationSubject extends Subject {
+ // Boiler-plate Subject.Factory for InternalIoOveruseConfigurationSubject
+ private static final Subject.Factory<
+ com.android.car.watchdog.InternalIoOveruseConfigurationSubject,
+ Iterable<IoOveruseConfiguration>> Io_OVERUSE_CONFIG_SUBJECT_FACTORY =
+ com.android.car.watchdog.InternalIoOveruseConfigurationSubject::new;
+
+ private final Iterable<IoOveruseConfiguration> mActual;
+
+ // User-defined entry point
+ public static InternalIoOveruseConfigurationSubject assertThat(
+ @Nullable Iterable<IoOveruseConfiguration> stats) {
+ return assertAbout(Io_OVERUSE_CONFIG_SUBJECT_FACTORY).that(stats);
+ }
+
+ public static Subject.Factory<InternalIoOveruseConfigurationSubject,
+ Iterable<IoOveruseConfiguration>> resourceOveruseStats() {
+ return Io_OVERUSE_CONFIG_SUBJECT_FACTORY;
+ }
+
+ public void containsExactly(IoOveruseConfiguration... stats) {
+ containsExactlyElementsIn(Arrays.asList(stats));
+ }
+
+ public void containsExactlyElementsIn(Iterable<IoOveruseConfiguration> expected) {
+ Truth.assertWithMessage("Expected: " + expected.toString() + "\nActual: "
+ + mActual.toString()).that(mActual)
+ .comparingElementsUsing(Correspondence.from(
+ InternalIoOveruseConfigurationSubject::isEquals, "is equal to"))
+ .containsExactlyElementsIn(expected);
+ }
+
+ public static boolean isEquals(IoOveruseConfiguration actual,
+ IoOveruseConfiguration expected) {
+ if (actual == null || expected == null) {
+ return (actual == null) && (expected == null);
+ }
+ return actual.componentLevelThresholds.name == expected.componentLevelThresholds.name
+ && isPerStateBytesEquals(actual.componentLevelThresholds.perStateWriteBytes,
+ expected.componentLevelThresholds.perStateWriteBytes)
+ && isPerStateThresholdEquals(actual.packageSpecificThresholds,
+ expected.packageSpecificThresholds)
+ && isPerStateThresholdEquals(actual.categorySpecificThresholds,
+ expected.categorySpecificThresholds)
+ && isAlertThresholdEquals(actual.systemWideThresholds,
+ expected.systemWideThresholds);
+ }
+
+ public static StringBuilder toStringBuilder(StringBuilder builder,
+ IoOveruseConfiguration config) {
+ builder.append("{Component-level thresholds: ")
+ .append(toString(config.componentLevelThresholds))
+ .append(", Package specific thresholds: [")
+ .append(config.packageSpecificThresholds.stream()
+ .map(InternalIoOveruseConfigurationSubject::toString)
+ .collect(Collectors.joining(", ")))
+ .append("], Category specific thresholds: [")
+ .append(config.categorySpecificThresholds.stream()
+ .map(InternalIoOveruseConfigurationSubject::toString)
+ .collect(Collectors.joining(", ")))
+ .append("], System wide thresholds: [")
+ .append(config.systemWideThresholds.stream()
+ .map(InternalIoOveruseConfigurationSubject::toString)
+ .collect(Collectors.joining(", ")))
+ .append("]}");
+ return builder;
+ }
+
+ public static String toString(PerStateIoOveruseThreshold threshold) {
+ StringBuilder builder = new StringBuilder();
+ builder.append("{Name: ").append(threshold.name).append(", WriteBytes: {fgBytes: ")
+ .append(threshold.perStateWriteBytes.foregroundBytes).append(", bgBytes: ")
+ .append(threshold.perStateWriteBytes.backgroundBytes).append(", gmBytes: ")
+ .append(threshold.perStateWriteBytes.garageModeBytes).append("}}");
+ return builder.toString();
+ }
+
+ public static String toString(IoOveruseAlertThreshold threshold) {
+ StringBuilder builder = new StringBuilder();
+ builder.append("{durationInSeconds: ").append(threshold.durationInSeconds)
+ .append(", writtenBytesPerSecond: ").append(threshold.writtenBytesPerSecond)
+ .append("}");
+ return builder.toString();
+ }
+
+ private InternalIoOveruseConfigurationSubject(FailureMetadata failureMetadata,
+ @Nullable Iterable<IoOveruseConfiguration> iterableSubject) {
+ super(failureMetadata, iterableSubject);
+ this.mActual = iterableSubject;
+ }
+
+ private static boolean isPerStateThresholdEquals(List<PerStateIoOveruseThreshold> actual,
+ List<PerStateIoOveruseThreshold> expected) {
+ Set<String> actualStr = toPerStateThresholdStrings(actual);
+ Set<String> expectedStr = toPerStateThresholdStrings(expected);
+ return actualStr.equals(expectedStr);
+ }
+
+ private static boolean isAlertThresholdEquals(List<IoOveruseAlertThreshold> actual,
+ List<IoOveruseAlertThreshold> expected) {
+ Set<String> actualStr = toAlertThresholdStrings(actual);
+ Set<String> expectedStr = toAlertThresholdStrings(expected);
+ return actualStr.equals(expectedStr);
+ }
+
+ private static boolean isPerStateBytesEquals(PerStateBytes acutal, PerStateBytes expected) {
+ return acutal.foregroundBytes == expected.foregroundBytes
+ && acutal.backgroundBytes == expected.backgroundBytes
+ && acutal.garageModeBytes == expected.garageModeBytes;
+ }
+
+ private static Set<String> toPerStateThresholdStrings(
+ List<PerStateIoOveruseThreshold> thresholds) {
+ return thresholds.stream().map(x -> String.format("%s:{%d,%d,%d}", x.name,
+ x.perStateWriteBytes.foregroundBytes, x.perStateWriteBytes.backgroundBytes,
+ x.perStateWriteBytes.garageModeBytes))
+ .collect(Collectors.toSet());
+ }
+
+ private static Set<String> toAlertThresholdStrings(
+ List<IoOveruseAlertThreshold> thresholds) {
+ return thresholds.stream().map(x -> String.format("%d:%d", x.durationInSeconds,
+ x.writtenBytesPerSecond)).collect(Collectors.toSet());
+ }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/watchdog/InternalResourceOveruseConfigurationSubject.java b/tests/carservice_unit_test/src/com/android/car/watchdog/InternalResourceOveruseConfigurationSubject.java
new file mode 100644
index 0000000..d324efe
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/watchdog/InternalResourceOveruseConfigurationSubject.java
@@ -0,0 +1,191 @@
+/*
+ * 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.watchdog;
+
+import static com.google.common.truth.Truth.assertAbout;
+
+import android.annotation.Nullable;
+import android.automotive.watchdog.internal.PackageMetadata;
+import android.automotive.watchdog.internal.ResourceOveruseConfiguration;
+import android.automotive.watchdog.internal.ResourceSpecificConfiguration;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.truth.Correspondence;
+import com.google.common.truth.FailureMetadata;
+import com.google.common.truth.Subject;
+import com.google.common.truth.Truth;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public final class InternalResourceOveruseConfigurationSubject extends Subject {
+ // Boiler-plate Subject.Factory for InternalResourceOveruseConfigurationSubject
+ private static final Subject.Factory<
+ com.android.car.watchdog.InternalResourceOveruseConfigurationSubject,
+ Iterable<ResourceOveruseConfiguration>> RESOURCE_OVERUSE_CONFIG_SUBJECT_FACTORY =
+ com.android.car.watchdog.InternalResourceOveruseConfigurationSubject::new;
+
+ private static final Correspondence<List<PackageMetadata>, List<PackageMetadata>>
+ METADATA_LIST_CORRESPONDENCE = Correspondence.from(
+ InternalResourceOveruseConfigurationSubject::isPackageMetadataEquals,
+ "is equal to");
+
+ private static final Correspondence<List<ResourceSpecificConfiguration>,
+ List<ResourceSpecificConfiguration>>
+ RESOURCE_SPECIFIC_CONFIG_LIST_CORRESPONDENCE = Correspondence.from(
+ InternalResourceOveruseConfigurationSubject::isResourceSpecificConfigEquals,
+ "is equal to");
+
+ private final Iterable<ResourceOveruseConfiguration> mActual;
+
+ // User-defined entry point
+ public static InternalResourceOveruseConfigurationSubject assertThat(
+ @Nullable Iterable<ResourceOveruseConfiguration> stats) {
+ return assertAbout(RESOURCE_OVERUSE_CONFIG_SUBJECT_FACTORY).that(stats);
+ }
+
+ public static Subject.Factory<InternalResourceOveruseConfigurationSubject,
+ Iterable<ResourceOveruseConfiguration>> resourceOveruseStats() {
+ return RESOURCE_OVERUSE_CONFIG_SUBJECT_FACTORY;
+ }
+
+ public void containsExactly(ResourceOveruseConfiguration... stats) {
+ containsExactlyElementsIn(Arrays.asList(stats));
+ }
+
+ public void containsExactlyElementsIn(Iterable<ResourceOveruseConfiguration> expected) {
+ Truth.assertWithMessage("Expected: " + toString(expected) + "\nActual: "
+ + toString(mActual)).that(mActual)
+ .comparingElementsUsing(Correspondence.from(
+ InternalResourceOveruseConfigurationSubject::isEquals, "is equal to"))
+ .containsExactlyElementsIn(expected);
+ }
+
+ public static boolean isPackageMetadataEquals(List<PackageMetadata> actual,
+ List<PackageMetadata> expected) {
+ Set<String> actualStr = toMetadataStrings(actual);
+ Set<String> expectedStr = toMetadataStrings(expected);
+ return actualStr.equals(expectedStr);
+ }
+
+ public static boolean isResourceSpecificConfigEquals(List<ResourceSpecificConfiguration> actual,
+ List<ResourceSpecificConfiguration> expected) {
+ if (actual.size() != expected.size()) {
+ return false;
+ }
+ if (actual.size() == 0) {
+ return true;
+ }
+ /*
+ * When more resource types are added make this comparison more generic. Because the
+ * resource overuse configuration should contain only one I/O overuse configuration, the
+ * comparison only checks for first I/O overuse configuration.
+ */
+ ResourceSpecificConfiguration actualElement = actual.get(0);
+ ResourceSpecificConfiguration expectedElement = expected.get(0);
+ if (actualElement.getTag() != expectedElement.getTag()) {
+ return false;
+ }
+ if (actualElement.getTag() != ResourceSpecificConfiguration.ioOveruseConfiguration) {
+ return false;
+ }
+ return InternalIoOveruseConfigurationSubject.isEquals(
+ actualElement.getIoOveruseConfiguration(),
+ expectedElement.getIoOveruseConfiguration());
+ }
+
+ public static boolean isEquals(ResourceOveruseConfiguration actual,
+ ResourceOveruseConfiguration expected) {
+ if (actual == null || expected == null) {
+ return (actual == null) && (expected == null);
+ }
+ return actual.componentType == expected.componentType
+ && ImmutableSet.copyOf(actual.safeToKillPackages).equals(
+ ImmutableSet.copyOf(expected.safeToKillPackages))
+ && ImmutableSet.copyOf(actual.vendorPackagePrefixes).equals(
+ ImmutableSet.copyOf(expected.vendorPackagePrefixes))
+ && METADATA_LIST_CORRESPONDENCE.compare(actual.packageMetadata,
+ expected.packageMetadata)
+ && RESOURCE_SPECIFIC_CONFIG_LIST_CORRESPONDENCE.compare(
+ actual.resourceSpecificConfigurations,
+ expected.resourceSpecificConfigurations);
+ }
+
+ private InternalResourceOveruseConfigurationSubject(FailureMetadata failureMetadata,
+ @Nullable Iterable<ResourceOveruseConfiguration> iterableSubject) {
+ super(failureMetadata, iterableSubject);
+ this.mActual = iterableSubject;
+ }
+
+ private static Set<String> toMetadataStrings(List<PackageMetadata> metadata) {
+ return metadata.stream().map(x -> String.format("%s:%d", x.packageName, x.appCategoryType))
+ .collect(Collectors.toSet());
+ }
+
+ private static String toString(
+ Iterable<ResourceOveruseConfiguration> configs) {
+ StringBuilder builder = new StringBuilder();
+ builder.append("[");
+ for (ResourceOveruseConfiguration config : configs) {
+ toStringBuilder(builder, config).append(", ");
+ }
+ builder.append("]");
+ return builder.toString();
+ }
+
+ private static StringBuilder toStringBuilder(StringBuilder builder,
+ ResourceOveruseConfiguration config) {
+ builder.append("{Component type: ").append(config.componentType)
+ .append(", Safe-to-kill packages: [")
+ .append(config.safeToKillPackages.stream().map(Object::toString)
+ .collect(Collectors.joining(", ")))
+ .append("], Vendor package prefixes: [")
+ .append(config.vendorPackagePrefixes.stream().map(Object::toString)
+ .collect(Collectors.joining(", ")))
+ .append("], Package Metadata: [")
+ .append(config.packageMetadata.stream()
+ .map(InternalResourceOveruseConfigurationSubject::toPackageMetadataString)
+ .collect(Collectors.joining(", ")))
+ .append("], Resource specific configurations: [")
+ .append(config.resourceSpecificConfigurations.stream()
+ .map(InternalResourceOveruseConfigurationSubject
+ ::toResourceOveruseConfigString).collect(Collectors.joining(", ")))
+ .append("]}");
+ return builder;
+ }
+
+ private static String toResourceOveruseConfigString(ResourceSpecificConfiguration config) {
+ StringBuilder builder = new StringBuilder();
+ builder.append("{Tag: ").append(config.getTag()).append(", Value: ");
+ if (config.getTag() == ResourceSpecificConfiguration.ioOveruseConfiguration) {
+ InternalIoOveruseConfigurationSubject.toStringBuilder(builder,
+ config.getIoOveruseConfiguration());
+ } else {
+ builder.append("UNKNOWN");
+ }
+ return builder.toString();
+ }
+
+ private static String toPackageMetadataString(PackageMetadata metadata) {
+ StringBuilder builder = new StringBuilder();
+ builder.append("{Name: ").append(metadata.packageName).append(", App category type: ")
+ .append(metadata.appCategoryType).append("}");
+ return builder.toString();
+ }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/watchdog/IoOveruseConfigurationSubject.java b/tests/carservice_unit_test/src/com/android/car/watchdog/IoOveruseConfigurationSubject.java
new file mode 100644
index 0000000..3629898
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/watchdog/IoOveruseConfigurationSubject.java
@@ -0,0 +1,123 @@
+/*
+ * 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.watchdog;
+
+import static com.google.common.truth.Truth.assertAbout;
+
+import android.annotation.Nullable;
+import android.car.watchdog.IoOveruseAlertThreshold;
+import android.car.watchdog.IoOveruseConfiguration;
+import android.car.watchdog.PerStateBytes;
+
+import com.google.common.base.Equivalence;
+import com.google.common.collect.Maps;
+import com.google.common.truth.Correspondence;
+import com.google.common.truth.FailureMetadata;
+import com.google.common.truth.Subject;
+import com.google.common.truth.Truth;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public final class IoOveruseConfigurationSubject extends Subject {
+ // Boiler-plate Subject.Factory for IoOveruseConfigurationSubject
+ private static final Subject.Factory<
+ com.android.car.watchdog.IoOveruseConfigurationSubject,
+ Iterable<IoOveruseConfiguration>> Io_OVERUSE_CONFIG_SUBJECT_FACTORY =
+ com.android.car.watchdog.IoOveruseConfigurationSubject::new;
+
+ private static final Equivalence<PerStateBytes> PER_STATE_BYTES_EQUIVALENCE =
+ new Equivalence<PerStateBytes>() {
+ @Override
+ protected boolean doEquivalent(PerStateBytes actual, PerStateBytes expected) {
+ return isPerStateBytesEquals(actual, expected);
+ }
+
+ @Override
+ protected int doHash(PerStateBytes perStateBytes) {
+ return (int) ((perStateBytes.getForegroundModeBytes() * 10 ^ 4)
+ + (perStateBytes.getBackgroundModeBytes() * 10 ^ 3)
+ + perStateBytes.getGarageModeBytes());
+ }
+ };
+
+ private final Iterable<IoOveruseConfiguration> mActual;
+
+ // User-defined entry point
+ public static IoOveruseConfigurationSubject assertThat(
+ @Nullable Iterable<IoOveruseConfiguration> stats) {
+ return assertAbout(Io_OVERUSE_CONFIG_SUBJECT_FACTORY).that(stats);
+ }
+
+ public static Subject.Factory<IoOveruseConfigurationSubject,
+ Iterable<IoOveruseConfiguration>> resourceOveruseStats() {
+ return Io_OVERUSE_CONFIG_SUBJECT_FACTORY;
+ }
+
+ public void containsExactly(IoOveruseConfiguration... stats) {
+ containsExactlyElementsIn(Arrays.asList(stats));
+ }
+
+ public void containsExactlyElementsIn(Iterable<IoOveruseConfiguration> expected) {
+ Truth.assertThat(mActual)
+ .comparingElementsUsing(Correspondence.from(
+ IoOveruseConfigurationSubject::isEquals, "is equal to"))
+ .containsExactlyElementsIn(expected);
+ }
+
+ public static boolean isEquals(IoOveruseConfiguration actual,
+ IoOveruseConfiguration expected) {
+ if (actual == null || expected == null) {
+ return (actual == null) && (expected == null);
+ }
+
+ return isPerStateBytesEquals(actual.getComponentLevelThresholds(),
+ expected.getComponentLevelThresholds())
+ && Maps.difference(actual.getPackageSpecificThresholds(),
+ expected.getPackageSpecificThresholds(), PER_STATE_BYTES_EQUIVALENCE).areEqual()
+ && Maps.difference(actual.getAppCategorySpecificThresholds(),
+ expected.getAppCategorySpecificThresholds(), PER_STATE_BYTES_EQUIVALENCE).areEqual()
+ && isAlertThresholdEquals(actual.getSystemWideThresholds(),
+ expected.getSystemWideThresholds());
+ }
+
+ private IoOveruseConfigurationSubject(FailureMetadata failureMetadata,
+ @Nullable Iterable<IoOveruseConfiguration> iterableSubject) {
+ super(failureMetadata, iterableSubject);
+ this.mActual = iterableSubject;
+ }
+
+ private static boolean isPerStateBytesEquals(PerStateBytes actual, PerStateBytes expected) {
+ return actual.getForegroundModeBytes() == expected.getForegroundModeBytes()
+ && actual.getBackgroundModeBytes() == expected.getBackgroundModeBytes()
+ && actual.getGarageModeBytes() == expected.getGarageModeBytes();
+ }
+
+ private static boolean isAlertThresholdEquals(List<IoOveruseAlertThreshold> actual,
+ List<IoOveruseAlertThreshold> expected) {
+ Set<String> actualStr = toAlertThresholdStrings(actual);
+ Set<String> expectedStr = toAlertThresholdStrings(expected);
+ return actualStr.equals(expectedStr);
+ }
+
+ private static Set<String> toAlertThresholdStrings(List<IoOveruseAlertThreshold> thresholds) {
+ return thresholds.stream().map(x -> String.format("%d:%d", x.getDurationInSeconds(),
+ x.getWrittenBytesPerSecond())).collect(Collectors.toSet());
+ }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/watchdog/PackageKillableStateSubject.java b/tests/carservice_unit_test/src/com/android/car/watchdog/PackageKillableStateSubject.java
new file mode 100644
index 0000000..953d1df
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/watchdog/PackageKillableStateSubject.java
@@ -0,0 +1,77 @@
+/*
+ * 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.watchdog;
+
+import static com.google.common.truth.Truth.assertAbout;
+
+import android.annotation.Nullable;
+import android.car.watchdog.PackageKillableState;
+
+import com.google.common.truth.Correspondence;
+import com.google.common.truth.FailureMetadata;
+import com.google.common.truth.Subject;
+import com.google.common.truth.Truth;
+
+import java.util.Arrays;
+
+public final class PackageKillableStateSubject extends Subject {
+ // Boiler-plate Subject.Factory for PackageKillableStateSubject
+ private static final Subject.Factory<
+ com.android.car.watchdog.PackageKillableStateSubject,
+ Iterable<PackageKillableState>> PACKAGE_KILLABLE_STATE_SUBJECT_FACTORY =
+ com.android.car.watchdog.PackageKillableStateSubject::new;
+
+ private final Iterable<PackageKillableState> mActual;
+
+ // User-defined entry point
+ public static PackageKillableStateSubject assertThat(
+ @Nullable Iterable<PackageKillableState> stats) {
+ return assertAbout(PACKAGE_KILLABLE_STATE_SUBJECT_FACTORY).that(stats);
+ }
+
+ public static Subject.Factory<PackageKillableStateSubject,
+ Iterable<PackageKillableState>> resourceOveruseStats() {
+ return PACKAGE_KILLABLE_STATE_SUBJECT_FACTORY;
+ }
+
+ public void containsExactly(PackageKillableState... stats) {
+ containsExactlyElementsIn(Arrays.asList(stats));
+ }
+
+ public void containsExactlyElementsIn(Iterable<PackageKillableState> expected) {
+ Truth.assertThat(mActual)
+ .comparingElementsUsing(Correspondence.from(
+ PackageKillableStateSubject::isEquals, "is equal to"))
+ .containsExactlyElementsIn(expected);
+ }
+
+ public static boolean isEquals(PackageKillableState actual,
+ PackageKillableState expected) {
+ if (actual == null || expected == null) {
+ return (actual == null) && (expected == null);
+ }
+ return actual.getPackageName().equals(expected.getPackageName())
+ && actual.getUserId() == expected.getUserId()
+ && actual.getKillableState() == expected.getKillableState();
+ }
+
+ private PackageKillableStateSubject(FailureMetadata failureMetadata,
+ @Nullable Iterable<PackageKillableState> iterableSubject) {
+ super(failureMetadata, iterableSubject);
+ this.mActual = iterableSubject;
+ }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/watchdog/ResourceOveruseConfigurationSubject.java b/tests/carservice_unit_test/src/com/android/car/watchdog/ResourceOveruseConfigurationSubject.java
new file mode 100644
index 0000000..5bfdf17
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/watchdog/ResourceOveruseConfigurationSubject.java
@@ -0,0 +1,85 @@
+/*
+ * 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.watchdog;
+
+import static com.google.common.truth.Truth.assertAbout;
+
+import android.annotation.Nullable;
+import android.car.watchdog.ResourceOveruseConfiguration;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+import com.google.common.truth.Correspondence;
+import com.google.common.truth.FailureMetadata;
+import com.google.common.truth.Subject;
+import com.google.common.truth.Truth;
+
+import java.util.Arrays;
+
+public final class ResourceOveruseConfigurationSubject extends Subject {
+ // Boiler-plate Subject.Factory for ResourceOveruseConfigurationSubject
+ private static final Subject.Factory<
+ com.android.car.watchdog.ResourceOveruseConfigurationSubject,
+ Iterable<ResourceOveruseConfiguration>> RESOURCE_OVERUSE_CONFIG_SUBJECT_FACTORY =
+ com.android.car.watchdog.ResourceOveruseConfigurationSubject::new;
+
+ private final Iterable<ResourceOveruseConfiguration> mActual;
+
+ // User-defined entry point
+ public static ResourceOveruseConfigurationSubject assertThat(
+ @Nullable Iterable<ResourceOveruseConfiguration> stats) {
+ return assertAbout(RESOURCE_OVERUSE_CONFIG_SUBJECT_FACTORY).that(stats);
+ }
+
+ public static Subject.Factory<ResourceOveruseConfigurationSubject,
+ Iterable<ResourceOveruseConfiguration>> resourceOveruseStats() {
+ return RESOURCE_OVERUSE_CONFIG_SUBJECT_FACTORY;
+ }
+
+ public void containsExactly(ResourceOveruseConfiguration... stats) {
+ containsExactlyElementsIn(Arrays.asList(stats));
+ }
+
+ public void containsExactlyElementsIn(Iterable<ResourceOveruseConfiguration> expected) {
+ Truth.assertThat(mActual)
+ .comparingElementsUsing(Correspondence.from(
+ ResourceOveruseConfigurationSubject::isEquals, "is equal to"))
+ .containsExactlyElementsIn(expected);
+ }
+
+ public static boolean isEquals(ResourceOveruseConfiguration actual,
+ ResourceOveruseConfiguration expected) {
+ if (actual == null || expected == null) {
+ return (actual == null) && (expected == null);
+ }
+ return actual.getComponentType() == expected.getComponentType()
+ && ImmutableSet.copyOf(actual.getSafeToKillPackages()).equals(
+ ImmutableSet.copyOf(expected.getSafeToKillPackages()))
+ && ImmutableSet.copyOf(actual.getVendorPackagePrefixes()).equals(
+ ImmutableSet.copyOf(expected.getVendorPackagePrefixes()))
+ && Maps.difference(actual.getPackagesToAppCategoryTypes(),
+ expected.getPackagesToAppCategoryTypes()).areEqual()
+ && IoOveruseConfigurationSubject.isEquals(actual.getIoOveruseConfiguration(),
+ expected.getIoOveruseConfiguration());
+ }
+
+ private ResourceOveruseConfigurationSubject(FailureMetadata failureMetadata,
+ @Nullable Iterable<ResourceOveruseConfiguration> iterableSubject) {
+ super(failureMetadata, iterableSubject);
+ this.mActual = iterableSubject;
+ }
+}