aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--OWNERS_networking5
-rw-r--r--car-lib/src/android/car/VehiclePropertyIds.java30
-rw-r--r--car-lib/src/android/car/telemetry/telemetry.proto30
-rw-r--r--car_product/app_overlays/car-ui-customizations/res/color/car_ui_switch_track_background_selector.xml34
-rw-r--r--car_product/app_overlays/car-ui-customizations/res/drawable/car_switch_track_focus_highlight.xml51
-rw-r--r--car_product/app_overlays/car-ui-customizations/res/drawable/car_ui_switch_track.xml38
-rw-r--r--car_product/app_overlays/car-ui-customizations/res/layout/car_ui_preference_two_action_switch.xml118
-rw-r--r--car_product/app_overlays/car-ui-customizations/res/layout/car_ui_preference_widget_switch.xml30
-rw-r--r--car_product/app_overlays/car-ui-customizations/res/values/styles.xml8
-rw-r--r--car_product/app_overlays/car-ui-customizations/res/xml/overlays.xml7
-rw-r--r--car_product/build/car_base.mk5
-rw-r--r--car_product/build/preinstalled-packages-product-car-base.xml12
-rw-r--r--car_product/car_ui_portrait/Android.mk10
-rw-r--r--car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/car_bottom_system_bar.xml2
-rw-r--r--car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/car_top_system_bar.xml5
-rw-r--r--car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/config.xml24
-rw-r--r--car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/car/displayarea/CarDisplayAreaController.java225
-rw-r--r--car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/car/displayarea/CarDisplayAreaOrganizer.java22
-rw-r--r--car_product/car_ui_portrait/rro/car-ui-customizations/Android.bp254
-rw-r--r--car_product/car_ui_portrait/rro/car-ui-customizations/Android.mk56
-rw-r--r--car_product/car_ui_portrait/rro/car-ui-customizations/AndroidManifest.xml10
-rw-r--r--car_product/car_ui_portrait/rro/car-ui-customizations/product.mk4
-rw-r--r--car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/Android.bp37
-rw-r--r--car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/Android.mk26
-rw-r--r--car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/AndroidManifest.xml8
-rw-r--r--car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/product.mk4
-rw-r--r--car_product/overlay-visual/frameworks/base/core/res/res/color/car_selection_controls_color.xml1
-rw-r--r--car_product/overlay-visual/frameworks/base/core/res/res/drawable/car_checkbox.xml3
-rw-r--r--car_product/overlay-visual/frameworks/base/core/res/res/drawable/car_checkbox_checked_mtrl.xml50
-rw-r--r--car_product/overlay-visual/frameworks/base/core/res/res/drawable/car_checkbox_checked_to_unchecked_mtrl_animation.xml28
-rw-r--r--car_product/overlay-visual/frameworks/base/core/res/res/drawable/car_checkbox_material_anim.xml33
-rw-r--r--car_product/overlay-visual/frameworks/base/core/res/res/drawable/car_checkbox_unchecked_mtrl.xml50
-rw-r--r--car_product/overlay-visual/frameworks/base/core/res/res/drawable/car_checkbox_unchecked_to_checked_mtrl_animation.xml28
-rw-r--r--car_product/overlay-visual/frameworks/base/core/res/res/values/styles_device_default.xml1
-rw-r--r--car_product/properties/bluetooth.prop9
-rw-r--r--car_product/sepolicy/private/carservice_app.te3
-rw-r--r--cpp/evs/manager/aidl/utils/src/Utils.cpp21
-rw-r--r--cpp/evs/manager/aidl/wrappers/src/HidlCamera.cpp17
-rw-r--r--cpp/powerpolicy/server/src/CarPowerPolicyServer.cpp16
-rw-r--r--cpp/powerpolicy/server/src/CarPowerPolicyServer.h2
-rw-r--r--cpp/powerpolicy/server/tests/CarPowerPolicyServerTest.cpp4
-rw-r--r--data/etc/Android.bp7
-rw-r--r--data/etc/com.android.car.ui.paintbooth.xml2
-rw-r--r--data/etc/com.google.android.car.networking.railway.xml23
-rw-r--r--packages/HideCameraApps/Android.bp29
-rw-r--r--packages/HideCameraApps/AndroidManifest.xml10
-rw-r--r--packages/ScriptExecutor/src/LuaEngine.cpp94
-rw-r--r--packages/ScriptExecutor/src/LuaEngine.h26
-rw-r--r--packages/ScriptExecutor/src/ScriptExecutorListener.cpp9
-rw-r--r--packages/ScriptExecutor/src/ScriptExecutorListener.h9
-rw-r--r--packages/ScriptExecutor/tests/functional/src/com/android/car/scriptexecutortest/functional/ScriptExecutorFunctionalTest.java96
-rw-r--r--packages/ScriptExecutor/tests/nonsystemuser/src/com/android/car/scriptexecutortest/nonsystemuser/ScriptExecutorNonSystemUserTest.java7
-rw-r--r--service/jni/evs/EvsServiceContext.cpp2
-rw-r--r--service/jni/evs/EvsServiceContext.h2
-rw-r--r--service/res/values-es/strings.xml2
-rw-r--r--service/res/values/config.xml5
-rw-r--r--service/res/values/overlayable.xml9
-rw-r--r--service/src/com/android/car/CarPropertyService.java25
-rw-r--r--service/src/com/android/car/CarShellCommand.java16
-rw-r--r--service/src/com/android/car/ICarImpl.java39
-rw-r--r--service/src/com/android/car/bluetooth/CarBluetoothService.java43
-rw-r--r--service/src/com/android/car/telemetry/CarTelemetryService.java183
-rw-r--r--service/src/com/android/car/telemetry/ResultStore.java8
-rw-r--r--service/src/com/android/car/telemetry/databroker/DataBroker.java60
-rw-r--r--service/src/com/android/car/telemetry/databroker/DataBrokerController.java136
-rw-r--r--service/src/com/android/car/telemetry/databroker/DataBrokerImpl.java183
-rw-r--r--service/src/com/android/car/telemetry/databroker/DataSubscriber.java27
-rw-r--r--service/src/com/android/car/telemetry/databroker/ScriptExecutionTask.java9
-rw-r--r--service/src/com/android/car/telemetry/publisher/AbstractPublisher.java40
-rw-r--r--service/src/com/android/car/telemetry/publisher/CarTelemetrydPublisher.java4
-rw-r--r--service/src/com/android/car/telemetry/publisher/ConnectivityPublisher.java4
-rw-r--r--service/src/com/android/car/telemetry/publisher/MemoryPublisher.java262
-rw-r--r--service/src/com/android/car/telemetry/publisher/PublisherFactory.java30
-rw-r--r--service/src/com/android/car/telemetry/publisher/StatsPublisher.java4
-rw-r--r--service/src/com/android/car/telemetry/publisher/VehiclePropertyPublisher.java4
-rw-r--r--service/src/com/android/car/telemetry/scriptexecutorinterface/IScriptExecutorListener.aidl21
-rw-r--r--service/src/com/android/car/user/InitialUserSetter.java24
-rw-r--r--tests/EmbeddedKitchenSinkApp/res/layout/backup_restore_fragment.xml12
-rw-r--r--tests/EmbeddedKitchenSinkApp/res/layout/car_telemetry_test.xml25
-rw-r--r--tests/EmbeddedKitchenSinkApp/res/layout/sensors.xml243
-rw-r--r--tests/EmbeddedKitchenSinkApp/res/values/strings.xml6
-rw-r--r--tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/backup/BackupAndRestoreFragment.java153
-rw-r--r--tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/telemetry/CarTelemetryTestFragment.java75
-rw-r--r--tests/RailwayReferenceApp/Android.bp49
-rw-r--r--tests/RailwayReferenceApp/AndroidManifest.xml38
-rw-r--r--tests/RailwayReferenceApp/res/layout/activity_main.xml248
-rw-r--r--tests/RailwayReferenceApp/res/values/colors.xml22
-rw-r--r--tests/RailwayReferenceApp/res/values/dimens.xml19
-rw-r--r--tests/RailwayReferenceApp/res/values/strings.xml31
-rw-r--r--tests/RailwayReferenceApp/res/values/styles.xml27
-rw-r--r--tests/RailwayReferenceApp/src/com/google/android/car/networking/railway/MainActivity.java27
-rw-r--r--tests/android_car_api_test/src/android/car/apitest/CarUserManagerLifecycleEventFilterTest.java6
-rw-r--r--tests/carservice_test/src/com/android/car/CarTelemetryManagerTest.java21
-rw-r--r--tests/carservice_unit_test/src/com/android/car/CarPropertyServiceUnitTest.java73
-rw-r--r--tests/carservice_unit_test/src/com/android/car/bluetooth/CarBluetoothServiceTest.java52
-rw-r--r--tests/carservice_unit_test/src/com/android/car/telemetry/CarTelemetryServiceTest.java199
-rw-r--r--tests/carservice_unit_test/src/com/android/car/telemetry/ResultStoreTest.java11
-rw-r--r--tests/carservice_unit_test/src/com/android/car/telemetry/databroker/DataBrokerControllerTest.java192
-rw-r--r--tests/carservice_unit_test/src/com/android/car/telemetry/databroker/DataBrokerTest.java147
-rw-r--r--tests/carservice_unit_test/src/com/android/car/telemetry/databroker/DataSubscriberTest.java27
-rw-r--r--tests/carservice_unit_test/src/com/android/car/telemetry/publisher/CarTelemetrydPublisherTest.java23
-rw-r--r--tests/carservice_unit_test/src/com/android/car/telemetry/publisher/ConnectivityPublisherTest.java12
-rw-r--r--tests/carservice_unit_test/src/com/android/car/telemetry/publisher/FakePublisherListener.java49
-rw-r--r--tests/carservice_unit_test/src/com/android/car/telemetry/publisher/MemoryPublisherTest.java297
-rw-r--r--tests/carservice_unit_test/src/com/android/car/telemetry/publisher/StatsPublisherTest.java24
-rw-r--r--tests/carservice_unit_test/src/com/android/car/telemetry/publisher/VehiclePropertyPublisherTest.java6
-rw-r--r--tests/carservice_unit_test/src/com/android/car/user/InitialUserSetterTest.java28
107 files changed, 4024 insertions, 893 deletions
diff --git a/OWNERS_networking b/OWNERS_networking
new file mode 100644
index 0000000000..b28bee3fcb
--- /dev/null
+++ b/OWNERS_networking
@@ -0,0 +1,5 @@
+ jmattis@google.com
+ serikb@google.com
+ twasilczyk@google.com
+ kevinme@google.com
+ chrisweir@google.com \ No newline at end of file
diff --git a/car-lib/src/android/car/VehiclePropertyIds.java b/car-lib/src/android/car/VehiclePropertyIds.java
index 13dba741e7..d0599acbba 100644
--- a/car-lib/src/android/car/VehiclePropertyIds.java
+++ b/car-lib/src/android/car/VehiclePropertyIds.java
@@ -2101,8 +2101,25 @@ public final class VehiclePropertyIds {
/**
* EV charge percent limit.
*
- * <p>Indicates the maximum charge percent threshold set by the user. Returns a float value from
- * 0 to 100. configArray contains the valid charge percent limit values.
+ * <p>Indicates the maximum charge percent threshold set by the user. Returns a float value
+ * from 0 to 100.
+ *
+ * <p>configArray is optional. If it is populated, it represents the valid charge percent limit
+ * values for the vehicle. Here is an example configArray:
+ * <ul>
+ * <li>configArray[0] = 20
+ * <li>configArray[1] = 40
+ * <li>configArray[2] = 60
+ * <li>configArray[3] = 80
+ * </ul>
+ *
+ * <p>Property Config:
+ * <ul>
+ * <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_READ_WRITE}
+ * <li>{@link VehicleAreaType#VEHICLE_AREA_TYPE_GLOBAL}
+ * <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE}
+ * <li>{@code Float} property type
+ * </ul>
*
* <p>Required Permissions:
* <ul>
@@ -2150,9 +2167,18 @@ public final class VehiclePropertyIds {
*
* <p>Returns 0 if the vehicle is not charging.
*
+ * <p>Property Config:
+ * <ul>
+ * <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_READ}
+ * <li>{@link VehicleAreaType#VEHICLE_AREA_TYPE_GLOBAL}
+ * <li>{@link android.car.hardware.CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS}
+ * <li>{@code Integer} property type
+ * </ul>
+ *
* <p>Required Permissions:
* <ul>
* <li>Dangerous permission {@link Car#PERMISSION_ENERGY} to read property.
+ * <li>Property is not writable.
* </ul>
*/
@RequiresPermission.Read(@RequiresPermission(Car.PERMISSION_ENERGY))
diff --git a/car-lib/src/android/car/telemetry/telemetry.proto b/car-lib/src/android/car/telemetry/telemetry.proto
index 555e19e05b..ddb4c2da82 100644
--- a/car-lib/src/android/car/telemetry/telemetry.proto
+++ b/car-lib/src/android/car/telemetry/telemetry.proto
@@ -103,9 +103,7 @@ message StatsPublisher {
}
// Publisher for connectivity stats.
-// It pulls connectivity metrics every 1 minute (configurable via android
-// properties).
-// TODO(b/197905656): pull netstats when driving session changes.
+// It pulls connectivity metrics when driving session changes.
message ConnectivityPublisher {
// Transports are from android.net.NetworkCapabilities.
enum Transport {
@@ -129,6 +127,26 @@ message ConnectivityPublisher {
optional OemType oem_type = 2;
}
+// Publisher for device-wide memory statistics.
+// It pulls data from /proc/meminfo every N seconds, where N is the read_interval_sec.
+message MemoryPublisher {
+ // Required.
+ // The number of seconds in between each memory snapshot.
+ // The smallest acceptable read interval is 1, which means one snapshot per second.
+ optional int32 read_interval_sec = 1;
+
+ // Optional.
+ // The maximum number of memory snapshots to collect before terminating the MetricsConfig.
+ // If unspecified, data will be collected until Lua script marks the MetricsConfig as finished
+ // or MetricsConfig is removed.
+ optional int32 max_snapshots = 2;
+
+ // Required.
+ // The maximum number of pending script execution tasks that the MemoryPublisher can produce
+ // before the publisher is throttled (rate-limited).
+ optional int32 max_pending_tasks = 3;
+}
+
// Specifies data publisher and its parameters.
message Publisher {
oneof publisher {
@@ -136,6 +154,7 @@ message Publisher {
CarTelemetrydPublisher cartelemetryd = 2;
StatsPublisher stats = 3;
ConnectivityPublisher connectivity = 4;
+ MemoryPublisher memory = 5;
}
}
@@ -160,7 +179,7 @@ message Subscriber {
// A message that encapsulates an error that's produced when collecting metrics.
// Any changes here should also be reflected on
// p/s/C/service/src/com/android/car/telemetry/scriptexecutorinterface/IScriptExecutorListener.aidl
-// p/s/C/package/ScriptExecutor/src/ScriptExecutorListener.h
+// p/s/C/packages/ScriptExecutor/src/ScriptExecutorListener.h
message TelemetryError {
enum ErrorType {
// Not used.
@@ -176,6 +195,9 @@ message TelemetryError {
// Used to log errors by a script itself, for instance, when a script received inputs outside
// of expected range.
LUA_SCRIPT_ERROR = 3;
+
+ // Used to log errors that occur due publisher failures.
+ PUBLISHER_FAILED = 4;
}
// Required.
diff --git a/car_product/app_overlays/car-ui-customizations/res/color/car_ui_switch_track_background_selector.xml b/car_product/app_overlays/car-ui-customizations/res/color/car_ui_switch_track_background_selector.xml
new file mode 100644
index 0000000000..e3e5e228db
--- /dev/null
+++ b/car_product/app_overlays/car-ui-customizations/res/color/car_ui_switch_track_background_selector.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2022 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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+ <item
+ android:state_checked="true" app:state_ux_restricted="true"
+ android:color="#7566B5FF" />
+ <item
+ android:state_checked="false" app:state_ux_restricted="true"
+ android:color="#75757575" />
+ <item
+ android:state_checked="true" android:state_enabled="false"
+ android:color="#7566B5" />
+ <item
+ android:state_enabled="false"
+ android:color="#75757575" />
+ <item
+ android:state_checked="true" android:color="#66B5FF" />
+ <item android:color="#757575" />
+</selector>
diff --git a/car_product/app_overlays/car-ui-customizations/res/drawable/car_switch_track_focus_highlight.xml b/car_product/app_overlays/car-ui-customizations/res/drawable/car_switch_track_focus_highlight.xml
new file mode 100644
index 0000000000..c40ecda083
--- /dev/null
+++ b/car_product/app_overlays/car-ui-customizations/res/drawable/car_switch_track_focus_highlight.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_focused="true" android:state_pressed="true">
+ <shape android:shape="rectangle">
+ <corners android:radius="36dp" />
+ <solid android:color="#8A94CBFF" />
+ <padding
+ android:left="12dp"
+ android:right="12dp" />
+ <size
+ android:height="72dp"
+ android:width="112dp" />
+ <stroke
+ android:width="4dp"
+ android:color="#94CBFF" />
+ </shape>
+ </item>
+ <item android:state_focused="true">
+ <shape android:shape="rectangle">
+ <corners android:radius="36dp" />
+ <solid android:color="#3D94CBFF" />
+ <padding
+ android:left="12dp"
+ android:right="12dp" />
+ <size
+ android:height="72dp"
+ android:width="112dp" />
+ <stroke
+ android:width="8dp"
+ android:color="#94CBFF" />
+ </shape>
+ </item>
+ <item>
+ <ripple android:color="?android:attr/colorControlHighlight" />
+ </item>
+</selector>
diff --git a/car_product/app_overlays/car-ui-customizations/res/drawable/car_ui_switch_track.xml b/car_product/app_overlays/car-ui-customizations/res/drawable/car_ui_switch_track.xml
new file mode 100644
index 0000000000..c73f2fbb55
--- /dev/null
+++ b/car_product/app_overlays/car-ui-customizations/res/drawable/car_ui_switch_track.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2022 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.
+-->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:bottom="14dp"
+ android:gravity="center"
+ android:top="14dp">
+ <shape android:shape="rectangle">
+ <corners android:radius="24dp" />
+ <padding
+ android:left="4dp"
+ android:right="4dp" />
+ <size
+ android:height="48dp"
+ android:width="88dp" />
+ <solid android:color="@color/car_ui_switch_track_background_selector" />
+ </shape>
+ </item>
+ <item
+ android:bottom="14dp"
+ android:drawable="@drawable/car_switch_track_focus_highlight"
+ android:gravity="center"
+ android:top="14dp" />
+</layer-list>
diff --git a/car_product/app_overlays/car-ui-customizations/res/layout/car_ui_preference_two_action_switch.xml b/car_product/app_overlays/car-ui-customizations/res/layout/car_ui_preference_two_action_switch.xml
new file mode 100644
index 0000000000..ef6f99173f
--- /dev/null
+++ b/car_product/app_overlays/car-ui-customizations/res/layout/car_ui_preference_two_action_switch.xml
@@ -0,0 +1,118 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2022 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.
+-->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:minHeight="?android:attr/listPreferredItemHeightSmall"
+ android:tag="carUiPreference">
+
+ <com.android.car.ui.uxr.DrawableStateConstraintLayout
+ android:id="@+id/car_ui_first_action_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center_vertical"
+ android:layout_weight = "1"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:background="?android:attr/selectableItemBackground">
+
+ <RelativeLayout
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent">
+
+ <com.android.car.ui.uxr.DrawableStateImageView
+ android:id="@android:id/icon"
+ android:layout_width="44dp"
+ android:layout_height="44dp"
+ android:layout_alignParentStart="true"
+ android:layout_centerVertical="true"
+ android:scaleType="fitCenter" />
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:layout_marginStart="16dp"
+ android:layout_toEndOf="@android:id/icon"
+ android:layout_centerVertical="true">
+
+ <com.android.car.ui.uxr.DrawableStateTextView
+ android:id="@android:id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:textAlignment="viewStart"
+ android:textAppearance="@style/TextAppearance.CarUi.PreferenceTitle" />
+
+ <com.android.car.ui.uxr.DrawableStateTextView
+ android:id="@android:id/summary"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:maxLines="2"
+ android:textAlignment="viewStart"
+ android:textAppearance="@style/TextAppearance.CarUi.PreferenceSummary" />
+ </LinearLayout>
+ </RelativeLayout>
+ </com.android.car.ui.uxr.DrawableStateConstraintLayout>
+
+ <RelativeLayout
+ android:id="@+id/car_ui_second_action_container"
+ android:layout_height="match_parent"
+ android:layout_width="wrap_content"
+ android:layout_weight = "0"
+ android:layout_gravity="center_vertical">
+
+ <View
+ android:id="@+id/car_ui_divider"
+ android:layout_width="1dp"
+ android:layout_height="match_parent"
+ android:layout_gravity="center_vertical"
+ android:layout_marginBottom="16dp"
+ android:layout_marginTop="16dp"
+ android:background="#75ffffff"/>
+
+ <com.android.car.ui.uxr.DrawableStateFrameLayout
+ android:id="@+id/car_ui_secondary_action"
+ android:layout_width="?android:attr/listPreferredItemHeightSmall"
+ android:layout_height="match_parent"
+ android:layout_alignParentStart="true"
+ android:background="?android:attr/selectableItemBackground">
+
+ <com.android.car.ui.uxr.DrawableStateSwitch
+ android:id="@+id/car_ui_secondary_action_concrete"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:clickable="false"
+ android:focusable="false"
+ style="@style/Widget.CarUi.SwitchPreference.Switch"/>
+ </com.android.car.ui.uxr.DrawableStateFrameLayout>
+
+ <!-- The widget frame is required for androidx preferences, but we won't use it. -->
+ <FrameLayout
+ android:id="@android:id/widget_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_alignParentEnd="true"
+ android:layout_toEndOf="@+id/car_ui_secondary_action" />
+ </RelativeLayout>
+
+</LinearLayout>
diff --git a/car_product/app_overlays/car-ui-customizations/res/layout/car_ui_preference_widget_switch.xml b/car_product/app_overlays/car-ui-customizations/res/layout/car_ui_preference_widget_switch.xml
new file mode 100644
index 0000000000..b4ffdc7030
--- /dev/null
+++ b/car_product/app_overlays/car-ui-customizations/res/layout/car_ui_preference_widget_switch.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2022 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.
+-->
+
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="?android:attr/listPreferredItemHeightSmall"
+ android:layout_height="?android:attr/listPreferredItemHeightSmall">
+ <com.android.car.ui.uxr.DrawableStateSwitch
+ android:id="@android:id/switch_widget"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:clickable="false"
+ android:focusable="false"
+ style="@style/Widget.CarUi.SwitchPreference.Switch"/>
+</FrameLayout>
diff --git a/car_product/app_overlays/car-ui-customizations/res/values/styles.xml b/car_product/app_overlays/car-ui-customizations/res/values/styles.xml
index 1e6102ca4c..1d978dab5d 100644
--- a/car_product/app_overlays/car-ui-customizations/res/values/styles.xml
+++ b/car_product/app_overlays/car-ui-customizations/res/values/styles.xml
@@ -63,6 +63,12 @@
<item name="android:paddingEnd">@dimen/car_ui_padding_4</item>
</style>
+ <style name="Widget.CarUi.SwitchPreference"/>
+
+ <style name="Widget.CarUi.SwitchPreference.Switch">
+ <item name="android:track">@drawable/car_ui_switch_track</item>
+ </style>
+
<style name="Widget.CarUi.SeekbarPreference"/>
<!-- Style applied to the seekbar widget within the seekbar preference -->
@@ -133,6 +139,4 @@
<item name="android:background">@android:color/transparent</item>
</style>
-
-
</resources>
diff --git a/car_product/app_overlays/car-ui-customizations/res/xml/overlays.xml b/car_product/app_overlays/car-ui-customizations/res/xml/overlays.xml
index a7aa0de217..c0982db0d0 100644
--- a/car_product/app_overlays/car-ui-customizations/res/xml/overlays.xml
+++ b/car_product/app_overlays/car-ui-customizations/res/xml/overlays.xml
@@ -58,6 +58,11 @@
<item target="drawable/car_ui_toolbar_menu_item_icon_ripple" value="@drawable/car_ui_toolbar_menu_item_icon_ripple"/>
<item target="id/car_ui_base_layout_content_container" value="@id/car_ui_base_layout_content_container" />
+ <item target="id/car_ui_first_action_container" value="@id/car_ui_first_action_container" />
+ <item target="id/car_ui_divider" value="@id/car_ui_divider" />
+ <item target="id/car_ui_second_action_container" value="@id/car_ui_second_action_container" />
+ <item target="id/car_ui_secondary_action" value="@id/car_ui_secondary_action" />
+ <item target="id/car_ui_secondary_action_concrete" value="@id/car_ui_secondary_action_concrete" />
<item target="id/car_ui_toolbar_background" value="@id/car_ui_toolbar_background" />
<item target="id/car_ui_toolbar_logo" value="@id/car_ui_toolbar_logo" />
<item target="id/car_ui_toolbar_menu_item_icon_container" value="@id/car_ui_toolbar_menu_item_icon_container"/>
@@ -82,6 +87,8 @@
<item target="layout/car_ui_alert_dialog_list" value="@layout/car_ui_alert_dialog_list"/>
<item target="layout/car_ui_base_layout_toolbar" value="@layout/car_ui_base_layout_toolbar"/>
+ <item target="layout/car_ui_preference_two_action_switch" value="@layout/car_ui_preference_two_action_switch"/>
+ <item target="layout/car_ui_preference_widget_switch" value="@layout/car_ui_preference_widget_switch"/>
<item target="layout/car_ui_preference_widget_seekbar" value="@layout/car_ui_preference_widget_seekbar"/>
<item target="layout/car_ui_toolbar_menu_item_primary" value="@layout/car_ui_toolbar_menu_item_primary"/>
<item target="layout/car_ui_toolbar_menu_item" value="@layout/car_ui_toolbar_menu_item"/>
diff --git a/car_product/build/car_base.mk b/car_product/build/car_base.mk
index 8823b28282..0455c214f4 100644
--- a/car_product/build/car_base.mk
+++ b/car_product/build/car_base.mk
@@ -62,6 +62,7 @@ PRODUCT_PACKAGES += \
# Android Camera service.
ifneq ($(ENABLE_CAMERA_SERVICE), true)
PRODUCT_PROPERTY_OVERRIDES += config.disable_cameraservice=true
+PRODUCT_PACKAGES += HideCameraApps
endif
# ENABLE_EVS_SERVICE must be set as true from the product's makefile if it wants to support
@@ -109,8 +110,8 @@ $(call inherit-product, $(SRC_TARGET_DIR)/product/core_minimal.mk)
PRODUCT_PROPERTY_OVERRIDES += \
pm.dexopt.disable_bg_dexopt=false \
pm.dexopt.downgrade_after_inactive_days=10 \
- dalvik.vm.dex2oat-cpu-set=0,1,2,3 \
- dalvik.vm.dex2oat-threads=4
+ dalvik.vm.dex2oat-cpu-set=0,1 \
+ dalvik.vm.dex2oat-threads=2
# Required init rc files for car
PRODUCT_COPY_FILES += \
diff --git a/car_product/build/preinstalled-packages-product-car-base.xml b/car_product/build/preinstalled-packages-product-car-base.xml
index 5da27ab223..bab7595f6b 100644
--- a/car_product/build/preinstalled-packages-product-car-base.xml
+++ b/car_product/build/preinstalled-packages-product-car-base.xml
@@ -362,6 +362,10 @@
<install-in-user-type package="com.google.android.car.networking.preferenceupdater">
<install-in user-type="FULL" />
</install-in-user-type>
+ <!-- Provides a reference for configuring internal networks. -->
+ <install-in-user-type package="com.google.android.car.networking.railway">
+ <install-in user-type="FULL" />
+ </install-in-user-type>
<install-in-user-type package="com.android.supplemental.process">
<install-in user-type="FULL" />
</install-in-user-type>
@@ -378,4 +382,12 @@
<install-in-user-type package="com.android.adservices.api">
<install-in user-type="FULL" />
</install-in-user-type>
+ <!-- Remove camera apps when ENABLE_CAMERA_SERVICE is false. -->
+ <install-in-user-type package="com.android.car.hidecameraapps">
+ <install-in user-type="FULL" />
+ </install-in-user-type>
+ <!-- Android Camera2 package is included when ENABLE_CAMERA_SERVICE is true -->
+ <install-in-user-type package="com.android.camera2">
+ <install-in user-type="FULL" />
+ </install-in-user-type>
</config>
diff --git a/car_product/car_ui_portrait/Android.mk b/car_product/car_ui_portrait/Android.mk
index 51c81e6902..53ad94bd88 100644
--- a/car_product/car_ui_portrait/Android.mk
+++ b/car_product/car_ui_portrait/Android.mk
@@ -14,14 +14,8 @@
#
car_ui_portrait_modules := \
- apps/HideApps
-
-# TODO(b/199553899): temporarily exclude car_ui_portrait from coverage report.
-# Car API xmls files are not generated when including car_ui_portrait.
-ifneq (,$(filter %car_ui_portrait,$(TARGET_PRODUCT)))
-car_ui_portrait_modules += \
rro/car-ui-customizations \
- rro/car-ui-toolbar-customizations
-endif #car_ui_portrait
+ rro/car-ui-toolbar-customizations \
+ apps/HideApps
include $(call all-named-subdir-makefiles,$(car_ui_portrait_modules))
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/car_bottom_system_bar.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/car_bottom_system_bar.xml
index f6b1d27a08..984d3025af 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/car_bottom_system_bar.xml
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/car_bottom_system_bar.xml
@@ -56,7 +56,7 @@
systemui:icon="@android:color/transparent"
systemui:highlightWhenSelected="true"
systemui:toggleSelected="true"
- systemui:intent="intent:#Intent;component=com.android.car.carlauncher/.AppGridActivity;launchFlags=0x24000000;end"
+ systemui:intent="intent:#Intent;action=com.android.car.carlauncher.ACTION_APP_GRID;package=com.android.car.carlauncher;launchFlags=0x24000000;end"
systemui:clearBackStack="true"/>
<com.android.systemui.car.systembar.CarUiPortraitSystemBarButton
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/car_top_system_bar.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/car_top_system_bar.xml
index eb3a65672c..97932b2bd9 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/car_top_system_bar.xml
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/layout/car_top_system_bar.xml
@@ -85,6 +85,11 @@
android:orientation="horizontal"
android:gravity="end">
+ <include layout="@layout/camera_privacy_chip"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_gravity="center_vertical" />
+
<include layout="@layout/mic_privacy_chip"
android:layout_width="wrap_content"
android:layout_height="match_parent"
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/config.xml b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/config.xml
index 649133626d..fb5c727072 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/config.xml
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/res/values/config.xml
@@ -53,21 +53,11 @@
</string-array>
<string-array name="config_ignoreOpeningForegroundDA" translatable="false">
- <item>com.android.car.carlauncher/.CarLauncher</item>
- <item>com.android.car.carlauncher/.ControlBarActivity</item>
- <item>android.car.cluster/.MainClusterActivity</item>
<item>com.android.car.settings/.FallbackHome</item>
- <item>com.google.android.gms/.auth.uiflows.common.UnpackingRedirectActivity</item>
- <item>com.google.android.gms/.auth.auto.SignInMethodActivity</item>
- <item>com.google.android.gms/.auth.uiflows.minutemaid.MinuteMaidActivity</item>
+ <item>android.car.usb.handler/android.car.usb.handler.UsbHostManagementActivity</item>
<item>com.google.android.car.setupwizard/.CarSetupWizardActivity</item>
- <item>com.google.android.car.setupwizard/.libs.uicommon.PlaceholderActivity</item>
- <item>android.car.usb.handler/.UsbHostManagementActivity</item>
- <item>android.car.cluster/.FakeFreeNavigationActivity</item>
- <item>com.android.mtp/.ReceiverActivity</item>
- <item>com.android.permissioncontroller/.permission.ui.GrantPermissionsActivity</item>
- <item>com.google.android.gms/com.google.android.location.auto.settings.LocationSettingsCheckerAutoActivity</item>
- <item>com.android.sdksetup/.DefaultActivity</item>
+ <item>com.google.android.car.setupwizard/.libs.uicommon.TrampolineActivity</item>
+ <item>com.google.android.apps.maps/com.google.android.apps.gmm.car.embedded.auxiliarymap.EmbeddedClusterActivity</item>
</string-array>
<!-- TODO(b/202413464): Move GAS components to the separate RRO. -->
@@ -79,7 +69,9 @@
<string name="config_controlBarActivity" translatable="false">
com.android.car.carlauncher/.ControlBarActivity
</string>
- <string name="config_backgroundActivity" translatable="false">
- com.google.android.apps.maps/com.google.android.maps.MapsActivity
- </string>
+ <!-- default relaunch will be the first item -->
+ <string-array name="config_backgroundActivities" translatable="false">
+ <item>com.google.android.apps.maps/com.google.android.maps.MapsActivity</item>
+ <item>com.android.car.ui.paintbooth/.MainActivity</item>
+ </string-array>
</resources>
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/car/displayarea/CarDisplayAreaController.java b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/car/displayarea/CarDisplayAreaController.java
index cfca1b6d08..17c752e229 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/car/displayarea/CarDisplayAreaController.java
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/car/displayarea/CarDisplayAreaController.java
@@ -27,7 +27,6 @@ import static com.android.systemui.car.displayarea.CarDisplayAreaOrganizer.CONTR
import static com.android.systemui.car.displayarea.CarDisplayAreaOrganizer.FEATURE_TITLE_BAR;
import static com.android.systemui.car.displayarea.CarDisplayAreaOrganizer.FEATURE_VOICE_PLATE;
import static com.android.systemui.car.displayarea.CarDisplayAreaOrganizer.FOREGROUND_DISPLAY_AREA_ROOT;
-import static com.android.systemui.car.displayarea.CarFullscreenTaskListener.MAPS;
import static com.android.systemui.car.displayarea.DisplayAreaComponent.DISPLAY_AREA_VISIBILITY_CHANGED;
import static com.android.systemui.car.displayarea.DisplayAreaComponent.FOREGROUND_DA_STATE.CONTROL_BAR;
import static com.android.systemui.car.displayarea.DisplayAreaComponent.FOREGROUND_DA_STATE.DEFAULT;
@@ -50,6 +49,7 @@ import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.util.ArraySet;
import android.util.DisplayMetrics;
import android.util.Log;
@@ -80,6 +80,7 @@ import com.android.wm.shell.common.HandlerExecutor;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -114,7 +115,8 @@ public class CarDisplayAreaController implements ConfigurationController.Configu
private final SyncTransactionQueue mSyncQueue;
private final CarDisplayAreaOrganizer mOrganizer;
private final CarFullscreenTaskListener mCarFullscreenTaskListener;
- private final String mControlBarActivityComponent;
+ private final ComponentName mControlBarActivityComponent;
+ private final List<ComponentName> mBackgroundActivityComponent;
private final HashMap<String, Boolean> mForegroundDAComponentsVisibilityMap;
private final ArraySet<ComponentName> mIgnoreOpeningForegroundDAComponentsSet;
private final int mTitleBarDragThreshold;
@@ -137,6 +139,7 @@ public class CarDisplayAreaController implements ConfigurationController.Configu
private final int mForegroundDisplayTop;
private final AssistUtils mAssistUtils;
private HashSet<Integer> mActiveTasksOnForegroundDA;
+ private HashSet<Integer> mActiveTasksOnBackgroundDA;
private final ConfigurationController mConfigurationController;
private final UiModeManager mUiModeManager;
private DisplayAreaAppearedInfo mForegroundApplicationsDisplay;
@@ -149,43 +152,98 @@ public class CarDisplayAreaController implements ConfigurationController.Configu
private WindowManager mTitleBarWindowManager;
private View mTitleBarView;
private boolean mIsForegroundDaVisible = false;
+ private boolean mIsForegroundDaFullScreen = false;
+ private boolean mIsUiModeNight = false;
+ /**
+ * The WindowContext that is registered with {@link #mTitleBarWindowManager} with options to
+ * specify the {@link RootDisplayArea} to attach the confirmation window.
+ */
+ @Nullable
+ private Context mTitleBarWindowContext;
private final TaskStackListener mOnActivityRestartAttemptListener = new TaskStackListener() {
@Override
public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
boolean homeTaskVisible, boolean clearedTask, boolean wasVisible)
throws RemoteException {
super.onActivityRestartAttempt(task, homeTaskVisible, clearedTask, wasVisible);
- updateForegroundDaVisibility(task.topActivity, -1);
+ logIfDebuggable("onActivityRestartAttempt " + task);
+ updateForegroundDaVisibility(task);
}
@Override
public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo)
throws RemoteException {
super.onTaskMovedToFront(taskInfo);
- updateForegroundDaVisibility(taskInfo.topActivityInfo.getComponentName(),
- taskInfo.taskId);
- }
-
- @Override
- public void onTaskRemoved(int taskId) throws RemoteException {
- super.onTaskRemoved(taskId);
- if (mActiveTasksOnForegroundDA == null) {
- return;
- }
- mActiveTasksOnForegroundDA.remove(taskId);
- if (mActiveTasksOnForegroundDA.isEmpty()) {
- logIfDebuggable("onTaskRemoved: no more tasks left in foreground DA ");
- startAnimation(CONTROL_BAR);
- }
+ logIfDebuggable("onTaskMovedToFront " + taskInfo);
+ updateForegroundDaVisibility(taskInfo);
}
};
- private boolean mIsUiModeNight = false;
- /**
- * The WindowContext that is registered with {@link #mTitleBarWindowManager} with options to
- * specify the {@link RootDisplayArea} to attach the confirmation window.
- */
- @Nullable
- private Context mTitleBarWindowContext;
+
+ private final CarFullscreenTaskListener.OnTaskChangeListener mOnTaskChangeListener =
+ new CarFullscreenTaskListener.OnTaskChangeListener() {
+ @Override
+ public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo) {
+ logIfDebuggable("onTaskAppeared" + taskInfo);
+ updateForegroundDaVisibility(taskInfo);
+ }
+
+ @Override
+ public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
+ Log.e(TAG, " onTaskVanished" + taskInfo);
+ boolean isBackgroundApp = false;
+ boolean isControlBarApp = false;
+ if (taskInfo.baseIntent != null) {
+ ComponentName cmp = taskInfo.baseIntent.getComponent();
+ if (cmp != null) {
+ isBackgroundApp = mBackgroundActivityComponent.contains(cmp);
+ isControlBarApp = cmp.equals(mControlBarActivityComponent);
+ }
+ }
+
+ if (mActiveTasksOnBackgroundDA != null
+ && mActiveTasksOnBackgroundDA.remove(taskInfo.taskId)) {
+ logIfDebuggable("removed task " + taskInfo.taskId
+ + " from background DA, total tasks: "
+ + mActiveTasksOnBackgroundDA.size());
+ }
+
+ if (isBackgroundApp && mActiveTasksOnBackgroundDA != null
+ && mActiveTasksOnBackgroundDA.isEmpty()) {
+ // re launch background app
+ logIfDebuggable("relaunching background app...");
+ Intent mapsIntent = new Intent();
+ mapsIntent.setComponent(mBackgroundActivityComponent.get(0));
+ mApplicationContext.startActivityAsUser(mapsIntent, UserHandle.CURRENT);
+ }
+
+ if (isControlBarApp) {
+ // re launch controlbar app
+ logIfDebuggable("relaunching controlbar app...");
+ Intent controlBarIntent = new Intent();
+ controlBarIntent.setComponent(mControlBarActivityComponent);
+ mApplicationContext.startActivityAsUser(controlBarIntent,
+ UserHandle.CURRENT);
+ }
+ if (taskInfo.displayAreaFeatureId == FEATURE_VOICE_PLATE) {
+ resetVoicePlateDisplayArea();
+ }
+ if (mActiveTasksOnForegroundDA == null) {
+ return;
+ }
+
+ if (mActiveTasksOnForegroundDA.remove(taskInfo.taskId)) {
+ logIfDebuggable("removed task " + taskInfo.taskId
+ + " from foreground DA, total tasks: "
+ + mActiveTasksOnForegroundDA.size());
+ }
+
+ if (mActiveTasksOnForegroundDA.isEmpty()
+ && isHostingDefaultApplicationDisplayAreaVisible()) {
+ logIfDebuggable("no more tasks left in foreground DA, closing... ");
+ startAnimation(CONTROL_BAR);
+ }
+ }
+ };
/**
* Initializes the controller
@@ -218,8 +276,16 @@ public class CarDisplayAreaController implements ConfigurationController.Configu
R.dimen.default_app_display_area_height);
mCarDisplayAreaTouchHandler = new CarDisplayAreaTouchHandler(
new HandlerExecutor(applicationContext.getMainThreadHandler()));
- mControlBarActivityComponent = applicationContext.getResources().getString(
- R.string.config_controlBarActivity);
+ mControlBarActivityComponent = ComponentName.unflattenFromString(
+ applicationContext.getResources().getString(
+ R.string.config_controlBarActivity));
+ mBackgroundActivityComponent = new ArrayList<>();
+ String[] backgroundActivities = mApplicationContext.getResources().getStringArray(
+ R.array.config_backgroundActivities);
+ for (String backgroundActivity : backgroundActivities) {
+ mBackgroundActivityComponent
+ .add(ComponentName.unflattenFromString(backgroundActivity));
+ }
mAssistantVoicePlateActivityName = ComponentName.unflattenFromString(
applicationContext.getResources().getString(
R.string.config_assistantVoicePlateActivity));
@@ -401,37 +467,13 @@ public class CarDisplayAreaController implements ConfigurationController.Configu
public void register() {
logIfDebuggable("register organizer and set default bounds");
// add CarFullscreenTaskListener to control the foreground DA when the task appears.
- mCarFullscreenTaskListener.registerOnTaskChangeListener(
- new CarFullscreenTaskListener.OnTaskChangeListener() {
- @Override
- public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo) {
- if (taskInfo.displayAreaFeatureId == FEATURE_VOICE_PLATE) {
- showVoicePlateDisplayArea();
- return;
- }
-
- if (taskInfo.displayAreaFeatureId == FEATURE_DEFAULT_TASK_CONTAINER
- && taskInfo.isVisible()
- && !shouldIgnoreOpeningForegroundDA(taskInfo)) {
- if (!isHostingDefaultApplicationDisplayAreaVisible()) {
- logIfDebuggable("opening DA on request for: " + taskInfo);
- startAnimation(DEFAULT);
- }
- }
- }
-
- @Override
- public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
- if (taskInfo.displayAreaFeatureId == FEATURE_VOICE_PLATE) {
- resetVoicePlateDisplayArea();
- }
- }
- });
+ mCarFullscreenTaskListener.registerOnTaskChangeListener(mOnTaskChangeListener);
ShellTaskOrganizer taskOrganizer = new ShellTaskOrganizer(mShellExecutor,
mApplicationContext);
taskOrganizer.addListenerForType(mCarFullscreenTaskListener, TASK_LISTENER_TYPE_FULLSCREEN);
+ taskOrganizer.registerOrganizer();
// Register DA organizer.
registerOrganizer();
@@ -510,15 +552,13 @@ public class CarDisplayAreaController implements ConfigurationController.Configu
}
}
- private void updateForegroundDaVisibility(ComponentName componentName, int taskId) {
- if (componentName == null || isDisplayAreaAnimating()) {
+ private void updateForegroundDaVisibility(ActivityManager.RunningTaskInfo taskInfo) {
+ if (taskInfo.baseIntent == null || taskInfo.baseIntent.getComponent() == null
+ || isDisplayAreaAnimating()) {
return;
}
- String packageName = componentName.getPackageName();
- boolean isMaps = packageName.contains(MAPS);
- boolean ignoreOpeningForegroundDA = mIgnoreOpeningForegroundDAComponentsSet.contains(
- componentName);
+ ComponentName componentName = taskInfo.baseIntent.getComponent();
// Voice plate will be shown as the top most layer. Also, we don't want to change the
// state of the DA's when voice plate is shown.
@@ -527,7 +567,24 @@ public class CarDisplayAreaController implements ConfigurationController.Configu
showVoicePlateDisplayArea();
return;
}
- if (isMaps) {
+
+ boolean isControlBar = componentName.equals(mControlBarActivityComponent);
+ boolean isBackgroundApp = mBackgroundActivityComponent.contains(componentName);
+ if (isBackgroundApp) {
+ // we don't want to change the state of the foreground DA when background
+ // apps are launched.
+ addActiveTaskToBackgroundDAMap(taskInfo.taskId);
+ return;
+ }
+
+ if (isControlBar) {
+ // we don't want to change the state of the foreground DA when
+ // controlbar apps are launched.
+ return;
+ }
+
+ if (mIsForegroundDaFullScreen) {
+ logIfDebuggable("foregroundDA in fullscreen mode, skip updating its state ");
return;
}
@@ -536,35 +593,58 @@ public class CarDisplayAreaController implements ConfigurationController.Configu
mAssistUtils.hideCurrentSession();
}
+ // Any task that does NOT meet all the below criteria should be ignored.
+ // 1. displayAreaFeatureId should be FEATURE_DEFAULT_TASK_CONTAINER
+ // 2. should be visible
+ // 3. for the current user ONLY. System user launches some tasks on cluster that should
+ // not affect the state of the foreground DA
+ // 4. any task that is manually defined to be ignored
+ if (!(taskInfo.displayAreaFeatureId == FEATURE_DEFAULT_TASK_CONTAINER
+ && taskInfo.isVisible()
+ && taskInfo.userId == ActivityManager.getCurrentUser()
+ && !shouldIgnoreOpeningForegroundDA(taskInfo))) {
+ return;
+ }
+
String name = componentName.flattenToShortString();
+ // check if the foreground DA is visible to the user
if (isHostingDefaultApplicationDisplayAreaVisible()) {
if (mForegroundDAComponentsVisibilityMap.containsKey(name)
&& mForegroundDAComponentsVisibilityMap.get(name)) {
+ // close the foreground DA
startAnimation(CONTROL_BAR);
}
- if (!ignoreOpeningForegroundDA) {
- addTaskToActiveMap(taskId);
- }
- } else if (!ignoreOpeningForegroundDA) {
+ addActiveTaskToForegroundDAMap(taskInfo.taskId);
+ } else {
logIfDebuggable("opening DA on request for cmp: " + componentName);
startAnimation(DEFAULT);
- addTaskToActiveMap(taskId);
+ addActiveTaskToForegroundDAMap(taskInfo.taskId);
}
- if (ignoreOpeningForegroundDA || name.equals(mControlBarActivityComponent)) {
- return;
- }
mForegroundDAComponentsVisibilityMap.replaceAll((n, v) -> name.equals(n));
}
- private void addTaskToActiveMap(int taskId) {
+ private void addActiveTaskToForegroundDAMap(int taskId) {
if (mActiveTasksOnForegroundDA == null) {
mActiveTasksOnForegroundDA = new HashSet<>();
}
if (taskId != -1) {
- logIfDebuggable("adding task to foreground DA: " + taskId);
mActiveTasksOnForegroundDA.add(taskId);
+ logIfDebuggable("added task to foreground DA: " + taskId + " total tasks: "
+ + mActiveTasksOnForegroundDA.size());
+ }
+ }
+
+ private void addActiveTaskToBackgroundDAMap(int taskId) {
+ if (mActiveTasksOnBackgroundDA == null) {
+ mActiveTasksOnBackgroundDA = new HashSet<>();
+ }
+ if (taskId != -1) {
+ mActiveTasksOnBackgroundDA.add(taskId);
+ logIfDebuggable("added task to background DA: " + taskId + " total tasks: "
+ + mActiveTasksOnBackgroundDA.size());
+
}
}
@@ -666,8 +746,7 @@ public class CarDisplayAreaController implements ConfigurationController.Configu
* This method should be called after the registration of DA's are done. The method expects a
* target state as an argument, according to which the animations will take place. For example,
* if the target state is {@link DisplayAreaComponent.FOREGROUND_DA_STATE#DEFAULT} then the
- * foreground
- * DA hosting default applications will animate to the default set height.
+ * foreground DA hosting default applications will animate to the default set height.
*/
public void startAnimation(DisplayAreaComponent.FOREGROUND_DA_STATE toState) {
// TODO: currently the animations are only bottom/up. Make it more generic animations here.
@@ -792,6 +871,7 @@ public class CarDisplayAreaController implements ConfigurationController.Configu
WindowContainerTransaction wct = new WindowContainerTransaction();
updateBounds(wct);
mOrganizer.applyTransaction(wct);
+ mIsForegroundDaFullScreen = false;
}
/** Updates the default and background display bounds for the given config. */
@@ -930,5 +1010,6 @@ public class CarDisplayAreaController implements ConfigurationController.Configu
});
mOrganizer.applyTransaction(wct);
+ mIsForegroundDaFullScreen = true;
}
}
diff --git a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/car/displayarea/CarDisplayAreaOrganizer.java b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/car/displayarea/CarDisplayAreaOrganizer.java
index c2a20f2352..390ec3805d 100644
--- a/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/car/displayarea/CarDisplayAreaOrganizer.java
+++ b/car_product/car_ui_portrait/apps/CarUiPortraitSystemUI/src/com/android/systemui/car/displayarea/CarDisplayAreaOrganizer.java
@@ -26,7 +26,6 @@ import android.car.Car;
import android.car.app.CarActivityManager;
import android.content.ComponentName;
import android.content.Context;
-import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Rect;
import android.hardware.display.DisplayManager;
@@ -45,6 +44,7 @@ import android.window.WindowContainerTransaction;
import com.android.systemui.R;
import com.android.wm.shell.common.SyncTransactionQueue;
+import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
@@ -73,10 +73,9 @@ public class CarDisplayAreaOrganizer extends DisplayAreaOrganizer {
private static final String TAG = "CarDisplayAreaOrganizer";
private final ComponentName mAssistantVoicePlateActivityName;
private final ComponentName mControlBarActivityName;
- private final ComponentName mBackGroundActivityName;
+ private final List<ComponentName> mBackGroundActivities;
private final Context mContext;
- private final Intent mMapsIntent;
private final SyncTransactionQueue mTransactionQueue;
private final Rect mBackgroundApplicationDisplayBounds = new Rect();
private final CarDisplayAreaAnimationController mAnimationController;
@@ -91,8 +90,10 @@ public class CarDisplayAreaOrganizer extends DisplayAreaOrganizer {
if (ready) {
CarActivityManager carAm = (CarActivityManager) car.getCarManager(
Car.CAR_ACTIVITY_SERVICE);
- setPersistentActivity(carAm, mBackGroundActivityName,
- BACKGROUND_TASK_CONTAINER, "Background");
+ for (ComponentName backgroundCmp : mBackGroundActivities) {
+ setPersistentActivity(carAm, backgroundCmp,
+ BACKGROUND_TASK_CONTAINER, "Background");
+ }
setPersistentActivity(carAm, mAssistantVoicePlateActivityName,
FEATURE_VOICE_PLATE, "VoicePlate");
setPersistentActivity(carAm, mControlBarActivityName,
@@ -159,16 +160,19 @@ public class CarDisplayAreaOrganizer extends DisplayAreaOrganizer {
public CarDisplayAreaOrganizer(Executor executor, Context context, SyncTransactionQueue tx) {
super(executor);
mContext = context;
- mMapsIntent = CarDisplayAreaUtils.getMapsIntent(context);
-
mTransactionQueue = tx;
// TODO(b/201712747): Gets the Assistant Activity by resolving the indirect Intent.
mAssistantVoicePlateActivityName = ComponentName.unflattenFromString(
context.getResources().getString(R.string.config_assistantVoicePlateActivity));
mControlBarActivityName = ComponentName.unflattenFromString(
context.getResources().getString(R.string.config_controlBarActivity));
- mBackGroundActivityName = ComponentName.unflattenFromString(
- context.getResources().getString(R.string.config_backgroundActivity));
+ mBackGroundActivities = new ArrayList<>();
+ String[] backgroundActivities = mContext.getResources().getStringArray(
+ R.array.config_backgroundActivities);
+ for (String backgroundActivity : backgroundActivities) {
+ mBackGroundActivities
+ .add(ComponentName.unflattenFromString(backgroundActivity));
+ }
mAnimationController = new CarDisplayAreaAnimationController(mContext);
mHandlerForAnimation = mContext.getMainThreadHandler();
diff --git a/car_product/car_ui_portrait/rro/car-ui-customizations/Android.bp b/car_product/car_ui_portrait/rro/car-ui-customizations/Android.bp
new file mode 100644
index 0000000000..db33173ef4
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/car-ui-customizations/Android.bp
@@ -0,0 +1,254 @@
+// Copyright (C) 2022 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"],
+}
+
+runtime_resource_overlay {
+ name: "GoogleCarUiPortraitBase",
+ manifest: "AndroidManifest.xml",
+ resource_dirs: ["res"],
+}
+
+override_runtime_resource_overlay {
+ name: "generated_caruiportrait_customization-com-android-car-ui-paintbooth",
+ base: "GoogleCarUiPortraitBase",
+ package_name: "com.android.car.ui.paintbooth.googlecaruiportrait.rro",
+ target_package_name: "com.android.car.ui.paintbooth",
+}
+
+override_runtime_resource_overlay {
+ name: "generated_caruiportrait_customization-com-google-android-car-ui-paintbooth",
+ base: "GoogleCarUiPortraitBase",
+ package_name: "com.android.car.ui.paintbooth.googlecaruiportrait.rro",
+ target_package_name: "com.google.android.car.ui.paintbooth",
+}
+
+override_runtime_resource_overlay {
+ name: "generated_caruiportrait_customization-com-google-android-carui-ats",
+ base: "GoogleCarUiPortraitBase",
+ package_name: "com.google.android.carui.ats.googlecaruiportrait.rro",
+ target_package_name: "com.google.android.carui.ats",
+}
+
+override_runtime_resource_overlay {
+ name: "generated_caruiportrait_customization-com-android-car-rotaryplayground",
+ base: "GoogleCarUiPortraitBase",
+ package_name: "com.android.car.rotaryplayground.googlecaruiportrait.rro",
+ target_package_name: "com.android.car.rotaryplayground",
+}
+
+override_runtime_resource_overlay {
+ name: "generated_caruiportrait_customization-com-android-car-themeplayground",
+ base: "GoogleCarUiPortraitBase",
+ package_name: "com.android.car.themeplayground.googlecaruiportrait.rro",
+ target_package_name: "com.android.car.themeplayground",
+}
+
+override_runtime_resource_overlay {
+ name: "generated_caruiportrait_customization-com-android-car-carlauncher",
+ base: "GoogleCarUiPortraitBase",
+ package_name: "com.android.car.carlauncher.googlecaruiportrait.rro",
+ target_package_name: "com.android.car.carlauncher",
+}
+
+override_runtime_resource_overlay {
+ name: "generated_caruiportrait_customization-com-android-car-home",
+ base: "GoogleCarUiPortraitBase",
+ package_name: "com.android.car.home.googlecaruiportrait.rro",
+ target_package_name: "com.android.car.home",
+}
+
+override_runtime_resource_overlay {
+ name: "generated_caruiportrait_customization-com-android-car-media",
+ base: "GoogleCarUiPortraitBase",
+ package_name: "com.android.car.media.googlecaruiportrait.rro",
+ target_package_name: "com.android.car.media",
+}
+
+override_runtime_resource_overlay {
+ name: "generated_caruiportrait_customization-com-android-car-messenger",
+ base: "GoogleCarUiPortraitBase",
+ package_name: "com.android.car.messenger.googlecaruiportrait.rro",
+ target_package_name: "com.android.car.messenger",
+}
+
+override_runtime_resource_overlay {
+ name: "generated_caruiportrait_customization-com-android-car-radio",
+ base: "GoogleCarUiPortraitBase",
+ package_name: "com.android.car.radio.googlecaruiportrait.rro",
+ target_package_name: "com.android.car.radio",
+}
+
+override_runtime_resource_overlay {
+ name: "generated_caruiportrait_customization-com-android-car-calendar",
+ base: "GoogleCarUiPortraitBase",
+ package_name: "com.android.car.calendar.googlecaruiportrait.rro",
+ target_package_name: "com.android.car.calendar",
+}
+
+override_runtime_resource_overlay {
+ name: "generated_caruiportrait_customization-com-android-car-systemupdater",
+ base: "GoogleCarUiPortraitBase",
+ package_name: "com.android.car.systemupdater.googlecaruiportrait.rro",
+ target_package_name: "com.android.car.systemupdater",
+}
+
+override_runtime_resource_overlay {
+ name: "generated_caruiportrait_customization-com-android-car-dialer",
+ base: "GoogleCarUiPortraitBase",
+ package_name: "com.android.car.dialer.googlecaruiportrait.rro",
+ target_package_name: "com.android.car.dialer",
+}
+
+override_runtime_resource_overlay {
+ name: "generated_caruiportrait_customization-com-android-car-linkviewer",
+ base: "GoogleCarUiPortraitBase",
+ package_name: "com.android.car.linkviewer.googlecaruiportrait.rro",
+ target_package_name: "com.android.car.linkviewer",
+}
+
+override_runtime_resource_overlay {
+ name: "generated_caruiportrait_customization-com-android-car-settings",
+ base: "GoogleCarUiPortraitBase",
+ package_name: "com.android.car.settings.googlecaruiportrait.rro",
+ target_package_name: "com.android.car.settings",
+}
+
+override_runtime_resource_overlay {
+ name: "generated_caruiportrait_customization-com-android-car-voicecontrol",
+ base: "GoogleCarUiPortraitBase",
+ package_name: "com.android.car.voicecontrol.googlecaruiportrait.rro",
+ target_package_name: "com.android.car.voicecontrol",
+}
+
+override_runtime_resource_overlay {
+ name: "generated_caruiportrait_customization-com-android-car-faceenroll",
+ base: "GoogleCarUiPortraitBase",
+ package_name: "com.android.car.faceenroll.googlecaruiportrait.rro",
+ target_package_name: "com.android.car.faceenroll",
+}
+
+override_runtime_resource_overlay {
+ name: "generated_caruiportrait_customization-com-android-car-developeroptions",
+ base: "GoogleCarUiPortraitBase",
+ package_name: "com.android.car.developeroptions.googlecaruiportrait.rro",
+ target_package_name: "com.android.car.developeroptions",
+}
+
+override_runtime_resource_overlay {
+ name: "generated_caruiportrait_customization-com-android-managedprovisioning",
+ base: "GoogleCarUiPortraitBase",
+ package_name: "com.android.managedprovisioning.googlecaruiportrait.rro",
+ target_package_name: "com.android.managedprovisioning",
+}
+
+override_runtime_resource_overlay {
+ name: "generated_caruiportrait_customization-com-android-settings-intelligence",
+ base: "GoogleCarUiPortraitBase",
+ package_name: "com.android.settings.intelligence.googlecaruiportrait.rro",
+ target_package_name: "com.android.settings.intelligence",
+}
+
+override_runtime_resource_overlay {
+ name: "generated_caruiportrait_customization-com-google-android-apps-automotive-inputmethod",
+ base: "GoogleCarUiPortraitBase",
+ package_name: "com.google.android.apps.automotive.inputmethod.googlecaruiportrait.rro",
+ target_package_name: "com.google.android.apps.automotive.inputmethod",
+}
+
+override_runtime_resource_overlay {
+ name: "generated_caruiportrait_customization-com-google-android-apps-automotive-inputmethod-dev",
+ base: "GoogleCarUiPortraitBase",
+ package_name: "com.google.android.apps.automotive.inputmethod.dev.googlecaruiportrait.rro",
+ target_package_name: "com.google.android.apps.automotive.inputmethod.dev",
+}
+
+override_runtime_resource_overlay {
+ name: "generated_caruiportrait_customization-com-google-android-apps-automotive-templates-host",
+ base: "GoogleCarUiPortraitBase",
+ package_name: "com.google.android.apps.automotive.templates.host.googlecaruiportrait.rro",
+ target_package_name: "com.google.android.apps.automotive.templates.host",
+}
+
+override_runtime_resource_overlay {
+ name: "generated_caruiportrait_customization-com-google-android-companiondevicesupport",
+ base: "GoogleCarUiPortraitBase",
+ package_name: "com.google.android.companiondevicesupport.googlecaruiportrait.rro",
+ target_package_name: "com.google.android.companiondevicesupport",
+}
+
+override_runtime_resource_overlay {
+ name: "generated_caruiportrait_customization-com-google-android-embedded-projection",
+ base: "GoogleCarUiPortraitBase",
+ package_name: "com.google.android.embedded.projection.googlecaruiportrait.rro",
+ target_package_name: "com.google.android.embedded.projection",
+}
+
+override_runtime_resource_overlay {
+ name: "generated_caruiportrait_customization-com-google-android-gms",
+ base: "GoogleCarUiPortraitBase",
+ package_name: "com.google.android.gms.googlecaruiportrait.rro",
+ target_package_name: "com.google.android.gms",
+}
+
+override_runtime_resource_overlay {
+ name: "generated_caruiportrait_customization-com-google-android-gsf",
+ base: "GoogleCarUiPortraitBase",
+ package_name: "com.google.android.gsf.googlecaruiportrait.rro",
+ target_package_name: "com.google.android.gsf",
+}
+
+override_runtime_resource_overlay {
+ name: "generated_caruiportrait_customization-com-google-android-packageinstaller",
+ base: "GoogleCarUiPortraitBase",
+ package_name: "com.google.android.packageinstaller.googlecaruiportrait.rro",
+ target_package_name: "com.google.android.packageinstaller",
+}
+
+override_runtime_resource_overlay {
+ name: "generated_caruiportrait_customization-com-google-android-permissioncontroller",
+ base: "GoogleCarUiPortraitBase",
+ package_name: "com.google.android.permissioncontroller.googlecaruiportrait.rro",
+ target_package_name: "com.google.android.permissioncontroller",
+}
+
+override_runtime_resource_overlay {
+ name: "generated_caruiportrait_customization-com-google-android-carassistant",
+ base: "GoogleCarUiPortraitBase",
+ package_name: "com.google.android.carassistant.googlecaruiportrait.rro",
+ target_package_name: "com.google.android.carassistant",
+}
+
+override_runtime_resource_overlay {
+ name: "generated_caruiportrait_customization-com-google-android-tts",
+ base: "GoogleCarUiPortraitBase",
+ package_name: "com.google.android.tts.googlecaruiportrait.rro",
+ target_package_name: "com.google.android.tts",
+}
+
+override_runtime_resource_overlay {
+ name: "generated_caruiportrait_customization-com-android-htmlviewer",
+ base: "GoogleCarUiPortraitBase",
+ package_name: "com.android.htmlviewer.googlecaruiportrait.rro",
+ target_package_name: "com.android.htmlviewer",
+}
+
+override_runtime_resource_overlay {
+ name: "generated_caruiportrait_customization-com-android-vending",
+ base: "GoogleCarUiPortraitBase",
+ package_name: "com.android.vending.googlecaruiportrait.rro",
+ target_package_name: "com.android.vending",
+}
diff --git a/car_product/car_ui_portrait/rro/car-ui-customizations/Android.mk b/car_product/car_ui_portrait/rro/car-ui-customizations/Android.mk
deleted file mode 100644
index ed4c5f8bba..0000000000
--- a/car_product/car_ui_portrait/rro/car-ui-customizations/Android.mk
+++ /dev/null
@@ -1,56 +0,0 @@
-#
-# 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.
-#
-
-LOCAL_PATH := $(call my-dir)
-include $(CLEAR_VARS)
-
-CAR_UI_RRO_SET_NAME := generated_caruiportrait_customization
-CAR_UI_RRO_MANIFEST_FILE := $(LOCAL_PATH)/AndroidManifest.xml
-CAR_UI_RESOURCE_DIR := $(LOCAL_PATH)/res
-CAR_UI_RRO_TARGETS := \
- com.android.car.ui.paintbooth \
- com.google.android.car.ui.paintbooth \
- com.google.android.carui.ats \
- com.android.car.rotaryplayground \
- com.android.car.themeplayground \
- com.android.car.carlauncher \
- com.android.car.home \
- com.android.car.media \
- com.android.car.messenger \
- com.android.car.radio \
- com.android.car.calendar \
- com.android.car.systemupdater \
- com.android.car.dialer \
- com.android.car.linkviewer \
- com.android.car.settings \
- com.android.car.voicecontrol \
- com.android.car.faceenroll \
- com.android.car.developeroptions \
- com.android.managedprovisioning \
- com.android.settings.intelligence \
- com.google.android.apps.automotive.inputmethod \
- com.google.android.apps.automotive.inputmethod.dev \
- com.google.android.apps.automotive.templates.host \
- com.google.android.companiondevicesupport \
- com.google.android.embedded.projection \
- com.google.android.gms \
- com.google.android.gsf \
- com.google.android.packageinstaller \
- com.google.android.permissioncontroller \
- com.google.android.carassistant \
- com.google.android.tts \
- com.android.htmlviewer \
- com.android.vending \ \ No newline at end of file
diff --git a/car_product/car_ui_portrait/rro/car-ui-customizations/AndroidManifest.xml b/car_product/car_ui_portrait/rro/car-ui-customizations/AndroidManifest.xml
index 9d3a1a4d2c..68828d5ac0 100644
--- a/car_product/car_ui_portrait/rro/car-ui-customizations/AndroidManifest.xml
+++ b/car_product/car_ui_portrait/rro/car-ui-customizations/AndroidManifest.xml
@@ -16,13 +16,13 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="{{RRO_PACKAGE_NAME}}">
+ package="will.be.replaced">
<application android:hasCode="false"/>
<overlay android:priority="10"
android:targetName="car-ui-lib"
- android:targetPackage="{{TARGET_PACKAGE_NAME}}"
+ android:targetPackage="will.be.replaced.too"
android:resourcesMap="@xml/overlays"
android:isStatic="true"
- android:requiredSystemPropertyName="ro.build.characteristics"
- android:requiredSystemPropertyValue="automotive"/>
-</manifest>
+ android:requiredSystemPropertyName="ro.build.car_ui_rros_enabled"
+ android:requiredSystemPropertyValue="true"/>
+</manifest> \ No newline at end of file
diff --git a/car_product/car_ui_portrait/rro/car-ui-customizations/product.mk b/car_product/car_ui_portrait/rro/car-ui-customizations/product.mk
index e7d41ec864..464a2b2f5d 100644
--- a/car_product/car_ui_portrait/rro/car-ui-customizations/product.mk
+++ b/car_product/car_ui_portrait/rro/car-ui-customizations/product.mk
@@ -50,3 +50,7 @@ PRODUCT_PACKAGES += \
generated_caruiportrait_customization-com-google-android-tts \
generated_caruiportrait_customization-com-android-htmlviewer \
generated_caruiportrait_customization-com-android-vending
+
+# This system property is used to enable the RROs on startup via
+# the requiredSystemPropertyName/Value attributes in the manifest
+PRODUCT_PRODUCT_PROPERTIES += ro.build.car_ui_rros_enabled=true
diff --git a/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/Android.bp b/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/Android.bp
new file mode 100644
index 0000000000..4a371d7fa1
--- /dev/null
+++ b/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/Android.bp
@@ -0,0 +1,37 @@
+// Copyright (C) 2022 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"],
+}
+
+runtime_resource_overlay {
+ name: "GoogleCarUiPortraitToolbarBase",
+ manifest: "AndroidManifest.xml",
+ resource_dirs: ["res"],
+}
+
+override_runtime_resource_overlay {
+ name: "generated_caruiportrait_toolbar-com-android-car-media",
+ base: "GoogleCarUiPortraitToolbarBase",
+ package_name: "com.android.car.media.googlecaruiportrait.toolbar.rro",
+ target_package_name: "com.android.car.media",
+}
+
+override_runtime_resource_overlay {
+ name: "generated_caruiportrait_toolbar-com-android-car-dialer",
+ base: "GoogleCarUiPortraitToolbarBase",
+ package_name: "com.android.car.dialer.googlecaruiportrait.toolbar.rro",
+ target_package_name: "com.android.car.dialer",
+}
diff --git a/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/Android.mk b/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/Android.mk
deleted file mode 100644
index bec7c9e4d3..0000000000
--- a/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/Android.mk
+++ /dev/null
@@ -1,26 +0,0 @@
-#
-# 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.
-#
-
-LOCAL_PATH := $(call my-dir)
-include $(CLEAR_VARS)
-
-CAR_UI_RRO_SET_NAME := generated_caruiportrait_toolbar
-CAR_UI_RRO_MANIFEST_FILE := $(LOCAL_PATH)/AndroidManifest.xml
-CAR_UI_RESOURCE_DIR := $(LOCAL_PATH)/res
-
-CAR_UI_RRO_TARGETS := \
- com.android.car.media \
- com.android.car.dialer \ \ No newline at end of file
diff --git a/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/AndroidManifest.xml b/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/AndroidManifest.xml
index c56657aca1..d17c0d00fc 100644
--- a/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/AndroidManifest.xml
+++ b/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/AndroidManifest.xml
@@ -16,14 +16,14 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="{{RRO_PACKAGE_NAME}}">
+ package="will.be.replaced">
<application android:hasCode="false"/>
<!-- priority should be greater than car-ui-customization -->
<overlay android:priority="11"
android:targetName="car-ui-lib"
- android:targetPackage="{{TARGET_PACKAGE_NAME}}"
+ android:targetPackage="will.be.replaced.too"
android:resourcesMap="@xml/overlays"
android:isStatic="true"
- android:requiredSystemPropertyName="ro.build.characteristics"
- android:requiredSystemPropertyValue="automotive"/>
+ android:requiredSystemPropertyName="ro.build.car_ui_rros_enabled"
+ android:requiredSystemPropertyValue="true"/>
</manifest>
diff --git a/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/product.mk b/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/product.mk
index 7b7cccd713..13723d0d73 100644
--- a/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/product.mk
+++ b/car_product/car_ui_portrait/rro/car-ui-toolbar-customizations/product.mk
@@ -19,3 +19,7 @@
PRODUCT_PACKAGES += \
generated_caruiportrait_toolbar-com-android-car-media \
generated_caruiportrait_toolbar-com-android-car-dialer \
+
+# This system property is used to enable the RROs on startup via
+# the requiredSystemPropertyName/Value attributes in the manifest
+PRODUCT_PRODUCT_PROPERTIES += ro.build.car_ui_rros_enabled=true
diff --git a/car_product/overlay-visual/frameworks/base/core/res/res/color/car_selection_controls_color.xml b/car_product/overlay-visual/frameworks/base/core/res/res/color/car_selection_controls_color.xml
index 540ac3e249..d417c272af 100644
--- a/car_product/overlay-visual/frameworks/base/core/res/res/color/car_selection_controls_color.xml
+++ b/car_product/overlay-visual/frameworks/base/core/res/res/color/car_selection_controls_color.xml
@@ -14,7 +14,6 @@
limitations under the License.
-->
-
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_checked="true" android:state_enabled="true"
android:color="?android:attr/colorControlActivated"/>
diff --git a/car_product/overlay-visual/frameworks/base/core/res/res/drawable/car_checkbox.xml b/car_product/overlay-visual/frameworks/base/core/res/res/drawable/car_checkbox.xml
index e6d6905123..ff520d56d4 100644
--- a/car_product/overlay-visual/frameworks/base/core/res/res/drawable/car_checkbox.xml
+++ b/car_product/overlay-visual/frameworks/base/core/res/res/drawable/car_checkbox.xml
@@ -14,11 +14,12 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
+
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:width="@dimen/car_checkbox_size"
android:height="@dimen/car_checkbox_size"
- android:drawable="@*android:drawable/btn_check_material_anim"/>
+ android:drawable="@drawable/car_checkbox_material_anim"/>
<item
android:width="@dimen/car_checkbox_focus_ring_size"
android:height="@dimen/car_checkbox_focus_ring_size"
diff --git a/car_product/overlay-visual/frameworks/base/core/res/res/drawable/car_checkbox_checked_mtrl.xml b/car_product/overlay-visual/frameworks/base/core/res/res/drawable/car_checkbox_checked_mtrl.xml
new file mode 100644
index 0000000000..05b103bf96
--- /dev/null
+++ b/car_product/overlay-visual/frameworks/base/core/res/res/drawable/car_checkbox_checked_mtrl.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:name="btn_checkbox_checked"
+ android:width="32dp"
+ android:viewportWidth="48"
+ android:height="32dp"
+ android:viewportHeight="48"
+ android:tint="@color/car_selection_controls_color">
+ <group
+ android:name="icon_null"
+ android:translateX="24"
+ android:translateY="24"
+ android:scaleX="0.2"
+ android:scaleY="0.2">
+ <group
+ android:name="check"
+ android:scaleX="7.5"
+ android:scaleY="7.5">
+ <path
+ android:name="check_path_merged"
+ android:pathData="M 7.0,-9.0 c 0.0,0.0 -14.0,0.0 -14.0,0.0 c -1.1044921875,0.0 -2.0,0.8955078125 -2.0,2.0 c 0.0,0.0 0.0,14.0 0.0,14.0 c 0.0,1.1044921875 0.8955078125,2.0 2.0,2.0 c 0.0,0.0 14.0,0.0 14.0,0.0 c 1.1044921875,0.0 2.0,-0.8955078125 2.0,-2.0 c 0.0,0.0 0.0,-14.0 0.0,-14.0 c 0.0,-1.1044921875 -0.8955078125,-2.0 -2.0,-2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z M -2.0,5.00001525879 c 0.0,0.0 -5.0,-5.00001525879 -5.0,-5.00001525879 c 0.0,0.0 1.41409301758,-1.41409301758 1.41409301758,-1.41409301758 c 0.0,0.0 3.58590698242,3.58601379395 3.58590698242,3.58601379395 c 0.0,0.0 7.58590698242,-7.58601379395 7.58590698242,-7.58601379395 c 0.0,0.0 1.41409301758,1.41409301758 1.41409301758,1.41409301758 c 0.0,0.0 -9.0,9.00001525879 -9.0,9.00001525879 Z"
+ android:fillColor="#FF000000" />
+ </group>
+ <group
+ android:name="box_dilate"
+ android:scaleX="7.5"
+ android:scaleY="7.5">
+ <path
+ android:name="box_inner_merged"
+ android:pathData="M 0.0,-1.0 l 0.0,0.0 c 0.5522847498,0.0 1.0,0.4477152502 1.0,1.0 l 0.0,0.0 c 0.0,0.5522847498 -0.4477152502,1.0 -1.0,1.0 l 0.0,0.0 c -0.5522847498,0.0 -1.0,-0.4477152502 -1.0,-1.0 l 0.0,0.0 c 0.0,-0.5522847498 0.4477152502,-1.0 1.0,-1.0 Z M 7.0,-9.0 c 0.0,0.0 -14.0,0.0 -14.0,0.0 c -1.1044921875,0.0 -2.0,0.8955078125 -2.0,2.0 c 0.0,0.0 0.0,14.0 0.0,14.0 c 0.0,1.1044921875 0.8955078125,2.0 2.0,2.0 c 0.0,0.0 14.0,0.0 14.0,0.0 c 1.1044921875,0.0 2.0,-0.8955078125 2.0,-2.0 c 0.0,0.0 0.0,-14.0 0.0,-14.0 c 0.0,-1.1044921875 -0.8955078125,-2.0 -2.0,-2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:fillColor="#FF000000"
+ android:fillAlpha="0" />
+ </group>
+ </group>
+</vector>
diff --git a/car_product/overlay-visual/frameworks/base/core/res/res/drawable/car_checkbox_checked_to_unchecked_mtrl_animation.xml b/car_product/overlay-visual/frameworks/base/core/res/res/drawable/car_checkbox_checked_to_unchecked_mtrl_animation.xml
new file mode 100644
index 0000000000..033661fdf7
--- /dev/null
+++ b/car_product/overlay-visual/frameworks/base/core/res/res/drawable/car_checkbox_checked_to_unchecked_mtrl_animation.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 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.
+-->
+
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:drawable="@drawable/car_checkbox_checked_mtrl">
+ <target
+ android:name="icon_null"
+ android:animation="@*android:anim/btn_checkbox_to_unchecked_icon_null_animation" />
+ <target
+ android:name="check_path_merged"
+ android:animation="@*android:anim/btn_checkbox_to_unchecked_check_path_merged_animation" />
+ <target
+ android:name="box_inner_merged"
+ android:animation="@*android:anim/btn_checkbox_to_unchecked_box_inner_merged_animation" />
+</animated-vector>
diff --git a/car_product/overlay-visual/frameworks/base/core/res/res/drawable/car_checkbox_material_anim.xml b/car_product/overlay-visual/frameworks/base/core/res/res/drawable/car_checkbox_material_anim.xml
new file mode 100644
index 0000000000..32ad5ff046
--- /dev/null
+++ b/car_product/overlay-visual/frameworks/base/core/res/res/drawable/car_checkbox_material_anim.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 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.
+-->
+
+<animated-selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:id="@+id/checked"
+ android:state_checked="true"
+ android:drawable="@drawable/car_checkbox_checked_mtrl" />
+ <item
+ android:id="@+id/unchecked"
+ android:drawable="@drawable/car_checkbox_unchecked_mtrl" />
+ <transition
+ android:fromId="@+id/unchecked"
+ android:toId="@+id/checked"
+ android:drawable="@drawable/car_checkbox_unchecked_to_checked_mtrl_animation" />
+ <transition
+ android:fromId="@+id/checked"
+ android:toId="@+id/unchecked"
+ android:drawable="@drawable/car_checkbox_checked_to_unchecked_mtrl_animation" />
+</animated-selector>
diff --git a/car_product/overlay-visual/frameworks/base/core/res/res/drawable/car_checkbox_unchecked_mtrl.xml b/car_product/overlay-visual/frameworks/base/core/res/res/drawable/car_checkbox_unchecked_mtrl.xml
new file mode 100644
index 0000000000..345f0bb5c2
--- /dev/null
+++ b/car_product/overlay-visual/frameworks/base/core/res/res/drawable/car_checkbox_unchecked_mtrl.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:name="btn_checkbox_unchecked"
+ android:width="32dp"
+ android:viewportWidth="48"
+ android:height="32dp"
+ android:viewportHeight="48"
+ android:tint="@color/car_selection_controls_color">
+ <group
+ android:name="icon_null"
+ android:translateX="24"
+ android:translateY="24"
+ android:scaleX="0.2"
+ android:scaleY="0.2">
+ <group
+ android:name="check"
+ android:scaleX="7.5"
+ android:scaleY="7.5">
+ <path
+ android:name="box_outer_merged"
+ android:pathData="M 7.0,-9.0 c 0.0,0.0 -14.0,0.0 -14.0,0.0 c -1.1044921875,0.0 -2.0,0.8955078125 -2.0,2.0 c 0.0,0.0 0.0,14.0 0.0,14.0 c 0.0,1.1044921875 0.8955078125,2.0 2.0,2.0 c 0.0,0.0 14.0,0.0 14.0,0.0 c 1.1044921875,0.0 2.0,-0.8955078125 2.0,-2.0 c 0.0,0.0 0.0,-14.0 0.0,-14.0 c 0.0,-1.1044921875 -0.8955078125,-2.0 -2.0,-2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z M -2.0,5.00001525879 c 0.0,0.0 -1.4234161377,-1.40159606934 -1.4234161377,-1.40159606934 c 0.0,0.0 1.41409301758,-1.41409301758 1.41409301758,-1.41409301758 c 0.0,0.0 0.00932312011719,-0.0124053955078 0.00932312011719,-0.0124053955078 c 0.0,0.0 0.0234069824219,-0.0235137939453 0.0234069824219,-0.0235137939453 c 0.0,0.0 1.41409301758,1.41409301758 1.41409301758,1.41409301758 c 0.0,0.0 -1.4375,1.43751525879 -1.4375,1.43751525879 Z"
+ android:fillColor="#FF000000"
+ android:fillAlpha="0" />
+ </group>
+ <group
+ android:name="box_dilate"
+ android:scaleX="7.5"
+ android:scaleY="7.5">
+ <path
+ android:name="box_inner_merged"
+ android:pathData="M -7.0,-7.0 l 14.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,14.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l -14.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-14.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z M 7.0,-9.0 c 0.0,0.0 -14.0,0.0 -14.0,0.0 c -1.1044921875,0.0 -2.0,0.8955078125 -2.0,2.0 c 0.0,0.0 0.0,14.0 0.0,14.0 c 0.0,1.1044921875 0.8955078125,2.0 2.0,2.0 c 0.0,0.0 14.0,0.0 14.0,0.0 c 1.1044921875,0.0 2.0,-0.8955078125 2.0,-2.0 c 0.0,0.0 0.0,-14.0 0.0,-14.0 c 0.0,-1.1044921875 -0.8955078125,-2.0 -2.0,-2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:fillColor="#FF000000" />
+ </group>
+ </group>
+</vector>
diff --git a/car_product/overlay-visual/frameworks/base/core/res/res/drawable/car_checkbox_unchecked_to_checked_mtrl_animation.xml b/car_product/overlay-visual/frameworks/base/core/res/res/drawable/car_checkbox_unchecked_to_checked_mtrl_animation.xml
new file mode 100644
index 0000000000..d191421a1b
--- /dev/null
+++ b/car_product/overlay-visual/frameworks/base/core/res/res/drawable/car_checkbox_unchecked_to_checked_mtrl_animation.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 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.
+-->
+
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:drawable="@drawable/car_checkbox_unchecked_mtrl">
+ <target
+ android:name="icon_null"
+ android:animation="@*android:anim/btn_checkbox_to_checked_icon_null_animation" />
+ <target
+ android:name="box_outer_merged"
+ android:animation="@*android:anim/btn_checkbox_to_checked_box_outer_merged_animation" />
+ <target
+ android:name="box_inner_merged"
+ android:animation="@*android:anim/btn_checkbox_to_checked_box_inner_merged_animation" />
+</animated-vector>
diff --git a/car_product/overlay-visual/frameworks/base/core/res/res/values/styles_device_default.xml b/car_product/overlay-visual/frameworks/base/core/res/res/values/styles_device_default.xml
index 831b3add3e..8c4ce1ce03 100644
--- a/car_product/overlay-visual/frameworks/base/core/res/res/values/styles_device_default.xml
+++ b/car_product/overlay-visual/frameworks/base/core/res/res/values/styles_device_default.xml
@@ -104,7 +104,6 @@
<style name="Widget.DeviceDefault.CompoundButton.CheckBox" parent="android:Widget.Material.CompoundButton.CheckBox">
<item name="android:button">@drawable/car_checkbox</item>
- <item name="android:buttonTint">@color/car_selection_controls_color</item>
</style>
<style name="Widget.DeviceDefault.CompoundButton.RadioButton"
diff --git a/car_product/properties/bluetooth.prop b/car_product/properties/bluetooth.prop
index 7d7b68a63a..76efe6afed 100644
--- a/car_product/properties/bluetooth.prop
+++ b/car_product/properties/bluetooth.prop
@@ -1,3 +1,12 @@
+# The default Bluetooth Class of Device
+# Service Field: 0x26 -> 38
+# - Bit 17: Networking
+# - Bit 18: Rendering
+# - Bit 21: Audio
+# Major Class: 0x04 -> 4 (Audio / Video)
+# Minor Class: 0x08 -> 8 (Car Audio)
+bluetooth.device.class_of_device=38,4,8
+
# The Bluetooth profiles that cars expect to have enabled. All other profiles
# are disabled by default.
bluetooth.profile.a2dp.sink.enabled=true
diff --git a/car_product/sepolicy/private/carservice_app.te b/car_product/sepolicy/private/carservice_app.te
index 6ec44346c0..f14bb2999c 100644
--- a/car_product/sepolicy/private/carservice_app.te
+++ b/car_product/sepolicy/private/carservice_app.te
@@ -113,6 +113,9 @@ binder_call(carservice_app, gpuservice)
# Allow reading and writing /proc/loadavg/
allow carservice_app proc_loadavg:file { open read getattr };
+# Allow reading /proc/meminfo/ for telemetry
+allow carservice_app proc_meminfo:file { open read getattr };
+
# Allow finding game_service and content_capture_service
allow carservice_app game_service:service_manager find;
allow carservice_app content_capture_service:service_manager find;
diff --git a/cpp/evs/manager/aidl/utils/src/Utils.cpp b/cpp/evs/manager/aidl/utils/src/Utils.cpp
index 99a3ec1def..1394c5898a 100644
--- a/cpp/evs/manager/aidl/utils/src/Utils.cpp
+++ b/cpp/evs/manager/aidl/utils/src/Utils.cpp
@@ -86,6 +86,12 @@ hidlevs::V1_0::DisplayState Utils::makeToHidl(DisplayState aidlState) {
HardwareBuffer Utils::makeHwBufferFromHidlBuffer(const hidlevs::V1_0::BufferDesc& hidlBuffer,
bool doDup) {
+ buffer_handle_t h = hidlBuffer.memHandle.getNativeHandle();
+ if (h == nullptr) {
+ LOG(WARNING) << "Buffer " << hidlBuffer.bufferId << " has an invalid native handle.";
+ return {};
+ }
+
HardwareBuffer hwBuffer = {
.description =
{
@@ -96,8 +102,7 @@ HardwareBuffer Utils::makeHwBufferFromHidlBuffer(const hidlevs::V1_0::BufferDesc
.usage = static_cast<BufferUsage>(hidlBuffer.usage),
.stride = static_cast<int>(hidlBuffer.stride),
},
- .handle = doDup ? ::android::dupToAidl(hidlBuffer.memHandle.getNativeHandle())
- : ::android::makeToAidl(hidlBuffer.memHandle.getNativeHandle()),
+ .handle = doDup ? ::android::dupToAidl(h) : ::android::makeToAidl(h),
};
return std::move(hwBuffer);
@@ -120,12 +125,16 @@ HardwareBufferDescription Utils::makeFromHidl(const HIDLHardwareBuffer& hidlBuff
HardwareBuffer Utils::makeHwBufferFromHidlBuffer(const hidlevs::V1_1::BufferDesc& hidlBuffer,
bool doDup) {
+ buffer_handle_t h = hidlBuffer.buffer.nativeHandle.getNativeHandle();
+ if (h == nullptr) {
+ LOG(WARNING) << "Buffer " << hidlBuffer.bufferId << " has an invalid native handle.";
+ return {};
+ }
+
HardwareBuffer hwBuffer = {
.description = makeFromHidl(hidlBuffer.buffer),
- .handle = doDup ? std::move(::android::dupToAidl(
- hidlBuffer.buffer.nativeHandle.getNativeHandle()))
- : std::move(::android::makeToAidl(
- hidlBuffer.buffer.nativeHandle.getNativeHandle())),
+ .handle = doDup ? std::move(::android::dupToAidl(h))
+ : std::move(::android::makeToAidl(h)),
};
return std::move(hwBuffer);
diff --git a/cpp/evs/manager/aidl/wrappers/src/HidlCamera.cpp b/cpp/evs/manager/aidl/wrappers/src/HidlCamera.cpp
index 3f7fe531d6..a86bdd6a2a 100644
--- a/cpp/evs/manager/aidl/wrappers/src/HidlCamera.cpp
+++ b/cpp/evs/manager/aidl/wrappers/src/HidlCamera.cpp
@@ -179,7 +179,13 @@ Return<hidlevs::V1_0::EvsResult> HidlCamera::doneWithFrame_1_1(
Return<hidlevs::V1_0::EvsResult> HidlCamera::setMaster() {
if (auto status = mAidlCamera->setPrimaryClient(); !status.isOk()) {
- return Utils::makeToHidl(static_cast<EvsResult>(status.getServiceSpecificError()));
+ EvsResult err = static_cast<EvsResult>(status.getServiceSpecificError());
+ if (err == EvsResult::PERMISSION_DENIED) {
+ // HIDL EvsManager implementations return EvsResult::OWNERSHIP_LOST
+ // if the primary client exists already.
+ err = EvsResult::OWNERSHIP_LOST;
+ }
+ return Utils::makeToHidl(err);
}
return hidlevs::V1_0::EvsResult::OK;
@@ -239,8 +245,13 @@ Return<void> HidlCamera::setIntParameter(hidlevs::V1_1::CameraParam id, int32_t
std::vector<int32_t> aidlValues;
auto status = mAidlCamera->setIntParameter(Utils::makeFromHidl(id), value, &aidlValues);
if (!status.isOk()) {
- _hidl_cb(Utils::makeToHidl(static_cast<EvsResult>(status.getServiceSpecificError())),
- {value});
+ EvsResult err = static_cast<EvsResult>(status.getServiceSpecificError());
+ if (err == EvsResult::PERMISSION_DENIED) {
+ // HIDL EvsManager implementations return EvsResult::INVALID_ARG if
+ // the client is not permitted to change parameters.
+ err = EvsResult::INVALID_ARG;
+ }
+ _hidl_cb(Utils::makeToHidl(err), {value});
return {};
}
diff --git a/cpp/powerpolicy/server/src/CarPowerPolicyServer.cpp b/cpp/powerpolicy/server/src/CarPowerPolicyServer.cpp
index 1443e6018d..049b68cfc3 100644
--- a/cpp/powerpolicy/server/src/CarPowerPolicyServer.cpp
+++ b/cpp/powerpolicy/server/src/CarPowerPolicyServer.cpp
@@ -241,8 +241,6 @@ CarPowerPolicyServer::CarPowerPolicyServer() :
mMessageHandler = new MessageHandlerImpl(this);
mDeathRecipient = ScopedAIBinder_DeathRecipient(
AIBinder_DeathRecipient_new(&CarPowerPolicyServer::onBinderDied));
- AIBinder_DeathRecipient_setOnUnlinked(mDeathRecipient.get(),
- &CarPowerPolicyServer::onBinderUnlinked);
mPropertyChangeListener = std::make_unique<PropertyChangeListener>(this);
mLinkUnlinkImpl = std::make_unique<AIBinderLinkUnlinkImpl>();
}
@@ -332,8 +330,11 @@ ScopedAStatus CarPowerPolicyServer::unregisterPowerPolicyChangeCallback(
return ScopedAStatus::fromServiceSpecificErrorWithMessage(EX_ILLEGAL_ARGUMENT, errorCause);
}
if (mOnBinderDiedContexts.find(clientId) != mOnBinderDiedContexts.end()) {
+ // We don't set a callback for unlinkToDeath but need to call unlinkToDeath to clean up the
+ // registered death recipient.
mLinkUnlinkImpl->unlinkToDeath(clientId, mDeathRecipient.get(),
static_cast<void*>(mOnBinderDiedContexts[clientId].get()));
+ mOnBinderDiedContexts.erase(clientId);
}
mPolicyChangeCallbacks.erase(it);
if (DEBUG) {
@@ -512,12 +513,6 @@ void CarPowerPolicyServer::onBinderDied(void* cookie) {
context->server->handleBinderDeath(context->clientId);
}
-void CarPowerPolicyServer::onBinderUnlinked(void* cookie) {
- // Delete the context associated with this cookie.
- OnBinderDiedContext* context = reinterpret_cast<OnBinderDiedContext*>(cookie);
- context->server->handleBinderUnlinked(context->clientId);
-}
-
void CarPowerPolicyServer::handleBinderDeath(const AIBinder* clientId) {
Mutex::Autolock lock(mMutex);
auto it = lookupPowerPolicyChangeCallback(mPolicyChangeCallbacks, clientId);
@@ -525,11 +520,6 @@ void CarPowerPolicyServer::handleBinderDeath(const AIBinder* clientId) {
ALOGW("Power policy callback(pid: %d) died", it->pid);
mPolicyChangeCallbacks.erase(it);
}
-}
-
-void CarPowerPolicyServer::handleBinderUnlinked(const AIBinder* clientId) {
- Mutex::Autolock lock(mMutex);
- // This should delete context.
mOnBinderDiedContexts.erase(clientId);
}
diff --git a/cpp/powerpolicy/server/src/CarPowerPolicyServer.h b/cpp/powerpolicy/server/src/CarPowerPolicyServer.h
index b214a50f0b..d498b4e2c0 100644
--- a/cpp/powerpolicy/server/src/CarPowerPolicyServer.h
+++ b/cpp/powerpolicy/server/src/CarPowerPolicyServer.h
@@ -155,7 +155,6 @@ public:
void connectToVhalHelper();
void handleBinderDeath(const AIBinder* client);
- void handleBinderUnlinked(const AIBinder* clientId);
void handleVhalDeath();
// Implements ICarPowerPolicySystemNotification.aidl.
@@ -232,7 +231,6 @@ private:
android::base::Result<void> init(const sp<android::Looper>& looper);
static void onBinderDied(void* cookie);
- static void onBinderUnlinked(void* cookie);
static std::string callbackToString(const CallbackInfo& callback);
// For test-only.
diff --git a/cpp/powerpolicy/server/tests/CarPowerPolicyServerTest.cpp b/cpp/powerpolicy/server/tests/CarPowerPolicyServerTest.cpp
index b616ae3e8f..af433864f4 100644
--- a/cpp/powerpolicy/server/tests/CarPowerPolicyServerTest.cpp
+++ b/cpp/powerpolicy/server/tests/CarPowerPolicyServerTest.cpp
@@ -93,8 +93,6 @@ public:
void onBinderDied(void* cookie) { mServer->onBinderDied(cookie); }
- void onBinderUnlinked(void* cookie) { mServer->onBinderUnlinked(cookie); }
-
std::vector<CallbackInfo> getPolicyChangeCallbacks() {
return mServer->getPolicyChangeCallbacks();
}
@@ -208,8 +206,6 @@ TEST_F(CarPowerPolicyServerTest, TestOnBinderDied) {
ASSERT_TRUE(server->getPolicyChangeCallbacks().empty());
- server->onBinderUnlinked(cookie);
-
ASSERT_EQ(server->countOnBinderDiedContexts(), static_cast<size_t>(0));
}
diff --git a/data/etc/Android.bp b/data/etc/Android.bp
index 43141d4405..4f40c79d95 100644
--- a/data/etc/Android.bp
+++ b/data/etc/Android.bp
@@ -204,6 +204,13 @@ prebuilt_etc {
}
prebuilt_etc {
+ name: "privapp_allowlist_com.google.android.car.networking.railway",
+ sub_dir: "permissions",
+ src: "com.google.android.car.networking.railway.xml",
+ filename_from_src: true,
+}
+
+prebuilt_etc {
name: "allowed_privapp_com.android.carshell",
sub_dir: "permissions",
src: "com.android.car.shell.xml",
diff --git a/data/etc/com.android.car.ui.paintbooth.xml b/data/etc/com.android.car.ui.paintbooth.xml
index 11bf304fe8..66a0147738 100644
--- a/data/etc/com.android.car.ui.paintbooth.xml
+++ b/data/etc/com.android.car.ui.paintbooth.xml
@@ -25,5 +25,7 @@
<permission name="android.permission.MANAGE_USERS"/>
<!-- For getting list of RROs for current user-->
<permission name="android.permission.INTERACT_ACROSS_USERS"/>
+ <!-- For ThemePlayground -->
+ <permission name="android.permission.MODIFY_DAY_NIGHT_MODE"/>
</privapp-permissions>
</permissions>
diff --git a/data/etc/com.google.android.car.networking.railway.xml b/data/etc/com.google.android.car.networking.railway.xml
new file mode 100644
index 0000000000..487c0a1e6b
--- /dev/null
+++ b/data/etc/com.google.android.car.networking.railway.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 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
+ -->
+<permissions>
+ <privapp-permissions package="com.google.android.car.networking.railway">
+ <permission name="android.permission.ACCESS_NETWORK_STATE" />
+ <permission name="android.permission.CHANGE_NETWORK_STATE" />
+ <permission name="android.permission.INTERNET" />
+ </privapp-permissions>
+</permissions>
diff --git a/packages/HideCameraApps/Android.bp b/packages/HideCameraApps/Android.bp
new file mode 100644
index 0000000000..3891a2adb0
--- /dev/null
+++ b/packages/HideCameraApps/Android.bp
@@ -0,0 +1,29 @@
+// Copyright (C) 2022 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 {
+ // See: http://go/android-license-faq
+ default_applicable_licenses: [
+ "Android-Apache-2.0",
+ ],
+}
+
+android_app {
+ name: "HideCameraApps",
+ sdk_version: "current",
+ overrides: [
+ "Camera",
+ "Camera2",
+ ],
+}
diff --git a/packages/HideCameraApps/AndroidManifest.xml b/packages/HideCameraApps/AndroidManifest.xml
new file mode 100644
index 0000000000..7b6b1cdf26
--- /dev/null
+++ b/packages/HideCameraApps/AndroidManifest.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.car.hidecameraapps">
+
+ <application
+ android:allowBackup="false"
+ android:debuggable="false"
+ android:label="HideCameraApps">
+ </application>
+</manifest>
diff --git a/packages/ScriptExecutor/src/LuaEngine.cpp b/packages/ScriptExecutor/src/LuaEngine.cpp
index c743be3035..3a5daf440e 100644
--- a/packages/ScriptExecutor/src/LuaEngine.cpp
+++ b/packages/ScriptExecutor/src/LuaEngine.cpp
@@ -18,6 +18,8 @@
#include "BundleWrapper.h"
+#include <android-base/logging.h>
+
#include <sstream>
#include <string>
#include <utility>
@@ -43,15 +45,21 @@ enum LuaNumReturnedResults {
ZERO_RETURNED_RESULTS = 0,
};
+// Prefix for logging messages coming from lua script.
+const char kLuaLogTag[] = "LUA: ";
+
// TODO(b/199415783): Revisit the topic of limits to potentially move it to standalone file.
constexpr int MAX_ARRAY_SIZE = 1000;
// Helper method that goes over Lua table fields one by one and populates PersistableBundle
// object wrapped in BundleWrapper.
-// It is assumed that Lua table is located on top of the Lua stack.
+// It is assumed that Lua table is located on top of the Lua stack. There could be other
+// items in the stack, this function will not touch them.
//
// Returns false if the conversion encountered unrecoverable error.
// Otherwise, returns true for success.
+//
+// If the function succeeds, the stack should be unchanged.
// In case of an error, there is no need to pop elements or clean the stack. When Lua calls C,
// the stack used to pass data between Lua and C is private for each call. According to
// https://www.lua.org/pil/26.1.html, after C function returns back to Lua, Lua
@@ -62,8 +70,8 @@ Result<void> convertLuaTableToBundle(lua_State* lua, BundleWrapper* bundleWrappe
// lua_next call pops the key from the top of the stack and finds the next
// key-value pair. It returns 0 if the next pair was not found.
// More on lua_next in: https://www.lua.org/manual/5.3/manual.html#lua_next
- lua_pushnil(lua); // First key is a null value.
- while (lua_next(lua, /* index = */ -2) != 0) {
+ lua_pushnil(lua); // First key is a null value, at index -1
+ while (lua_next(lua, /* table index = */ -2) != 0) {
// 'key' is at index -2 and 'value' is at index -1
// -1 index is the top of the stack.
// remove 'value' and keep 'key' for next iteration
@@ -239,9 +247,11 @@ int LuaEngine::loadScript(const char* scriptBody) {
}
// Register limited set of reserved methods for Lua to call native side.
+ lua_register(mLuaState, "log", LuaEngine::scriptLog);
lua_register(mLuaState, "on_success", LuaEngine::onSuccess);
lua_register(mLuaState, "on_script_finished", LuaEngine::onScriptFinished);
lua_register(mLuaState, "on_error", LuaEngine::onError);
+ lua_register(mLuaState, "on_metrics_report", LuaEngine::onMetricsReport);
return status;
}
@@ -281,6 +291,17 @@ int LuaEngine::run() {
return status;
}
+int LuaEngine::scriptLog(lua_State* lua) {
+ const auto n = lua_gettop(lua);
+ // Loop through each argument, Lua table indices range from [1 .. N] instead of [0 .. N-1].
+ // Negative indexes are stack positions and positive indexes are argument positions.
+ for (int i = 1; i <= n; i++) {
+ const char* message = lua_tostring(lua, i);
+ LOG(INFO) << kLuaLogTag << message;
+ }
+ return ZERO_RETURNED_RESULTS;
+}
+
int LuaEngine::onSuccess(lua_State* lua) {
// Any script we run can call on_success only with a single argument of Lua table type.
if (lua_gettop(lua) != 1 || !lua_istable(lua, /* index =*/-1)) {
@@ -348,6 +369,73 @@ int LuaEngine::onError(lua_State* lua) {
return ZERO_RETURNED_RESULTS;
}
+int LuaEngine::onMetricsReport(lua_State* lua) {
+ // Any script we run can call on_metrics_report with at most 2 arguments of Lua table type.
+ if (lua_gettop(lua) > 2 || !lua_istable(lua, /* index =*/-1)) {
+ sListener->onError(ERROR_TYPE_LUA_SCRIPT_ERROR,
+ "on_metrics_report should push 1 to 2 parameters of Lua table type. "
+ "The first table is a metrics report and the second is an optional "
+ "state to save",
+ "");
+ return ZERO_RETURNED_RESULTS;
+ }
+
+ // stack with 2 items: stack with 1 item:
+ // index -1: state_to_persist index -1: report
+ // index -2: report
+ // If the stack has 2 items, top of the stack is the state.
+ // If the stack only has one item, top of the stack is the report.
+
+ // Process the top of the stack. Create helper object and populate Java PersistableBundle
+ // object.
+ BundleWrapper topBundleWrapper(sListener->getCurrentJNIEnv());
+ // If the helper function succeeds, it should not change the stack
+ const auto status = convertLuaTableToBundle(lua, &topBundleWrapper);
+ if (!status.ok()) {
+ sListener->onError(ERROR_TYPE_LUA_SCRIPT_ERROR, status.error().message().c_str(), "");
+ // We explicitly must tell Lua how many results we return, which is 0 in this case.
+ // More on the topic: https://www.lua.org/manual/5.3/manual.html#lua_CFunction
+ return ZERO_RETURNED_RESULTS;
+ }
+
+ // If the script provided 1 argument, return now
+ if (lua_gettop(lua) == 1) {
+ sListener->onMetricsReport(topBundleWrapper.getBundle(), nullptr);
+ return ZERO_RETURNED_RESULTS;
+ }
+
+ // Otherwise the script provided a report and a state
+ // pop the state_to_persist because it has already been processed in topBundleWrapper
+ lua_pop(lua, 1);
+
+ // check that the second argument is also a table
+ if (!lua_istable(lua, /* index =*/-1)) {
+ sListener->onError(ERROR_TYPE_LUA_SCRIPT_ERROR,
+ "on_metrics_report should push 1 to 2 parameters of Lua table type. "
+ "The first table is a metrics report and the second is an optional "
+ "state to save",
+ "");
+ return ZERO_RETURNED_RESULTS;
+ }
+
+ // process the report
+ BundleWrapper bottomBundleWrapper(sListener->getCurrentJNIEnv());
+ const auto statusBottom = convertLuaTableToBundle(lua, &bottomBundleWrapper);
+ if (!statusBottom.ok()) {
+ sListener->onError(ERROR_TYPE_LUA_SCRIPT_ERROR, statusBottom.error().message().c_str(), "");
+ // We explicitly must tell Lua how many results we return, which is 0 in this case.
+ // More on the topic: https://www.lua.org/manual/5.3/manual.html#lua_CFunction
+ return ZERO_RETURNED_RESULTS;
+ }
+
+ // Top of the stack = state, bottom of the stack = report
+ sListener->onMetricsReport(bottomBundleWrapper.getBundle(), topBundleWrapper.getBundle());
+
+ // We explicitly must tell Lua how many results we return, which is 0 in this case.
+ // More on the topic: https://www.lua.org/manual/5.3/manual.html#lua_CFunction
+ return ZERO_RETURNED_RESULTS;
+}
+
} // namespace scriptexecutor
} // namespace car
} // namespace android
diff --git a/packages/ScriptExecutor/src/LuaEngine.h b/packages/ScriptExecutor/src/LuaEngine.h
index e5ffd0dcef..71c6efc81d 100644
--- a/packages/ScriptExecutor/src/LuaEngine.h
+++ b/packages/ScriptExecutor/src/LuaEngine.h
@@ -61,6 +61,16 @@ public:
static void resetListener(ScriptExecutorListener* listener);
private:
+ // Invoked by a running Lua script to produce a log to logcat. This is useful for debugging.
+ // This does not invoke ScriptExecutorListener. Scripts are expected to call one of the
+ // terminating functions to end the script execution.
+ // This method returns 0 to indicate that no results were pushed to Lua stack according
+ // to Lua C function calling convention.
+ // More info: https://www.lua.org/manual/5.3/manual.html#lua_CFunction
+ // Usage in lua script:
+ // log("selected gear: ", g)
+ static int scriptLog(lua_State* lua);
+
// Invoked by a running Lua script to store intermediate results.
// The script will provide the results as a Lua table.
// We currently support only non-nested fields in the table and the fields can be the following
@@ -92,6 +102,22 @@ private:
// More info: https://www.lua.org/manual/5.3/manual.html#lua_CFunction
static int onError(lua_State* lua);
+ // Invoked by a running Lua script to produce a metrics report without completing
+ // the script's lifecycle,
+ // The metrics report will be sent to CarTelemetryService and then to the user.
+ // The script will provide the report as a Lua table.
+ // We currently support only non-nested fields in the table and the fields can be the following
+ // Lua types: boolean, number, integer, and string.
+ // The result pushed by Lua is converted to Android PersistableBundle and forwarded to
+ // ScriptExecutor service via callback interface.
+ // This method returns 0 to indicate that no results were pushed to Lua stack according
+ // to Lua C function calling convention.
+ // More info: https://www.lua.org/manual/5.3/manual.html#lua_CFunction
+ // Usage in lua script:
+ // on_metrics_report(report_as_a_table)
+ // on_metrics_report(report_as_a_table, saved_state_as_a_table)
+ static int onMetricsReport(lua_State* lua);
+
// Points to the current listener object.
// Lua cannot call non-static class methods. We need to access listener object instance in
// Lua callbacks. Therefore, callbacks callable by Lua are static class methods and the pointer
diff --git a/packages/ScriptExecutor/src/ScriptExecutorListener.cpp b/packages/ScriptExecutor/src/ScriptExecutorListener.cpp
index b9cd7c8e55..9f6f0e8ec1 100644
--- a/packages/ScriptExecutor/src/ScriptExecutorListener.cpp
+++ b/packages/ScriptExecutor/src/ScriptExecutorListener.cpp
@@ -77,6 +77,15 @@ void ScriptExecutorListener::onError(const ErrorType errorType, const char* mess
messageStringRef.get(), stackTraceRef.get());
}
+void ScriptExecutorListener::onMetricsReport(jobject reportBundle, jobject stateBundle) {
+ JNIEnv* env = getCurrentJNIEnv();
+ ScopedLocalRef<jclass> listenerClassRef(env, env->GetObjectClass(mScriptExecutorListener));
+ jmethodID onMetricsReport =
+ env->GetMethodID(listenerClassRef.get(), "onMetricsReport",
+ "(Landroid/os/PersistableBundle;Landroid/os/PersistableBundle;)V");
+ env->CallVoidMethod(mScriptExecutorListener, onMetricsReport, reportBundle, stateBundle);
+}
+
JNIEnv* ScriptExecutorListener::getCurrentJNIEnv() {
JNIEnv* env;
if (mJavaVM->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
diff --git a/packages/ScriptExecutor/src/ScriptExecutorListener.h b/packages/ScriptExecutor/src/ScriptExecutorListener.h
index 44e0e2b7c5..d914831cc2 100644
--- a/packages/ScriptExecutor/src/ScriptExecutorListener.h
+++ b/packages/ScriptExecutor/src/ScriptExecutorListener.h
@@ -28,7 +28,7 @@ namespace scriptexecutor {
// Changes in this enum must also be reflected in:
// p/s/C/service/src/com/android/car/telemetry/scriptexecutorinterface/IScriptExecutorListener.aidl
-// p/s/C/service/src/com/android/car/telemetry/proto/telemetry.proto
+// p/s/C/car-lib/src/android/car/telemetry/telemetry.proto
enum ErrorType {
/**
* Default error type.
@@ -51,6 +51,11 @@ enum ErrorType {
* inputs outside of expected range.
*/
ERROR_TYPE_LUA_SCRIPT_ERROR = 3,
+
+ /**
+ * Used to log errors for publisher failures.
+ */
+ ERROR_TYPE_PUBLISHER_FAILED = 4,
};
// Wrapper class for IScriptExecutorListener.aidl.
@@ -66,6 +71,8 @@ public:
void onError(const ErrorType errorType, const char* message, const char* stackTrace);
+ void onMetricsReport(jobject reportBundle, jobject stateBundle);
+
JNIEnv* getCurrentJNIEnv();
private:
diff --git a/packages/ScriptExecutor/tests/functional/src/com/android/car/scriptexecutortest/functional/ScriptExecutorFunctionalTest.java b/packages/ScriptExecutor/tests/functional/src/com/android/car/scriptexecutortest/functional/ScriptExecutorFunctionalTest.java
index e9c85573eb..68bc2b17d4 100644
--- a/packages/ScriptExecutor/tests/functional/src/com/android/car/scriptexecutortest/functional/ScriptExecutorFunctionalTest.java
+++ b/packages/ScriptExecutor/tests/functional/src/com/android/car/scriptexecutortest/functional/ScriptExecutorFunctionalTest.java
@@ -79,6 +79,13 @@ public final class ScriptExecutorFunctionalTest {
mResponseLatch.countDown();
}
+ @Override
+ public void onMetricsReport(PersistableBundle report, PersistableBundle stateToPersist) {
+ mFinalResult = report;
+ mInterimResult = stateToPersist;
+ mResponseLatch.countDown();
+ }
+
private boolean awaitResponse(int waitTimeSec) throws InterruptedException {
return mResponseLatch.await(waitTimeSec, TimeUnit.SECONDS);
}
@@ -894,6 +901,95 @@ public final class ScriptExecutorFunctionalTest {
.contains("is an array with values of type=boolean, which is not supported yet");
}
+ @Test
+ public void invokeScript_onMetricsReport_returnsReport() throws Exception {
+ String returnFinalResultScript =
+ "function script_completes(data, state)\n"
+ + " result = {data = state.input + 1}\n"
+ + " on_metrics_report(result)\n"
+ + "end\n";
+ PersistableBundle previousState = new PersistableBundle();
+ previousState.putInt("input", 1);
+
+ runScriptAndWaitForResponse(
+ returnFinalResultScript, "script_completes", mEmptyPublishedData, previousState);
+
+ // Expect to get back a bundle with a single key-value pair {"data": 2}
+ // because data = state.input + 1 as in the script body above.
+ assertThat(mListener.mFinalResult.size()).isEqualTo(1);
+ assertThat(mListener.mFinalResult.getLong("data")).isEqualTo(2);
+ assertThat(mListener.mInterimResult).isNull();
+ }
+
+ @Test
+ public void invokeScript_onMetricsReport_returnsReportAndState() throws Exception {
+ String returnFinalResultScript =
+ "function script_completes(data, state)\n"
+ + " result = {data = state.input + 1}\n"
+ + " on_metrics_report(result, state)\n"
+ + "end\n";
+ PersistableBundle previousState = new PersistableBundle();
+ previousState.putInt("input", 1);
+
+ runScriptAndWaitForResponse(
+ returnFinalResultScript, "script_completes", mEmptyPublishedData, previousState);
+
+ // Expect to get back a bundle with a single key-value pair {"data": 2}
+ // because data = state.input + 1 as in the script body above.
+ assertThat(mListener.mFinalResult.size()).isEqualTo(1);
+ assertThat(mListener.mFinalResult.getLong("data")).isEqualTo(2);
+ assertThat(mListener.mInterimResult.size()).isEqualTo(1);
+ assertThat(mListener.mInterimResult.getLong("input")).isEqualTo(1);
+ }
+
+ @Test
+ public void invokeScript_onMetricsReport_returnsError() throws Exception {
+ String returnFinalResultScript =
+ "function script_completes(data, state)\n"
+ + " on_metrics_report()\n"
+ + "end\n";
+
+ runScriptAndWaitForResponse(
+ returnFinalResultScript, "script_completes", mEmptyPublishedData,
+ mEmptyIterimResult);
+
+ assertThat(mListener.mErrorType)
+ .isEqualTo(IScriptExecutorListener.ERROR_TYPE_LUA_SCRIPT_ERROR);
+ assertThat(mListener.mMessage).contains("on_metrics_report should push 1 to 2 parameters");
+ }
+
+ @Test
+ public void invokeScript_onMetricsReportBadReport_returnsError() throws Exception {
+ String returnFinalResultScript =
+ "function script_completes(data, state)\n"
+ + " on_metrics_report('string argument')\n"
+ + "end\n";
+
+ runScriptAndWaitForResponse(
+ returnFinalResultScript, "script_completes", mEmptyPublishedData,
+ mEmptyIterimResult);
+
+ assertThat(mListener.mErrorType)
+ .isEqualTo(IScriptExecutorListener.ERROR_TYPE_LUA_SCRIPT_ERROR);
+ assertThat(mListener.mMessage).contains("on_metrics_report should push 1 to 2 parameters");
+ }
+
+ @Test
+ public void invokeScript_onMetricsReportBadState_returnsError() throws Exception {
+ String returnFinalResultScript =
+ "function script_completes(data, state)\n"
+ + " on_metrics_report(data, 'string argument')\n"
+ + "end\n";
+
+ runScriptAndWaitForResponse(
+ returnFinalResultScript, "script_completes", mEmptyPublishedData,
+ mEmptyIterimResult);
+
+ assertThat(mListener.mErrorType)
+ .isEqualTo(IScriptExecutorListener.ERROR_TYPE_LUA_SCRIPT_ERROR);
+ assertThat(mListener.mMessage).contains("on_metrics_report should push 1 to 2 parameters");
+ }
+
// Helper method to invoke the script and wait for it to complete and return a response.
private void runScriptAndWaitForResponse(
String script,
diff --git a/packages/ScriptExecutor/tests/nonsystemuser/src/com/android/car/scriptexecutortest/nonsystemuser/ScriptExecutorNonSystemUserTest.java b/packages/ScriptExecutor/tests/nonsystemuser/src/com/android/car/scriptexecutortest/nonsystemuser/ScriptExecutorNonSystemUserTest.java
index 6949e3eb74..b745c34516 100644
--- a/packages/ScriptExecutor/tests/nonsystemuser/src/com/android/car/scriptexecutortest/nonsystemuser/ScriptExecutorNonSystemUserTest.java
+++ b/packages/ScriptExecutor/tests/nonsystemuser/src/com/android/car/scriptexecutortest/nonsystemuser/ScriptExecutorNonSystemUserTest.java
@@ -77,6 +77,13 @@ public final class ScriptExecutorNonSystemUserTest {
mResponseLatch.countDown();
}
+ @Override
+ public void onMetricsReport(PersistableBundle report, PersistableBundle stateToPersist) {
+ mFinalResult = report;
+ mInterimResult = stateToPersist;
+ mResponseLatch.countDown();
+ }
+
private boolean awaitResponse(int waitTimeSec) throws InterruptedException {
return mResponseLatch.await(waitTimeSec, TimeUnit.SECONDS);
}
diff --git a/service/jni/evs/EvsServiceContext.cpp b/service/jni/evs/EvsServiceContext.cpp
index 071e24e589..e189e6fb4c 100644
--- a/service/jni/evs/EvsServiceContext.cpp
+++ b/service/jni/evs/EvsServiceContext.cpp
@@ -201,7 +201,7 @@ bool EvsServiceContext::openCamera(const char* id) {
}
if (isCameraOpened()) {
- if (!strcmp(id, mCameraIdInUse)) {
+ if (mCameraIdInUse == id) {
LOG(DEBUG) << "Camera " << id << " is has opened already.";
return true;
} else {
diff --git a/service/jni/evs/EvsServiceContext.h b/service/jni/evs/EvsServiceContext.h
index 4c4d6940e2..780e6f486b 100644
--- a/service/jni/evs/EvsServiceContext.h
+++ b/service/jni/evs/EvsServiceContext.h
@@ -163,7 +163,7 @@ private:
std::set<int> mBufferRecords GUARDED_BY(mLock);
// A name of the camera device currently in use.
- const char* mCameraIdInUse;
+ std::string_view mCameraIdInUse;
// List of available camera devices
std::vector<::aidl::android::hardware::automotive::evs::CameraDesc> mCameraList;
diff --git a/service/res/values-es/strings.xml b/service/res/values-es/strings.xml
index a33b5483d3..eb1bed3c28 100644
--- a/service/res/values-es/strings.xml
+++ b/service/res/values-es/strings.xml
@@ -16,7 +16,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="car_permission_label" msgid="2215078736675564541">"información sobre el coche"</string>
+ <string name="car_permission_label" msgid="2215078736675564541">"Información sobre el coche"</string>
<string name="car_permission_desc" msgid="3584369074931334964">"acceder a los datos de tu coche"</string>
<string name="car_permission_label_camera" msgid="3725702064841827180">"acceder a la cámara del coche"</string>
<string name="car_permission_desc_camera" msgid="917024932164501426">"Acceder a las cámaras del coche."</string>
diff --git a/service/res/values/config.xml b/service/res/values/config.xml
index a993fa3bc6..409fa9f3cd 100644
--- a/service/res/values/config.xml
+++ b/service/res/values/config.xml
@@ -75,6 +75,11 @@
connection process. Disable this default to implement your own policy. -->
<bool name="useDefaultBluetoothConnectionPolicy">true</bool>
+ <!-- Configuration to enable or disable the default Bluetooth Power Policy. This
+ policy determines when to enable and/or disable the Bluetooth adapter based on
+ CarPowerManager power states. Disable this default to implement your own policy. -->
+ <bool name="useDefaultBluetoothPowerPolicy">true</bool>
+
<!-- Service responsible for displaying information on the car instrument cluster. -->
<string name="instrumentClusterRendererService" translatable="false">android.car.cluster/.ClusterRenderingService</string>
diff --git a/service/res/values/overlayable.xml b/service/res/values/overlayable.xml
index 57f8ad1069..449fde29f1 100644
--- a/service/res/values/overlayable.xml
+++ b/service/res/values/overlayable.xml
@@ -15,10 +15,10 @@
These can be overridden by OEM's by using an RRO overlay app.
See CarServiceOverlay*.apk for sample overlay apps.
For Static RROs: Based on the target, define priority in this range
- Android 1 - 999
- SOC vendor 1000-1999
- Supplier 2000- 9999
- Car OEM 10000 -19999
+ Android 1 - 999
+ SOC vendor 1000 - 1999
+ Supplier 2000 - 4999
+ Car OEM 5000 - 7999
For Dynamic RROs: Use ro.android.car.carservice.overlay.packages system property to define
dynamic RROs. Currently it is recommended to use only one dynamic RRO for CarService
resource overlay.
@@ -36,6 +36,7 @@
<item type="integer" name="audioVolumeKeyEventTimeoutMs"/>
<item type="bool" name="displayOffMuteLockAllAudio"/>
<item type="bool" name="useDefaultBluetoothConnectionPolicy"/>
+ <item type="bool" name="useDefaultBluetoothPowerPolicy"/>
<item type="string" name="instrumentClusterRendererService" translatable="false"/>
<item type="string" name="config_clusterHomeActivity" translatable="false"/>
<item type="string" name="rotaryService" translatable="false"/>
diff --git a/service/src/com/android/car/CarPropertyService.java b/service/src/com/android/car/CarPropertyService.java
index 481d5eff67..68972298b0 100644
--- a/service/src/com/android/car/CarPropertyService.java
+++ b/service/src/com/android/car/CarPropertyService.java
@@ -621,7 +621,6 @@ public class CarPropertyService extends ICarProperty.Stub
@Override
public void onPropertyChange(List<CarPropertyEvent> events) {
Map<Client, List<CarPropertyEvent>> eventsToDispatch = new ArrayMap<>();
-
synchronized (mLock) {
for (int i = 0; i < events.size(); i++) {
CarPropertyEvent event = events.get(i);
@@ -644,16 +643,22 @@ public class CarPropertyService extends ICarProperty.Stub
p.add(event);
}
}
+ }
- // Parse the dispatch list to send events
- for (Client client : eventsToDispatch.keySet()) {
- try {
- client.onEvent(eventsToDispatch.get(client));
- } catch (RemoteException ex) {
- // If we cannot send a record, its likely the connection snapped. Let binder
- // death handle the situation.
- Slogf.e(TAG, "onEvent calling failed: " + ex);
- }
+ // Parse the dispatch list to send events. We must call the callback outside the
+ // scoped lock since the callback might call some function in CarPropertyService
+ // which might cause dead-lock.
+ // In rare cases, if this specific client is unregistered after the lock but before
+ // the callback, we would call callback on an unregistered client which should be ok because
+ // 'onEvent' is an async oneway callback that might be delivered after unregistration
+ // anyway.
+ for (Client client : eventsToDispatch.keySet()) {
+ try {
+ client.onEvent(eventsToDispatch.get(client));
+ } catch (RemoteException ex) {
+ // If we cannot send a record, its likely the connection snapped. Let binder
+ // death handle the situation.
+ Slogf.e(TAG, "onEvent calling failed: " + ex);
}
}
}
diff --git a/service/src/com/android/car/CarShellCommand.java b/service/src/com/android/car/CarShellCommand.java
index bf28451852..c8fe0a6649 100644
--- a/service/src/com/android/car/CarShellCommand.java
+++ b/service/src/com/android/car/CarShellCommand.java
@@ -34,6 +34,7 @@ import static com.android.car.CarServiceUtils.toIntArray;
import static com.android.car.power.PolicyReader.POWER_STATE_ON;
import static com.android.car.power.PolicyReader.POWER_STATE_WAIT_FOR_VHAL;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
@@ -2891,6 +2892,21 @@ final class CarShellCommand extends BasicShellCommandHandler {
writer.flush();
resultLatch.countDown();
}
+
+ @Override
+ public void onMetricsReport(
+ @NonNull PersistableBundle report,
+ @Nullable PersistableBundle stateToPersist) {
+ writer.println("Script produced a report without finishing");
+ report.size(); // unparcel()'s
+ writer.println("report: " + report);
+ if (stateToPersist != null) {
+ stateToPersist.size(); // unparcel()'s
+ writer.println("state to persist: " + stateToPersist);
+ }
+ writer.flush();
+ resultLatch.countDown();
+ }
};
mScriptExecutor.invokeScript(
script,
diff --git a/service/src/com/android/car/ICarImpl.java b/service/src/com/android/car/ICarImpl.java
index 4c685984b6..89abd1e288 100644
--- a/service/src/com/android/car/ICarImpl.java
+++ b/service/src/com/android/car/ICarImpl.java
@@ -16,6 +16,8 @@
package com.android.car;
+import static android.car.builtin.content.pm.PackageManagerHelper.PROPERTY_CAR_SERVICE_PACKAGE_NAME;
+
import static com.android.car.CarServiceImpl.CAR_SERVICE_INIT_TIMING_MIN_DURATION_MS;
import static com.android.car.CarServiceImpl.CAR_SERVICE_INIT_TIMING_TAG;
import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DEPRECATED_CODE;
@@ -25,6 +27,7 @@ import static com.android.car.internal.SystemConstants.ICAR_SYSTEM_SERVER_CLIENT
import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityManager;
import android.car.Car;
import android.car.CarFeatures;
import android.car.ICar;
@@ -39,6 +42,8 @@ import android.car.builtin.util.Slogf;
import android.car.builtin.util.TimingsTraceLog;
import android.car.user.CarUserManager;
import android.content.Context;
+import android.content.om.OverlayInfo;
+import android.content.om.OverlayManager;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.frameworks.automotive.powerpolicy.internal.ICarPowerPolicySystemNotification;
@@ -50,6 +55,7 @@ import android.os.IBinder;
import android.os.Parcel;
import android.os.Process;
import android.os.RemoteException;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
@@ -699,6 +705,7 @@ public class ICarImpl extends ICar.Stub {
dumpVersions(writer);
dumpAllServices(writer);
dumpAllHals(writer);
+ dumpRROs(writer);
} else if ("--list".equals(args[0])) {
dumpListOfServices(writer);
return;
@@ -744,6 +751,38 @@ public class ICarImpl extends ICar.Stub {
}
}
+ private void dumpRROs(IndentingPrintWriter writer) {
+ writer.println("*Dump Car Service RROs*");
+
+ String packageName = SystemProperties.get(
+ PROPERTY_CAR_SERVICE_PACKAGE_NAME, /*def= */null);
+ if (packageName == null) {
+ writer.println("Car Service updatable package name is null.");
+ return;
+ }
+
+ OverlayManager manager = mContext.getSystemService(OverlayManager.class);
+
+ List<OverlayInfo> installedOverlaysForSystem = manager.getOverlayInfosForTarget(packageName,
+ UserHandle.SYSTEM);
+ writer.println("RROs for System User");
+ for (int i = 0; i < installedOverlaysForSystem.size(); i++) {
+ OverlayInfo overlayInfo = installedOverlaysForSystem.get(i);
+ writer.printf("Overlay: %s, Enabled: %b \n", overlayInfo.getPackageName(),
+ overlayInfo.isEnabled());
+ }
+
+ int currentUser = ActivityManager.getCurrentUser();
+ writer.printf("RROs for Current User: %d\n", currentUser);
+ List<OverlayInfo> installedOverlaysForCurrentUser = manager.getOverlayInfosForTarget(
+ packageName, UserHandle.of(currentUser));
+ for (int i = 0; i < installedOverlaysForCurrentUser.size(); i++) {
+ OverlayInfo overlayInfo = installedOverlaysForCurrentUser.get(i);
+ writer.printf("Overlay: %s, Enabled: %b \n", overlayInfo.getPackageName(),
+ overlayInfo.isEnabled());
+ }
+ }
+
@ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
private void dumpVersions(IndentingPrintWriter writer) {
writer.println("*Dump versions*");
diff --git a/service/src/com/android/car/bluetooth/CarBluetoothService.java b/service/src/com/android/car/bluetooth/CarBluetoothService.java
index 3f3b38344f..631d353dc6 100644
--- a/service/src/com/android/car/bluetooth/CarBluetoothService.java
+++ b/service/src/com/android/car/bluetooth/CarBluetoothService.java
@@ -77,6 +77,8 @@ public class CarBluetoothService implements CarServiceBase {
// invalid. This lock protects all our internal objects.
private final Object mPerUserLock = new Object();
+ // Default Bluetooth power policy, per user, enabled with an overlay
+ private final boolean mUseDefaultPowerPolicy;
@GuardedBy("mPerUserLock")
private BluetoothPowerPolicy mBluetoothPowerPolicy;
@@ -89,7 +91,7 @@ public class CarBluetoothService implements CarServiceBase {
private BluetoothProfileInhibitManager mInhibitManager;
// Default Bluetooth device connection policy, per user, enabled with an overlay
- private final boolean mUseDefaultPolicy;
+ private final boolean mUseDefaultConnectionPolicy;
@GuardedBy("mPerUserLock")
private BluetoothDeviceConnectionPolicy mBluetoothDeviceConnectionPolicy;
@@ -156,8 +158,10 @@ public class CarBluetoothService implements CarServiceBase {
mUserId = UserManagerHelper.USER_NULL;
mContext = context;
mUserServiceHelper = userSwitchService;
- mUseDefaultPolicy = mContext.getResources().getBoolean(
+ mUseDefaultConnectionPolicy = mContext.getResources().getBoolean(
R.bool.useDefaultBluetoothConnectionPolicy);
+ mUseDefaultPowerPolicy = mContext.getResources().getBoolean(
+ R.bool.useDefaultBluetoothPowerPolicy);
}
/**
@@ -203,12 +207,16 @@ public class CarBluetoothService implements CarServiceBase {
createBluetoothUserServiceLocked();
createBluetoothDeviceManagerLocked();
createBluetoothProfileInhibitManagerLocked();
- createBluetoothPowerPolicyLocked();
+ // Determine if we need to begin the default power policy
+ mBluetoothPowerPolicy = null;
+ if (mUseDefaultPowerPolicy) {
+ createBluetoothPowerPolicyLocked();
+ }
createBluetoothConnectionRetryManagerLocked();
- // Determine if we need to begin the default policy
+ // Determine if we need to begin the default device connection policy
mBluetoothDeviceConnectionPolicy = null;
- if (mUseDefaultPolicy) {
+ if (mUseDefaultConnectionPolicy) {
createBluetoothDeviceConnectionPolicyLocked();
}
if (DBG) {
@@ -468,7 +476,7 @@ public class CarBluetoothService implements CarServiceBase {
}
}
- /**
+ /**
* Determine if we are using the default device connection policy or not
*
* @return true if the default policy is active, false otherwise
@@ -479,6 +487,17 @@ public class CarBluetoothService implements CarServiceBase {
}
}
+ /**
+ * Determine if we are using the default power policy or not
+ *
+ * @return true if the default policy is active, false otherwise
+ */
+ public boolean isUsingDefaultPowerPolicy() {
+ synchronized (mPerUserLock) {
+ return mBluetoothPowerPolicy != null;
+ }
+ }
+
/**
* Initiate automatated connecting of devices based on the prioritized device lists for each
* profile.
@@ -626,7 +645,10 @@ public class CarBluetoothService implements CarServiceBase {
synchronized (mPerUserLock) {
writer.printf("User ID: %d\n", mUserId);
writer.printf("User Proxies: %s\n", mCarBluetoothUserService != null ? "Yes" : "No");
- writer.printf("Using default policy? %s\n", mUseDefaultPolicy ? "Yes" : "No");
+ writer.printf("Using default connection policy? %s\n",
+ mUseDefaultConnectionPolicy ? "Yes" : "No");
+ writer.printf("Using default power policy? %s\n",
+ mUseDefaultPowerPolicy ? "Yes" : "No");
// Device Connection Policy
if (mBluetoothDeviceConnectionPolicy != null) {
@@ -635,6 +657,13 @@ public class CarBluetoothService implements CarServiceBase {
writer.printf("BluetoothDeviceConnectionPolicy: null\n");
}
+ // Power Policy
+ if (mBluetoothPowerPolicy != null) {
+ mBluetoothPowerPolicy.dump(writer);
+ } else {
+ writer.printf("BluetoothPowerPolicy: null\n");
+ }
+
// Device Manager status
mDeviceManager.dump(writer);
diff --git a/service/src/com/android/car/telemetry/CarTelemetryService.java b/service/src/com/android/car/telemetry/CarTelemetryService.java
index e9e8b1ab62..5a2165b07b 100644
--- a/service/src/com/android/car/telemetry/CarTelemetryService.java
+++ b/service/src/com/android/car/telemetry/CarTelemetryService.java
@@ -39,6 +39,7 @@ import android.car.telemetry.ICarTelemetryReportListener;
import android.car.telemetry.ICarTelemetryReportReadyListener;
import android.car.telemetry.ICarTelemetryService;
import android.car.telemetry.TelemetryProto;
+import android.car.telemetry.TelemetryProto.TelemetryError;
import android.content.Context;
import android.os.Handler;
import android.os.HandlerThread;
@@ -46,6 +47,7 @@ import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.util.ArrayMap;
+import android.util.Log;
import com.android.car.CarLocalServices;
import com.android.car.CarLog;
@@ -57,16 +59,18 @@ import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
import com.android.car.internal.util.IndentingPrintWriter;
import com.android.car.systeminterface.SystemInterface;
import com.android.car.telemetry.databroker.DataBroker;
-import com.android.car.telemetry.databroker.DataBrokerController;
import com.android.car.telemetry.databroker.DataBrokerImpl;
+import com.android.car.telemetry.databroker.ScriptExecutionTask;
import com.android.car.telemetry.publisher.PublisherFactory;
import com.android.car.telemetry.sessioncontroller.SessionController;
import com.android.car.telemetry.systemmonitor.SystemMonitor;
+import com.android.car.telemetry.systemmonitor.SystemMonitorEvent;
import com.android.internal.annotations.VisibleForTesting;
import com.google.protobuf.InvalidProtocolBufferException;
import java.io.File;
+import java.time.Duration;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
@@ -82,19 +86,80 @@ public class CarTelemetryService extends ICarTelemetryService.Stub implements Ca
private static final String PUBLISHER_DIR = "publisher";
public static final String TELEMETRY_DIR = "telemetry";
+ /**
+ * Priorities range from 0 to 100, with 0 being the highest priority and 100 being the lowest.
+ * A {@link ScriptExecutionTask} must have equal or higher priority than the threshold in order
+ * to be executed.
+ * The following constants are chosen with the idea that subscribers with a priority of 0
+ * must be executed as soon as data is published regardless of system health conditions.
+ * Otherwise {@link ScriptExecutionTask}s are executed from the highest priority to the lowest
+ * subject to system health constraints from {@link SystemMonitor}.
+ */
+ public static final int TASK_PRIORITY_HI = 0;
+ public static final int TASK_PRIORITY_MED = 50;
+ public static final int TASK_PRIORITY_LOW = 100;
+
private final Context mContext;
private final CarPropertyService mCarPropertyService;
private final Dependencies mDependencies;
private final HandlerThread mTelemetryThread = CarServiceUtils.getHandlerThread(
CarTelemetryService.class.getSimpleName());
private final Handler mTelemetryHandler = new Handler(mTelemetryThread.getLooper());
+ private final UidPackageMapper mUidMapper;
+
+ private final DataBroker.DataBrokerListener mDataBrokerListener =
+ new DataBroker.DataBrokerListener() {
+ @Override
+ public void onEventConsumed(
+ @NonNull String metricsConfigName, @NonNull PersistableBundle state) {
+ mResultStore.putInterimResult(metricsConfigName, state);
+ mDataBroker.scheduleNextTask();
+ }
+ @Override
+ public void onReportFinished(@NonNull String metricsConfigName) {
+ cleanupMetricsConfig(metricsConfigName); // schedules next script execution task
+ if (mResultStore.getErrorResult(metricsConfigName, false) != null
+ || mResultStore.getFinalResult(metricsConfigName, false) != null) {
+ onReportReady(metricsConfigName);
+ }
+ }
+
+ @Override
+ public void onReportFinished(
+ @NonNull String metricsConfigName, @NonNull PersistableBundle report) {
+ cleanupMetricsConfig(metricsConfigName); // schedules next script execution task
+ mResultStore.putFinalResult(metricsConfigName, report);
+ onReportReady(metricsConfigName);
+ }
+
+ @Override
+ public void onReportFinished(
+ @NonNull String metricsConfigName, @NonNull TelemetryProto.TelemetryError error) {
+ cleanupMetricsConfig(metricsConfigName); // schedules next script execution task
+ mResultStore.putErrorResult(metricsConfigName, error);
+ onReportReady(metricsConfigName);
+ }
+
+ @Override
+ public void onMetricsReport(
+ @NonNull String metricsConfigName,
+ @NonNull PersistableBundle report,
+ @Nullable PersistableBundle state) {
+ // TODO(b/229134432): ResultStore should be able to store multiple reports
+ mResultStore.putFinalResult(metricsConfigName, report);
+ if (state != null) {
+ mResultStore.putInterimResult(metricsConfigName, state);
+ }
+ onReportReady(metricsConfigName);
+ mDataBroker.scheduleNextTask();
+ }
+ };
// accessed and updated on the main thread
private boolean mReleased = false;
// all the following fields are accessed and updated on the telemetry thread
private DataBroker mDataBroker;
- private DataBrokerController mDataBrokerController;
private ICarTelemetryReportReadyListener mReportReadyListener;
private MetricsConfigStore mMetricsConfigStore;
private OnShutdownReboot mOnShutdownReboot;
@@ -103,12 +168,8 @@ public class CarTelemetryService extends ICarTelemetryService.Stub implements Ca
private SessionController mSessionController;
private SystemMonitor mSystemMonitor;
private TimingsTraceLog mTelemetryThreadTraceLog; // can only be used on telemetry thread
- private final UidPackageMapper mUidMapper;
static class Dependencies {
- /**
- * Get a PublisherFactory instance.
- */
/** Returns a new PublisherFactory instance. */
public PublisherFactory getPublisherFactory(
@@ -130,15 +191,22 @@ public class CarTelemetryService extends ICarTelemetryService.Stub implements Ca
}
public CarTelemetryService(Context context, CarPropertyService carPropertyService) {
- this(context, carPropertyService, new Dependencies());
+ this(context, carPropertyService, new Dependencies(), null, null);
}
@VisibleForTesting
- CarTelemetryService(Context context, CarPropertyService carPropertyService, Dependencies deps) {
+ CarTelemetryService(
+ Context context,
+ CarPropertyService carPropertyService,
+ Dependencies deps,
+ DataBroker dataBroker,
+ SessionController sessionController) {
mContext = context;
mCarPropertyService = carPropertyService;
mDependencies = deps;
mUidMapper = mDependencies.getUidPackageMapper(mContext, mTelemetryHandler);
+ mDataBroker = dataBroker;
+ mSessionController = sessionController;
}
@Override
@@ -148,6 +216,9 @@ public class CarTelemetryService extends ICarTelemetryService.Stub implements Ca
CarLog.TAG_TELEMETRY, TraceHelper.TRACE_TAG_CAR_SERVICE);
mTelemetryThreadTraceLog.traceBegin("init");
SystemInterface systemInterface = CarLocalServices.getService(SystemInterface.class);
+ // starts metrics collection after boot complete
+ systemInterface.scheduleActionForBootCompleted(
+ this::startMetricsCollection, Duration.ZERO);
// full root directory path is /data/system/car/telemetry
File rootDirectory = new File(systemInterface.getSystemCarDir(), TELEMETRY_DIR);
File publisherDirectory = new File(rootDirectory, PUBLISHER_DIR);
@@ -156,18 +227,20 @@ public class CarTelemetryService extends ICarTelemetryService.Stub implements Ca
mUidMapper.init();
mMetricsConfigStore = new MetricsConfigStore(rootDirectory);
mResultStore = new ResultStore(rootDirectory);
- mSessionController = new SessionController(mContext, mTelemetryHandler);
+ if (mSessionController == null) {
+ mSessionController = new SessionController(mContext, mTelemetryHandler);
+ }
mPublisherFactory = mDependencies.getPublisherFactory(mCarPropertyService,
mTelemetryHandler, mContext, publisherDirectory, mSessionController,
mResultStore, mUidMapper);
- mDataBroker = new DataBrokerImpl(mContext, mPublisherFactory, mResultStore,
- mTelemetryThreadTraceLog);
+ if (mDataBroker == null) {
+ mDataBroker = new DataBrokerImpl(mContext, mPublisherFactory, mResultStore,
+ mTelemetryThreadTraceLog);
+ }
+ mDataBroker.setDataBrokerListener(mDataBrokerListener);
ActivityManager activityManager = mContext.getSystemService(ActivityManager.class);
mSystemMonitor = SystemMonitor.create(activityManager, mTelemetryHandler);
- // controller starts metrics collection after boot complete
- mDataBrokerController = new DataBrokerController(mDataBroker, mTelemetryHandler,
- mMetricsConfigStore, this::onReportReady, mSystemMonitor,
- systemInterface.getSystemStateInterface(), mSessionController);
+ mSystemMonitor.setSystemMonitorCallback(this::onSystemMonitorEvent);
mTelemetryThreadTraceLog.traceEnd();
// save state at reboot and shutdown
mOnShutdownReboot = new OnShutdownReboot(mContext);
@@ -283,7 +356,16 @@ public class CarTelemetryService extends ICarTelemetryService.Stub implements Ca
// for this config and add config to the DataBroker for metrics collection.
mResultStore.removeResult(metricsConfigName);
mDataBroker.removeMetricsConfig(metricsConfigName);
- mDataBroker.addMetricsConfig(metricsConfigName, metricsConfig);
+ // add config to DataBroker could fail due to invalid metrics configurations, such as
+ // containing an illegal field. An example is setting the read_interval_sec to 0 in
+ // MemoryPublisher. The read_interval_sec must be at least 1.
+ try {
+ mDataBroker.addMetricsConfig(metricsConfigName, metricsConfig);
+ } catch (IllegalArgumentException | IllegalStateException e) {
+ Slogf.w(CarLog.TAG_TELEMETRY, "Invalid config, failed to add to DataBroker", e);
+ removeMetricsConfig(metricsConfigName); // clean up
+ return STATUS_ADD_METRICS_CONFIG_PARSE_FAILED;
+ }
// TODO(b/199410900): update logic once metrics configs have expiration dates
return STATUS_ADD_METRICS_CONFIG_SUCCEEDED;
}
@@ -430,8 +512,7 @@ public class CarTelemetryService extends ICarTelemetryService.Stub implements Ca
}
/**
- * Implementation of the functional interface {@link DataBrokerController.ReportReadyListener}.
- * Invoked from {@link DataBrokerController} when a script produces a report or a runtime error.
+ * Invoked when a script produces a report or a runtime error.
*/
private void onReportReady(@NonNull String metricsConfigName) {
if (mReportReadyListener == null) {
@@ -476,6 +557,72 @@ public class CarTelemetryService extends ICarTelemetryService.Stub implements Ca
}
}
+ /**
+ * Starts collecting data. Once data is sent by publishers, DataBroker will arrange scripts to
+ * run. This method is called by some thread on executor service, therefore the work needs to
+ * be posted on the telemetry thread.
+ */
+ private void startMetricsCollection() {
+ mTelemetryHandler.post(() -> {
+ for (TelemetryProto.MetricsConfig config :
+ mMetricsConfigStore.getActiveMetricsConfigs()) {
+ try {
+ mDataBroker.addMetricsConfig(config.getName(), config);
+ } catch (IllegalArgumentException | IllegalStateException e) {
+ Slogf.w(CarLog.TAG_TELEMETRY,
+ "Loading MetricsConfig from disk failed, stopping MetricsConfig("
+ + config.getName() + ") and storing error", e);
+ removeMetricsConfig(config.getName()); // clean up
+ TelemetryError error = TelemetryError.newBuilder()
+ .setErrorType(TelemetryError.ErrorType.PUBLISHER_FAILED)
+ .setMessage("Publisher failed when loading MetricsConfig from disk")
+ .setStackTrace(Log.getStackTraceString(e))
+ .build();
+ // this will remove the MetricsConfig from disk and clean up its associated
+ // subscribers and tasks from CarTelemetryService, and also notify the client
+ // that an error report is available for them
+ mDataBrokerListener.onReportFinished(config.getName(), error);
+ }
+ }
+ // By this point all publishers are instantiated according to the active configs
+ // and subscribed to session updates. The publishers are ready to handle session updates
+ // that this call might trigger.
+ mSessionController.initSession();
+ });
+ }
+
+ /**
+ * Listens to {@link SystemMonitorEvent} and changes the cut-off priority
+ * for {@link DataBroker} such that only tasks with the same or more urgent
+ * priority can be run.
+ *
+ * Highest priority is 0 and lowest is 100.
+ *
+ * @param event the {@link SystemMonitorEvent} received.
+ */
+ private void onSystemMonitorEvent(@NonNull SystemMonitorEvent event) {
+ if (event.getCpuUsageLevel() == SystemMonitorEvent.USAGE_LEVEL_HI
+ || event.getMemoryUsageLevel() == SystemMonitorEvent.USAGE_LEVEL_HI) {
+ mDataBroker.setTaskExecutionPriority(TASK_PRIORITY_HI);
+ } else if (event.getCpuUsageLevel() == SystemMonitorEvent.USAGE_LEVEL_MED
+ || event.getMemoryUsageLevel() == SystemMonitorEvent.USAGE_LEVEL_MED) {
+ mDataBroker.setTaskExecutionPriority(TASK_PRIORITY_MED);
+ } else {
+ mDataBroker.setTaskExecutionPriority(TASK_PRIORITY_LOW);
+ }
+ }
+
+ /**
+ * As a MetricsConfig completes its lifecycle, it should be cleaned up from the service.
+ * It will be removed from the MetricsConfigStore, all subscribers should be unsubscribed,
+ * and associated tasks should be removed from DataBroker.
+ */
+ private void cleanupMetricsConfig(String metricsConfigName) {
+ mMetricsConfigStore.removeMetricsConfig(metricsConfigName);
+ mDataBroker.removeMetricsConfig(metricsConfigName);
+ mDataBroker.scheduleNextTask();
+ }
+
@VisibleForTesting
Handler getTelemetryHandler() {
return mTelemetryHandler;
diff --git a/service/src/com/android/car/telemetry/ResultStore.java b/service/src/com/android/car/telemetry/ResultStore.java
index 6fa8d0d580..324c802f38 100644
--- a/service/src/com/android/car/telemetry/ResultStore.java
+++ b/service/src/com/android/car/telemetry/ResultStore.java
@@ -305,6 +305,14 @@ public class ResultStore {
}
/**
+ * Deletes associated publisher data.
+ */
+ public void removePublisherData(@NonNull String publisherName) {
+ mPublisherCache.remove(publisherName);
+ IoUtils.deleteSilently(mPublisherDataDirectory, publisherName);
+ }
+
+ /**
* Deletes script result associated with the given config name. If result does not exist, this
* method does not do anything.
*/
diff --git a/service/src/com/android/car/telemetry/databroker/DataBroker.java b/service/src/com/android/car/telemetry/databroker/DataBroker.java
index 644ad27af1..4c0c40788f 100644
--- a/service/src/com/android/car/telemetry/databroker/DataBroker.java
+++ b/service/src/com/android/car/telemetry/databroker/DataBroker.java
@@ -17,21 +17,61 @@
package com.android.car.telemetry.databroker;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.car.telemetry.TelemetryProto;
+import android.os.PersistableBundle;
/** Interface for the data path. Handles data forwarding from publishers to subscribers */
public interface DataBroker {
/**
- * Interface for receiving notification that script finished.
+ * Interface for receiving notification from DataBroker.
*/
- interface ScriptFinishedCallback {
+ interface DataBrokerListener {
/**
- * Listens to script finished event.
+ * Called when subscribers consumed an event and an interim state should be saved as a
+ * result.
+ * @param metricsConfigName that uniquely identifies the config whose script finished.
+ * @param state an interim state to be saved for the next script execution.
+ */
+ void onEventConsumed(@NonNull String metricsConfigName, @NonNull PersistableBundle state);
+
+ /**
+ * Called when a MetricsConfig's lifecycle ends without a report or error.
+ *
+ * @param metricsConfigName that uniquely identifies the config whose script finished.
+ */
+ void onReportFinished(@NonNull String metricsConfigName);
+
+ /**
+ * Called when a MetricsConfig's lifecycle ends and a metrics report is produced by it.
*
* @param metricsConfigName that uniquely identifies the config whose script finished.
+ * @param report the final report produced by the MetricsConfig.
*/
- void onScriptFinished(@NonNull String metricsConfigName);
+ void onReportFinished(@NonNull String metricsConfigName, @NonNull PersistableBundle report);
+
+ /**
+ * Called when a MetricsConfig's lifecycle ends and an error is produced.
+ *
+ * @param metricsConfigName that uniquely identifies the config that terminated.
+ */
+ void onReportFinished(
+ @NonNull String metricsConfigName,
+ @NonNull TelemetryProto.TelemetryError error);
+
+ /**
+ * Called when a MetricsConfig produces a metrics report without ending its lifecycle.
+ *
+ * @param metricsConfigName that uniquely identifies the config whose script produced a
+ * report.
+ * @param report the metrics report.
+ * @param state optional state to persist for the next script execution.
+ */
+ void onMetricsReport(
+ @NonNull String metricsConfigName,
+ @NonNull PersistableBundle report,
+ @Nullable PersistableBundle state);
}
/**
@@ -61,8 +101,12 @@ public interface DataBroker {
/**
* Adds a {@link ScriptExecutionTask} to the priority queue. This method will schedule the
* next task if a task is not currently running.
+ *
+ * @param task The task that contains the script and published data for ScriptExecutor.
+ * @return The number of tasks that are pending execution that are produced by the calling
+ * publisher.
*/
- void addTaskToQueue(@NonNull ScriptExecutionTask task);
+ int addTaskToQueue(@NonNull ScriptExecutionTask task);
/**
* Checks system health state and executes a task if condition allows.
@@ -70,11 +114,9 @@ public interface DataBroker {
void scheduleNextTask();
/**
- * Sets callback for notifying script finished.
- *
- * @param callback script finished callback.
+ * Sets listener for DataBroker events.
*/
- void setOnScriptFinishedCallback(@NonNull ScriptFinishedCallback callback);
+ void setDataBrokerListener(@NonNull DataBrokerListener dataBrokerListener);
/**
* Sets the priority which affects which subscribers can consume data. Invoked by controller to
diff --git a/service/src/com/android/car/telemetry/databroker/DataBrokerController.java b/service/src/com/android/car/telemetry/databroker/DataBrokerController.java
deleted file mode 100644
index ad8ecbd9b1..0000000000
--- a/service/src/com/android/car/telemetry/databroker/DataBrokerController.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * 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.telemetry.databroker;
-
-import android.annotation.NonNull;
-import android.car.telemetry.TelemetryProto;
-import android.os.Handler;
-
-import com.android.car.systeminterface.SystemStateInterface;
-import com.android.car.telemetry.MetricsConfigStore;
-import com.android.car.telemetry.sessioncontroller.SessionController;
-import com.android.car.telemetry.systemmonitor.SystemMonitor;
-import com.android.car.telemetry.systemmonitor.SystemMonitorEvent;
-
-import java.time.Duration;
-
-/**
- * DataBrokerController instantiates the DataBroker and manages what Publishers
- * it can read from based current system states and policies.
- */
-public class DataBrokerController {
-
- /** Interface for report ready notifications. */
- public interface ReportReadyListener{
- /** Sends a notification when the metrics config reached a terminal state. */
- void onReportReady(@NonNull String metricsConfigName);
- }
-
- /**
- * Priorities range from 0 to 100, with 0 being the highest priority and 100 being the lowest.
- * A {@link ScriptExecutionTask} must have equal or higher priority than the threshold in order
- * to be executed.
- * The following constants are chosen with the idea that subscribers with a priority of 0
- * must be executed as soon as data is published regardless of system health conditions.
- * Otherwise {@link ScriptExecutionTask}s are executed from the highest priority to the lowest
- * subject to system health constraints from {@link SystemMonitor}.
- */
- public static final int TASK_PRIORITY_HI = 0;
- public static final int TASK_PRIORITY_MED = 50;
- public static final int TASK_PRIORITY_LOW = 100;
-
- private final DataBroker mDataBroker;
- private final Handler mTelemetryHandler;
- private final MetricsConfigStore mMetricsConfigStore;
- private final ReportReadyListener mReportReadyListener;
- private final SystemMonitor mSystemMonitor;
- private final SystemStateInterface mSystemStateInterface;
- private final SessionController mSessionController;
-
- public DataBrokerController(
- @NonNull DataBroker dataBroker,
- @NonNull Handler telemetryHandler,
- @NonNull MetricsConfigStore metricsConfigStore,
- @NonNull ReportReadyListener reportReadyListener,
- @NonNull SystemMonitor systemMonitor,
- @NonNull SystemStateInterface systemStateInterface,
- @NonNull SessionController sessionController) {
- mDataBroker = dataBroker;
- mTelemetryHandler = telemetryHandler;
- mMetricsConfigStore = metricsConfigStore;
- mReportReadyListener = reportReadyListener;
- mSystemMonitor = systemMonitor;
- mSystemStateInterface = systemStateInterface;
- mSessionController = sessionController;
-
- mDataBroker.setOnScriptFinishedCallback(
- metricsConfigName -> onScriptFinished(metricsConfigName));
- mSystemMonitor.setSystemMonitorCallback(this::onSystemMonitorEvent);
- mSystemStateInterface.scheduleActionForBootCompleted(
- this::startMetricsCollection, Duration.ZERO);
- }
-
- /**
- * Starts collecting data. Once data is sent by publishers, DataBroker will arrange scripts to
- * run. This method is called by some thread on executor service, therefore the work needs to
- * be posted on the telemetry thread.
- */
- private void startMetricsCollection() {
- mTelemetryHandler.post(() -> {
- for (TelemetryProto.MetricsConfig config :
- mMetricsConfigStore.getActiveMetricsConfigs()) {
- mDataBroker.addMetricsConfig(config.getName(), config);
- }
- // By this point all publishers are instantiated according to the active configs
- // and subscribed to session updates. The publishers are ready to handle session updates
- // that this call might trigger.
- mSessionController.initSession();
- });
- }
-
- /**
- * Listens to script finished event from {@link DataBroker}.
- *
- * @param metricsConfigName the unique identifier of the config whose script finished.
- */
- public void onScriptFinished(@NonNull String metricsConfigName) {
- mMetricsConfigStore.removeMetricsConfig(metricsConfigName);
- mDataBroker.removeMetricsConfig(metricsConfigName);
- mReportReadyListener.onReportReady(metricsConfigName);
- }
-
- /**
- * Listens to {@link SystemMonitorEvent} and changes the cut-off priority
- * for {@link DataBroker} such that only tasks with the same or more urgent
- * priority can be run.
- *
- * Highest priority is 0 and lowest is 100.
- *
- * @param event the {@link SystemMonitorEvent} received.
- */
- public void onSystemMonitorEvent(@NonNull SystemMonitorEvent event) {
- if (event.getCpuUsageLevel() == SystemMonitorEvent.USAGE_LEVEL_HI
- || event.getMemoryUsageLevel() == SystemMonitorEvent.USAGE_LEVEL_HI) {
- mDataBroker.setTaskExecutionPriority(TASK_PRIORITY_HI);
- } else if (event.getCpuUsageLevel() == SystemMonitorEvent.USAGE_LEVEL_MED
- || event.getMemoryUsageLevel() == SystemMonitorEvent.USAGE_LEVEL_MED) {
- mDataBroker.setTaskExecutionPriority(TASK_PRIORITY_MED);
- } else {
- mDataBroker.setTaskExecutionPriority(TASK_PRIORITY_LOW);
- }
- }
-}
diff --git a/service/src/com/android/car/telemetry/databroker/DataBrokerImpl.java b/service/src/com/android/car/telemetry/databroker/DataBrokerImpl.java
index 9e415d7f8f..cb3226f547 100644
--- a/service/src/com/android/car/telemetry/databroker/DataBrokerImpl.java
+++ b/service/src/com/android/car/telemetry/databroker/DataBrokerImpl.java
@@ -24,6 +24,7 @@ import android.car.builtin.util.Slogf;
import android.car.builtin.util.TimingsTraceLog;
import android.car.telemetry.TelemetryProto;
import android.car.telemetry.TelemetryProto.MetricsConfig;
+import android.car.telemetry.TelemetryProto.TelemetryError;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -40,6 +41,8 @@ import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.ArrayMap;
+import android.util.Log;
+import android.util.SparseIntArray;
import com.android.car.CarLog;
import com.android.car.CarServiceUtils;
@@ -56,6 +59,7 @@ import java.io.IOException;
import java.io.OutputStream;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
+import java.util.Iterator;
import java.util.List;
import java.util.concurrent.PriorityBlockingQueue;
@@ -98,6 +102,12 @@ public class DataBrokerImpl implements DataBroker {
new PriorityBlockingQueue<>();
/**
+ * Index is the type of {@link TelemetryProto.Publisher}, value is the number of tasks pending
+ * script execution that are produced by that publisher.
+ */
+ private final SparseIntArray mPublisherCountArray = new SparseIntArray();
+
+ /**
* Maps MetricsConfig name to its subscriptions. This map is useful for removing MetricsConfigs.
*/
private final ArrayMap<String, List<DataSubscriber>> mSubscriptionMap = new ArrayMap<>();
@@ -124,7 +134,7 @@ public class DataBrokerImpl implements DataBroker {
*/
private String mCurrentMetricsConfigName;
private IScriptExecutor mScriptExecutor;
- private ScriptFinishedCallback mScriptFinishedCallback;
+ private DataBrokerListener mDataBrokerListener;
/**
* Used only for the purpose of tracking the duration of running a script. The duration
@@ -151,6 +161,35 @@ public class DataBrokerImpl implements DataBroker {
}
};
+ private final AbstractPublisher.PublisherListener mPublisherListener =
+ new AbstractPublisher.PublisherListener() {
+ @Override
+ public void onPublisherFailure(
+ @NonNull List<TelemetryProto.MetricsConfig> affectedConfigs,
+ @Nullable Throwable error) {
+ Slogf.w(CarLog.TAG_TELEMETRY, "Publisher failed", error);
+ // when a publisher fails, construct an TelemetryError result and send to client
+ String stackTrace = null;
+ if (error != null) {
+ stackTrace = Log.getStackTraceString(error);
+ }
+ TelemetryError telemetryError = buildTelemetryError(
+ TelemetryError.ErrorType.PUBLISHER_FAILED, "Publisher failed", stackTrace);
+ for (TelemetryProto.MetricsConfig config : affectedConfigs) {
+ // this will remove the MetricsConfig and notify the client of result
+ mDataBrokerListener.onReportFinished(config.getName(), telemetryError);
+ }
+ }
+
+ @Override
+ public void onConfigFinished(@NonNull TelemetryProto.MetricsConfig metricsConfig) {
+ String configName = metricsConfig.getName();
+ Slogf.i(CarLog.TAG_TELEMETRY,
+ "Publisher sets MetricsConfig(" + configName + ") as finished");
+ mDataBrokerListener.onReportFinished(configName);
+ }
+ };
+
public DataBrokerImpl(
@NonNull Context context,
@NonNull PublisherFactory publisherFactory,
@@ -160,18 +199,10 @@ public class DataBrokerImpl implements DataBroker {
mPublisherFactory = publisherFactory;
mResultStore = resultStore;
mScriptExecutorListener = new ScriptExecutorListener(this);
- mPublisherFactory.initialize(this::onPublisherFailure);
+ mPublisherFactory.initialize(mPublisherListener);
mScriptExecutionTraceLog = traceLog;
}
- private void onPublisherFailure(
- @NonNull AbstractPublisher publisher,
- @NonNull List<TelemetryProto.MetricsConfig> affectedConfigs,
- @Nullable Throwable error) {
- // TODO(b/193680465): disable MetricsConfig and log the error
- Slogf.w(CarLog.TAG_TELEMETRY, "publisher failed", error);
- }
-
@Nullable
private String findExecutorPackage() {
PackageInfo info = null;
@@ -285,15 +316,8 @@ public class DataBrokerImpl implements DataBroker {
metricsConfig,
subscriber);
dataSubscribers.add(dataSubscriber);
-
- try {
- // The publisher will start sending data to the subscriber.
- // TODO(b/191378559): handle bad configs
- publisher.addDataSubscriber(dataSubscriber);
- } catch (IllegalArgumentException e) {
- Slogf.w(CarLog.TAG_TELEMETRY, "Invalid config", e);
- return;
- }
+ // addDataSubscriber could throw an exception, let CarTelemetryService handle it
+ publisher.addDataSubscriber(dataSubscriber);
}
mSubscriptionMap.put(metricsConfigName, dataSubscribers);
}
@@ -322,7 +346,16 @@ public class DataBrokerImpl implements DataBroker {
// iterating, so it may or may not reflect any updates since the iterator was created.
// But since adding & polling from queue should happen in the same thread, the task queue
// should not be changed while tasks are being iterated and removed.
- mTaskQueue.removeIf(task -> task.isAssociatedWithMetricsConfig(metricsConfigName));
+ Iterator<ScriptExecutionTask> it = mTaskQueue.iterator();
+ while (it.hasNext()) {
+ ScriptExecutionTask task = it.next();
+ if (task.isAssociatedWithMetricsConfig(metricsConfigName)) {
+ mTaskQueue.remove(task);
+ mPublisherCountArray.append(
+ task.getPublisherType(),
+ mPublisherCountArray.get(task.getPublisherType()) - 1);
+ }
+ }
}
@Override
@@ -330,15 +363,20 @@ public class DataBrokerImpl implements DataBroker {
mPublisherFactory.removeAllDataSubscribers();
mSubscriptionMap.clear();
mTaskQueue.clear();
+ mPublisherCountArray.clear();
}
@Override
- public void addTaskToQueue(@NonNull ScriptExecutionTask task) {
+ public int addTaskToQueue(@NonNull ScriptExecutionTask task) {
if (mDisabled) {
- return;
+ return mPublisherCountArray.get(task.getPublisherType());
}
mTaskQueue.add(task);
+ mPublisherCountArray.append(
+ task.getPublisherType(),
+ mPublisherCountArray.get(task.getPublisherType()) + 1);
scheduleNextTask();
+ return mPublisherCountArray.get(task.getPublisherType());
}
/**
@@ -359,11 +397,11 @@ public class DataBrokerImpl implements DataBroker {
}
@Override
- public void setOnScriptFinishedCallback(@NonNull ScriptFinishedCallback callback) {
+ public void setDataBrokerListener(@NonNull DataBrokerListener dataBrokerListener) {
if (mDisabled) {
return;
}
- mScriptFinishedCallback = callback;
+ mDataBrokerListener = dataBrokerListener;
}
@Override
@@ -419,6 +457,9 @@ public class DataBrokerImpl implements DataBroker {
return;
}
mTaskQueue.poll(); // remove task from queue
+ mPublisherCountArray.append(
+ task.getPublisherType(),
+ mPublisherCountArray.get(task.getPublisherType()) - 1);
// update current config name because a script is currently running
mCurrentMetricsConfigName = task.getMetricsConfig().getName();
mScriptExecutionTraceLog.traceBegin(
@@ -494,18 +535,44 @@ public class DataBrokerImpl implements DataBroker {
}
}
+ private TelemetryError buildTelemetryError(
+ @NonNull TelemetryError.ErrorType errorType,
+ @NonNull String message,
+ @Nullable String stackTrace) {
+ TelemetryError.Builder error = TelemetryError.newBuilder()
+ .setErrorType(errorType)
+ .setMessage(message);
+ if (stackTrace != null) {
+ error.setStackTrace(stackTrace);
+ }
+ return error.build();
+ }
+
+ /**
+ * This helper method should be called as soon as script execution returns.
+ * It returns the name of the MetricsConfig whose script returned.
+ */
+ private String endScriptExecution() {
+ mScriptExecutionTraceLog.traceEnd(); // end trace as soon as script completes running
+ mTelemetryHandler.removeMessages(MSG_STOP_HANGING_SCRIPT); // script did not hang
+ // get and set the mCurrentMetricsConfigName to null
+ String configName = mCurrentMetricsConfigName;
+ mCurrentMetricsConfigName = null;
+ return configName;
+ }
+
/** Stores final metrics and schedules the next task. */
private void onScriptFinished(@NonNull PersistableBundle result) {
if (DEBUG) {
Slogf.d(CarLog.TAG_TELEMETRY, "A script finished, storing the final result.");
}
mTelemetryHandler.post(() -> {
- mScriptExecutionTraceLog.traceEnd(); // end trace as soon as script completes running
- mTelemetryHandler.removeMessages(MSG_STOP_HANGING_SCRIPT);
- mResultStore.putFinalResult(mCurrentMetricsConfigName, result);
- mScriptFinishedCallback.onScriptFinished(mCurrentMetricsConfigName);
- mCurrentMetricsConfigName = null;
- scheduleNextTask();
+ String configName = endScriptExecution();
+ if (configName == null) {
+ return;
+ }
+ // delegate to DataBrokerListener to handle storing data and scheduling next task
+ mDataBrokerListener.onReportFinished(configName, result);
});
}
@@ -515,11 +582,12 @@ public class DataBrokerImpl implements DataBroker {
Slogf.d(CarLog.TAG_TELEMETRY, "A script succeeded, storing the interim result.");
}
mTelemetryHandler.post(() -> {
- mScriptExecutionTraceLog.traceEnd(); // end trace as soon as script completes running
- mTelemetryHandler.removeMessages(MSG_STOP_HANGING_SCRIPT);
- mResultStore.putInterimResult(mCurrentMetricsConfigName, stateToPersist);
- mCurrentMetricsConfigName = null;
- scheduleNextTask();
+ String configName = endScriptExecution();
+ if (configName == null) {
+ return;
+ }
+ // delegate to DataBrokerListener to handle storing data and scheduling next task
+ mDataBrokerListener.onEventConsumed(configName, stateToPersist);
});
}
@@ -531,18 +599,31 @@ public class DataBrokerImpl implements DataBroker {
errorType, message, stackTrace);
}
mTelemetryHandler.post(() -> {
- mScriptExecutionTraceLog.traceEnd(); // end trace as soon as script completes running
- mTelemetryHandler.removeMessages(MSG_STOP_HANGING_SCRIPT);
- TelemetryProto.TelemetryError.Builder error = TelemetryProto.TelemetryError.newBuilder()
- .setErrorType(TelemetryProto.TelemetryError.ErrorType.forNumber(errorType))
- .setMessage(message);
- if (stackTrace != null) {
- error.setStackTrace(stackTrace);
+ String configName = endScriptExecution();
+ if (configName == null) {
+ return;
}
- mResultStore.putErrorResult(mCurrentMetricsConfigName, error.build());
- mScriptFinishedCallback.onScriptFinished(mCurrentMetricsConfigName);
- mCurrentMetricsConfigName = null;
- scheduleNextTask();
+ // delegate to DataBrokerListener to handle storing data and scheduling next task
+ mDataBrokerListener.onReportFinished(
+ configName,
+ buildTelemetryError(
+ TelemetryError.ErrorType.forNumber(errorType),
+ message,
+ stackTrace));
+ });
+ }
+
+ private void onMetricsReport(
+ @NonNull PersistableBundle report, @Nullable PersistableBundle stateToPersist) {
+ if (DEBUG) {
+ Slogf.d(CarLog.TAG_TELEMETRY, "A script produced a report without finishing.");
+ }
+ mTelemetryHandler.post(() -> {
+ String configName = endScriptExecution();
+ if (configName == null) {
+ return;
+ }
+ mDataBrokerListener.onMetricsReport(configName, report, stateToPersist);
});
}
@@ -580,6 +661,16 @@ public class DataBrokerImpl implements DataBroker {
}
dataBroker.onScriptError(errorType, message, stackTrace);
}
+
+ @Override
+ public void onMetricsReport(
+ @NonNull PersistableBundle report, @Nullable PersistableBundle stateToPersist) {
+ DataBrokerImpl dataBroker = mWeakDataBroker.get();
+ if (dataBroker == null) {
+ return;
+ }
+ dataBroker.onMetricsReport(report, stateToPersist);
+ }
}
/** Callback handler to handle scheduling and rescheduling of {@link ScriptExecutionTask}s. */
diff --git a/service/src/com/android/car/telemetry/databroker/DataSubscriber.java b/service/src/com/android/car/telemetry/databroker/DataSubscriber.java
index 016d7410f9..49693e90df 100644
--- a/service/src/com/android/car/telemetry/databroker/DataSubscriber.java
+++ b/service/src/com/android/car/telemetry/databroker/DataSubscriber.java
@@ -58,21 +58,38 @@ public class DataSubscriber {
}
/**
+ * Returns the publisher type (as a number) indicates which type of
+ * {@link TelemetryProto.Publisher} will publish the data.
+ */
+ private int getPublisherType() {
+ return getPublisherParam().getPublisherCase().getNumber();
+ }
+
+ /**
* Creates a {@link ScriptExecutionTask} and pushes it to the priority queue where the task
* will be pending execution. Flag isLargeData indicates whether data is large.
+ *
+ * @param data The published data.
+ * @param isLargeData Whether the data is large.
+ * @return The number of tasks that are pending execution that are produced by the calling
+ * publisher.
*/
- public void push(@NonNull PersistableBundle data, boolean isLargeData) {
+ public int push(@NonNull PersistableBundle data, boolean isLargeData) {
ScriptExecutionTask task = new ScriptExecutionTask(
- this, data, SystemClock.elapsedRealtime(), isLargeData);
- mDataBroker.addTaskToQueue(task);
+ this, data, SystemClock.elapsedRealtime(), isLargeData, getPublisherType());
+ return mDataBroker.addTaskToQueue(task);
}
/**
* Creates a {@link ScriptExecutionTask} and pushes it to the priority queue where the task
* will be pending execution. Defaults isLargeData flag to false.
+ *
+ * @param data The published data.
+ * @return The number of tasks that are pending execution that are produced by the calling
+ * publisher.
*/
- public void push(@NonNull PersistableBundle data) {
- push(data, false);
+ public int push(@NonNull PersistableBundle data) {
+ return push(data, false);
}
/** Returns the {@link TelemetryProto.MetricsConfig}. */
diff --git a/service/src/com/android/car/telemetry/databroker/ScriptExecutionTask.java b/service/src/com/android/car/telemetry/databroker/ScriptExecutionTask.java
index 80b312fbf7..5aadf8c64f 100644
--- a/service/src/com/android/car/telemetry/databroker/ScriptExecutionTask.java
+++ b/service/src/com/android/car/telemetry/databroker/ScriptExecutionTask.java
@@ -28,6 +28,7 @@ import android.os.PersistableBundle;
* The object can be accessed from any thread. See {@link DataSubscriber} for thread-safety.
*/
public class ScriptExecutionTask implements Comparable<ScriptExecutionTask> {
+ private final int mPublisherType;
private final long mTimestampMillis;
private final DataSubscriber mSubscriber;
private final PersistableBundle mData;
@@ -37,11 +38,17 @@ public class ScriptExecutionTask implements Comparable<ScriptExecutionTask> {
@NonNull DataSubscriber subscriber,
@NonNull PersistableBundle data,
long elapsedRealtimeMillis,
- boolean isLargeData) {
+ boolean isLargeData,
+ int publisherType) {
mTimestampMillis = elapsedRealtimeMillis;
mSubscriber = subscriber;
mData = data;
mIsLargeData = isLargeData;
+ mPublisherType = publisherType;
+ }
+
+ public int getPublisherType() {
+ return mPublisherType;
}
/** Returns the priority of the task. */
diff --git a/service/src/com/android/car/telemetry/publisher/AbstractPublisher.java b/service/src/com/android/car/telemetry/publisher/AbstractPublisher.java
index ec9b0f3b38..a3c624a461 100644
--- a/service/src/com/android/car/telemetry/publisher/AbstractPublisher.java
+++ b/service/src/com/android/car/telemetry/publisher/AbstractPublisher.java
@@ -17,6 +17,7 @@
package com.android.car.telemetry.publisher;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.car.telemetry.TelemetryProto;
import com.android.car.telemetry.databroker.DataSubscriber;
@@ -35,23 +36,30 @@ import java.util.List;
* <p>The methods must be called from the telemetry thread.
*/
public abstract class AbstractPublisher {
- private final PublisherFailureListener mFailureListener;
+ private final PublisherListener mPublisherListener;
/**
- * Listener for publisher failures, such as failing to connect to a underlying service or
- * invalid Publisher configuration. When publishers fail, the affected configs should be
- * disabled, because the associated scripts cannot receive data from the failed publishers.
+ * Listener for publisher updates, such as failing to connect to a underlying service or
+ * invalid Publisher configuration.
*/
- public interface PublisherFailureListener {
- /** Called by publishers when they fail. */
+ public interface PublisherListener {
+ /**
+ * Called by publishers when they fail.
+ * When publishers fail, the affected configs should be disabled, because the associated
+ * scripts cannot receive data from the failed publishers.
+ */
void onPublisherFailure(
- @NonNull AbstractPublisher publisher,
@NonNull List<TelemetryProto.MetricsConfig> affectedConfigs,
- @NonNull Throwable error);
+ @Nullable Throwable error);
+
+ /**
+ * Called by publishers when a config satisfies terminating conditions.
+ */
+ void onConfigFinished(@NonNull TelemetryProto.MetricsConfig metricsConfig);
}
- AbstractPublisher(@NonNull PublisherFailureListener failureListener) {
- mFailureListener = failureListener;
+ AbstractPublisher(@NonNull PublisherListener listener) {
+ mPublisherListener = listener;
}
/**
@@ -91,11 +99,17 @@ public abstract class AbstractPublisher {
public abstract boolean hasDataSubscriber(@NonNull DataSubscriber subscriber);
/**
- * Notifies the failure Listener that this publisher failed. See
- * {@link PublisherFailureListener} for details.
+ * Notifies the PublisherListener that this publisher failed.
*/
protected void onPublisherFailure(
@NonNull List<TelemetryProto.MetricsConfig> affectedConfigs, @NonNull Throwable error) {
- mFailureListener.onPublisherFailure(this, affectedConfigs, error);
+ mPublisherListener.onPublisherFailure(affectedConfigs, error);
+ }
+
+ /**
+ * Notifies the PublisherListener that a MetricsConfig should be marked as finished.
+ */
+ protected void onConfigFinished(@NonNull TelemetryProto.MetricsConfig metricsConfig) {
+ mPublisherListener.onConfigFinished(metricsConfig);
}
}
diff --git a/service/src/com/android/car/telemetry/publisher/CarTelemetrydPublisher.java b/service/src/com/android/car/telemetry/publisher/CarTelemetrydPublisher.java
index 4514d313bb..10da54781c 100644
--- a/service/src/com/android/car/telemetry/publisher/CarTelemetrydPublisher.java
+++ b/service/src/com/android/car/telemetry/publisher/CarTelemetrydPublisher.java
@@ -81,8 +81,8 @@ public class CarTelemetrydPublisher extends AbstractPublisher {
};
CarTelemetrydPublisher(
- @NonNull PublisherFailureListener failureListener, @NonNull Handler telemetryHandler) {
- super(failureListener);
+ @NonNull PublisherListener listener, @NonNull Handler telemetryHandler) {
+ super(listener);
this.mTelemetryHandler = telemetryHandler;
}
diff --git a/service/src/com/android/car/telemetry/publisher/ConnectivityPublisher.java b/service/src/com/android/car/telemetry/publisher/ConnectivityPublisher.java
index 3ff0ea8c66..f161cd5d42 100644
--- a/service/src/com/android/car/telemetry/publisher/ConnectivityPublisher.java
+++ b/service/src/com/android/car/telemetry/publisher/ConnectivityPublisher.java
@@ -87,12 +87,12 @@ public class ConnectivityPublisher extends AbstractPublisher {
private NetworkStatsManagerProxy mNetworkStatsManager;
ConnectivityPublisher(
- @NonNull PublisherFailureListener failureListener,
+ @NonNull PublisherListener listener,
@NonNull NetworkStatsManagerProxy networkStatsManager,
@NonNull Handler telemetryHandler,
@NonNull ResultStore resultStore,
@NonNull SessionController sessionController, @NonNull UidPackageMapper uidMapper) {
- super(failureListener);
+ super(listener);
mNetworkStatsManager = networkStatsManager;
mTelemetryHandler = telemetryHandler;
mResultStore = resultStore;
diff --git a/service/src/com/android/car/telemetry/publisher/MemoryPublisher.java b/service/src/com/android/car/telemetry/publisher/MemoryPublisher.java
new file mode 100644
index 0000000000..eff703d2f2
--- /dev/null
+++ b/service/src/com/android/car/telemetry/publisher/MemoryPublisher.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2022 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.telemetry.publisher;
+
+import android.annotation.NonNull;
+import android.car.builtin.os.TraceHelper;
+import android.car.builtin.util.TimingsTraceLog;
+import android.car.telemetry.TelemetryProto;
+import android.car.telemetry.TelemetryProto.Publisher.PublisherCase;
+import android.os.Handler;
+import android.os.PersistableBundle;
+
+import com.android.car.CarLog;
+import com.android.car.telemetry.ResultStore;
+import com.android.car.telemetry.databroker.DataSubscriber;
+import com.android.car.telemetry.sessioncontroller.SessionAnnotation;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+
+/**
+ * Publisher implementation for {@link TelemetryProto.MemoryPublisher}.
+ *
+ * <p>It pulls data from /proc/meminfo periodically and sends the file as String to the Lua script.
+ *
+ * <p>This publisher only allows for one Subscriber at a time. It will pull data until the
+ * MetricsConfig is removed or until the maximum snapshot is reached.
+ *
+ * <p>Failure to read from /proc/meminfo will cause a {@link TelemetryProto.TelemetryError} to be
+ * returned to the client.
+ */
+public class MemoryPublisher extends AbstractPublisher {
+
+ private static final int MILLIS_IN_SECOND = 1000;
+ @VisibleForTesting
+ static final int THROTTLE_MILLIS = 60 * MILLIS_IN_SECOND;
+ @VisibleForTesting
+ static final String BUNDLE_KEY_NUM_SNAPSHOTS_UNTIL_FINISH = "num_snapshots_left";
+ @VisibleForTesting
+ static final String BUNDLE_KEY_COLLECT_INDEFINITELY = "collect_indefinitely";
+ @VisibleForTesting
+ static final String DATA_BUNDLE_KEY_MEMINFO = "meminfo";
+ @VisibleForTesting
+ static final String DATA_BUNDLE_KEY_TIMESTAMP = "timestamp";
+
+ private final Runnable mReadMeminfoRunnable = this::readMemInfo;
+ private final Handler mTelemetryHandler;
+ private final Path mMeminfoPath;
+ private final ResultStore mResultStore;
+ private final TimingsTraceLog mTraceLog;
+
+ private MemorySubscriberWrapper mSubscriber;
+ private PersistableBundle mPublisherState;
+ private SessionAnnotation mSessionAnnotation;
+
+ MemoryPublisher(
+ @NonNull PublisherListener listener,
+ @NonNull Handler telemetryHandler,
+ @NonNull ResultStore resultStore) {
+ this(listener, telemetryHandler, resultStore, Paths.get("/proc/meminfo"));
+ }
+
+ @VisibleForTesting
+ MemoryPublisher(
+ @NonNull PublisherListener listener,
+ @NonNull Handler telemetryHandler,
+ @NonNull ResultStore resultStore,
+ @NonNull Path meminfoPath) {
+ super(listener);
+ mTelemetryHandler = telemetryHandler;
+ mResultStore = resultStore;
+ mMeminfoPath = meminfoPath;
+ mPublisherState = mResultStore.getPublisherData(
+ MemoryPublisher.class.getSimpleName(), false);
+ mTraceLog = new TimingsTraceLog(
+ CarLog.TAG_TELEMETRY, TraceHelper.TRACE_TAG_CAR_SERVICE);
+ }
+
+ @Override
+ protected void handleSessionStateChange(@NonNull SessionAnnotation annotation) {
+ mSessionAnnotation = annotation;
+ }
+
+ @Override
+ public void addDataSubscriber(@NonNull DataSubscriber subscriber) {
+ if (mSubscriber != null) {
+ throw new IllegalStateException("Only one subscriber is allowed for MemoryPublisher.");
+ }
+ Preconditions.checkArgument(
+ subscriber.getPublisherParam().getPublisherCase() == PublisherCase.MEMORY,
+ "Only subscribers for memory statistics are supported by this class.");
+ // the minimum allowed read_rate is 1, i.e. one snapshot per second
+ if (subscriber.getPublisherParam().getMemory().getReadIntervalSec() <= 0) {
+ throw new IllegalArgumentException("MemoryPublisher read_rate must be at least 1");
+ }
+ if (subscriber.getPublisherParam().getMemory().getMaxPendingTasks() <= 0) {
+ throw new IllegalArgumentException("max_pending_tasks in MemoryPublisher must be set"
+ + " as a throttling threshold");
+ }
+ // if the subscriber is new, i.e. it is added from CarTelemetryManager#addMetricsConifg(),
+ // then the protobuf max_snapshots field is the number of snapshots left
+ int numSnapshotsLeft = subscriber.getPublisherParam().getMemory().getMaxSnapshots();
+ // if client does not specify max_snapshots, the publisher will publisher until
+ // the MetricsConfig's lifecycle ends or when the MetricsConfig is removed
+ boolean collectIndefinitely = numSnapshotsLeft <= 0;
+ // if the subscriber is not new, i.e. it is loaded from disk, then the number of snapshots
+ // left is whatever value from last time, which is stored in the publisher state
+ if (mPublisherState != null) {
+ numSnapshotsLeft = mPublisherState.getInt(
+ BUNDLE_KEY_NUM_SNAPSHOTS_UNTIL_FINISH, numSnapshotsLeft);
+ collectIndefinitely = mPublisherState.getBoolean(
+ BUNDLE_KEY_COLLECT_INDEFINITELY, numSnapshotsLeft <= 0);
+ }
+ mSubscriber = new MemorySubscriberWrapper(
+ subscriber, numSnapshotsLeft, collectIndefinitely);
+ readMemInfo();
+ }
+
+ @Override
+ public void removeDataSubscriber(@NonNull DataSubscriber subscriber) {
+ if (mSubscriber == null || !mSubscriber.mDataSubscriber.equals(subscriber)) {
+ return;
+ }
+ resetPublisher();
+ }
+
+ @Override
+ public void removeAllDataSubscribers() {
+ resetPublisher();
+ }
+
+ @Override
+ public boolean hasDataSubscriber(@NonNull DataSubscriber subscriber) {
+ return mSubscriber != null && mSubscriber.mDataSubscriber.equals(subscriber);
+ }
+
+ private void resetPublisher() {
+ mTelemetryHandler.removeCallbacks(mReadMeminfoRunnable);
+ mSubscriber = null;
+ mResultStore.removePublisherData(MemoryPublisher.class.getSimpleName());
+ }
+
+ private void readMemInfo() {
+ if (mSubscriber == null) {
+ return;
+ }
+ mTraceLog.traceBegin("Reading /proc/meminfo and publishing");
+ // Read timestamp and meminfo and create published data
+ PersistableBundle data = new PersistableBundle();
+ data.putLong(DATA_BUNDLE_KEY_TIMESTAMP, System.currentTimeMillis());
+ String meminfo;
+ try {
+ meminfo = new String(Files.readAllBytes(mMeminfoPath));
+ } catch (IOException e) {
+ // Return failure to client as error
+ onPublisherFailure(Arrays.asList(mSubscriber.mMetricsConfig), e);
+ resetPublisher();
+ mTraceLog.traceEnd();
+ return;
+ }
+ data.putString(DATA_BUNDLE_KEY_MEMINFO, meminfo);
+ // add sessions info to published data if available
+ if (mSessionAnnotation != null) {
+ mSessionAnnotation.addAnnotationsToBundle(data);
+ }
+ // publish data, enqueue data for script execution
+ int numPendingTasks = mSubscriber.push(data);
+ if (mSubscriber.isDone()) {
+ // terminate the MetricsConfig
+ onConfigFinished(mSubscriber.mMetricsConfig);
+ resetPublisher();
+ mTraceLog.traceEnd();
+ return;
+ }
+
+ // update publisher state
+ if (mPublisherState == null) {
+ mPublisherState = new PersistableBundle();
+ }
+ mPublisherState.putBoolean(
+ BUNDLE_KEY_COLLECT_INDEFINITELY,
+ mSubscriber.mCollectIndefinitely);
+ mPublisherState.putInt(
+ BUNDLE_KEY_NUM_SNAPSHOTS_UNTIL_FINISH,
+ mSubscriber.mNumSnapshotsLeft);
+ mResultStore.putPublisherData(MemoryPublisher.class.getSimpleName(), mPublisherState);
+
+ // if there are too many pending tasks from this publisher, throttle this publisher
+ // by reducing the publishing frequency
+ int delayMillis = numPendingTasks < mSubscriber.mMaxPendingTasks
+ ? mSubscriber.mPublisherProto.getMemory().getReadIntervalSec() * MILLIS_IN_SECOND
+ : THROTTLE_MILLIS;
+ // schedule the next Runnable to read meminfo
+ mTelemetryHandler.postDelayed(mReadMeminfoRunnable, delayMillis);
+ mTraceLog.traceEnd();
+ }
+
+ private static final class MemorySubscriberWrapper {
+ /**
+ * Whether to keep collecting the meminfo snapshots until end of MetricsConfig lifecycle or
+ * MetricsConfig removed.
+ * This flag should be set to true when the max_snapshots field is unspecified in
+ * {@link TelemetryProto.Publisher}.
+ */
+ private boolean mCollectIndefinitely;
+ /**
+ * Number of snapshots until the publisher stops collecting data.
+ */
+ private int mNumSnapshotsLeft;
+ /**
+ * Maximum number of memory-related pending tasks before throttling this publisher
+ */
+ private int mMaxPendingTasks;
+ private DataSubscriber mDataSubscriber;
+ private TelemetryProto.MetricsConfig mMetricsConfig;
+ private TelemetryProto.Publisher mPublisherProto;
+
+ private MemorySubscriberWrapper(
+ DataSubscriber dataSubscriber, int numSnapshotsLeft, boolean collectIndefinitely) {
+ mDataSubscriber = dataSubscriber;
+ mNumSnapshotsLeft = numSnapshotsLeft;
+ mCollectIndefinitely = collectIndefinitely;
+ mMetricsConfig = dataSubscriber.getMetricsConfig();
+ mPublisherProto = dataSubscriber.getPublisherParam();
+ mMaxPendingTasks = mPublisherProto.getMemory().getMaxPendingTasks();
+ }
+
+ /** Publishes data and returns the number of pending tasks by this publisher. */
+ private int push(PersistableBundle data) {
+ if (mNumSnapshotsLeft > 0) {
+ mNumSnapshotsLeft--;
+ }
+ return mDataSubscriber.push(data);
+ }
+
+ private boolean isDone() {
+ if (mCollectIndefinitely) {
+ return false;
+ }
+ return mNumSnapshotsLeft == 0;
+ }
+ }
+}
diff --git a/service/src/com/android/car/telemetry/publisher/PublisherFactory.java b/service/src/com/android/car/telemetry/publisher/PublisherFactory.java
index 1251d06324..735ec2bb31 100644
--- a/service/src/com/android/car/telemetry/publisher/PublisherFactory.java
+++ b/service/src/com/android/car/telemetry/publisher/PublisherFactory.java
@@ -59,7 +59,8 @@ public class PublisherFactory {
private CarTelemetrydPublisher mCarTelemetrydPublisher;
private StatsPublisher mStatsPublisher;
private ConnectivityPublisher mConnectivityPublisher;
- private AbstractPublisher.PublisherFailureListener mFailureListener;
+ private MemoryPublisher mMemoryPublisher;
+ private AbstractPublisher.PublisherListener mPublisherListener;
// To enable publishers to subscribe to session updates if needed.
private final SessionController mSessionController;
// To enable publishers to store pulled data in the event of suspend-to-RAM or shutdown.
@@ -85,20 +86,20 @@ public class PublisherFactory {
/** Returns the publisher by given type. This method is thread-safe. */
@NonNull
public AbstractPublisher getPublisher(@NonNull TelemetryProto.Publisher.PublisherCase type) {
- Preconditions.checkState(mFailureListener != null, "PublisherFactory is not initialized");
+ Preconditions.checkState(mPublisherListener != null, "PublisherFactory is not initialized");
// No need to optimize locks, as this method is infrequently called.
synchronized (mLock) {
switch (type.getNumber()) {
case TelemetryProto.Publisher.VEHICLE_PROPERTY_FIELD_NUMBER:
if (mVehiclePropertyPublisher == null) {
mVehiclePropertyPublisher = new VehiclePropertyPublisher(
- mCarPropertyService, mFailureListener, mTelemetryHandler);
+ mCarPropertyService, mPublisherListener, mTelemetryHandler);
}
return mVehiclePropertyPublisher;
case TelemetryProto.Publisher.CARTELEMETRYD_FIELD_NUMBER:
if (mCarTelemetrydPublisher == null) {
mCarTelemetrydPublisher = new CarTelemetrydPublisher(
- mFailureListener, mTelemetryHandler);
+ mPublisherListener, mTelemetryHandler);
}
return mCarTelemetrydPublisher;
case TelemetryProto.Publisher.STATS_FIELD_NUMBER:
@@ -107,7 +108,7 @@ public class PublisherFactory {
Preconditions.checkState(stats != null, "StatsManager not found");
StatsManagerProxy statsManager = new StatsManagerImpl(stats);
mStatsPublisher = new StatsPublisher(
- mFailureListener, statsManager, mPublisherDirectory,
+ mPublisherListener, statsManager, mPublisherDirectory,
mTelemetryHandler);
}
return mStatsPublisher;
@@ -118,12 +119,18 @@ public class PublisherFactory {
mContext.getSystemService(NetworkStatsManager.class));
mConnectivityPublisher =
new ConnectivityPublisher(
- mFailureListener,
+ mPublisherListener,
new NetworkStatsManagerProxy(networkStatsManager),
mTelemetryHandler, mResultStore, mSessionController,
mUidMapper);
}
return mConnectivityPublisher;
+ case TelemetryProto.Publisher.MEMORY_FIELD_NUMBER:
+ if (mMemoryPublisher == null) {
+ mMemoryPublisher = new MemoryPublisher(
+ mPublisherListener, mTelemetryHandler, mResultStore);
+ }
+ return mMemoryPublisher;
default:
throw new IllegalArgumentException(
"Publisher type " + type + " is not supported");
@@ -144,17 +151,20 @@ public class PublisherFactory {
if (mStatsPublisher != null) {
mStatsPublisher.removeAllDataSubscribers();
}
+ if (mMemoryPublisher != null) {
+ mMemoryPublisher.removeAllDataSubscribers();
+ }
}
/**
- * Initializes the factory and sets the publisher failure listener for all the publishers.
+ * Initializes the factory and sets the publisher listener for all the publishers.
* This is expected to be called before {@link #getPublisher} method. This is not the best
* approach, but it suits for this case.
*/
- public void initialize(@NonNull AbstractPublisher.PublisherFailureListener listener) {
+ public void initialize(@NonNull AbstractPublisher.PublisherListener listener) {
Preconditions.checkState(
- mFailureListener == null, "PublisherFactory is already initialized");
- mFailureListener = listener;
+ mPublisherListener == null, "PublisherFactory is already initialized");
+ mPublisherListener = listener;
for (TelemetryProto.Publisher.PublisherCase publisher : sForceInitPublishers) {
getPublisher(publisher);
}
diff --git a/service/src/com/android/car/telemetry/publisher/StatsPublisher.java b/service/src/com/android/car/telemetry/publisher/StatsPublisher.java
index 2979993605..7b40eb58bc 100644
--- a/service/src/com/android/car/telemetry/publisher/StatsPublisher.java
+++ b/service/src/com/android/car/telemetry/publisher/StatsPublisher.java
@@ -166,11 +166,11 @@ public class StatsPublisher extends AbstractPublisher {
private final PersistableBundle mSavedStatsConfigs;
StatsPublisher(
- @NonNull PublisherFailureListener failureListener,
+ @NonNull PublisherListener listener,
@NonNull StatsManagerProxy statsManager,
@NonNull File publisherDirectory,
@NonNull Handler telemetryHandler) {
- super(failureListener);
+ super(listener);
mStatsManager = statsManager;
mTelemetryHandler = telemetryHandler;
mSavedStatsConfigsFile = new File(publisherDirectory, SAVED_STATS_CONFIGS_FILE);
diff --git a/service/src/com/android/car/telemetry/publisher/VehiclePropertyPublisher.java b/service/src/com/android/car/telemetry/publisher/VehiclePropertyPublisher.java
index c503e88a57..64bed3f368 100644
--- a/service/src/com/android/car/telemetry/publisher/VehiclePropertyPublisher.java
+++ b/service/src/com/android/car/telemetry/publisher/VehiclePropertyPublisher.java
@@ -79,9 +79,9 @@ public class VehiclePropertyPublisher extends AbstractPublisher {
public VehiclePropertyPublisher(
@NonNull CarPropertyService carPropertyService,
- @NonNull PublisherFailureListener failureListener,
+ @NonNull PublisherListener listener,
@NonNull Handler handler) {
- super(failureListener);
+ super(listener);
mCarPropertyService = carPropertyService;
mTelemetryHandler = handler;
// Load car property list once, as the list doesn't change runtime.
diff --git a/service/src/com/android/car/telemetry/scriptexecutorinterface/IScriptExecutorListener.aidl b/service/src/com/android/car/telemetry/scriptexecutorinterface/IScriptExecutorListener.aidl
index 99fed2dad9..2857889241 100644
--- a/service/src/com/android/car/telemetry/scriptexecutorinterface/IScriptExecutorListener.aidl
+++ b/service/src/com/android/car/telemetry/scriptexecutorinterface/IScriptExecutorListener.aidl
@@ -54,9 +54,21 @@ interface IScriptExecutorListener {
void onError(int errorType, String message, @nullable String stackTrace);
/**
+ * Called by ScriptExecutor when a function completes successfully and produces a
+ * metrics report. The script is not "finished" yet.
+ * It can also provide optional state that the script wants CarTelemetryService to persist.
+ *
+ * @param report metrics report that will be uploaded.
+ * @param stateToPersist key-value pairs to persist
+ */
+ void onMetricsReport(
+ in PersistableBundle report,
+ in @nullable PersistableBundle stateToPersist);
+
+ /**
* Any changes to the following ERROR_TYPE_* constants must be also reflected in the following files:
- * p/s/C/package/ScriptExecutor/src/ScriptExecutorListener.h
- * p/s/C/service/src/com/android/car/telemetry/proto/telemetry.proto
+ * p/s/C/packages/ScriptExecutor/src/ScriptExecutorListener.h
+ * p/s/C/car-lib/src/android/car/telemetry/telemetry.proto
*/
/**
@@ -80,5 +92,10 @@ interface IScriptExecutorListener {
* inputs outside of expected range.
*/
const int ERROR_TYPE_LUA_SCRIPT_ERROR = 3;
+
+ /**
+ * Used to log errors due to publisher failure.
+ */
+ const int ERROR_TYPE_PUBLISHER_FAILED = 4;
}
diff --git a/service/src/com/android/car/user/InitialUserSetter.java b/service/src/com/android/car/user/InitialUserSetter.java
index 3fc703b17d..1d12194130 100644
--- a/service/src/com/android/car/user/InitialUserSetter.java
+++ b/service/src/com/android/car/user/InitialUserSetter.java
@@ -392,7 +392,7 @@ final class InitialUserSetter {
}
private void executeDefaultBehavior(@NonNull InitialUserInfo info, boolean fallback) {
- if (!hasInitialUser()) {
+ if (!hasValidInitialUser()) {
if (DBG) Slogf.d(TAG, "executeDefaultBehavior(): no initial user, creating it");
createAndSwitchUser(new Builder(TYPE_CREATE)
.setNewUserName(mNewUserName)
@@ -807,6 +807,9 @@ final class InitialUserSetter {
return users;
}
+ // TODO(b/231473748): this method should NOT be used to define if it's the first boot - we
+ // should create a new method for that instead (which would check the proper signals) and change
+ // CarUserService.getInitialUserInfoRequestType() to use it instead
/**
* Checks whether the device has an initial user that can be switched to.
*/
@@ -821,6 +824,25 @@ final class InitialUserSetter {
return false;
}
+ // TODO(b/231473748): temporary method that ignores ephemeral user while hasInitialUser() is
+ // used to define if it's first boot - once there is an isInitialBoot() for that purpose, this
+ // method should be removed (and its logic moved to hasInitialUser())
+ @VisibleForTesting
+ boolean hasValidInitialUser() {
+ // TODO(b/231473748): should call method that ignores partial, dying, or pre-created
+ List<UserHandle> allUsers = getAllUsers();
+ for (int i = 0; i < allUsers.size(); i++) {
+ UserHandle user = allUsers.get(i);
+ if (mUserHandleHelper.isManagedProfile(user)
+ || mUserHandleHelper.isEphemeralUser(user)) {
+ continue;
+ }
+
+ return true;
+ }
+ return false;
+ }
+
private static List<Integer> userListToUserIdList(List<UserHandle> allUsers) {
ArrayList<Integer> list = new ArrayList<>(allUsers.size());
for (int i = 0; i < allUsers.size(); i++) {
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/backup_restore_fragment.xml b/tests/EmbeddedKitchenSinkApp/res/layout/backup_restore_fragment.xml
index c906c02e25..14efb5f934 100644
--- a/tests/EmbeddedKitchenSinkApp/res/layout/backup_restore_fragment.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/backup_restore_fragment.xml
@@ -27,6 +27,18 @@
android:layout_height="match_parent"
android:orientation="vertical" >
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+ <Button
+ android:id="@+id/show_transport"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Show Transport"/>
+
+ </LinearLayout>
+
<!-- actions required -->
<LinearLayout android:layout_width="match_parent"
android:layout_height="wrap_content"
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/car_telemetry_test.xml b/tests/EmbeddedKitchenSinkApp/res/layout/car_telemetry_test.xml
index 297370545d..2a5d05ffeb 100644
--- a/tests/EmbeddedKitchenSinkApp/res/layout/car_telemetry_test.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/car_telemetry_test.xml
@@ -339,6 +339,31 @@
android:layout_height="wrap_content"
android:text="@string/get_report"/>
</LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/memory_config"/>
+ <Button
+ android:id="@+id/send_memory_config"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/add_metrics_config"/>
+ <Button
+ android:id="@+id/remove_memory_config"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/remove_metrics_config"/>
+ <Button
+ android:id="@+id/get_memory_report"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/get_report"/>
+ </LinearLayout>
</LinearLayout> <!-- @+id/metrics_config_buttons -->
<TextView
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/sensors.xml b/tests/EmbeddedKitchenSinkApp/res/layout/sensors.xml
index a788d91d53..9349620f70 100644
--- a/tests/EmbeddedKitchenSinkApp/res/layout/sensors.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/sensors.xml
@@ -13,85 +13,172 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
- android:layout_height="match_parent"
- android:orientation="vertical">
- <!-- Top area -->
- <LinearLayout
- android:id="@+id/location_layout"
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:scrollbars="none"
+ android:layout_weight="1">
+ <TableLayout
+ android:id="@+id/table_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:orientation="vertical"
- android:layout_marginLeft="30dp">
- <TextView
- android:id="@+id/location_title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/location_title"
- android:textStyle="bold"/>
- <TextView
- android:id="@+id/location_info"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:scrollbars="vertical"/>
- <TextView
- android:id="@+id/accel_info"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
- <TextView
- android:id="@+id/gyro_info"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
- <TextView
- android:id="@+id/mag_info"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
- <TextView
- android:id="@+id/accel_uncal_info"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
- <TextView
- android:id="@+id/gyro_uncal_info"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
- <TextView
- android:id="@+id/accel_limited_axes_info"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
- <TextView
- android:id="@+id/gyro_limited_axes_info"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
- <TextView
- android:id="@+id/accel_limited_axes_uncal_info"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
- <TextView
- android:id="@+id/gyro_limited_axes_uncal_info"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
- </LinearLayout>
-
- <!-- Bottom area -->
- <LinearLayout
- android:id="@+id/sensor_layout"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="vertical"
+ android:layout_marginRight="30dp"
android:layout_marginLeft="30dp"
- android:layout_below="@+id/location_layout">
- <TextView
- android:id="@+id/sensor_title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/sensor_title"
- android:textStyle="bold"/>
- <TextView
- android:id="@+id/car_sensor_info"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:scrollbars="vertical"/>
- </LinearLayout>
+ android:isScrollContainer="true">
+ <!-- Location Rows -->
+ <TableRow
+ android:id="@+id/location_title_row"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="5dp">
+ <TextView
+ android:id="@+id/location_title_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/location_title"
+ android:textStyle="bold"/>
+ </TableRow>
+ <TableRow
+ android:id="@+id/location_info_row"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="5dp">
+ <TextView
+ android:id="@+id/location_info"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:scrollbars="vertical"/>
+ </TableRow>
+
+ <!-- Sensor Rows -->
+ <TableRow
+ android:id="@+id/sensor_title_row"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="5dp"
+ android:layout_marginBottom="5dp">
+ <TextView
+ android:id="@+id/sensor_title_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/sensor_title"
+ android:textStyle="bold"/>
+ </TableRow>
+ <TableRow
+ android:id="@+id/accel_info_row"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="5dp">
+ <TextView
+ android:id="@+id/accel_info"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ </TableRow>
+ <TableRow
+ android:id="@+id/gyro_info_row"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="5dp">
+ <TextView
+ android:id="@+id/gyro_info"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ </TableRow>
+ <TableRow
+ android:id="@+id/mag_info_row"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="5dp">
+ <TextView
+ android:id="@+id/mag_info"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ </TableRow>
+ <TableRow
+ android:id="@+id/accel_uncal_info_row"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="5dp">
+ <TextView
+ android:id="@+id/accel_uncal_info"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ </TableRow>
+ <TableRow
+ android:id="@+id/gyro_uncal_info_row"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="5dp">
+ <TextView
+ android:id="@+id/gyro_uncal_info"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ </TableRow>
+ <TableRow
+ android:id="@+id/accel_limited_axes_info_row"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="5dp">
+ <TextView
+ android:id="@+id/accel_limited_axes_info"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ </TableRow>
+ <TableRow
+ android:id="@+id/gyro_limited_axes_info_row"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="5dp">
+ <TextView
+ android:id="@+id/gyro_limited_axes_info"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ </TableRow>
+ <TableRow
+ android:id="@+id/accel_limited_axes_uncal_info_row"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="5dp">
+ <TextView
+ android:id="@+id/accel_limited_axes_uncal_info"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ </TableRow>
+ <TableRow
+ android:id="@+id/gyro_limited_axes_uncal_info_row"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="5dp">
+ <TextView
+ android:id="@+id/gyro_limited_axes_uncal_info"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ </TableRow>
+
+ <!-- Car Sensor Rows -->
+ <TableRow
+ android:id="@+id/car_sensor_title_row"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="5dp"
+ android:layout_marginBottom="5dp">
+ <TextView
+ android:id="@+id/car_sensor_title_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/car_sensor_title"
+ android:textStyle="bold"/>
+ </TableRow>
+ <TableRow
+ android:id="@+id/car_sensor_info_row"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="5dp">
+ <TextView
+ android:id="@+id/car_sensor_info"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ </TableRow>
-</RelativeLayout>
+ </TableLayout>
+</ScrollView> \ No newline at end of file
diff --git a/tests/EmbeddedKitchenSinkApp/res/values/strings.xml b/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
index a4b22e716d..7c2af2f3e2 100644
--- a/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
@@ -241,8 +241,9 @@
<string name="radio_na" translatable="false">N/A</string>
<!-- sensors test -->
- <string name="location_title" translatable="false">Location/Orientation:</string>
- <string name="sensor_title" translatable="false">Car Sensor Data:</string>
+ <string name="location_title" translatable="false">Location Data:</string>
+ <string name="sensor_title" translatable="false">Sensor Data:</string>
+ <string name="car_sensor_title" translatable="false">Car Sensor Data:</string>
<string name="sensor_na" translatable="false">N/A</string>
<string name="sensor_environment" translatable="false">Environment[%1$s]: temperature=%2$s</string>
@@ -395,6 +396,7 @@
<string name="wtf_occurred_config" translatable="false">wtf_occurred:</string>
<string name="wifi_netstats_config" translatable="false">wifi_netstats:</string>
<string name="stats_and_connectivity_config" translatable="false">stats and connectivity:</string>
+ <string name="memory_config" translatable="false">memory:</string>
<string name="add_metrics_config" translatable="false">Add MetricsConfig</string>
<string name="remove_metrics_config" translatable="false">Remove MetricsConfig</string>
<string name="download_data" translatable="false">Download Data</string>
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/backup/BackupAndRestoreFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/backup/BackupAndRestoreFragment.java
index 4f5049d948..ae434da926 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/backup/BackupAndRestoreFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/backup/BackupAndRestoreFragment.java
@@ -19,7 +19,13 @@ package com.google.android.car.kitchensink.backup;
import android.annotation.Nullable;
import android.app.AlertDialog;
import android.app.backup.BackupManager;
+import android.app.backup.RestoreObserver;
+import android.app.backup.RestoreSession;
+import android.app.backup.RestoreSet;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
import android.os.Bundle;
+import android.os.UserHandle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -30,18 +36,27 @@ import androidx.fragment.app.Fragment;
import com.google.android.car.kitchensink.R;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
public final class BackupAndRestoreFragment extends Fragment {
private static final String TAG = BackupAndRestoreFragment.class.getSimpleName();
private static final String TRANSPORT_DIR_NAME =
"com.google.android.car.kitchensink.backup.KitchenSinkBackupTransport";
+ private static final long CURRENT_SET_TOKEN = 1;
+
+ private final int mUserId = UserHandle.myUserId();
private BackupManager mBackupManager;
private Button mBackupButton;
private Button mRestoreButton;
+ private Button mShowTransportButton;
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -57,32 +72,156 @@ public final class BackupAndRestoreFragment extends Fragment {
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
+ mShowTransportButton = view.findViewById(R.id.show_transport);
mBackupButton = view.findViewById(R.id.backup);
mRestoreButton = view.findViewById(R.id.restore);
+ mShowTransportButton.setOnClickListener((v) -> showTransport());
mBackupButton.setOnClickListener((v) -> backup());
mRestoreButton.setOnClickListener((v) -> restore());
}
- private void backup() {
+ private void showTransport() {
boolean isEnabled = mBackupManager.isBackupEnabled();
Log.v(TAG, "backup is enabled: " + isEnabled);
if (!isEnabled) {
- mBackupManager.setBackupEnabled(true);
+ showMessage("Backup is not enabled yet.\nEnable backup first.");
+ return;
}
String[] allTransports = mBackupManager.listAllTransports();
Log.v(TAG, "All transports: " + Arrays.toString(allTransports));
String currentTransport = mBackupManager.getCurrentTransport();
- Log.v(TAG, "Current Transport: " + currentTransport);
+ Log.v(TAG, "Current Transport:" + currentTransport);
- StringBuilder sb = new StringBuilder();
+ StringBuilder sb = new StringBuilder().append("All transports: ");
Arrays.stream(allTransports).forEach(t -> sb.append('\n').append(t));
- showMessage("Backup button clicked.\nAll transports: " + sb
- + "\nCurrent Transport: " + currentTransport);
+ sb.append("\nCurrent Transport:\n").append(currentTransport);
+ showMessage(sb.toString());
+ }
+
+ private void backup() {
+ boolean isEnabled = mBackupManager.isBackupEnabled();
+ Log.v(TAG, "backup is enabled: " + isEnabled);
+ if (!isEnabled) {
+ showMessage("Backup is not enabled yet.\nEnable backup first.");
+ return;
+ }
+
+ Executors.newSingleThreadExecutor().execute(() -> {
+ backupNow();
+ requireActivity().runOnUiThread(() ->
+ showMessage("backup is queued, waiting for it to complete."));
+ });
+ }
+
+ private void backupNow() {
+ PackageManager packageManager = getActivity().getPackageManager();
+ List<PackageInfo> installedPackages = null;
+ try {
+ installedPackages = packageManager.getInstalledPackagesAsUser(/* flags= */0, mUserId);
+ Log.v(TAG, "installed packages: " + installedPackages);
+ } catch (Exception e) {
+ Log.e(TAG, "exception in backupNow()", e);
+ return;
+ }
+
+ if (installedPackages != null) {
+ String[] packages = installedPackages.stream().map(p -> p.packageName)
+ .toArray(String[]::new);
+
+ List<String> filteredPackages = new ArrayList<>();
+
+ for (String p : packages) {
+ try {
+ boolean eligible = mBackupManager.isAppEligibleForBackup(p);
+ Log.v(TAG, "eligible: " + eligible + " package name: " + p);
+ if (eligible) {
+ filteredPackages.add(p);
+ Log.v(TAG, "adding package to filtered packages");
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "isAppEligibleForBackup() cannot connect: ", e);
+ }
+ }
+ // Currently, the observer is not waiting enough time for the backup to finish
+ // Will implement it later for full functionality
+ int res = mBackupManager.requestBackup(packages, /* observer= */ null);
+ Log.v(TAG, "request backup returned code: " + res);
+ if (res == 0) {
+ Log.v(TAG, "request backup res successful!");
+ }
+ }
}
private void restore() {
- showMessage("restore button clicked.");
+ boolean isEnabled = mBackupManager.isBackupEnabled();
+ Log.v(TAG, "backup is enabled: " + isEnabled);
+ if (!isEnabled) {
+ showMessage("Backup is not enabled yet.\nClick enable backup first.");
+ return;
+ }
+
+ // TODO: use Handler / HandlerThread instead
+ Executors.newSingleThreadExecutor().execute(() -> {
+ restoreNow();
+ requireActivity().runOnUiThread(() -> showMessage("restore is complete"));
+ });
+ }
+
+ private void restoreNow() {
+ RestoreObserverLocal observer = new RestoreObserverLocal();
+ RestoreSession session = null;
+ try {
+ session = mBackupManager.beginRestoreSession();
+ Log.v(TAG, "current restore session: " + session);
+ if (session != null) {
+ int err = session.getAvailableRestoreSets(observer);
+ if (err == 0) {
+ observer.waitForCompletion();
+ int restoreResult = session.restoreAll(CURRENT_SET_TOKEN, observer);
+ Log.v(TAG, "restore all returned code: " + restoreResult);
+ if (restoreResult == 0) {
+ Log.i(TAG, "restore successful!!");
+ }
+ } else {
+ Log.v(TAG, "Unable to contact server for restore" + err);
+ }
+ } else {
+ Log.i(TAG, "No restore session");
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "exception in beginRestoreSession(): ", e);
+ } finally {
+ if (session != null) {
+ try {
+ session.endRestoreSession();
+ } catch (Exception e) {
+ Log.w(TAG, "Failed to end the restore session!", e);
+ }
+ }
+ }
+ }
+
+ private static final class RestoreObserverLocal extends RestoreObserver {
+ final CountDownLatch mLatch = new CountDownLatch(1);
+
+ @Override
+ public void restoreSetsAvailable(RestoreSet[] result) {
+ mLatch.countDown();
+ }
+
+ public void waitForCompletion() {
+ boolean received = false;
+ try {
+ received = mLatch.await(120, TimeUnit.SECONDS);
+ } catch (InterruptedException ex) {
+ Log.e(TAG, "Current thread is stopped during restore: ", ex);
+ Thread.currentThread().interrupt();
+ }
+ if (!received) {
+ Log.w(TAG, "Restore operation is timed out after 120 seconds.");
+ }
+ }
}
private void showMessage(String pattern, Object... args) {
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/telemetry/CarTelemetryTestFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/telemetry/CarTelemetryTestFragment.java
index 88114e35b4..fcab21a85a 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/telemetry/CarTelemetryTestFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/telemetry/CarTelemetryTestFragment.java
@@ -418,6 +418,52 @@ public class CarTelemetryTestFragment extends Fragment {
private static final String STATS_AND_CONNECTIVITY_CONFIG_NAME =
METRICS_CONFIG_STATS_AND_CONNECTIVITY_V1.getName();
+ /** MemoryPublisher section. */
+ private static final String LUA_SCRIPT_ON_MEMORY =
+ new StringBuilder()
+ .append("function onMemory(published_data, state)\n")
+ .append(" local iterations = state['iterations']\n")
+ .append(" if iterations == nil then\n")
+ .append(" iterations = 0\n")
+ .append(" end\n")
+ .append(" state['iterations'] = iterations + 1\n")
+ .append(" report = {}\n")
+ .append(" local ts_key = 'timestamp_' .. iterations\n")
+ .append(" report[ts_key] = published_data['timestamp']\n")
+ .append(" local meminfo = published_data['meminfo']\n")
+ .append(" local available_memory = string.match(meminfo, "
+ + "'.*MemAvailable:%s*(%d+).*')\n")
+ .append(" local mem_key = 'available_memory_' .. iterations\n")
+ .append(" report[mem_key] = available_memory\n")
+ .append(" if iterations >= 2 then \n")
+ .append(" on_script_finished(report)\n")
+ .append(" else \n")
+ .append(" on_metrics_report(report, state)\n")
+ .append(" end\n")
+ .append("end\n")
+ .toString();
+ private static final TelemetryProto.Publisher MEMORY_PUBLISHER =
+ TelemetryProto.Publisher.newBuilder()
+ .setMemory(
+ TelemetryProto.MemoryPublisher.newBuilder()
+ .setReadIntervalSec(3)
+ .setMaxPendingTasks(10)
+ .build())
+ .build();
+ private static final TelemetryProto.MetricsConfig METRICS_CONFIG_MEMORY_V1 =
+ TelemetryProto.MetricsConfig.newBuilder()
+ .setName("memory_config")
+ .setVersion(1)
+ .setScript(LUA_SCRIPT_ON_MEMORY)
+ .addSubscribers(
+ TelemetryProto.Subscriber.newBuilder()
+ .setHandler("onMemory")
+ .setPublisher(MEMORY_PUBLISHER)
+ .setPriority(SCRIPT_EXECUTION_PRIORITY_HIGH))
+ .build();
+ private static final String MEMORY_CONFIG_NAME =
+ METRICS_CONFIG_MEMORY_V1.getName();
+
private final Executor mExecutor = Executors.newSingleThreadExecutor();
private boolean mReceiveReportNotification = false;
@@ -538,6 +584,13 @@ public class CarTelemetryTestFragment extends Fragment {
.setOnClickListener(this::onRemoveDrivingSessionsConfigBtnClick);
view.findViewById(R.id.get_driving_sessions_report)
.setOnClickListener(this::onGetDrivingSessionsReportBtnClick);
+ /** MemoryPublisher section */
+ view.findViewById(R.id.send_memory_config)
+ .setOnClickListener(this::onSendMemoryConfigBtnClick);
+ view.findViewById(R.id.remove_memory_config)
+ .setOnClickListener(this::onRemoveMemoryConfigBtnClick);
+ view.findViewById(R.id.get_memory_report)
+ .setOnClickListener(this::onGetMemoryReportBtnClick);
/** Print mem info button */
view.findViewById(R.id.print_mem_info_btn).setOnClickListener(this::onPrintMemInfoBtnClick);
return view;
@@ -716,6 +769,8 @@ public class CarTelemetryTestFragment extends Fragment {
}
private void onSendWifiNetstatsConfigBtnClick(View view) {
+ showOutput("If the config is added successfully, it will produce a report on the top "
+ + "3 wifi network traffic consumers after 1 driving sessions.");
mCarTelemetryManager.addMetricsConfig(
WIFI_TOP_CONSUMERS_CONFIG_NAME,
METRICS_CONFIG_WIFI_TOP_CONSUMERS
@@ -906,6 +961,26 @@ public class CarTelemetryTestFragment extends Fragment {
WIFI_STATS_DRIVING_SESSIONS_CONFIG_NAME, mExecutor, mListener);
}
+ private void onSendMemoryConfigBtnClick(View view) {
+ showOutput("If the MetricsConfig is added successfully, it will produce 3 metrics "
+ + "reports on available memory. The reports are produced 3 seconds apart. "
+ + "After 3 reports, the MetricsConfig's lifecycle is considered finished.");
+ mCarTelemetryManager.addMetricsConfig(
+ MEMORY_CONFIG_NAME,
+ METRICS_CONFIG_MEMORY_V1.toByteArray(),
+ mExecutor,
+ mAddMetricsConfigCallback);
+ }
+
+ private void onRemoveMemoryConfigBtnClick(View view) {
+ showOutput("Removing MetricsConfig for memory...");
+ mCarTelemetryManager.removeMetricsConfig(MEMORY_CONFIG_NAME);
+ }
+
+ private void onGetMemoryReportBtnClick(View view) {
+ mCarTelemetryManager.getFinishedReport(MEMORY_CONFIG_NAME, mExecutor, mListener);
+ }
+
/** Gets a MemoryInfo object for the device's current memory status. */
private ActivityManager.MemoryInfo getAvailableMemory() {
ActivityManager activityManager = getActivity().getSystemService(ActivityManager.class);
diff --git a/tests/RailwayReferenceApp/Android.bp b/tests/RailwayReferenceApp/Android.bp
new file mode 100644
index 0000000000..4b8badba36
--- /dev/null
+++ b/tests/RailwayReferenceApp/Android.bp
@@ -0,0 +1,49 @@
+// Copyright (C) 2022 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"],
+}
+
+android_app {
+ name: "RailwayReferenceApp",
+
+ srcs: ["src/**/*.java"],
+
+ resource_dirs: ["res"],
+
+ platform_apis: true,
+
+ optimize: {
+ enabled: false,
+ },
+
+ enforce_uses_libs: false,
+ dex_preopt: {
+ enabled: false,
+ },
+
+ privileged: true,
+ required: ["privapp_allowlist_com.google.android.car.networking.railway"],
+
+ certificate: "platform",
+
+ static_libs: [
+ "androidx-constraintlayout_constraintlayout",
+ ],
+
+
+} \ No newline at end of file
diff --git a/tests/RailwayReferenceApp/AndroidManifest.xml b/tests/RailwayReferenceApp/AndroidManifest.xml
new file mode 100644
index 0000000000..5f10ff887d
--- /dev/null
+++ b/tests/RailwayReferenceApp/AndroidManifest.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 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.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.google.android.car.networking.railway">
+
+ <uses-sdk
+ android:minSdkVersion="28"
+ android:targetSdkVersion="33"/>
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+ <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
+
+ <application android:label="RailwayReferenceApp">
+ <activity android:name=".MainActivity"
+ android:label="@string/app_name"
+ android:theme="@android:style/Theme.DeviceDefault.NoActionBar"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/tests/RailwayReferenceApp/res/layout/activity_main.xml b/tests/RailwayReferenceApp/res/layout/activity_main.xml
new file mode 100644
index 0000000000..6e0b6e5082
--- /dev/null
+++ b/tests/RailwayReferenceApp/res/layout/activity_main.xml
@@ -0,0 +1,248 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 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.
+ -->
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_margin="@dimen/margin_size"
+ android:background="@color/activity_background">
+
+ <Button
+ android:id="@+id/updateButton"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:textAppearance="@style/TextAppearance.AppCompat.Button"
+ android:text="@string/update_button_title"
+ android:layout_margin="@dimen/margin_size"
+ app:layout_constraintBottom_toBottomOf="@id/interfaceNameInput"
+ app:layout_constraintEnd_toStartOf="@id/guideline50"
+ app:layout_constraintStart_toStartOf="@id/guideline25"
+ app:layout_constraintTop_toBottomOf="@id/networkCapabilitiesInput"
+ android:backgroundTint="@color/green_button_background"/>
+ <Button
+ android:id="@+id/connectButton"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:textAppearance="@style/TextAppearance.AppCompat.Button"
+ android:text="@string/connect_button_title"
+ android:layout_margin="@dimen/margin_size"
+ app:layout_constraintBottom_toBottomOf="@id/interfaceNameInput2"
+ app:layout_constraintEnd_toStartOf="@id/guideline50"
+ app:layout_constraintStart_toStartOf="@id/guideline25"
+ app:layout_constraintTop_toBottomOf="@id/enableDisableTitle"
+ android:backgroundTint="@color/green_button_background" />
+ <Button
+ android:id="@+id/enableButton"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:textAppearance="@style/TextAppearance.AppCompat.Button"
+ android:text="@string/enable_button_title"
+ android:layout_margin="@dimen/margin_size"
+ app:layout_constraintEnd_toStartOf="@id/guideline25"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/interfaceNameInput2"
+ android:backgroundTint="@color/green_button_background" />
+ <Button
+ android:id="@+id/disableButton"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:textAppearance="@style/TextAppearance.AppCompat.Button"
+ android:text="@string/disable_button_title"
+ android:layout_margin="@dimen/margin_size"
+ app:layout_constraintEnd_toStartOf="@id/guideline50"
+ app:layout_constraintStart_toStartOf="@id/guideline25"
+ app:layout_constraintTop_toBottomOf="@id/interfaceNameInput2"
+ android:backgroundTint="@color/red_button_background"/>
+ <EditText
+ android:id="@+id/allowedPackageNamesInput"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_margin="@dimen/margin_size"
+ android:background="@android:color/white"
+ app:layout_constraintEnd_toStartOf="@id/guideline50"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/allowedPackageNamesTitle" />
+
+ <EditText
+ android:id="@+id/ipConfigurationInput"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_margin="@dimen/margin_size"
+ android:background="@android:color/white"
+ app:layout_constraintEnd_toStartOf="@id/guideline25"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/ipConfigurationTitle" />
+
+ <EditText
+ android:id="@+id/interfaceNameInput"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_margin="@dimen/margin_size"
+ android:background="@android:color/white"
+ app:layout_constraintEnd_toStartOf="@id/guideline25"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/interfaceNameTitle" />
+ <EditText
+ android:id="@+id/networkCapabilitiesInput"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_margin="@dimen/margin_size"
+ android:background="@android:color/white"
+ app:layout_constraintEnd_toStartOf="@id/guideline50"
+ app:layout_constraintStart_toStartOf="@id/guideline25"
+ app:layout_constraintTop_toBottomOf="@id/networkCapabilitiesTitle" />
+
+ <EditText
+ android:id="@+id/interfaceNameInput2"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_margin="@dimen/margin_size"
+ android:background="@android:color/white"
+ app:layout_constraintEnd_toStartOf="@id/guideline25"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/interfaceNameTitle2" />
+ <ScrollView
+ android:id="@+id/currentEthernetNetworksScrollView"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_margin="@dimen/margin_size"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toEndOf="@id/guideline50"
+ app:layout_constraintTop_toBottomOf="@id/currentEthernetNetworksTitle">
+ <TextView
+ android:id="@+id/currentEthernetNetworksOutput"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="@color/scrolling_text_field_background" />
+ </ScrollView>
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_margin="@dimen/margin_size"
+ android:textAppearance="@style/TitleText"
+ android:text="@string/activity_title"
+ app:layout_constraintEnd_toStartOf="@id/guideline50"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent" />
+ <TextView
+ android:id="@+id/updateConfigurationTitle"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_margin="@dimen/margin_size"
+ android:layout_marginTop="32dp"
+ android:textAppearance="@style/SectionHeaderText"
+ android:text="@string/update_configuration_section_title"
+ app:layout_constraintEnd_toStartOf="@id/guideline50"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/title" />
+
+ <TextView
+ android:id="@+id/allowedPackageNamesTitle"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_margin="@dimen/margin_size"
+ android:textAppearance="@style/BodyText"
+ android:text="@string/allowed_package_names_title"
+ app:layout_constraintEnd_toStartOf="@id/guideline50"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/updateConfigurationTitle" />
+
+ <TextView
+ android:id="@+id/ipConfigurationTitle"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_margin="@dimen/margin_size"
+ android:textAppearance="@style/BodyText"
+ android:text="@string/ip_configuration_title"
+ app:layout_constraintEnd_toStartOf="@id/guideline25"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/allowedPackageNamesInput" />
+
+ <TextView
+ android:id="@+id/interfaceNameTitle"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_margin="@dimen/margin_size"
+ android:textAppearance="@style/BodyText"
+ android:text="@string/interface_name_title"
+ app:layout_constraintEnd_toStartOf="@id/guideline25"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/ipConfigurationInput" />
+ <TextView
+ android:id="@+id/networkCapabilitiesTitle"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_margin="@dimen/margin_size"
+ android:textAppearance="@style/BodyText"
+ android:text="@string/network_capabilities_title"
+ app:layout_constraintEnd_toStartOf="@id/guideline50"
+ app:layout_constraintStart_toStartOf="@id/guideline25"
+ app:layout_constraintTop_toBottomOf="@id/allowedPackageNamesInput" />
+
+ <TextView
+ android:id="@+id/enableDisableTitle"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_margin="@dimen/margin_size"
+ android:layout_marginTop="32dp"
+ android:textAppearance="@style/SectionHeaderText"
+ android:text="@string/enable_disable_section_title"
+ app:layout_constraintEnd_toStartOf="@id/guideline50"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/interfaceNameInput" />
+
+ <TextView
+ android:id="@+id/interfaceNameTitle2"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_margin="@dimen/margin_size"
+ android:textAppearance="@style/BodyText"
+ android:text="@string/network_capabilities_title"
+ app:layout_constraintEnd_toStartOf="@id/guideline25"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/enableDisableTitle" />
+
+ <TextView
+ android:id="@+id/currentEthernetNetworksTitle"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_margin="@dimen/margin_size"
+ android:text="@string/current_ethernet_networks_section_title"
+ android:textAppearance="@style/SectionHeaderText"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toEndOf="@id/guideline50"
+ app:layout_constraintTop_toTopOf="parent" />
+
+
+ <androidx.constraintlayout.widget.Guideline
+ android:id="@+id/guideline50"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="@dimen/margin_size"
+ android:orientation="vertical"
+ app:layout_constraintGuide_percent=".5" />
+ <androidx.constraintlayout.widget.Guideline
+ android:id="@+id/guideline25"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="@dimen/margin_size"
+ android:orientation="vertical"
+ app:layout_constraintGuide_percent=".25" />
+
+</androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file
diff --git a/tests/RailwayReferenceApp/res/values/colors.xml b/tests/RailwayReferenceApp/res/values/colors.xml
new file mode 100644
index 0000000000..5d7cd14062
--- /dev/null
+++ b/tests/RailwayReferenceApp/res/values/colors.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 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.
+ -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <color name="activity_background">#E8EAED</color> <!-- gray -->
+ <color name="scrolling_text_field_background">#F1F3F4</color> <!-- light gray -->
+ <color name="green_button_background">#198639</color>
+ <color name="red_button_background">#DC362E</color>
+</resources> \ No newline at end of file
diff --git a/tests/RailwayReferenceApp/res/values/dimens.xml b/tests/RailwayReferenceApp/res/values/dimens.xml
new file mode 100644
index 0000000000..45f6ff2258
--- /dev/null
+++ b/tests/RailwayReferenceApp/res/values/dimens.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 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.
+ -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <dimen name="margin_size">16dp</dimen>
+</resources> \ No newline at end of file
diff --git a/tests/RailwayReferenceApp/res/values/strings.xml b/tests/RailwayReferenceApp/res/values/strings.xml
new file mode 100644
index 0000000000..8886c371b9
--- /dev/null
+++ b/tests/RailwayReferenceApp/res/values/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 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.
+ -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" translatable="false">Railway Reference App</string>
+ <string name="update_button_title">Update</string>
+ <string name="connect_button_title">Test Connect</string>
+ <string name="enable_button_title">Enable</string>
+ <string name="disable_button_title">Disable</string>
+ <string name="activity_title">Vehicular Networking App</string>
+ <string name="update_configuration_section_title">Update Configuration</string>
+ <string name="allowed_package_names_title">Allowed Package Names</string>
+ <string name="ip_configuration_title">Update Configuration (IPv4 only)</string>
+ <string name="interface_name_title">Interface Name</string>
+ <string name="network_capabilities_title">Network Capabilities</string>
+ <string name="enable_disable_section_title">Enable/Disable/Connect Interface</string>
+ <string name="current_ethernet_networks_section_title">Current Ethernet Networks</string>
+</resources> \ No newline at end of file
diff --git a/tests/RailwayReferenceApp/res/values/styles.xml b/tests/RailwayReferenceApp/res/values/styles.xml
new file mode 100644
index 0000000000..ffd3e8d61d
--- /dev/null
+++ b/tests/RailwayReferenceApp/res/values/styles.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 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.
+ -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <style name="BodyText" parent="TextAppearance.AppCompat.Body2">
+ <item name="android:textColor">@android:color/black</item>
+ </style>
+ <style name="SectionHeaderText" parent="TextAppearance.AppCompat.Subhead">
+ <item name="android:textColor">@android:color/black</item>
+ </style>
+ <style name="TitleText" parent="TextAppearance.AppCompat.Title">
+ <item name="android:textColor">@android:color/black</item>
+ </style>
+</resources> \ No newline at end of file
diff --git a/tests/RailwayReferenceApp/src/com/google/android/car/networking/railway/MainActivity.java b/tests/RailwayReferenceApp/src/com/google/android/car/networking/railway/MainActivity.java
new file mode 100644
index 0000000000..3983d4aa99
--- /dev/null
+++ b/tests/RailwayReferenceApp/src/com/google/android/car/networking/railway/MainActivity.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2022 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.networking.railway;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public final class MainActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+ }
+}
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarUserManagerLifecycleEventFilterTest.java b/tests/android_car_api_test/src/android/car/apitest/CarUserManagerLifecycleEventFilterTest.java
index 5c083224d9..a25c6932be 100644
--- a/tests/android_car_api_test/src/android/car/apitest/CarUserManagerLifecycleEventFilterTest.java
+++ b/tests/android_car_api_test/src/android/car/apitest/CarUserManagerLifecycleEventFilterTest.java
@@ -137,9 +137,11 @@ public final class CarUserManagerLifecycleEventFilterTest extends CarMultiUserTe
// Switch back to the initial user
switchUser(initialUserId);
- // Wait to receive events.
+ // Wait for all listeners to receive all expected events.
waitUntil("Listeners have not received all expected events", EVENTS_TIMEOUT_MS,
- () -> mListeners[4].listener.getAllReceivedEvents().size() == 2);
+ () -> (mListeners[0].listener.getAllReceivedEvents().size() == 3
+ && mListeners[1].listener.getAllReceivedEvents().size() == 3
+ && mListeners[4].listener.getAllReceivedEvents().size() == 2));
// unregister listeners.
for (Listener listener : mListeners) {
diff --git a/tests/carservice_test/src/com/android/car/CarTelemetryManagerTest.java b/tests/carservice_test/src/com/android/car/CarTelemetryManagerTest.java
index 1448d687b9..f53e8ef1b7 100644
--- a/tests/carservice_test/src/com/android/car/CarTelemetryManagerTest.java
+++ b/tests/carservice_test/src/com/android/car/CarTelemetryManagerTest.java
@@ -117,6 +117,27 @@ public class CarTelemetryManagerTest extends MockedCarTestBase {
}
@Test
+ public void testAddMetricsConfig_invalidFieldInConfig_shouldFail() throws Exception {
+ // configure a bad publisher, read interval is not allowed to be less than 1
+ TelemetryProto.Publisher.Builder badPublisher =
+ TelemetryProto.Publisher.newBuilder().setMemory(
+ TelemetryProto.MemoryPublisher.newBuilder().setReadIntervalSec(-1));
+ TelemetryProto.Subscriber.Builder badSubscriber =
+ TelemetryProto.Subscriber.newBuilder()
+ .setHandler("handler_fn_1")
+ .setPublisher(badPublisher);
+ TelemetryProto.MetricsConfig config =
+ METRICS_CONFIG_V1.toBuilder().addSubscribers(badSubscriber).build();
+
+ mCarTelemetryManager.addMetricsConfig(
+ CONFIG_NAME, config.toByteArray(), DIRECT_EXECUTOR, mAddMetricsConfigCallback);
+
+ mAddMetricsConfigCallback.mSemaphore.acquire();
+ assertThat(mAddMetricsConfigCallback.mAddConfigStatusMap.get(CONFIG_NAME)).isEqualTo(
+ STATUS_ADD_METRICS_CONFIG_PARSE_FAILED);
+ }
+
+ @Test
public void testSetClearListener() {
CarTelemetryManager.ReportReadyListener listener = metricsConfigName -> { };
diff --git a/tests/carservice_unit_test/src/com/android/car/CarPropertyServiceUnitTest.java b/tests/carservice_unit_test/src/com/android/car/CarPropertyServiceUnitTest.java
index a60201f45c..f7588f72ea 100644
--- a/tests/carservice_unit_test/src/com/android/car/CarPropertyServiceUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/CarPropertyServiceUnitTest.java
@@ -18,6 +18,8 @@ package com.android.car;
import static android.car.hardware.property.CarPropertyManager.SENSOR_RATE_ONCHANGE;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyFloat;
@@ -33,10 +35,13 @@ import android.car.VehicleAreaType;
import android.car.VehiclePropertyIds;
import android.car.hardware.CarPropertyConfig;
import android.car.hardware.CarPropertyValue;
+import android.car.hardware.property.CarPropertyEvent;
import android.car.hardware.property.ICarPropertyEventListener;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Pair;
import android.util.SparseArray;
import com.android.car.hal.PropertyHalService;
@@ -47,6 +52,10 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
@RunWith(MockitoJUnitRunner.class)
public final class CarPropertyServiceUnitTest {
@@ -196,4 +205,68 @@ public final class CarPropertyServiceUnitTest {
verify(mHalService).unsubscribeProperty(HVAC_TEMP);
}
+
+ private static class EventListener extends ICarPropertyEventListener.Stub{
+ private final CarPropertyService mService;
+ private List<CarPropertyEvent> mEvents;
+ private Exception mException;
+
+ EventListener(CarPropertyService service) {
+ mService = service;
+ }
+
+ @Override
+ public void onEvent(List<CarPropertyEvent> events) throws RemoteException {
+ CountDownLatch latch = new CountDownLatch(1);
+ new Thread(() -> {
+ // Call a function in CarPropertyService to make sure there is no dead lock. This
+ // call doesn't actually do anything. We must use a new thread here because
+ // the same thread can obtain the same lock again even if there is a dead lock.
+ mService.getReadPermission(0);
+ latch.countDown();
+ }, "onEventThread").start();
+ try {
+ if (!latch.await(5, TimeUnit.SECONDS)) {
+ mException = new Exception("timeout waiting for getReadPermission, dead lock?");
+ return;
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ mException = new Exception("waiting for onEvent thread interrupted");
+ return;
+ }
+ mEvents = events;
+ }
+
+ List<CarPropertyEvent> getEvents() throws Exception {
+ if (mException != null) {
+ throw mException;
+ }
+ return mEvents;
+ }
+ }
+
+ @Test
+ public void testOnEventCallback_MustNotHaveDeadLock() throws Exception {
+ // This test checks that CarPropertyService must not hold any lock while calling
+ // ICarPropertyListener's onEvent callback, otherwise it might cause dead lock if
+ // the callback calls another function in CarPropertyService that requires the same lock.
+
+ // We don't care about the result for getReadPermission so just return an empty map.
+ when(mHalService.getPermissionsForAllProperties()).thenReturn(
+ new SparseArray<Pair<String, String>>());
+ mService.init();
+
+ // Initially HVAC_TEMP is not subscribed, so should return -1.
+ when(mHalService.getSampleRate(HVAC_TEMP)).thenReturn(-1f);
+ CarPropertyValue<Float> value = new CarPropertyValue<Float>(HVAC_TEMP, 0, 1.0f);
+ when(mHalService.getPropertySafe(HVAC_TEMP, 0)).thenReturn(value);
+ EventListener listener = new EventListener(mService);
+
+ mService.registerListener(HVAC_TEMP, /* rate= */ SENSOR_RATE_ONCHANGE, listener);
+ List<CarPropertyEvent> events = List.of(new CarPropertyEvent(0, value));
+ mService.onPropertyChange(events);
+
+ assertThat(listener.getEvents()).isEqualTo(events);
+ }
}
diff --git a/tests/carservice_unit_test/src/com/android/car/bluetooth/CarBluetoothServiceTest.java b/tests/carservice_unit_test/src/com/android/car/bluetooth/CarBluetoothServiceTest.java
index d8d5e3e9fd..2761e4ac9b 100644
--- a/tests/carservice_unit_test/src/com/android/car/bluetooth/CarBluetoothServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/bluetooth/CarBluetoothServiceTest.java
@@ -146,16 +146,16 @@ public class CarBluetoothServiceTest {
/**
* Preconditions:
- * - Policy flag is true
+ * - Device connection policy flag is true
*
* Action:
* - Initialize service
*
* Outcome:
- * - Default policy should be created
+ * - Default device connection policy should be created
*/
@Test
- public void testResourceFlagTrue_doCreateDefaultPolicy() {
+ public void testConnectionResourceFlagTrue_doCreateDefaultConnectionPolicy() {
when(mMockResources.getBoolean(
R.bool.useDefaultBluetoothConnectionPolicy)).thenReturn(true);
mCarBluetoothService = new CarBluetoothService(mMockContext, mMockUserSwitchService);
@@ -166,16 +166,16 @@ public class CarBluetoothServiceTest {
/**
* Preconditions:
- * - Policy flag is false
+ * - Device connection policy flag is false
*
* Action:
* - Initialize service
*
* Outcome:
- * - Default policy should not be created
+ * - Default device connection policy should not be created
*/
@Test
- public void testResourceFlagFalse_doNotCreateDefaultPolicy() {
+ public void testConnectionResourceFlagFalse_doNotCreateDefaultConnectionPolicy() {
when(mMockResources.getBoolean(
R.bool.useDefaultBluetoothConnectionPolicy)).thenReturn(false);
mCarBluetoothService = new CarBluetoothService(mMockContext, mMockUserSwitchService);
@@ -183,4 +183,44 @@ public class CarBluetoothServiceTest {
mUserSwitchCallback.onServiceConnected(mMockPerUserCarService);
Assert.assertFalse(mCarBluetoothService.isUsingDefaultConnectionPolicy());
}
+
+ /**
+ * Preconditions:
+ * - Power policy flag is true
+ *
+ * Action:
+ * - Initialize service
+ *
+ * Outcome:
+ * - Default power policy should be created
+ */
+ @Test
+ public void testPowerResourceFlagTrue_doCreateDefaultPowerPolicy() {
+ when(mMockResources.getBoolean(
+ R.bool.useDefaultBluetoothPowerPolicy)).thenReturn(true);
+ mCarBluetoothService = new CarBluetoothService(mMockContext, mMockUserSwitchService);
+ mCarBluetoothService.init();
+ mUserSwitchCallback.onServiceConnected(mMockPerUserCarService);
+ Assert.assertTrue(mCarBluetoothService.isUsingDefaultPowerPolicy());
+ }
+
+ /**
+ * Preconditions:
+ * - Power policy flag is false
+ *
+ * Action:
+ * - Initialize service
+ *
+ * Outcome:
+ * - Default power policy should not be created
+ */
+ @Test
+ public void testPowerResourceFlagFalse_doNotCreateDefaultPowerPolicy() {
+ when(mMockResources.getBoolean(
+ R.bool.useDefaultBluetoothPowerPolicy)).thenReturn(false);
+ mCarBluetoothService = new CarBluetoothService(mMockContext, mMockUserSwitchService);
+ mCarBluetoothService.init();
+ mUserSwitchCallback.onServiceConnected(mMockPerUserCarService);
+ Assert.assertFalse(mCarBluetoothService.isUsingDefaultPowerPolicy());
+ }
}
diff --git a/tests/carservice_unit_test/src/com/android/car/telemetry/CarTelemetryServiceTest.java b/tests/carservice_unit_test/src/com/android/car/telemetry/CarTelemetryServiceTest.java
index 086425ad63..27dfeafc32 100644
--- a/tests/carservice_unit_test/src/com/android/car/telemetry/CarTelemetryServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/telemetry/CarTelemetryServiceTest.java
@@ -22,6 +22,10 @@ import static android.car.telemetry.CarTelemetryManager.STATUS_GET_METRICS_CONFI
import static android.car.telemetry.CarTelemetryManager.STATUS_GET_METRICS_CONFIG_PENDING;
import static android.car.telemetry.CarTelemetryManager.STATUS_GET_METRICS_CONFIG_RUNTIME_ERROR;
+import static com.android.car.telemetry.CarTelemetryService.TASK_PRIORITY_HI;
+import static com.android.car.telemetry.CarTelemetryService.TASK_PRIORITY_LOW;
+import static com.android.car.telemetry.CarTelemetryService.TASK_PRIORITY_MED;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
@@ -29,6 +33,7 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -52,9 +57,11 @@ import com.android.car.CarPropertyService;
import com.android.car.CarServiceUtils;
import com.android.car.power.CarPowerManagementService;
import com.android.car.systeminterface.SystemInterface;
-import com.android.car.systeminterface.SystemStateInterface;
+import com.android.car.telemetry.databroker.DataBroker;
import com.android.car.telemetry.publisher.PublisherFactory;
+import com.android.car.telemetry.sessioncontroller.SessionController;
import com.android.car.telemetry.systemmonitor.SystemMonitor;
+import com.android.car.telemetry.systemmonitor.SystemMonitorEvent;
import org.junit.Before;
import org.junit.Test;
@@ -75,24 +82,28 @@ public class CarTelemetryServiceTest extends AbstractExtendedMockitoCarServiceTe
.setName(METRICS_CONFIG_NAME).setVersion(2).setScript("no-op").build();
private CarTelemetryService mService;
+ private DataBroker.DataBrokerListener mDataBrokerListener;
private File mTempSystemCarDir;
private Handler mTelemetryHandler;
private MetricsConfigStore mMetricsConfigStore;
private ResultStore mResultStore;
+ private SystemMonitor.SystemMonitorCallback mSystemMonitorCallback;
@Mock private ActivityManager mMockActivityManager;
+ @Mock private CarPowerManagementService mMockCarPowerManagementService;
@Mock private CarPropertyService mMockCarPropertyService;
+ @Mock private CarTelemetryService.Dependencies mDependencies;
@Mock private Context mMockContext;
+ @Mock private DataBroker mMockDataBroker;
@Mock private ICarTelemetryReportListener mMockReportListener;
@Mock private ICarTelemetryReportReadyListener mMockReportReadyListener;
- @Mock private SystemInterface mMockSystemInterface;
- @Mock private SystemStateInterface mMockSystemStateInterface;
- @Mock private CarPowerManagementService mMockCarPowerManagementService;
- @Mock private CarTelemetryService.Dependencies mDependencies;
- @Mock private UidPackageMapper mMockUidMapper;
@Mock private PublisherFactory mPublisherFactory;
- @Mock private SystemMonitor mMockSystemMonitor;
@Mock private ResultReceiver mMockAddMetricsConfigCallback;
+ @Mock private SessionController mMockSessionController;
+ @Mock private SystemInterface mMockSystemInterface;
+ @Mock private SystemMonitor mMockSystemMonitor;
+ @Mock private UidPackageMapper mMockUidMapper;
+
public CarTelemetryServiceTest() {
super(CarLog.TAG_TELEMETRY);
@@ -118,18 +129,32 @@ public class CarTelemetryServiceTest extends AbstractExtendedMockitoCarServiceTe
mTempSystemCarDir = Files.createTempDirectory("telemetry_test").toFile();
when(mMockSystemInterface.getSystemCarDir()).thenReturn(mTempSystemCarDir);
- when(mMockSystemInterface.getSystemStateInterface()).thenReturn(mMockSystemStateInterface);
when(mDependencies.getUidPackageMapper(any(), any())).thenReturn(mMockUidMapper);
when(mDependencies.getPublisherFactory(any(), any(), any(), any(), any(), any(), any()))
.thenReturn(mPublisherFactory);
- mService = new CarTelemetryService(mMockContext, mMockCarPropertyService, mDependencies);
+ mService = new CarTelemetryService(
+ mMockContext,
+ mMockCarPropertyService,
+ mDependencies,
+ mMockDataBroker,
+ mMockSessionController);
mService.init();
mTelemetryHandler = mService.getTelemetryHandler();
CarServiceUtils.runOnLooperSync(mTelemetryHandler.getLooper(), () -> { });
+ ArgumentCaptor<DataBroker.DataBrokerListener> dataBrokerListenerArgumentCaptor =
+ ArgumentCaptor.forClass(DataBroker.DataBrokerListener.class);
+ verify(mMockDataBroker).setDataBrokerListener(dataBrokerListenerArgumentCaptor.capture());
+ mDataBrokerListener = dataBrokerListenerArgumentCaptor.getValue();
+
+ ArgumentCaptor<SystemMonitor.SystemMonitorCallback> systemMonitorCallbackCaptor =
+ ArgumentCaptor.forClass(SystemMonitor.SystemMonitorCallback.class);
+ verify(mMockSystemMonitor).setSystemMonitorCallback(systemMonitorCallbackCaptor.capture());
+ mSystemMonitorCallback = systemMonitorCallbackCaptor.getValue();
+
mMetricsConfigStore = mService.getMetricsConfigStore();
mResultStore = mService.getResultStore();
}
@@ -368,4 +393,160 @@ public class CarTelemetryServiceTest extends AbstractExtendedMockitoCarServiceTe
verify(mMockReportReadyListener).onReady(eq(name1));
verify(mMockReportReadyListener).onReady(eq(name2));
}
+
+ @Test
+ public void testOnEventConsumed_shouldStoreInterimResult() {
+ mDataBrokerListener.onEventConsumed(METRICS_CONFIG_NAME, new PersistableBundle());
+
+ CarServiceUtils.runOnLooperSync(mTelemetryHandler.getLooper(), () -> { });
+ assertThat(mResultStore.getInterimResult(METRICS_CONFIG_NAME)).isNotNull();
+ verify(mMockDataBroker).scheduleNextTask();
+ }
+
+ @Test
+ public void testOnReportFinished_removesConfigAndDoesNotNotifyClient() throws Exception {
+ mService.setReportReadyListener(mMockReportReadyListener);
+ mMetricsConfigStore.addMetricsConfig(METRICS_CONFIG_V1);
+
+ mDataBrokerListener.onReportFinished(METRICS_CONFIG_NAME);
+
+ CarServiceUtils.runOnLooperSync(mTelemetryHandler.getLooper(), () -> { });
+ assertThat(mMetricsConfigStore.getActiveMetricsConfigs()).isEmpty();
+ verify(mMockReportReadyListener, never()).onReady(any());
+ verify(mMockDataBroker).scheduleNextTask();
+ }
+
+ @Test
+ public void testOnReportFinished_withReport_removesConfigAndNotifiesClient() throws Exception {
+ mService.setReportReadyListener(mMockReportReadyListener);
+ mMetricsConfigStore.addMetricsConfig(METRICS_CONFIG_V1);
+
+ mDataBrokerListener.onReportFinished(METRICS_CONFIG_NAME, new PersistableBundle());
+
+ CarServiceUtils.runOnLooperSync(mTelemetryHandler.getLooper(), () -> { });
+ assertThat(mMetricsConfigStore.getActiveMetricsConfigs()).isEmpty();
+ assertThat(mResultStore.getFinalResult(METRICS_CONFIG_NAME, false)).isNotNull();
+ verify(mMockReportReadyListener).onReady(eq(METRICS_CONFIG_NAME));
+ verify(mMockDataBroker).scheduleNextTask();
+ }
+
+ @Test
+ public void testOnReportFinished_withError_removesConfigAndNotifiesClient() throws Exception {
+ mService.setReportReadyListener(mMockReportReadyListener);
+ mMetricsConfigStore.addMetricsConfig(METRICS_CONFIG_V1);
+
+ mDataBrokerListener.onReportFinished(
+ METRICS_CONFIG_NAME, TelemetryProto.TelemetryError.newBuilder().build());
+
+ CarServiceUtils.runOnLooperSync(mTelemetryHandler.getLooper(), () -> { });
+ assertThat(mMetricsConfigStore.getActiveMetricsConfigs()).isEmpty();
+ assertThat(mResultStore.getErrorResult(METRICS_CONFIG_NAME, false)).isNotNull();
+ verify(mMockReportReadyListener).onReady(eq(METRICS_CONFIG_NAME));
+ verify(mMockDataBroker).scheduleNextTask();
+ }
+
+ @Test
+ public void testOnMetricsReport_savesReportAndConfigStillActive() throws Exception {
+ mService.setReportReadyListener(mMockReportReadyListener);
+ mMetricsConfigStore.addMetricsConfig(METRICS_CONFIG_V1);
+ PersistableBundle bundle = new PersistableBundle();
+
+ mDataBrokerListener.onMetricsReport(METRICS_CONFIG_NAME, bundle, bundle);
+
+ CarServiceUtils.runOnLooperSync(mTelemetryHandler.getLooper(), () -> { });
+ assertThat(mMetricsConfigStore.getActiveMetricsConfigs())
+ .containsExactly(METRICS_CONFIG_V1);
+ assertThat(mResultStore.getInterimResult(METRICS_CONFIG_NAME)).isEqualTo(bundle);
+ assertThat(mResultStore.getFinalResult(METRICS_CONFIG_NAME, false)).isEqualTo(bundle);
+ verify(mMockReportReadyListener).onReady(eq(METRICS_CONFIG_NAME));
+ verify(mMockDataBroker).scheduleNextTask();
+ }
+
+ @Test
+ public void testOnBootCompleted_shouldStartMetricsCollection() {
+ mMetricsConfigStore.addMetricsConfig(METRICS_CONFIG_V1);
+ ArgumentCaptor<Runnable> mRunnableCaptor = ArgumentCaptor.forClass(Runnable.class);
+
+ // verify that startsMetricsCollection() is scheduled to run on boot complete
+ verify(mMockSystemInterface).scheduleActionForBootCompleted(
+ mRunnableCaptor.capture(), any());
+ // run startMetricsCollection()
+ mRunnableCaptor.getValue().run();
+
+ CarServiceUtils.runOnLooperSync(mTelemetryHandler.getLooper(), () -> { });
+ verify(mMockDataBroker).addMetricsConfig(eq(METRICS_CONFIG_NAME), eq(METRICS_CONFIG_V1));
+ verify(mMockSessionController).initSession();
+ }
+
+ @Test
+ public void testStartMetricsCollection_shouldReportFailure() {
+ mMetricsConfigStore.addMetricsConfig(METRICS_CONFIG_V1);
+ doThrow(IllegalArgumentException.class)
+ .when(mMockDataBroker).addMetricsConfig(any(), any());
+ ArgumentCaptor<Runnable> mRunnableCaptor = ArgumentCaptor.forClass(Runnable.class);
+ // startsMetricsCollection() is scheduled to run on boot complete
+ verify(mMockSystemInterface).scheduleActionForBootCompleted(
+ mRunnableCaptor.capture(), any());
+
+ mRunnableCaptor.getValue().run(); // run startMetricsCollection()
+
+ CarServiceUtils.runOnLooperSync(mTelemetryHandler.getLooper(), () -> { });
+ assertThat(mMetricsConfigStore.getActiveMetricsConfigs()).isEmpty();
+ assertThat(mResultStore.getErrorResult(METRICS_CONFIG_NAME, false)).isNotNull();
+ }
+
+ @Test
+ public void testOnSystemEvent_setDataBrokerPriorityCorrectlyForHighCpuUsage() {
+ SystemMonitorEvent highCpuEvent = new SystemMonitorEvent();
+ highCpuEvent.setCpuUsageLevel(SystemMonitorEvent.USAGE_LEVEL_HI);
+ highCpuEvent.setMemoryUsageLevel(SystemMonitorEvent.USAGE_LEVEL_LOW);
+
+ mSystemMonitorCallback.onSystemMonitorEvent(highCpuEvent);
+
+ verify(mMockDataBroker).setTaskExecutionPriority(eq(TASK_PRIORITY_HI));
+ }
+
+ @Test
+ public void testOnSystemEvent_setDataBrokerPriorityCorrectlyForHighMemUsage() {
+ SystemMonitorEvent highMemEvent = new SystemMonitorEvent();
+ highMemEvent.setCpuUsageLevel(SystemMonitorEvent.USAGE_LEVEL_LOW);
+ highMemEvent.setMemoryUsageLevel(SystemMonitorEvent.USAGE_LEVEL_HI);
+
+ mSystemMonitorCallback.onSystemMonitorEvent(highMemEvent);
+
+ verify(mMockDataBroker).setTaskExecutionPriority(eq(TASK_PRIORITY_HI));
+ }
+
+ @Test
+ public void testOnSystemEvent_setDataBrokerPriorityCorrectlyForMedCpuUsage() {
+ SystemMonitorEvent medCpuEvent = new SystemMonitorEvent();
+ medCpuEvent.setCpuUsageLevel(SystemMonitorEvent.USAGE_LEVEL_MED);
+ medCpuEvent.setMemoryUsageLevel(SystemMonitorEvent.USAGE_LEVEL_LOW);
+
+ mSystemMonitorCallback.onSystemMonitorEvent(medCpuEvent);
+
+ verify(mMockDataBroker).setTaskExecutionPriority(eq(TASK_PRIORITY_MED));
+ }
+
+ @Test
+ public void testOnSystemEvent_setDataBrokerPriorityCorrectlyForMedMemUsage() {
+ SystemMonitorEvent medMemEvent = new SystemMonitorEvent();
+ medMemEvent.setCpuUsageLevel(SystemMonitorEvent.USAGE_LEVEL_LOW);
+ medMemEvent.setMemoryUsageLevel(SystemMonitorEvent.USAGE_LEVEL_MED);
+
+ mSystemMonitorCallback.onSystemMonitorEvent(medMemEvent);
+
+ verify(mMockDataBroker).setTaskExecutionPriority(eq(TASK_PRIORITY_MED));
+ }
+
+ @Test
+ public void testOnSystemEvent_setDataBrokerPriorityCorrectlyForLowUsage() {
+ SystemMonitorEvent lowUsageEvent = new SystemMonitorEvent();
+ lowUsageEvent.setCpuUsageLevel(SystemMonitorEvent.USAGE_LEVEL_LOW);
+ lowUsageEvent.setMemoryUsageLevel(SystemMonitorEvent.USAGE_LEVEL_LOW);
+
+ mSystemMonitorCallback.onSystemMonitorEvent(lowUsageEvent);
+
+ verify(mMockDataBroker).setTaskExecutionPriority(eq(TASK_PRIORITY_LOW));
+ }
}
diff --git a/tests/carservice_unit_test/src/com/android/car/telemetry/ResultStoreTest.java b/tests/carservice_unit_test/src/com/android/car/telemetry/ResultStoreTest.java
index 8340e6dd57..49395f42b7 100644
--- a/tests/carservice_unit_test/src/com/android/car/telemetry/ResultStoreTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/telemetry/ResultStoreTest.java
@@ -299,6 +299,17 @@ public class ResultStoreTest {
}
@Test
+ public void testRemovePublisherData_shouldDelete() throws Exception {
+ String publisherName = "publisher 1";
+ writeBundleToFile(mTestPublisherDataDir, publisherName, TEST_PUBLISHER_BUNDLE);
+ mResultStore = new ResultStore(mTestRootDir); // reload data
+
+ mResultStore.removePublisherData(publisherName);
+
+ assertThat(mResultStore.getPublisherData(publisherName, true)).isNull();
+ }
+
+ @Test
public void testRemoveResult_whenInterimResult_shouldDelete() throws Exception {
String metricsConfigName = "my_metrics_config";
writeBundleToFile(mTestInterimResultDir, metricsConfigName, TEST_INTERIM_BUNDLE);
diff --git a/tests/carservice_unit_test/src/com/android/car/telemetry/databroker/DataBrokerControllerTest.java b/tests/carservice_unit_test/src/com/android/car/telemetry/databroker/DataBrokerControllerTest.java
deleted file mode 100644
index 37b661495a..0000000000
--- a/tests/carservice_unit_test/src/com/android/car/telemetry/databroker/DataBrokerControllerTest.java
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * 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.telemetry.databroker;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.car.telemetry.TelemetryProto;
-import android.os.Handler;
-
-import com.android.car.systeminterface.SystemStateInterface;
-import com.android.car.telemetry.MetricsConfigStore;
-import com.android.car.telemetry.sessioncontroller.SessionController;
-import com.android.car.telemetry.systemmonitor.SystemMonitor;
-import com.android.car.telemetry.systemmonitor.SystemMonitorEvent;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.InjectMocks;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
-
-import java.util.Arrays;
-
-@RunWith(MockitoJUnitRunner.class)
-public class DataBrokerControllerTest {
-
- @Mock private DataBroker mMockDataBroker;
- @Mock private Handler mMockHandler;
- @Mock private MetricsConfigStore mMockMetricsConfigStore;
- @Mock private DataBrokerController.ReportReadyListener mMockReportReadyListener;
- @Mock private SystemMonitor mMockSystemMonitor;
- @Mock private SystemStateInterface mMockSystemStateInterface;
- @Mock private SessionController mMockSessionController;
-
- @Captor ArgumentCaptor<Integer> mPriorityCaptor;
-
- @InjectMocks private DataBrokerController mController;
-
- private static final TelemetryProto.Publisher PUBLISHER =
- TelemetryProto.Publisher.newBuilder()
- .setVehicleProperty(
- TelemetryProto.VehiclePropertyPublisher
- .newBuilder()
- .setReadRate(1)
- .setVehiclePropertyId(1000))
- .build();
- private static final TelemetryProto.Subscriber SUBSCRIBER =
- TelemetryProto.Subscriber.newBuilder()
- .setHandler("handler_func")
- .setPublisher(PUBLISHER)
- .build();
- private static final TelemetryProto.MetricsConfig CONFIG =
- TelemetryProto.MetricsConfig.newBuilder()
- .setName("config_name")
- .setVersion(1)
- .setScript("function init() end")
- .addSubscribers(SUBSCRIBER)
- .build();
- private static final String CONFIG_NAME = CONFIG.getName();
-
- @Before
- public void setup() {
- when(mMockHandler.post(any(Runnable.class))).thenAnswer(i -> {
- Runnable runnable = i.getArgument(0);
- runnable.run();
- return true;
- });
- }
-
- @Test
- public void testOnInit_setsOnScriptFinishedCallback() {
- // Checks that mMockDataBroker's setOnScriptFinishedCallback is called after it's injected
- // into controller's constructor with @InjectMocks
- verify(mMockDataBroker).setOnScriptFinishedCallback(
- any(DataBroker.ScriptFinishedCallback.class));
- }
-
- @Test
- public void testOnBootCompleted_shouldStartMetricsCollection() {
- when(mMockMetricsConfigStore.getActiveMetricsConfigs()).thenReturn(Arrays.asList(CONFIG));
- ArgumentCaptor<Runnable> mRunnableCaptor = ArgumentCaptor.forClass(Runnable.class);
- verify(mMockSystemStateInterface).scheduleActionForBootCompleted(
- mRunnableCaptor.capture(), any());
-
- mRunnableCaptor.getValue().run(); // startMetricsCollection();
-
- verify(mMockDataBroker).addMetricsConfig(eq(CONFIG_NAME), eq(CONFIG));
- verify(mMockSessionController).initSession();
- }
-
- @Test
- public void testOnScriptFinished_shouldRemoveConfig() {
- mController.onScriptFinished(CONFIG_NAME);
-
- verify(mMockMetricsConfigStore).removeMetricsConfig(eq(CONFIG_NAME));
- verify(mMockDataBroker).removeMetricsConfig(eq(CONFIG_NAME));
- verify(mMockReportReadyListener).onReportReady(eq(CONFIG_NAME));
- }
-
- @Test
- public void testOnSystemEvent_setDataBrokerPriorityCorrectlyForHighCpuUsage() {
- SystemMonitorEvent highCpuEvent = new SystemMonitorEvent();
- highCpuEvent.setCpuUsageLevel(SystemMonitorEvent.USAGE_LEVEL_HI);
- highCpuEvent.setMemoryUsageLevel(SystemMonitorEvent.USAGE_LEVEL_LOW);
-
- mController.onSystemMonitorEvent(highCpuEvent);
-
- verify(mMockDataBroker, atLeastOnce())
- .setTaskExecutionPriority(mPriorityCaptor.capture());
- assertThat(mPriorityCaptor.getValue())
- .isEqualTo(DataBrokerController.TASK_PRIORITY_HI);
- }
-
- @Test
- public void testOnSystemEvent_setDataBrokerPriorityCorrectlyForHighMemUsage() {
- SystemMonitorEvent highMemEvent = new SystemMonitorEvent();
- highMemEvent.setCpuUsageLevel(SystemMonitorEvent.USAGE_LEVEL_LOW);
- highMemEvent.setMemoryUsageLevel(SystemMonitorEvent.USAGE_LEVEL_HI);
-
- mController.onSystemMonitorEvent(highMemEvent);
-
- verify(mMockDataBroker, atLeastOnce())
- .setTaskExecutionPriority(mPriorityCaptor.capture());
- assertThat(mPriorityCaptor.getValue())
- .isEqualTo(DataBrokerController.TASK_PRIORITY_HI);
- }
-
- @Test
- public void testOnSystemEvent_setDataBrokerPriorityCorrectlyForMedCpuUsage() {
- SystemMonitorEvent medCpuEvent = new SystemMonitorEvent();
- medCpuEvent.setCpuUsageLevel(SystemMonitorEvent.USAGE_LEVEL_MED);
- medCpuEvent.setMemoryUsageLevel(SystemMonitorEvent.USAGE_LEVEL_LOW);
-
- mController.onSystemMonitorEvent(medCpuEvent);
-
- verify(mMockDataBroker, atLeastOnce())
- .setTaskExecutionPriority(mPriorityCaptor.capture());
- assertThat(mPriorityCaptor.getValue())
- .isEqualTo(DataBrokerController.TASK_PRIORITY_MED);
- }
-
- @Test
- public void testOnSystemEvent_setDataBrokerPriorityCorrectlyForMedMemUsage() {
- SystemMonitorEvent medMemEvent = new SystemMonitorEvent();
- medMemEvent.setCpuUsageLevel(SystemMonitorEvent.USAGE_LEVEL_LOW);
- medMemEvent.setMemoryUsageLevel(SystemMonitorEvent.USAGE_LEVEL_MED);
-
- mController.onSystemMonitorEvent(medMemEvent);
-
- verify(mMockDataBroker, atLeastOnce())
- .setTaskExecutionPriority(mPriorityCaptor.capture());
- assertThat(mPriorityCaptor.getValue())
- .isEqualTo(DataBrokerController.TASK_PRIORITY_MED);
- }
-
- @Test
- public void testOnSystemEvent_setDataBrokerPriorityCorrectlyForLowUsage() {
- SystemMonitorEvent lowUsageEvent = new SystemMonitorEvent();
- lowUsageEvent.setCpuUsageLevel(SystemMonitorEvent.USAGE_LEVEL_LOW);
- lowUsageEvent.setMemoryUsageLevel(SystemMonitorEvent.USAGE_LEVEL_LOW);
-
- mController.onSystemMonitorEvent(lowUsageEvent);
-
- verify(mMockDataBroker, atLeastOnce())
- .setTaskExecutionPriority(mPriorityCaptor.capture());
- assertThat(mPriorityCaptor.getValue())
- .isEqualTo(DataBrokerController.TASK_PRIORITY_LOW);
- }
-}
diff --git a/tests/carservice_unit_test/src/com/android/car/telemetry/databroker/DataBrokerTest.java b/tests/carservice_unit_test/src/com/android/car/telemetry/databroker/DataBrokerTest.java
index eee8200b43..3f012881f5 100644
--- a/tests/carservice_unit_test/src/com/android/car/telemetry/databroker/DataBrokerTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/telemetry/databroker/DataBrokerTest.java
@@ -19,10 +19,13 @@ package com.android.car.telemetry.databroker;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
+import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -55,6 +58,7 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
@@ -63,6 +67,7 @@ import org.mockito.stubbing.Answer;
import java.io.IOException;
import java.io.InputStream;
+import java.util.Arrays;
import java.util.Collections;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
@@ -112,6 +117,7 @@ public final class DataBrokerTest extends AbstractExtendedMockitoCarServiceTestC
private PersistableBundle mData = new PersistableBundle();
private DataBrokerImpl mDataBroker;
private FakeScriptExecutor mFakeScriptExecutor;
+ private AbstractPublisher.PublisherListener mPublisherListener;
private ScriptExecutionTask mHighPriorityTask;
private ScriptExecutionTask mLowPriorityTask;
@@ -122,7 +128,7 @@ public final class DataBrokerTest extends AbstractExtendedMockitoCarServiceTestC
@Mock
private CarPropertyService mMockCarPropertyService;
@Mock
- private DataBroker.ScriptFinishedCallback mMockScriptFinishedCallback;
+ private DataBroker.DataBrokerListener mMockDataBrokerListener;
@Mock
private IBinder mMockScriptExecutorBinder;
@Mock
@@ -156,23 +162,30 @@ public final class DataBrokerTest extends AbstractExtendedMockitoCarServiceTestC
when(mMockPublisherFactory.getPublisher(any())).thenReturn(mAbstractPublisher);
mDataBroker = new DataBrokerImpl(
mMockContext, mMockPublisherFactory, mMockResultStore, mMockTimingsTraceLog);
- mDataBroker.setOnScriptFinishedCallback(mMockScriptFinishedCallback);
+ mDataBroker.setDataBrokerListener(mMockDataBrokerListener);
// add IdleHandler to get notified when all messages and posts are handled
mDataBroker.getTelemetryHandler().getLooper().getQueue().addIdleHandler(() -> {
mIdleHandlerLatch.countDown();
return true;
});
+ ArgumentCaptor<AbstractPublisher.PublisherListener> listenerCaptor =
+ ArgumentCaptor.forClass(AbstractPublisher.PublisherListener.class);
+ verify(mMockPublisherFactory).initialize(listenerCaptor.capture());
+ mPublisherListener = listenerCaptor.getValue();
+
mHighPriorityTask = new ScriptExecutionTask(
new DataSubscriber(mDataBroker, METRICS_CONFIG_FOO, SUBSCRIBER_FOO),
mData,
SystemClock.elapsedRealtime(),
- false);
+ false,
+ TelemetryProto.Publisher.PublisherCase.STATS.getNumber());
mLowPriorityTask = new ScriptExecutionTask(
new DataSubscriber(mDataBroker, METRICS_CONFIG_BAR, SUBSCRIBER_BAR),
mData,
SystemClock.elapsedRealtime(),
- false);
+ false,
+ TelemetryProto.Publisher.PublisherCase.MEMORY.getNumber());
}
private void mockPackageManager() throws Exception {
@@ -271,25 +284,6 @@ public final class DataBrokerTest extends AbstractExtendedMockitoCarServiceTestC
}
@Test
- public void testScheduleNextTask_whenTaskCompletes_shouldAutomaticallyScheduleNextTask()
- throws Exception {
- PriorityBlockingQueue<ScriptExecutionTask> taskQueue = mDataBroker.getTaskQueue();
- // add two tasks into the queue for execution
- taskQueue.add(mHighPriorityTask);
- taskQueue.add(mHighPriorityTask);
-
- mDataBroker.scheduleNextTask(); // start a task
- waitForTelemetryThreadToFinish();
- // end a task, should automatically schedule the next task
- mFakeScriptExecutor.notifyScriptSuccess(mData); // posts to telemetry handler
-
- waitForTelemetryThreadToFinish();
- // verify queue is empty, both tasks are polled and executed
- assertThat(taskQueue.peek()).isNull();
- assertThat(mFakeScriptExecutor.getInvokeScriptCount()).isEqualTo(2);
- }
-
- @Test
public void testScheduleNextTask_onScriptSuccess_shouldStoreInterimResult() throws Exception {
mData.putBoolean("script is finished", false);
mData.putDouble("value of euler's number", 2.71828);
@@ -300,8 +294,9 @@ public final class DataBrokerTest extends AbstractExtendedMockitoCarServiceTestC
mFakeScriptExecutor.notifyScriptSuccess(mData); // posts to telemetry handler
waitForTelemetryThreadToFinish();
+ assertThat(mDataBroker.getTaskQueue().peek()).isNull();
assertThat(mFakeScriptExecutor.getInvokeScriptCount()).isEqualTo(1);
- verify(mMockResultStore).putInterimResult(
+ verify(mMockDataBrokerListener).onEventConsumed(
eq(mHighPriorityTask.getMetricsConfig().getName()), eq(mData));
}
@@ -322,9 +317,7 @@ public final class DataBrokerTest extends AbstractExtendedMockitoCarServiceTestC
waitForTelemetryThreadToFinish();
assertThat(mFakeScriptExecutor.getInvokeScriptCount()).isEqualTo(1);
- verify(mMockResultStore).putErrorResult(
- eq(METRICS_CONFIG_FOO.getName()), eq(expectedError));
- verify(mMockScriptFinishedCallback).onScriptFinished(eq(NAME_FOO));
+ verify(mMockDataBrokerListener).onReportFinished(eq(NAME_FOO), eq(expectedError));
}
@Test
@@ -340,9 +333,24 @@ public final class DataBrokerTest extends AbstractExtendedMockitoCarServiceTestC
waitForTelemetryThreadToFinish();
assertThat(mFakeScriptExecutor.getInvokeScriptCount()).isEqualTo(1);
- verify(mMockResultStore).putFinalResult(
- eq(mHighPriorityTask.getMetricsConfig().getName()), eq(mData));
- verify(mMockScriptFinishedCallback).onScriptFinished(eq(NAME_FOO));
+ verify(mMockDataBrokerListener).onReportFinished(eq(NAME_FOO), eq(mData));
+ }
+
+ @Test
+ public void testScheduleNextTask_whenScriptProducesReport_shouldStoreFinalResult()
+ throws Exception {
+ mData.putBoolean("script produces report", true);
+ mData.putDouble("value of pi", 3.14159265359);
+ mDataBroker.getTaskQueue().add(mHighPriorityTask);
+
+ mDataBroker.scheduleNextTask();
+ waitForTelemetryThreadToFinish();
+ mFakeScriptExecutor.notifyMetricsReport(mData); // posts to telemetry handler
+
+ waitForTelemetryThreadToFinish();
+ assertThat(mFakeScriptExecutor.getInvokeScriptCount()).isEqualTo(1);
+ verify(mMockDataBrokerListener).onMetricsReport(
+ eq(mHighPriorityTask.getMetricsConfig().getName()), eq(mData), isNull());
}
@Test
@@ -367,7 +375,8 @@ public final class DataBrokerTest extends AbstractExtendedMockitoCarServiceTestC
new DataSubscriber(mDataBroker, METRICS_CONFIG_FOO, SUBSCRIBER_FOO),
data,
SystemClock.elapsedRealtime(),
- true);
+ true,
+ TelemetryProto.Publisher.PublisherCase.STATS.getNumber());
mDataBroker.getTaskQueue().add(highPriorityTask);
mDataBroker.scheduleNextTask();
@@ -383,7 +392,8 @@ public final class DataBrokerTest extends AbstractExtendedMockitoCarServiceTestC
new DataSubscriber(mDataBroker, METRICS_CONFIG_FOO, SUBSCRIBER_FOO),
data,
SystemClock.elapsedRealtime(),
- false);
+ false,
+ TelemetryProto.Publisher.PublisherCase.STATS.getNumber());
mDataBroker.getTaskQueue().add(highPriorityTask);
mDataBroker.scheduleNextTask();
@@ -396,17 +406,18 @@ public final class DataBrokerTest extends AbstractExtendedMockitoCarServiceTestC
public void testScheduleNextTask_largeInputPipeIOException_shouldIgnoreCurrentTask()
throws Exception {
PriorityBlockingQueue<ScriptExecutionTask> taskQueue = mDataBroker.getTaskQueue();
- ScriptExecutionTask highPriorityTask = new ScriptExecutionTask(
+ taskQueue.add(new ScriptExecutionTask(
new DataSubscriber(mDataBroker, METRICS_CONFIG_FOO, SUBSCRIBER_FOO),
new PersistableBundle(),
SystemClock.elapsedRealtime(),
- true);
- taskQueue.add(highPriorityTask); // invokeScriptForLargeInput() path
+ true, // invokeScriptForLargeInput() path
+ TelemetryProto.Publisher.PublisherCase.STATS.getNumber()));
taskQueue.add(new ScriptExecutionTask(
new DataSubscriber(mDataBroker, METRICS_CONFIG_FOO, SUBSCRIBER_FOO),
new PersistableBundle(),
SystemClock.elapsedRealtime(),
- false)); // invokeScript() path
+ false, // invokeScript() path
+ TelemetryProto.Publisher.PublisherCase.STATS.getNumber()));
ParcelFileDescriptor[] fds = ParcelFileDescriptor.createPipe();
when(ParcelFileDescriptor.createPipe()).thenReturn(fds);
fds[1].close(); // cause IO Exception in invokeScriptForLargeInput() path
@@ -524,6 +535,32 @@ public final class DataBrokerTest extends AbstractExtendedMockitoCarServiceTestC
}
@Test
+ public void testAddTaskToQueue_shouldReturnCorrectCount() throws Exception {
+ // since addTaskToQueue() calls scheduleNextTask(), script executor will be invoked,
+ // which polls a task from the queue,
+ assertThat(mDataBroker.addTaskToQueue(mHighPriorityTask)).isEqualTo(1);
+ // this will poll the task that was just added, which means stats publisher count will be
+ // decremented to 0
+ // as long as the test does not make ScriptExecutor return, no other task will be polled
+ // because a script is currently running.
+ waitForTelemetryThreadToFinish();
+ // StatsPublisher publishes once
+ mDataBroker.addTaskToQueue(mHighPriorityTask);
+ // MemoryPublisher publishes 3 times
+ mDataBroker.addTaskToQueue(mLowPriorityTask);
+ mDataBroker.addTaskToQueue(mLowPriorityTask);
+ mDataBroker.addTaskToQueue(mLowPriorityTask);
+
+ // expect 1 existing task + 1 new task = 2
+ int statsTaskCount = mDataBroker.addTaskToQueue(mHighPriorityTask);
+ // expect 3 existing tasks + 1 new task = 4
+ int memoryTaskCount = mDataBroker.addTaskToQueue(mLowPriorityTask);
+
+ assertThat(statsTaskCount).isEqualTo(2);
+ assertThat(memoryTaskCount).isEqualTo(4);
+ }
+
+ @Test
public void testAddMetricsConfig_newMetricsConfig() {
mDataBroker.addMetricsConfig(NAME_BAR, METRICS_CONFIG_BAR);
@@ -545,6 +582,14 @@ public final class DataBrokerTest extends AbstractExtendedMockitoCarServiceTestC
}
@Test
+ public void testAddMetricsConfig_whenInvalidConfig_shouldThrowException() {
+ doThrow(new IllegalArgumentException()).when(mAbstractPublisher).addDataSubscriber(any());
+
+ assertThrows(IllegalArgumentException.class,
+ () -> mDataBroker.addMetricsConfig(NAME_FOO, METRICS_CONFIG_FOO));
+ }
+
+ @Test
public void testRemoveMetricsConfiguration_shouldRemoveAllAssociatedTasks() {
mDataBroker.addMetricsConfig(NAME_FOO, METRICS_CONFIG_FOO);
mDataBroker.addMetricsConfig(NAME_BAR, METRICS_CONFIG_BAR);
@@ -552,7 +597,8 @@ public final class DataBrokerTest extends AbstractExtendedMockitoCarServiceTestC
new DataSubscriber(mDataBroker, METRICS_CONFIG_FOO, SUBSCRIBER_FOO),
mData,
SystemClock.elapsedRealtime(),
- false);
+ false,
+ TelemetryProto.Publisher.PublisherCase.STATS.getNumber());
PriorityBlockingQueue<ScriptExecutionTask> taskQueue = mDataBroker.getTaskQueue();
taskQueue.add(mHighPriorityTask); // associated with METRICS_CONFIG_FOO
taskQueue.add(mLowPriorityTask); // associated with METRICS_CONFIG_BAR
@@ -586,6 +632,24 @@ public final class DataBrokerTest extends AbstractExtendedMockitoCarServiceTestC
assertThat(mDataBroker.getSubscriptionMap()).isEmpty();
}
+ @Test
+ public void testPublisherListener_whenFailure_shouldSetConfigFinishedWithReport() {
+ mPublisherListener.onPublisherFailure(
+ Arrays.asList(METRICS_CONFIG_FOO, METRICS_CONFIG_BAR), null);
+
+ verify(mMockDataBrokerListener).onReportFinished(
+ eq(NAME_FOO), any(TelemetryProto.TelemetryError.class));
+ verify(mMockDataBrokerListener).onReportFinished(
+ eq(NAME_BAR), any(TelemetryProto.TelemetryError.class));
+ }
+
+ @Test
+ public void testPublisherListener_whenNoReport_shouldSetConfigFinished() {
+ mPublisherListener.onConfigFinished(METRICS_CONFIG_FOO);
+
+ verify(mMockDataBrokerListener).onReportFinished(eq(NAME_FOO));
+ }
+
private void waitForTelemetryThreadToFinish() throws Exception {
assertWithMessage("handler not idle in %sms", TIMEOUT_MS)
.that(mIdleHandlerLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
@@ -674,6 +738,15 @@ public final class DataBrokerTest extends AbstractExtendedMockitoCarServiceTestC
}
}
+ /** Mocks script finished without completing its lifecycle. */
+ public void notifyMetricsReport(PersistableBundle bundle) {
+ try {
+ mListener.onMetricsReport(bundle, null);
+ } catch (RemoteException e) {
+ // nothing to do
+ }
+ }
+
/** Fails the next N invokeScript() call. */
public void failNextApiCalls(int n) {
mFailApi = n;
diff --git a/tests/carservice_unit_test/src/com/android/car/telemetry/databroker/DataSubscriberTest.java b/tests/carservice_unit_test/src/com/android/car/telemetry/databroker/DataSubscriberTest.java
index 9229d57065..6f3d6462c3 100644
--- a/tests/carservice_unit_test/src/com/android/car/telemetry/databroker/DataSubscriberTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/telemetry/databroker/DataSubscriberTest.java
@@ -18,10 +18,16 @@ package com.android.car.telemetry.databroker;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
import android.car.telemetry.TelemetryProto;
+import android.os.PersistableBundle;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
@@ -52,6 +58,27 @@ public class DataSubscriberTest {
private DataBroker mMockDataBroker;
@Test
+ public void testPush_shouldAddTaskToQueue() {
+ DataSubscriber dataSubscriber = new DataSubscriber(mMockDataBroker, METRICS_CONFIG_FOO,
+ SUBSCRIBER_FOO);
+ int expectedNumPendingTasks = 10;
+ when(mMockDataBroker.addTaskToQueue(any())).thenReturn(expectedNumPendingTasks);
+ PersistableBundle publishedData = new PersistableBundle();
+
+ int numPendingTasks = dataSubscriber.push(publishedData);
+
+ assertThat(numPendingTasks).isEqualTo(expectedNumPendingTasks);
+ ArgumentCaptor<ScriptExecutionTask> taskCaptor =
+ ArgumentCaptor.forClass(ScriptExecutionTask.class);
+ verify(mMockDataBroker).addTaskToQueue(taskCaptor.capture());
+ ScriptExecutionTask task = taskCaptor.getValue();
+ assertThat(task.getData()).isEqualTo(publishedData);
+ assertThat(task.getMetricsConfig()).isEqualTo(METRICS_CONFIG_FOO);
+ assertThat(task.getPublisherType()).isEqualTo(
+ TelemetryProto.Publisher.PublisherCase.VEHICLE_PROPERTY.getNumber());
+ }
+
+ @Test
public void testEquals_whenSame_shouldBeEqual() {
DataSubscriber foo = new DataSubscriber(mMockDataBroker, METRICS_CONFIG_FOO,
SUBSCRIBER_FOO);
diff --git a/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/CarTelemetrydPublisherTest.java b/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/CarTelemetrydPublisherTest.java
index b5c6d62d6c..ae600b1857 100644
--- a/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/CarTelemetrydPublisherTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/CarTelemetrydPublisherTest.java
@@ -49,8 +49,6 @@ import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
-import java.util.List;
-
@RunWith(MockitoJUnitRunner.class)
public class CarTelemetrydPublisherTest extends AbstractExtendedMockitoTestCase {
private static final String SERVICE_NAME = ICarTelemetryInternal.DESCRIPTOR + "/default";
@@ -63,16 +61,13 @@ public class CarTelemetrydPublisherTest extends AbstractExtendedMockitoTestCase
private final FakeHandlerWrapper mFakeHandlerWrapper =
new FakeHandlerWrapper(Looper.getMainLooper(), FakeHandlerWrapper.Mode.IMMEDIATE);
+ private final FakePublisherListener mFakePublisherListener = new FakePublisherListener();
@Mock private IBinder mMockBinder;
@Mock private DataSubscriber mMockDataSubscriber;
@Captor private ArgumentCaptor<IBinder.DeathRecipient> mLinkToDeathCallbackCaptor;
- // These 2 variables are set in onPublisherFailure() callback.
- @Nullable private Throwable mPublisherFailure;
- @Nullable private List<TelemetryProto.MetricsConfig> mFailedConfigs;
-
private FakeCarTelemetryInternal mFakeCarTelemetryInternal;
private CarTelemetrydPublisher mPublisher;
@@ -83,7 +78,7 @@ public class CarTelemetrydPublisherTest extends AbstractExtendedMockitoTestCase
@Before
public void setUp() throws Exception {
mPublisher = new CarTelemetrydPublisher(
- this::onPublisherFailure, mFakeHandlerWrapper.getMockHandler());
+ mFakePublisherListener, mFakeHandlerWrapper.getMockHandler());
mFakeCarTelemetryInternal = new FakeCarTelemetryInternal(mMockBinder);
when(mMockDataSubscriber.getPublisherParam()).thenReturn(PUBLISHER_PARAMS_1);
when(mMockBinder.queryLocalInterface(any())).thenReturn(mFakeCarTelemetryInternal);
@@ -173,9 +168,9 @@ public class CarTelemetrydPublisherTest extends AbstractExtendedMockitoTestCase
mLinkToDeathCallbackCaptor.getValue().binderDied();
assertThat(mFakeCarTelemetryInternal.mSetListenerCallCount).isEqualTo(1);
- assertThat(mPublisherFailure).hasMessageThat()
+ assertThat(mFakePublisherListener.mPublisherFailure).hasMessageThat()
.contains("ICarTelemetryInternal binder died");
- assertThat(mFailedConfigs).hasSize(1); // got all the failed configs
+ assertThat(mFakePublisherListener.mFailedConfigs).hasSize(1); // got all the failed configs
}
@Test
@@ -184,15 +179,9 @@ public class CarTelemetrydPublisherTest extends AbstractExtendedMockitoTestCase
mPublisher.addDataSubscriber(mMockDataSubscriber);
- assertThat(mPublisherFailure).hasMessageThat()
+ assertThat(mFakePublisherListener.mPublisherFailure).hasMessageThat()
.contains("Cannot set CarData listener");
- assertThat(mFailedConfigs).hasSize(1);
- }
-
- private void onPublisherFailure(AbstractPublisher publisher,
- List<TelemetryProto.MetricsConfig> affectedConfigs, Throwable error) {
- mPublisherFailure = error;
- mFailedConfigs = affectedConfigs;
+ assertThat(mFakePublisherListener.mFailedConfigs).hasSize(1);
}
private static class FakeCarTelemetryInternal implements ICarTelemetryInternal {
diff --git a/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/ConnectivityPublisherTest.java b/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/ConnectivityPublisherTest.java
index dd2ae3ffa1..66ceb64de6 100644
--- a/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/ConnectivityPublisherTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/ConnectivityPublisherTest.java
@@ -157,6 +157,7 @@ public class ConnectivityPublisherTest {
private final FakeDataSubscriber mDataSubscriberCell =
new FakeDataSubscriber(METRICS_CONFIG, SUBSCRIBER_CELL_OEM_NONE);
+ private final FakePublisherListener mFakePublisherListener = new FakePublisherListener();
private final FakeNetworkStatsManager mFakeManager = new FakeNetworkStatsManager();
private ConnectivityPublisher mPublisher; // subject
@@ -176,7 +177,7 @@ public class ConnectivityPublisherTest {
when(mMockUidMapper.getPackagesForUid(anyInt())).thenReturn(List.of("pkg1"));
mPublisher =
new ConnectivityPublisher(
- this::onPublisherFailure, mFakeManager, mFakeHandler.getMockHandler(),
+ mFakePublisherListener, mFakeManager, mFakeHandler.getMockHandler(),
mResultStore, mMockSessionController, mMockUidMapper);
verify(mMockSessionController).registerCallback(
mSessionControllerCallbackArgumentCaptor.capture());
@@ -488,12 +489,6 @@ public class ConnectivityPublisherTest {
assertThat(result.getLongArray("txBytes")).asList().containsExactly(1000L);
}
- private void onPublisherFailure(
- AbstractPublisher publisher,
- List<TelemetryProto.MetricsConfig> affectedConfigs,
- Throwable error) {
- }
-
private static class FakeDataSubscriber extends DataSubscriber {
private final ArrayList<PushedData> mPushedData = new ArrayList<>();
@@ -503,8 +498,9 @@ public class ConnectivityPublisherTest {
}
@Override
- public void push(PersistableBundle data, boolean isLargeData) {
+ public int push(PersistableBundle data, boolean isLargeData) {
mPushedData.add(new PushedData(data, isLargeData));
+ return mPushedData.size();
}
/** Returns the pushed data by the given index. */
diff --git a/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/FakePublisherListener.java b/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/FakePublisherListener.java
new file mode 100644
index 0000000000..d62f8ab619
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/FakePublisherListener.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2022 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.telemetry.publisher;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.car.telemetry.TelemetryProto;
+
+import java.util.List;
+
+/**
+ * Test implementation of
+ * {@link com.android.car.telemetry.publisher.AbstractPublisher.PublisherListener}.
+ * This class is used for all PublisherTests.
+ */
+public class FakePublisherListener implements AbstractPublisher.PublisherListener {
+ // Default is null, value is set in onConfigFinished
+ public TelemetryProto.MetricsConfig mFinishedConfig;
+
+ // Default is null, values are set in onPublisherFailure
+ public List<TelemetryProto.MetricsConfig> mFailedConfigs;
+ public Throwable mPublisherFailure;
+
+ @Override
+ public void onPublisherFailure(@NonNull List<TelemetryProto.MetricsConfig> affectedConfigs,
+ @Nullable Throwable error) {
+ mFailedConfigs = affectedConfigs;
+ mPublisherFailure = error;
+ }
+
+ @Override
+ public void onConfigFinished(@NonNull TelemetryProto.MetricsConfig metricsConfig) {
+ mFinishedConfig = metricsConfig;
+ }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/MemoryPublisherTest.java b/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/MemoryPublisherTest.java
new file mode 100644
index 0000000000..40ab9ffa0a
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/MemoryPublisherTest.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2022 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.telemetry.publisher;
+
+import static com.android.car.telemetry.publisher.MemoryPublisher.BUNDLE_KEY_COLLECT_INDEFINITELY;
+import static com.android.car.telemetry.publisher.MemoryPublisher.BUNDLE_KEY_NUM_SNAPSHOTS_UNTIL_FINISH;
+import static com.android.car.telemetry.publisher.MemoryPublisher.DATA_BUNDLE_KEY_MEMINFO;
+import static com.android.car.telemetry.publisher.MemoryPublisher.THROTTLE_MILLIS;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.car.telemetry.TelemetryProto;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PersistableBundle;
+import android.os.SystemClock;
+
+import com.android.car.telemetry.ResultStore;
+import com.android.car.telemetry.databroker.DataSubscriber;
+import com.android.car.test.FakeHandlerWrapper;
+
+import com.google.common.collect.Range;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+@RunWith(MockitoJUnitRunner.class)
+public class MemoryPublisherTest {
+ private static final int TEN_SECONDS = 10;
+ private static final String FAKE_MEMINFO = new StringBuilder()
+ .append("MemTotal: 7645304 kB\n")
+ .append("MemFree: 1927364 kB\n")
+ .append("MemAvailable: 5312884 kB\n")
+ .append("Buffers: 224380 kB\n")
+ .toString();
+ private static final TelemetryProto.Publisher MEMORY_PUBLISHER_TEN_SEC =
+ TelemetryProto.Publisher.newBuilder()
+ .setMemory(
+ TelemetryProto.MemoryPublisher.newBuilder()
+ .setReadIntervalSec(TEN_SECONDS)
+ .setMaxSnapshots(2)
+ .setMaxPendingTasks(10))
+ .build();
+ private static final TelemetryProto.Subscriber SUBSCRIBER_TEN_SEC =
+ TelemetryProto.Subscriber.newBuilder()
+ .setHandler("handler_fn_1")
+ .setPublisher(MEMORY_PUBLISHER_TEN_SEC)
+ .build();
+ private static final TelemetryProto.MetricsConfig METRICS_CONFIG =
+ TelemetryProto.MetricsConfig.newBuilder()
+ .setName("myconfig")
+ .setVersion(1)
+ .addSubscribers(SUBSCRIBER_TEN_SEC)
+ .build();
+
+ private final FakeHandlerWrapper mFakeHandlerWrapper =
+ new FakeHandlerWrapper(Looper.getMainLooper(), FakeHandlerWrapper.Mode.QUEUEING);
+ private final FakePublisherListener mFakePublisherListener = new FakePublisherListener();
+
+ private MemoryPublisher mPublisher; // subject
+ private File mTempFile;
+
+ @Captor
+ private ArgumentCaptor<PersistableBundle> mBundleCaptor;
+ @Mock
+ private DataSubscriber mMockDataSubscriber;
+ @Mock
+ private ResultStore mMockResultStore;
+
+ @Before
+ public void setUp() throws Exception {
+ // set up fake /proc/meminfo file
+ File tempDir = Files.createTempDirectory("car_telemetry_test").toFile();
+ mTempFile = File.createTempFile("fake_meminfo", "", tempDir);
+ Files.write(mTempFile.toPath(), FAKE_MEMINFO.getBytes(StandardCharsets.UTF_8));
+
+ // set up mocks
+ when(mMockDataSubscriber.getSubscriber()).thenReturn(SUBSCRIBER_TEN_SEC);
+ when(mMockDataSubscriber.getMetricsConfig()).thenReturn(METRICS_CONFIG);
+ when(mMockDataSubscriber.getPublisherParam()).thenReturn(SUBSCRIBER_TEN_SEC.getPublisher());
+
+ // create MemoryPublisher
+ mPublisher = createPublisher(mTempFile.toPath());
+ }
+
+ /**
+ * Emulates a restart by creating a new MemoryPublisher. StatsManager and PersistableBundle
+ * stays the same.
+ */
+ private MemoryPublisher createPublisher(Path meminfoPath) {
+ return new MemoryPublisher(
+ mFakePublisherListener,
+ mFakeHandlerWrapper.getMockHandler(),
+ mMockResultStore,
+ meminfoPath);
+ }
+
+ @Test
+ public void testAddDataSubscriber_pullsMeminfo() {
+ mPublisher.addDataSubscriber(mMockDataSubscriber);
+
+ assertThat(mPublisher.hasDataSubscriber(mMockDataSubscriber)).isTrue();
+ verify(mMockDataSubscriber).push(mBundleCaptor.capture());
+ assertThat(mBundleCaptor.getValue().getString(DATA_BUNDLE_KEY_MEMINFO))
+ .isEqualTo(FAKE_MEMINFO);
+ }
+
+ @Test
+ public void testAddDataSubscriber_whenDataSubscriberAlreadyExists_throwsException() {
+ mPublisher.addDataSubscriber(mMockDataSubscriber);
+
+ assertThrows(IllegalStateException.class,
+ () -> mPublisher.addDataSubscriber(mMockDataSubscriber));
+ }
+
+ @Test
+ public void testAddDataSubscriber_whenConfigHasIllegalFields_throwsException() {
+ // read_interval_sec cannot be less than 1
+ TelemetryProto.Publisher badReadIntervalPublisher = TelemetryProto.Publisher.newBuilder()
+ .setMemory(TelemetryProto.MemoryPublisher.newBuilder().setReadIntervalSec(0))
+ .build();
+ DataSubscriber mockDataSubscriber1 = mock(DataSubscriber.class);
+ when(mockDataSubscriber1.getPublisherParam()).thenReturn(badReadIntervalPublisher);
+ // max_pending_tasks cannot be unspecified (or less than 1)
+ TelemetryProto.Publisher badThrottleFieldPublisher = TelemetryProto.Publisher.newBuilder()
+ .setMemory(TelemetryProto.MemoryPublisher.newBuilder().setReadIntervalSec(1))
+ .build();
+ DataSubscriber mockDataSubscriber2 = mock(DataSubscriber.class);
+ when(mockDataSubscriber2.getPublisherParam()).thenReturn(badThrottleFieldPublisher);
+
+ assertThrows(IllegalArgumentException.class,
+ () -> mPublisher.addDataSubscriber(mockDataSubscriber1));
+ assertThrows(IllegalArgumentException.class,
+ () -> mPublisher.addDataSubscriber(mockDataSubscriber2));
+ }
+
+ @Test
+ public void testAddDataSubscriber_schedulesNextPullBasedOnReadRate() {
+ mPublisher.addDataSubscriber(mMockDataSubscriber);
+
+ Message message = mFakeHandlerWrapper.getQueuedMessages().get(0);
+ assertThatMessageIsScheduledWithGivenDelay(message, TEN_SECONDS * 1000);
+ }
+
+ @Test
+ public void testAddDataSubscriber_maxSnapshotsReached_removesDataSubscriber() {
+ // From MEMORY_PUBLISHER_TEN_SEC, max_snapshots = 2.
+ mPublisher.addDataSubscriber(mMockDataSubscriber); // This is the first snapshot
+
+ assertThat(mFakeHandlerWrapper.getQueuedMessages()).hasSize(1);
+ mFakeHandlerWrapper.dispatchQueuedMessages(); // This is the second snapshot
+
+ // verify the MetricsConfig is removed
+ assertThat(mFakeHandlerWrapper.getQueuedMessages()).hasSize(0);
+ assertThat(mPublisher.hasDataSubscriber(mMockDataSubscriber)).isFalse();
+ verify(mMockDataSubscriber, times(2)).push(any());
+ assertThat(mFakePublisherListener.mFinishedConfig).isEqualTo(METRICS_CONFIG);
+ }
+
+ @Test
+ public void testAddDataSubscriber_savePublisherState() {
+ mPublisher.addDataSubscriber(mMockDataSubscriber);
+
+ verify(mMockResultStore).putPublisherData(
+ eq(MemoryPublisher.class.getSimpleName()), mBundleCaptor.capture());
+ // it is equal to 1 because the max_snapshot is set to 2, and it pulled once in
+ // addDataSubscriber(), so there is 1 pull remaining
+ assertThat(mBundleCaptor.getValue().getInt(BUNDLE_KEY_NUM_SNAPSHOTS_UNTIL_FINISH))
+ .isEqualTo(1);
+ }
+
+ @Test
+ public void testAddDataSubscriber_whenReadMeminfoFailed_shouldNotifyFailure() {
+ mPublisher = createPublisher(Paths.get("bad_path"));
+
+ mPublisher.addDataSubscriber(mMockDataSubscriber);
+
+ assertThat(mFakePublisherListener.mFailedConfigs).containsExactly(METRICS_CONFIG);
+ }
+
+ @Test
+ public void testAddDataSubscriber_invalidConfiguration_throwsException() {
+ // read_rate is not allowed to be 0
+ TelemetryProto.Publisher badPublisher = MEMORY_PUBLISHER_TEN_SEC.toBuilder().setMemory(
+ TelemetryProto.MemoryPublisher.newBuilder().setReadIntervalSec(0)).build();
+ TelemetryProto.Subscriber badSubscriber = SUBSCRIBER_TEN_SEC.toBuilder()
+ .setPublisher(badPublisher).build();
+ DataSubscriber mockSubscriber = mock(DataSubscriber.class);
+ when(mockSubscriber.getSubscriber()).thenReturn(badSubscriber);
+ when(mockSubscriber.getPublisherParam()).thenReturn(badPublisher);
+
+ assertThrows(IllegalArgumentException.class,
+ () -> mPublisher.addDataSubscriber(mockSubscriber));
+ }
+
+ @Test
+ public void testRemoveDataSubscriber_removesSubscriberAndStopsPullingMeminfo() {
+ mPublisher.addDataSubscriber(mMockDataSubscriber);
+
+ mPublisher.removeDataSubscriber(mMockDataSubscriber);
+
+ assertThat(mPublisher.hasDataSubscriber(mMockDataSubscriber)).isFalse();
+ assertThat(mFakeHandlerWrapper.getQueuedMessages()).hasSize(0);
+ }
+
+ @Test
+ public void testReadMeminfo_whenPreviousStateExists_shouldContinueFromPrevious() {
+ PersistableBundle publisherState = new PersistableBundle();
+ publisherState.putInt(BUNDLE_KEY_NUM_SNAPSHOTS_UNTIL_FINISH, 1);
+ publisherState.putBoolean(BUNDLE_KEY_COLLECT_INDEFINITELY, false);
+ when(mMockResultStore.getPublisherData(any(), anyBoolean())).thenReturn(publisherState);
+ mPublisher = createPublisher(mTempFile.toPath());
+
+ // since there is 1 snapshot left, this is the last read and the subscriber will be removed
+ mPublisher.addDataSubscriber(mMockDataSubscriber);
+
+ assertThat(mPublisher.hasDataSubscriber(mMockDataSubscriber)).isFalse();
+ assertThat(mFakeHandlerWrapper.getQueuedMessages()).hasSize(0);
+ verify(mMockResultStore).removePublisherData(eq(MemoryPublisher.class.getSimpleName()));
+ }
+
+ @Test
+ public void testRemoveDataSubscriber_ifDoesNotMatch_keepsSubscriber() {
+ mPublisher.addDataSubscriber(mMockDataSubscriber);
+ DataSubscriber differentSubscriber = Mockito.mock(DataSubscriber.class);
+
+ mPublisher.removeDataSubscriber(differentSubscriber);
+
+ assertThat(mPublisher.hasDataSubscriber(mMockDataSubscriber)).isTrue();
+ assertThat(mFakeHandlerWrapper.getQueuedMessages()).hasSize(1);
+ }
+
+ @Test
+ public void testRemoveAllDataSubscriber_removesSubscriberAndStopsPullingMeminfo() {
+ mPublisher.addDataSubscriber(mMockDataSubscriber);
+
+ mPublisher.removeAllDataSubscribers();
+
+ assertThat(mPublisher.hasDataSubscriber(mMockDataSubscriber)).isFalse();
+ assertThat(mFakeHandlerWrapper.getQueuedMessages()).hasSize(0);
+ }
+
+ @Test
+ public void testReadMeminfo_shouldThrottlePublisher() {
+ // 100 MemoryPublisher-related tasks pending script execution > the throttle limit
+ when(mMockDataSubscriber.push(any())).thenReturn(100);
+
+ mPublisher.addDataSubscriber(mMockDataSubscriber);
+
+ assertThat(mPublisher.hasDataSubscriber(mMockDataSubscriber)).isTrue();
+ assertThat(mFakeHandlerWrapper.getQueuedMessages()).hasSize(1);
+ Message message = mFakeHandlerWrapper.getQueuedMessages().get(0);
+ assertThatMessageIsScheduledWithGivenDelay(message, THROTTLE_MILLIS);
+ }
+
+ private static void assertThatMessageIsScheduledWithGivenDelay(Message msg, long delayMillis) {
+ long expectedTimeMillis = SystemClock.uptimeMillis() + delayMillis;
+ long deltaMillis = 1000; // +/- 1 seconds is good enough for testing
+ assertThat(msg.getWhen()).isIn(Range
+ .closed(expectedTimeMillis - deltaMillis, expectedTimeMillis + deltaMillis));
+ }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/StatsPublisherTest.java b/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/StatsPublisherTest.java
index bd9b375700..e1359fd5f0 100644
--- a/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/StatsPublisherTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/StatsPublisherTest.java
@@ -81,7 +81,6 @@ import java.io.File;
import java.io.FileInputStream;
import java.nio.file.Files;
import java.util.Arrays;
-import java.util.List;
@RunWith(MockitoJUnitRunner.class)
public class StatsPublisherTest {
@@ -258,14 +257,11 @@ public class StatsPublisherTest {
private final FakeHandlerWrapper mFakeHandlerWrapper =
new FakeHandlerWrapper(Looper.getMainLooper(), FakeHandlerWrapper.Mode.QUEUEING);
+ private final FakePublisherListener mFakePublisherListener = new FakePublisherListener();
private File mRootDirectory;
private StatsPublisher mPublisher; // subject
- // These 2 variables are set in onPublisherFailure() callback. Defaults to null.
- private Throwable mPublisherFailure;
- private List<TelemetryProto.MetricsConfig> mFailedConfigs;
-
@Mock private StatsManagerProxy mStatsManager;
@Captor private ArgumentCaptor<PersistableBundle> mBundleCaptor;
@@ -283,7 +279,7 @@ public class StatsPublisherTest {
*/
private StatsPublisher createRestartedPublisher() throws Exception {
return new StatsPublisher(
- this::onPublisherFailure,
+ mFakePublisherListener,
mStatsManager,
mRootDirectory,
mFakeHandlerWrapper.getMockHandler());
@@ -398,8 +394,9 @@ public class StatsPublisherTest {
mPublisher.addDataSubscriber(DATA_SUBSCRIBER_1);
- assertThat(mPublisherFailure).hasMessageThat().contains("Failed to add config");
- assertThat(mFailedConfigs).hasSize(1); // got all the failed configs
+ assertThat(mFakePublisherListener.mPublisherFailure)
+ .hasMessageThat().contains("Failed to add config");
+ assertThat(mFakePublisherListener.mFailedConfigs).hasSize(1); // got all the failed configs
}
@Test
@@ -518,8 +515,9 @@ public class StatsPublisherTest {
// subscriber shouldn't get data, because of EMPTY_METRICS_REPORT.
verify(subscriber, times(0)).push(any(), anyBoolean());
- assertThat(mFailedConfigs).containsExactly(METRICS_CONFIG);
- assertThat(mPublisherFailure).hasMessageThat().contains("Found invalid configs");
+ assertThat(mFakePublisherListener.mFailedConfigs).containsExactly(METRICS_CONFIG);
+ assertThat(mFakePublisherListener.mPublisherFailure)
+ .hasMessageThat().contains("Found invalid configs");
}
private PersistableBundle getSavedStatsConfigs() throws Exception {
@@ -532,12 +530,6 @@ public class StatsPublisherTest {
}
}
- private void onPublisherFailure(AbstractPublisher publisher,
- List<TelemetryProto.MetricsConfig> affectedConfigs, Throwable error) {
- mPublisherFailure = error;
- mFailedConfigs = affectedConfigs;
- }
-
private static void assertThatMessageIsScheduledWithGivenDelay(Message msg, long delayMillis) {
long expectedTimeMillis = SystemClock.uptimeMillis() + delayMillis;
long deltaMillis = 1000; // +/- 1 seconds is good enough for testing
diff --git a/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/VehiclePropertyPublisherTest.java b/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/VehiclePropertyPublisherTest.java
index 307eacf05e..792119c155 100644
--- a/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/VehiclePropertyPublisherTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/VehiclePropertyPublisherTest.java
@@ -87,6 +87,7 @@ public class VehiclePropertyPublisherTest {
private final FakeHandlerWrapper mFakeHandlerWrapper =
new FakeHandlerWrapper(Looper.getMainLooper(), FakeHandlerWrapper.Mode.IMMEDIATE);
+ private final FakePublisherListener mFakePublisherListener = new FakePublisherListener();
@Mock
private DataSubscriber mMockDataSubscriber;
@@ -107,7 +108,7 @@ public class VehiclePropertyPublisherTest {
.thenReturn(List.of(PROP_CONFIG_1, PROP_CONFIG_2_WRITE_ONLY));
mVehiclePropertyPublisher = new VehiclePropertyPublisher(
mMockCarPropertyService,
- this::onPublisherFailure,
+ mFakePublisherListener,
mFakeHandlerWrapper.getMockHandler());
}
@@ -203,7 +204,4 @@ public class VehiclePropertyPublisherTest {
// TODO(b/197269115): add more assertions on the contents of
// PersistableBundle object.
}
-
- private void onPublisherFailure(AbstractPublisher publisher,
- List<TelemetryProto.MetricsConfig> affectedConfigs, Throwable error) { }
}
diff --git a/tests/carservice_unit_test/src/com/android/car/user/InitialUserSetterTest.java b/tests/carservice_unit_test/src/com/android/car/user/InitialUserSetterTest.java
index 1d79e4d04e..9eef161b12 100644
--- a/tests/carservice_unit_test/src/com/android/car/user/InitialUserSetterTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/user/InitialUserSetterTest.java
@@ -728,6 +728,23 @@ public final class InitialUserSetterTest extends AbstractExtendedMockitoTestCase
}
@Test
+ public void testDefaultBehavior_nonFirstBoot_ok_targetIsCurrentUserWhichIsEphemeral()
+ throws Exception {
+ mockGetAliveUsers(CURRENT_USER_ID);
+ expectEphemeralUserExists(mMockedUserHandleHelper, CURRENT_USER_ID);
+ UserHandle newUser = expectAdminUserExists(mMockedUserHandleHelper, USER_ID);
+ expectCreateFullUser(USER_ID, OWNER_NAME, UserManagerHelper.FLAG_ADMIN, newUser);
+ expectSwitchUser(USER_ID);
+
+ mSetter.set(new Builder(InitialUserSetter.TYPE_DEFAULT_BEHAVIOR).build());
+
+ verifyUserSwitched(USER_ID);
+ verifyFallbackDefaultBehaviorNeverCalled();
+ verifySystemUserUnlocked();
+ assertInitialUserSet(newUser);
+ }
+
+ @Test
public void testDefaultBehavior_nonFirstBoot_fail_switchFail() throws Exception {
expectHasInitialUser(USER_ID);
expectSwitchUserFails(USER_ID);
@@ -974,6 +991,16 @@ public final class InitialUserSetterTest extends AbstractExtendedMockitoTestCase
}
@Test
+ public void testHasInitialUser_hasOnlyEphemeralUser() {
+ mockIsHeadlessSystemUserMode(true);
+ mockGetAliveUsers(USER_SYSTEM, 10);
+ expectEphemeralUserExists(mMockedUserHandleHelper, 10);
+
+ // TODO(b/231473748): should call hasInitialUser() instead
+ assertThat(mSetter.hasValidInitialUser()).isFalse();
+ }
+
+ @Test
public void testHasInitialUser_hasOnlyWorkProfile() {
mockIsHeadlessSystemUserMode(true);
@@ -1081,6 +1108,7 @@ public final class InitialUserSetterTest extends AbstractExtendedMockitoTestCase
private UserHandle expectHasInitialUser(@UserIdInt int userId, boolean isGuest,
boolean supportsOverrideUserIdProperty) {
doReturn(true).when(mSetter).hasInitialUser();
+ doReturn(true).when(mSetter).hasValidInitialUser();
doReturn(userId).when(mSetter).getInitialUser(supportsOverrideUserIdProperty);
return isGuest
? expectGuestUserExists(mMockedUserHandleHelper, userId, /* isEphemeral= */ true)