diff options
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) |