Merge "More multi-user fixes on SampleClusterService"
diff --git a/car-cluster-logging-renderer/Android.mk b/car-cluster-logging-renderer/Android.mk
index 27b0bbc..0f0435a 100644
--- a/car-cluster-logging-renderer/Android.mk
+++ b/car-cluster-logging-renderer/Android.mk
@@ -32,5 +32,8 @@
LOCAL_JAVA_LIBRARIES += android.car
+LOCAL_STATIC_ANDROID_LIBRARIES += \
+ androidx.car_car-cluster
+
include $(BUILD_PACKAGE)
endif
diff --git a/car-cluster-logging-renderer/AndroidManifest.xml b/car-cluster-logging-renderer/AndroidManifest.xml
index 5fc8d57..81fa46d 100644
--- a/car-cluster-logging-renderer/AndroidManifest.xml
+++ b/car-cluster-logging-renderer/AndroidManifest.xml
@@ -15,8 +15,14 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.car.cluster.loggingrenderer"
+ android:sharedUserId="android.uid.system"
android:versionCode="1"
android:versionName="1.0">
+
+ <protected-broadcast android:name="android.car.cluster.NAVIGATION_STATE_UPDATE"/>
+
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
+
<application android:label="@string/app_name"
android:icon="@drawable/ic_launcher"
android:directBootAware="true"
diff --git a/car-cluster-logging-renderer/src/android/car/cluster/loggingrenderer/LoggingClusterRenderingService.java b/car-cluster-logging-renderer/src/android/car/cluster/loggingrenderer/LoggingClusterRenderingService.java
index 381b7ee..bb9991e 100644
--- a/car-cluster-logging-renderer/src/android/car/cluster/loggingrenderer/LoggingClusterRenderingService.java
+++ b/car-cluster-logging-renderer/src/android/car/cluster/loggingrenderer/LoggingClusterRenderingService.java
@@ -18,17 +18,23 @@
import android.car.cluster.renderer.InstrumentClusterRenderingService;
import android.car.cluster.renderer.NavigationRenderer;
import android.car.navigation.CarNavigationInstrumentCluster;
-import android.graphics.Bitmap;
+import android.content.Intent;
import android.os.Bundle;
+import android.os.UserHandle;
import android.util.Log;
+
+import androidx.car.cluster.navigation.NavigationState;
+import androidx.versionedparcelable.ParcelUtils;
+
import com.google.android.collect.Lists;
/**
* Dummy implementation of {@link LoggingClusterRenderingService} to log all interaction.
*/
public class LoggingClusterRenderingService extends InstrumentClusterRenderingService {
-
private static final String TAG = LoggingClusterRenderingService.class.getSimpleName();
+ private static final String NAV_STATE_BUNDLE_KEY = "navstate";
+ private static final int NAV_STATE_EVENT_ID = 1;
@Override
protected NavigationRenderer getNavigationRenderer() {
@@ -45,7 +51,26 @@
@Override
public void onEvent(int eventType, Bundle bundle) {
- Log.i(TAG, "onEvent, eventType: " + eventType + ", bundle: " + bundle);
+ StringBuilder bundleSummary = new StringBuilder();
+ if (eventType == NAV_STATE_EVENT_ID) {
+ bundle.setClassLoader(ParcelUtils.class.getClassLoader());
+ NavigationState navState = NavigationState
+ .fromParcelable(bundle.getParcelable(NAV_STATE_BUNDLE_KEY));
+ bundleSummary.append(navState.toString());
+
+ // Sending broadcast for testing.
+ Intent intent = new Intent("android.car.cluster.NAVIGATION_STATE_UPDATE");
+ intent.putExtra(NAV_STATE_BUNDLE_KEY, bundle);
+ sendBroadcastAsUser(intent, UserHandle.ALL);
+ } else {
+ for (String key : bundle.keySet()) {
+ bundleSummary.append(key);
+ bundleSummary.append("=");
+ bundleSummary.append(bundle.get(key));
+ bundleSummary.append(" ");
+ }
+ }
+ Log.i(TAG, "onEvent(" + eventType + ", " + bundleSummary + ")");
}
};
diff --git a/car_product/overlay/packages/apps/PackageInstaller/res/drawable/ic_check_box_checked.xml b/car_product/overlay/packages/apps/PackageInstaller/res/drawable/ic_check_box_checked.xml
index 9a6717a..8e9369a 100644
--- a/car_product/overlay/packages/apps/PackageInstaller/res/drawable/ic_check_box_checked.xml
+++ b/car_product/overlay/packages/apps/PackageInstaller/res/drawable/ic_check_box_checked.xml
@@ -16,9 +16,9 @@
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="@*android:dimen/car_icon_size"
- android:height="@*android:dimen/car_icon_size"
- android:tint="@*android:color/car_teal_700"
+ android:width="@dimen/checkbox_size"
+ android:height="@dimen/checkbox_size"
+ android:tint="@color/checkbox_color_checked"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
diff --git a/car_product/overlay/packages/apps/PackageInstaller/res/drawable/ic_check_box_unchecked.xml b/car_product/overlay/packages/apps/PackageInstaller/res/drawable/ic_check_box_unchecked.xml
index 522935c..c4916ee 100644
--- a/car_product/overlay/packages/apps/PackageInstaller/res/drawable/ic_check_box_unchecked.xml
+++ b/car_product/overlay/packages/apps/PackageInstaller/res/drawable/ic_check_box_unchecked.xml
@@ -16,9 +16,9 @@
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="@*android:dimen/car_icon_size"
- android:height="@*android:dimen/car_icon_size"
- android:tint="@*android:color/car_tint"
+ android:width="@dimen/checkbox_size"
+ android:height="@dimen/checkbox_size"
+ android:tint="@color/checkbox_color_unchecked"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
diff --git a/car_product/overlay/packages/apps/PackageInstaller/res/layout/grant_permissions.xml b/car_product/overlay/packages/apps/PackageInstaller/res/layout/grant_permissions.xml
index 58f6ea3..b9874a5 100644
--- a/car_product/overlay/packages/apps/PackageInstaller/res/layout/grant_permissions.xml
+++ b/car_product/overlay/packages/apps/PackageInstaller/res/layout/grant_permissions.xml
@@ -50,7 +50,7 @@
<CheckBox
android:id="@+id/do_not_ask_checkbox"
android:layout_width="wrap_content"
- android:layout_height="@dimen/checkbox_height"
+ android:layout_height="wrap_content"
android:layout_marginStart="@dimen/checkbox_start_margin"
android:layout_marginEnd="@dimen/checkbox_end_margin"
android:gravity="center_vertical"
diff --git a/car_product/overlay/packages/apps/PackageInstaller/res/values/colors.xml b/car_product/overlay/packages/apps/PackageInstaller/res/values/colors.xml
new file mode 100644
index 0000000..c3f7b7b
--- /dev/null
+++ b/car_product/overlay/packages/apps/PackageInstaller/res/values/colors.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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>
+ <color name="checkbox_color_unchecked">@*android:color/car_tint</color>
+ <color name="checkbox_color_checked">@*android:color/accent_device_default_light</color>
+</resources>
\ No newline at end of file
diff --git a/car_product/overlay/packages/apps/PackageInstaller/res/values/dimens.xml b/car_product/overlay/packages/apps/PackageInstaller/res/values/dimens.xml
index 0fc8ed5..078ad7f 100644
--- a/car_product/overlay/packages/apps/PackageInstaller/res/values/dimens.xml
+++ b/car_product/overlay/packages/apps/PackageInstaller/res/values/dimens.xml
@@ -30,11 +30,13 @@
<dimen name="checkbox_end_margin">@*android:dimen/car_keyline_1</dimen>
<dimen name="checkbox_left_padding">@*android:dimen/car_keyline_1_keyline_3_diff</dimen>
- <dimen name="checkbox_text_size">@*android:dimen/car_body3_size</dimen>
- <dimen name="checkbox_height">@*android:dimen/car_single_line_list_item_height</dimen>
+ <dimen name="checkbox_text_size">@*android:dimen/car_label1_size</dimen>
+ <dimen name="checkbox_text_height">@*android:dimen/car_single_line_list_item_height</dimen>
<dimen name="permission_dialog_button_margin_top">@*android:dimen/car_padding_2</dimen>
<dimen name="permission_dialog_allow_button_height">@*android:dimen/car_dialog_action_bar_height</dimen>
<dimen name="permission_dialog_appearance_padding_end">@*android:dimen/car_keyline_1</dimen>
+
+ <dimen name="checkbox_size">@*android:dimen/car_primary_icon_size</dimen>
</resources>
\ No newline at end of file
diff --git a/service/res/drawable/exit_button_background.xml b/service/res/drawable/button_background.xml
similarity index 85%
rename from service/res/drawable/exit_button_background.xml
rename to service/res/drawable/button_background.xml
index 92d32fd..c714124 100644
--- a/service/res/drawable/exit_button_background.xml
+++ b/service/res/drawable/button_background.xml
@@ -14,6 +14,6 @@
limitations under the License.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android">
- <solid android:color="@color/exit_button_background"/>
- <corners android:radius="@dimen/exit_button_radius"/>
+ <solid android:color="@color/button_background"/>
+ <corners android:radius="@dimen/button_radius"/>
</shape>
diff --git a/service/res/drawable/exit_button_ripple_background.xml b/service/res/drawable/button_ripple_background.xml
similarity index 92%
rename from service/res/drawable/exit_button_ripple_background.xml
rename to service/res/drawable/button_ripple_background.xml
index 54fd00f..418c508 100644
--- a/service/res/drawable/exit_button_ripple_background.xml
+++ b/service/res/drawable/button_ripple_background.xml
@@ -16,5 +16,5 @@
<ripple
xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/ripple_background">
- <item android:drawable="@drawable/exit_button_background" />
+ <item android:drawable="@drawable/button_background" />
</ripple>
diff --git a/service/res/layout/activity_blocking.xml b/service/res/layout/activity_blocking.xml
index 687afbe..5eddbf9 100644
--- a/service/res/layout/activity_blocking.xml
+++ b/service/res/layout/activity_blocking.xml
@@ -19,19 +19,55 @@
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@color/activity_blocking_activity_background"
- android:padding="@dimen/blocking_activity_padding"
android:gravity="center">
<TextView
- android:id="@+id/activity_blocked_title"
+ android:id="@+id/blocking_text"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
- android:textColor="@color/blocking_text"
android:textAppearance="@style/ActivityBlockingActivityText" />
+
+ <LinearLayout
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:layout_marginTop="@dimen/common_margin"
+ android:orientation="horizontal"
+ android:gravity="center">
+ <ImageView
+ android:id="@+id/blocked_app_icon"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content" />
+ <TextView
+ android:id="@+id/blocked_app_name"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_marginStart="@dimen/common_margin"
+ android:textColor="@color/blocking_text"
+ android:textAppearance="@style/ActivityBlockingActivityText" />
+ </LinearLayout>
+
<Button
android:id="@+id/exit"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
- android:layout_marginTop="@dimen/exit_button_top_margin"
+ android:layout_marginTop="@dimen/common_margin"
android:text="@string/exit_button"
- style="@style/ExitButtonStyle"/>
+ style="@style/ButtonStyle"/>
+
+ <!-- Widgets to display debug info. They should not show for non-user build. -->
+ <Button
+ android:id="@+id/toggle_debug_info"
+ android:visibility="gone"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_marginTop="@dimen/common_margin"
+ android:text="@string/debug_button_text"
+ style="@style/ButtonStyle"/>
+
+ <TextView
+ android:id="@+id/debug_info"
+ android:visibility="gone"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_marginTop="@dimen/common_margin"
+ android:textAppearance="@style/ActivityBlockingActivityText" />
</LinearLayout>
diff --git a/service/res/values-h1920dp/dimens.xml b/service/res/values-h1920dp/dimens.xml
index 938fb17..42de507 100644
--- a/service/res/values-h1920dp/dimens.xml
+++ b/service/res/values-h1920dp/dimens.xml
@@ -19,8 +19,6 @@
<resources>
<!-- Text size in ActivityBlockingActivity. -->
<dimen name="blocking_text_size">40sp</dimen>
- <!-- Padding of exit button in ActivityBlockingActivity. -->
- <dimen name="exit_button_padding">28dp</dimen>
- <!-- Top margin of exit button in ActivityBlockingActivity. -->
- <dimen name="exit_button_top_margin">20dp</dimen>
+ <!-- Padding of button in ActivityBlockingActivity. -->
+ <dimen name="button_padding">28dp</dimen>
</resources>
diff --git a/service/res/values-h800dp/dimens.xml b/service/res/values-h800dp/dimens.xml
index 9594554..6dd33fe 100644
--- a/service/res/values-h800dp/dimens.xml
+++ b/service/res/values-h800dp/dimens.xml
@@ -17,6 +17,6 @@
*/
-->
<resources>
- <!-- Padding of exit button in ActivityBlockingActivity. -->
- <dimen name="exit_button_padding">28dp</dimen>
+ <!-- Padding of button in ActivityBlockingActivity. -->
+ <dimen name="button_padding">28dp</dimen>
</resources>
diff --git a/service/res/values-night/colors.xml b/service/res/values-night/colors.xml
index 65f8fb1..6f30b68 100644
--- a/service/res/values-night/colors.xml
+++ b/service/res/values-night/colors.xml
@@ -18,17 +18,17 @@
-->
<resources>
<!-- Semi-transparent background color of blocking activity. -->
- <!-- In Call background scrim: black @ 80% -->
- <color name="activity_blocking_activity_background">#cc000000</color>
+ <!-- In Call background scrim: black -->
+ <color name="activity_blocking_activity_background">@android:color/black</color>
<!-- Color of text in blocking activity. -->
<color name="blocking_text">#fff5f5f5</color>
- <!-- Background color of exit button. -->
- <color name="exit_button_background">#ff80cbc4</color>
+ <!-- Background color of button. -->
+ <color name="button_background">#ff80cbc4</color>
- <!-- Color of exit button text. -->
- <color name="exit_button_text">#ff212121</color>
+ <!-- Color of button text. -->
+ <color name="button_text">#ff212121</color>
<!-- Ripple color. -->
<color name="ripple_background">#3dffffff</color>
diff --git a/service/res/values-w1280dp/dimens.xml b/service/res/values-w1280dp/dimens.xml
deleted file mode 100644
index f5469f2..0000000
--- a/service/res/values-w1280dp/dimens.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2018, 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>
- <!-- Padding of ActivityBlockingActivity. -->
- <dimen name="blocking_activity_padding">148dp</dimen>
-</resources>
diff --git a/service/res/values-w1920dp/dimens.xml b/service/res/values-w1920dp/dimens.xml
deleted file mode 100644
index 75d2deb..0000000
--- a/service/res/values-w1920dp/dimens.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2018, 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>
- <!-- Padding of ActivityBlockingActivity. -->
- <dimen name="blocking_activity_padding">192dp</dimen>
-</resources>
diff --git a/service/res/values-w690dp/dimens.xml b/service/res/values-w690dp/dimens.xml
deleted file mode 100644
index 0b06ada..0000000
--- a/service/res/values-w690dp/dimens.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2018, 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>
- <!-- Padding of ActivityBlockingActivity. -->
- <dimen name="blocking_activity_padding">112dp</dimen>
-</resources>
diff --git a/service/res/values/colors.xml b/service/res/values/colors.xml
index ed86a3b..6764116 100644
--- a/service/res/values/colors.xml
+++ b/service/res/values/colors.xml
@@ -18,18 +18,18 @@
-->
<resources>
<!-- Semi-transparent background color of blocking activity. -->
- <!-- In Call background scrim: grey 200 @ 90% -->
- <color name="activity_blocking_activity_background">#e6eeeeee</color>
+ <!-- In Call background scrim: grey 200 -->
+ <color name="activity_blocking_activity_background">#ffeeeeee</color>
<!-- Color of text in blocking activity. -->
<color name="blocking_text">#ff212121</color>
- <!-- Background color of exit button. -->
- <color name="exit_button_background">#ff00796b</color>
+ <!-- Background color of button. -->
+ <color name="button_background">#ff00796b</color>
- <!-- Color of exit button text. -->
- <color name="exit_button_text">#fffafafa</color>
+ <!-- Color of button text. -->
+ <color name="button_text">#fffafafa</color>
- <!-- Ripple color of exit button background. -->
+ <!-- Ripple color of button background. -->
<color name="ripple_background">#3d000000</color>
</resources>
diff --git a/service/res/values/dimens.xml b/service/res/values/dimens.xml
index f75a358..23653db 100644
--- a/service/res/values/dimens.xml
+++ b/service/res/values/dimens.xml
@@ -19,18 +19,14 @@
<resources>
<!-- Text size in ActivityBlockingActivity. -->
<dimen name="blocking_text_size">32sp</dimen>
- <!-- Padding of ActivityBlockingActivity. -->
- <dimen name="blocking_activity_padding">20dp</dimen>
-
- <!-- Minimum height of exit button in ActivityBlockingActivity. -->
- <dimen name="exit_button_min_height">56dp</dimen>
- <!-- Minimum width of exit button in ActivityBlockingActivity. -->
- <dimen name="exit_button_min_width">156dp</dimen>
- <!-- Padding of exit button in ActivityBlockingActivity. -->
- <dimen name="exit_button_padding">16dp</dimen>
- <!-- Radius of exit button in ActivityBlockingActivity. -->
- <dimen name="exit_button_radius">4dp</dimen>
- <!-- Top margin of exit button. It should be half the height of
- button (exit_button_min_height). -->
- <dimen name="exit_button_top_margin">28dp</dimen>
+ <!-- Widget margin. It should be half the height of button. -->
+ <dimen name="common_margin">28dp</dimen>
+ <!-- Minimum height of button in ActivityBlockingActivity. -->
+ <dimen name="button_min_height">56dp</dimen>
+ <!-- Minimum width of button in ActivityBlockingActivity. -->
+ <dimen name="button_min_width">156dp</dimen>
+ <!-- Padding of button in ActivityBlockingActivity. -->
+ <dimen name="button_padding">16dp</dimen>
+ <!-- Radius of button in ActivityBlockingActivity. -->
+ <dimen name="button_radius">4dp</dimen>
</resources>
diff --git a/service/res/values/strings.xml b/service/res/values/strings.xml
index 2de39f1..599e41f 100644
--- a/service/res/values/strings.xml
+++ b/service/res/values/strings.xml
@@ -103,10 +103,12 @@
<string name="car_can_bus_failure_desc">CAN bus does not respond. Unplug and plug back headunit
box and restart the car</string>
- <!-- Blocking activity: Message to show to user when an activity is not allowed during driving. [CHAR LIMIT=120] -->
- <string name="activity_blocked_string">For your safety, this activity isn’t available while you’re driving</string>
- <!-- Blocking activity: Button text that restarts the current blocked application. [CHAR LIMIT=10] -->
- <string name="exit_button">OK</string>
+ <!-- Blocking activity: Message to show to user when a feature of current application is not allowed. [CHAR LIMIT=120] -->
+ <string name="activity_blocked_text">For your safety, this activity isn’t available while you’re driving</string>
+ <!-- Blocking activity: Text for button that shows debug info for non-user build. [CHAR LIMIT=10] -->
+ <string name="debug_button_text">Debug Info</string>
+ <!-- Blocking activity: Text for button that restarts the current blocked application. [CHAR LIMIT=15] -->
+ <string name="exit_button">Restart App</string>
<!-- Permission text: apps can control diagnostic data [CHAR LIMIT=NONE] -->
<string name="car_permission_label_diag_read">Diagnostic Data</string>
diff --git a/service/res/values/styles.xml b/service/res/values/styles.xml
index 4098e96..35b0b29 100644
--- a/service/res/values/styles.xml
+++ b/service/res/values/styles.xml
@@ -17,21 +17,22 @@
<!-- TextAppearances for ActivityBlockingActivity. -->
<style name="ActivityBlockingActivityText">
<item name="android:fontFamily">sans-serif-medium</item>
+ <item name="android:textColor">@color/blocking_text</item>
<item name="android:textStyle">normal</item>
<item name="android:textSize">@dimen/blocking_text_size</item>
</style>
- <!-- Style of exit button. -->
- <style name="ExitButtonStyle">
- <item name="android:background">@drawable/exit_button_ripple_background</item>
+ <!-- Style for buttons in ActivityBlockingActivity. -->
+ <style name="ButtonStyle">
+ <item name="android:background">@drawable/button_ripple_background</item>
<item name="android:fontFamily">sans-serif-medium</item>
- <item name="android:minHeight">@dimen/exit_button_min_height</item>
- <item name="android:minWidth">@dimen/exit_button_min_width</item>
- <item name="android:paddingHorizontal">@dimen/exit_button_padding</item>
+ <item name="android:minHeight">@dimen/button_min_height</item>
+ <item name="android:minWidth">@dimen/button_min_width</item>
+ <item name="android:paddingHorizontal">@dimen/button_padding</item>
<item name="android:textAllCaps">true</item>
<item name="android:textAlignment">center</item>
<item name="android:textAppearance">@style/ActivityBlockingActivityText</item>
- <item name="android:textColor">@color/exit_button_text</item>
+ <item name="android:textColor">@color/button_text</item>
</style>
</resources>
diff --git a/service/src/com/android/car/BluetoothAutoConnectStateMachine.java b/service/src/com/android/car/BluetoothAutoConnectStateMachine.java
index 981db5b..cf9b98e 100644
--- a/service/src/com/android/car/BluetoothAutoConnectStateMachine.java
+++ b/service/src/com/android/car/BluetoothAutoConnectStateMachine.java
@@ -48,12 +48,9 @@
public static final int CONNECT_TIMEOUT = 103;
public static final int DEVICE_CONNECTED = 104;
public static final int DEVICE_DISCONNECTED = 105;
- // The following is used when PBAP and MAP should be connected to,
- // after device connects on HFP.
- public static final int CHECK_CLIENT_PROFILES = 1006;
+ public static final int ADAPTER_OFF = 106;
public static final int CONNECTION_TIMEOUT_MS = 8000;
- static final int CONNECT_MORE_PROFILES_TIMEOUT_MS = 2000;
BluetoothAutoConnectStateMachine(BluetoothDeviceConnectionPolicy policy) {
@@ -84,9 +81,7 @@
/**
* Idle State is the Initial State, when the system is accepting incoming 'CONNECT' requests.
- * Attempts a connection whenever the state transitions into Idle.
- * If the policy finds a device to connect on a profile, transitions to Processing.
- * If there is nothing to connect to, wait for the next 'CONNECT' message to try next.
+ * Upon 'CONNECT' message move to processing and beging connecting to devices.
*/
private class Idle extends State {
@Override
@@ -94,7 +89,6 @@
if (DBG) {
Log.d(TAG, "Enter Idle");
}
- connectToBluetoothDevice();
}
@Override
@@ -107,29 +101,21 @@
if (DBG) {
Log.d(TAG, "Idle->Connect:");
}
- connectToBluetoothDevice();
+ transitionTo(mProcessing);
break;
}
case DEVICE_CONNECTED: {
- if (DBG) {
- Log.d(TAG, "Idle->DeviceConnected: Ignored");
- }
- break;
- }
-
- case CHECK_CLIENT_PROFILES: {
- removeMessages(CHECK_CLIENT_PROFILES);
BluetoothDeviceConnectionPolicy.ConnectionParams params =
(BluetoothDeviceConnectionPolicy.ConnectionParams) msg.obj;
- BluetoothDevice device = params.getBluetoothDevice();
- // After pairing/disconnect, always try to connect to both PBAP and MAP
- if (DBG) {
- Log.d(TAG, "try to connect to PBAP/MAP after pairing or disconnect: "
- + Utils.getDeviceDebugInfo(device));
+ if (params.getBluetoothProfile() == BluetoothProfile.HEADSET_CLIENT) {
+ mPolicy.connectToDeviceOnProfile(BluetoothProfile.PBAP_CLIENT,
+ params.getBluetoothDevice());
+ mPolicy.connectToDeviceOnProfile(BluetoothProfile.MAP_CLIENT,
+ params.getBluetoothDevice());
+ } else if (DBG) {
+ Log.d(TAG, "Idle->DeviceConnected: Ignored");
}
- mPolicy.connectToDeviceOnProfile(BluetoothProfile.PBAP_CLIENT, device);
- mPolicy.connectToDeviceOnProfile(BluetoothProfile.MAP_CLIENT, device);
break;
}
@@ -143,24 +129,6 @@
return true;
}
- /**
- * Instruct the policy to find and connect to a device on a connectable profile.
- * If the policy reports that there is nothing to connect to, stay in the Idle state.
- * If it found a {device, profile} combination to attempt a connection, move to
- * Processing state
- */
- private void connectToBluetoothDevice() {
- boolean deviceToConnectFound = mPolicy.findDeviceToConnect();
- if (deviceToConnectFound) {
- transitionTo(mProcessing);
- } else {
- // Stay in Idle State and wait for the next 'CONNECT' message.
- if (DBG) {
- Log.d(TAG, "Idle->No device to connect");
- }
- }
- }
-
@Override
public void exit() {
if (DBG) {
@@ -173,20 +141,30 @@
/**
* Processing state indicates the system is processing a auto connect trigger and will ignore
* connection requests.
+ * If there are no devices to connect or upon completion, transition back to idle.
*/
private class Processing extends State {
+ int mConnectionsInProgress;
@Override
public void enter() {
if (DBG) {
Log.d(TAG, "Enter Processing");
}
-
+ mConnectionsInProgress = 0;
+ sendMessageDelayed(CONNECT_TIMEOUT, CONNECTION_TIMEOUT_MS);
+ for (Integer profile : mPolicy.mProfilesToConnect) {
+ connectDeviceOnProfile(profile);
+ }
+ if (mConnectionsInProgress == 0) {
+ transitionTo(mIdle);
+ }
}
@Override
public boolean processMessage(Message msg) {
if (DBG) {
Log.d(TAG, "Processing processMessage " + msg.what);
+ Log.d(TAG, "Connections in Progress = " + mConnectionsInProgress);
}
BluetoothDeviceConnectionPolicy.ConnectionParams params;
switch (msg.what) {
@@ -203,10 +181,21 @@
case DEVICE_CONNECTED:
// fall through
case DEVICE_DISCONNECTED: {
- removeMessages(CONNECT_TIMEOUT);
- transitionTo(mIdle);
+ mConnectionsInProgress--;
+ params = (BluetoothDeviceConnectionPolicy.ConnectionParams) msg.obj;
+ connectDeviceOnProfile(params.getBluetoothProfile());
+
+ if (mConnectionsInProgress == 0) {
+ transitionTo(mIdle);
+ } else {
+ removeMessages(CONNECT_TIMEOUT);
+ sendMessageDelayed(CONNECT_TIMEOUT, CONNECTION_TIMEOUT_MS);
+ }
break;
}
+ case ADAPTER_OFF:
+ transitionTo(mIdle);
+ break;
default:
if (DBG) {
@@ -222,6 +211,19 @@
if (DBG) {
Log.d(TAG, "Exit Processing");
}
+ removeMessages(CONNECT_TIMEOUT);
+
+ }
+
+ void connectDeviceOnProfile(int profile) {
+ BluetoothDevicesInfo devInfo = mPolicy.mProfileToConnectableDevicesMap.get(profile);
+ if (devInfo != null && devInfo.isProfileConnectableLocked()) {
+ BluetoothDevice device = devInfo.getNextDeviceInQueueLocked();
+ if (device != null) {
+ mConnectionsInProgress++;
+ mPolicy.connectToDeviceOnProfile(profile, device);
+ }
+ }
}
}
diff --git a/service/src/com/android/car/BluetoothDeviceConnectionPolicy.java b/service/src/com/android/car/BluetoothDeviceConnectionPolicy.java
index f9c8a1e..51bbf8d 100644
--- a/service/src/com/android/car/BluetoothDeviceConnectionPolicy.java
+++ b/service/src/com/android/car/BluetoothDeviceConnectionPolicy.java
@@ -97,7 +97,7 @@
private final Object mSetupLock = new Object();
// The main data structure that holds on to the {profile:list of known and connectible devices}
- private HashMap<Integer, BluetoothDevicesInfo> mProfileToConnectableDevicesMap;
+ HashMap<Integer, BluetoothDevicesInfo> mProfileToConnectableDevicesMap;
/// TODO(vnori): fix this. b/70029056
private static final int NUM_SUPPORTED_PHONE_CONNECTIONS = 4; // num of HFP and PBAP connections
@@ -133,10 +133,11 @@
private final FastPairProvider mFastPairProvider;
// The Bluetooth profiles that the CarService will try to auto-connect on.
- private final List<Integer> mProfilesToConnect;
+ final List<Integer> mProfilesToConnect;
private final List<Integer> mPrioritiesSupported;
private static final int MAX_CONNECT_RETRIES = 1;
private static final int PROFILE_NOT_AVAILABLE = -1;
+ private int mUserId;
// Device & Profile currently being connected on
private ConnectionParams mConnectionInFlight;
@@ -318,6 +319,8 @@
initiateConnection();
} else if (currState == BluetoothAdapter.STATE_OFF) {
// Write currently connected device snapshot to file.
+ mBluetoothAutoConnectStateMachine.sendMessage(
+ BluetoothAutoConnectStateMachine.ADAPTER_OFF);
writeDeviceInfoToSettings();
resetBluetoothDevicesConnectionInfo();
}
@@ -539,6 +542,7 @@
}
return;
}
+ mUserId = ActivityManager.getCurrentUser();
mBluetoothAutoConnectStateMachine = BluetoothAutoConnectStateMachine.make(this);
readAndRebuildDeviceMapFromSettings();
setupBluetoothEventsIntentFilterLocked();
@@ -1217,32 +1221,6 @@
+ deviceThatConnected);
}
- // If the device just connected to HEADSET_CLIENT profile, initiate
- // connections on PBAP & MAP profiles but let that begin after a timeout period.
- // timeout allows A2DP profile to complete its connection, so that there is no race
- // condition between
- // Phone trying to connect on A2DP
- // and, Car trying to connect on PBAP & MAP.
- if (didConnect && profileToUpdate == BluetoothProfile.HEADSET_CLIENT) {
- // Unlock the profiles PBAP, MAP in BluetoothDevicesInfo, so that they can be
- // connected on.
- for (Integer profile : Arrays.asList(BluetoothProfile.PBAP_CLIENT,
- BluetoothProfile.MAP_CLIENT)) {
- BluetoothDevicesInfo devInfo = mProfileToConnectableDevicesMap.get(profile);
- if (devInfo == null) {
- Log.e(TAG, "Unexpected: No device Queue for this profile: " + profile);
- return false;
- }
- devInfo.setDeviceAvailableToConnectLocked(true);
- }
- if (DBG) {
- Log.d(TAG, "connect to PBAP/MAP after disconnect: ");
- }
- mBluetoothAutoConnectStateMachine.sendMessageDelayed(
- BluetoothAutoConnectStateMachine.CHECK_CLIENT_PROFILES, params,
- BluetoothAutoConnectStateMachine.CONNECT_MORE_PROFILES_TIMEOUT_MS);
- }
-
// If the connection update is on a different profile or device (a very rare possibility),
// it is handled automatically. Just logging it here.
if (DBG) {
@@ -1422,18 +1400,17 @@
if (DBG) {
Log.d(TAG, "Profile: " + profileToUpdate + " Writing: " + joinedDeviceNames);
}
- long userId = ActivityManager.getCurrentUser();
switch (profileToUpdate) {
case BluetoothProfile.A2DP_SINK:
Settings.Secure.putStringForUser(mContext.getContentResolver(),
KEY_BLUETOOTH_AUTOCONNECT_MUSIC_DEVICES,
- joinedDeviceNames, (int) userId);
+ joinedDeviceNames, mUserId);
break;
case BluetoothProfile.HEADSET_CLIENT:
Settings.Secure.putStringForUser(mContext.getContentResolver(),
KEY_BLUETOOTH_AUTOCONNECT_PHONE_DEVICES,
- joinedDeviceNames, (int) userId);
+ joinedDeviceNames, mUserId);
break;
case BluetoothProfile.PBAP_CLIENT:
@@ -1443,12 +1420,12 @@
case BluetoothProfile.MAP_CLIENT:
Settings.Secure.putStringForUser(mContext.getContentResolver(),
KEY_BLUETOOTH_AUTOCONNECT_MESSAGING_DEVICES,
- joinedDeviceNames, (int) userId);
+ joinedDeviceNames, mUserId);
break;
case BluetoothProfile.PAN:
Settings.Secure.putStringForUser(mContext.getContentResolver(),
KEY_BLUETOOTH_AUTOCONNECT_NETWORK_DEVICES,
- joinedDeviceNames, (int) userId);
+ joinedDeviceNames, mUserId);
break;
}
}
@@ -1489,26 +1466,25 @@
}
// Read from Settings.Secure for the current user. There are 3 keys 1 each for Phone
// (HFP & PBAP), 1 for Music (A2DP) and 1 for Messaging device (MAP)
- long userId = ActivityManager.getCurrentUser();
for (Integer profile : mProfilesToConnect) {
switch (profile) {
case BluetoothProfile.A2DP_SINK:
devices = Settings.Secure.getStringForUser(mContext.getContentResolver(),
- KEY_BLUETOOTH_AUTOCONNECT_MUSIC_DEVICES, (int) userId);
+ KEY_BLUETOOTH_AUTOCONNECT_MUSIC_DEVICES, mUserId);
break;
case BluetoothProfile.PBAP_CLIENT:
// fall through
case BluetoothProfile.HEADSET_CLIENT:
devices = Settings.Secure.getStringForUser(mContext.getContentResolver(),
- KEY_BLUETOOTH_AUTOCONNECT_PHONE_DEVICES, (int) userId);
+ KEY_BLUETOOTH_AUTOCONNECT_PHONE_DEVICES, mUserId);
break;
case BluetoothProfile.MAP_CLIENT:
devices = Settings.Secure.getStringForUser(mContext.getContentResolver(),
- KEY_BLUETOOTH_AUTOCONNECT_MESSAGING_DEVICES, (int) userId);
+ KEY_BLUETOOTH_AUTOCONNECT_MESSAGING_DEVICES, mUserId);
break;
case BluetoothProfile.PAN:
devices = Settings.Secure.getStringForUser(mContext.getContentResolver(),
- KEY_BLUETOOTH_AUTOCONNECT_NETWORK_DEVICES, (int) userId);
+ KEY_BLUETOOTH_AUTOCONNECT_NETWORK_DEVICES, mUserId);
default:
Log.e(TAG, "Unexpected profile");
break;
diff --git a/service/src/com/android/car/ICarImpl.java b/service/src/com/android/car/ICarImpl.java
index 2081148..736ffc8 100644
--- a/service/src/com/android/car/ICarImpl.java
+++ b/service/src/com/android/car/ICarImpl.java
@@ -35,6 +35,7 @@
import android.util.Slog;
import android.util.TimingsTraceLog;
+import com.android.car.audio.CarAudioService;
import com.android.car.cluster.InstrumentClusterService;
import com.android.car.garagemode.GarageModeService;
import com.android.car.hal.VehicleHal;
diff --git a/service/src/com/android/car/SystemActivityMonitoringService.java b/service/src/com/android/car/SystemActivityMonitoringService.java
index 2df9b00..468dde4 100644
--- a/service/src/com/android/car/SystemActivityMonitoringService.java
+++ b/service/src/com/android/car/SystemActivityMonitoringService.java
@@ -85,6 +85,7 @@
void onActivityLaunch(TopTaskInfoContainer topTask);
}
+ private static final int INVALID_STACK_ID = -1;
private final Context mContext;
private final IActivityManager mAm;
private final ProcessObserver mProcessObserver;
@@ -97,7 +98,7 @@
private final SparseArray<TopTaskInfoContainer> mTopTasks = new SparseArray<>();
/** K: uid, V : list of pid */
private final Map<Integer, Set<Integer>> mForegroundUidPids = new ArrayMap<>();
- private int mFocusedStackId = -1;
+ private int mFocusedStackId = INVALID_STACK_ID;
/**
* Temporary container to dispatch tasks for onActivityLaunch. Only used in handler thread.
@@ -253,7 +254,7 @@
Log.e(CarLog.TAG_AM, "cannot getTasks", e);
return;
}
- int focusedStackId = -1;
+ int focusedStackId = INVALID_STACK_ID;
try {
// TODO(b/66955160): Someone on the Auto-team should probably re-work the code in the
// synchronized block below based on this new API.
@@ -269,10 +270,13 @@
ActivityLaunchListener listener;
synchronized (this) {
listener = mActivityLaunchListener;
+ Set<Integer> allStackIds = new ArraySet<>(infos.size());
+ Set<Integer> stackIdsToRemove = new ArraySet<>(infos.size());
for (StackInfo info : infos) {
int stackId = info.stackId;
+ allStackIds.add(info.stackId);
if (info.taskNames.length == 0 || !info.visible) { // empty stack or not shown
- mTopTasks.remove(stackId);
+ stackIdsToRemove.add(stackId);
continue;
}
TopTaskInfoContainer newTopTaskInfo = new TopTaskInfoContainer(
@@ -290,6 +294,19 @@
}
}
}
+ for (int i = 0; i < mTopTasks.size(); i++) {
+ TopTaskInfoContainer topTask = mTopTasks.valueAt(i);
+ if (topTask == null) {
+ Log.wtf(CarLog.TAG_AM, "unexpected null value in sparse array");
+ continue;
+ }
+ if (!allStackIds.contains(mTopTasks.keyAt(i))) {
+ stackIdsToRemove.add(mTopTasks.keyAt(i));
+ }
+ }
+ for (int stackIdToRemove : stackIdsToRemove) {
+ mTopTasks.remove(stackIdToRemove);
+ }
mFocusedStackId = focusedStackId;
}
if (listener != null) {
diff --git a/service/src/com/android/car/SystemStateControllerService.java b/service/src/com/android/car/SystemStateControllerService.java
index e0b1161..c15ab6d 100644
--- a/service/src/com/android/car/SystemStateControllerService.java
+++ b/service/src/com/android/car/SystemStateControllerService.java
@@ -18,6 +18,8 @@
import android.content.Context;
import android.content.res.Resources;
+import com.android.car.audio.CarAudioService;
+
import java.io.PrintWriter;
public class SystemStateControllerService implements CarServiceBase {
diff --git a/service/src/com/android/car/Utils.java b/service/src/com/android/car/Utils.java
index 78f5a8e..284e610 100644
--- a/service/src/com/android/car/Utils.java
+++ b/service/src/com/android/car/Utils.java
@@ -10,6 +10,9 @@
static final Boolean DBG = false;
static String getDeviceDebugInfo(BluetoothDevice device) {
+ if (device == null) {
+ return "(null)";
+ }
return "(name = " + device.getName() + ", addr = " + device.getAddress() + ")";
}
diff --git a/service/src/com/android/car/CarAudioDeviceInfo.java b/service/src/com/android/car/audio/CarAudioDeviceInfo.java
similarity index 99%
rename from service/src/com/android/car/CarAudioDeviceInfo.java
rename to service/src/com/android/car/audio/CarAudioDeviceInfo.java
index 65e3510..2e35c56 100644
--- a/service/src/com/android/car/CarAudioDeviceInfo.java
+++ b/service/src/com/android/car/audio/CarAudioDeviceInfo.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.car;
+package com.android.car.audio;
import android.media.AudioDeviceInfo;
import android.media.AudioDevicePort;
@@ -24,6 +24,7 @@
import android.media.AudioPort;
import android.util.Log;
+import com.android.car.CarLog;
import com.android.internal.util.Preconditions;
import java.io.PrintWriter;
diff --git a/service/src/com/android/car/CarAudioService.java b/service/src/com/android/car/audio/CarAudioService.java
similarity index 99%
rename from service/src/com/android/car/CarAudioService.java
rename to service/src/com/android/car/audio/CarAudioService.java
index 1e2e481..ebfe93e 100644
--- a/service/src/com/android/car/CarAudioService.java
+++ b/service/src/com/android/car/audio/CarAudioService.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.car;
+package com.android.car.audio;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -54,6 +54,10 @@
import android.util.SparseIntArray;
import android.view.KeyEvent;
+import com.android.car.BinderInterfaceContainer;
+import com.android.car.CarLog;
+import com.android.car.CarServiceBase;
+import com.android.car.R;
import com.android.internal.util.Preconditions;
import java.io.PrintWriter;
@@ -65,6 +69,9 @@
import java.util.Set;
import java.util.stream.Collectors;
+/**
+ * Service responsible for interaction with car's audio system.
+ */
public class CarAudioService extends ICarAudio.Stub implements CarServiceBase {
private static final int DEFAULT_AUDIO_USAGE = AudioAttributes.USAGE_MEDIA;
diff --git a/service/src/com/android/car/CarVolumeGroup.java b/service/src/com/android/car/audio/CarVolumeGroup.java
similarity index 92%
rename from service/src/com/android/car/CarVolumeGroup.java
rename to service/src/com/android/car/audio/CarVolumeGroup.java
index 448979b..eff3b4c 100644
--- a/service/src/com/android/car/CarVolumeGroup.java
+++ b/service/src/com/android/car/audio/CarVolumeGroup.java
@@ -13,8 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-package com.android.car;
+package com.android.car.audio;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -60,7 +59,7 @@
mContexts = contexts;
mStoredGainIndex = Settings.Global.getInt(mContentResolver,
- CarAudioManager.getVolumeSettingsKeyForGroup(mId), -1);;
+ CarAudioManager.getVolumeSettingsKeyForGroup(mId), -1);
}
int getId() {
@@ -71,6 +70,9 @@
return mContexts;
}
+ /**
+ * @return Array of bus numbers in this {@link CarVolumeGroup}
+ */
int[] getBusNumbers() {
final int[] busNumbers = new int[mBusToCarAudioDeviceInfos.size()];
for (int i = 0; i < busNumbers.length; i++) {
@@ -123,7 +125,7 @@
}
}
- int getDefaultGainIndex() {
+ private int getDefaultGainIndex() {
return getIndexForGain(mDefaultGain);
}
@@ -139,16 +141,20 @@
return mCurrentGainIndex;
}
+ /**
+ * Sets the gain on this group, gain will be set on all buses within same bus.
+ * @param gainIndex The gain index
+ */
void setCurrentGainIndex(int gainIndex) {
int gainInMillibels = getGainForIndex(gainIndex);
Preconditions.checkArgument(
gainInMillibels >= mMinGain && gainInMillibels <= mMaxGain,
- "Gain out of range (" +
- mMinGain + ":" +
- mMaxGain +") " +
- gainInMillibels + "index " +
- gainIndex);
+ "Gain out of range ("
+ + mMinGain + ":"
+ + mMaxGain + ") "
+ + gainInMillibels + "index "
+ + gainIndex);
for (int i = 0; i < mBusToCarAudioDeviceInfos.size(); i++) {
CarAudioDeviceInfo info = mBusToCarAudioDeviceInfos.valueAt(i);
@@ -174,6 +180,9 @@
return (gainInMillibel - mMinGain) / mStepSize;
}
+ /**
+ * Gets {@link AudioDevicePort} from a context number
+ */
@Nullable
AudioDevicePort getAudioDevicePortForContext(int contextNumber) {
final int busNumber = mContextToBus.get(contextNumber, -1);
@@ -191,6 +200,7 @@
+ " buses: " + Arrays.toString(getBusNumbers());
}
+ /** Writes to dumpsys output */
void dump(PrintWriter writer) {
writer.println("CarVolumeGroup " + mId);
writer.printf("\tGain in millibel (min / max / default/ current): %d %d %d %d\n",
diff --git a/service/src/com/android/car/CarVolumeGroupsHelper.java b/service/src/com/android/car/audio/CarVolumeGroupsHelper.java
similarity index 87%
rename from service/src/com/android/car/CarVolumeGroupsHelper.java
rename to service/src/com/android/car/audio/CarVolumeGroupsHelper.java
index af2c6dd..eddcd4f 100644
--- a/service/src/com/android/car/CarVolumeGroupsHelper.java
+++ b/service/src/com/android/car/audio/CarVolumeGroupsHelper.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.car;
+package com.android.car.audio;
import android.annotation.XmlRes;
import android.content.Context;
@@ -23,12 +23,18 @@
import android.util.Log;
import android.util.Xml;
+import com.android.car.CarLog;
+import com.android.car.R;
+
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
+/**
+ * A helper class loads all volume groups from the configuration XML file.
+ */
/* package */ class CarVolumeGroupsHelper {
private static final String TAG_VOLUME_GROUPS = "volumeGroups";
@@ -43,14 +49,18 @@
mXmlConfiguration = xmlConfiguration;
}
+ /**
+ * @return all {@link CarVolumeGroup} read from configuration.
+ */
CarVolumeGroup[] loadVolumeGroups() {
List<CarVolumeGroup> carVolumeGroups = new ArrayList<>();
try (XmlResourceParser parser = mContext.getResources().getXml(mXmlConfiguration)) {
AttributeSet attrs = Xml.asAttributeSet(parser);
int type;
// Traverse to the first start tag
- while ((type=parser.next()) != XmlResourceParser.END_DOCUMENT
+ while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT
&& type != XmlResourceParser.START_TAG) {
+ // ignored
}
if (!TAG_VOLUME_GROUPS.equals(parser.getName())) {
@@ -58,7 +68,7 @@
}
int outerDepth = parser.getDepth();
int id = 0;
- while ((type=parser.next()) != XmlResourceParser.END_DOCUMENT
+ while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT
&& (type != XmlResourceParser.END_TAG || parser.getDepth() > outerDepth)) {
if (type == XmlResourceParser.END_TAG) {
continue;
@@ -80,7 +90,7 @@
List<Integer> contexts = new ArrayList<>();
int innerDepth = parser.getDepth();
- while ((type=parser.next()) != XmlResourceParser.END_DOCUMENT
+ while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT
&& (type != XmlResourceParser.END_TAG || parser.getDepth() > innerDepth)) {
if (type == XmlResourceParser.END_TAG) {
continue;
diff --git a/service/src/com/android/car/pm/ActivityBlockingActivity.java b/service/src/com/android/car/pm/ActivityBlockingActivity.java
index b39910e..fd792a6 100644
--- a/service/src/com/android/car/pm/ActivityBlockingActivity.java
+++ b/service/src/com/android/car/pm/ActivityBlockingActivity.java
@@ -15,6 +15,11 @@
*/
package com.android.car.pm;
+import static com.android.car.pm.CarPackageManagerService.BLOCKING_INTENT_EXTRA_BLOCKED_ACTIVITY_NAME;
+import static com.android.car.pm.CarPackageManagerService.BLOCKING_INTENT_EXTRA_BLOCKED_TASK_ID;
+import static com.android.car.pm.CarPackageManagerService.BLOCKING_INTENT_EXTRA_IS_ROOT_ACTIVITY_DO;
+import static com.android.car.pm.CarPackageManagerService.BLOCKING_INTENT_EXTRA_ROOT_ACTIVITY_NAME;
+
import android.app.Activity;
import android.car.Car;
import android.car.CarNotConnectedException;
@@ -24,32 +29,39 @@
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
+import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.Button;
+import android.widget.ImageView;
import android.widget.TextView;
+import androidx.annotation.Nullable;
+
import com.android.car.CarLog;
import com.android.car.R;
/**
* Default activity that will be launched when the current foreground activity is not allowed.
- * Additional information on blocked Activity will be passed as extra in Intent
- * via {@link #INTENT_KEY_BLOCKED_ACTIVITY} key.
+ * Additional information on blocked Activity should be passed as intent extras.
*/
public class ActivityBlockingActivity extends Activity {
- public static final String INTENT_KEY_BLOCKED_ACTIVITY = "blocked_activity";
- public static final String EXTRA_BLOCKED_TASK = "blocked_task";
-
private static final int INVALID_TASK_ID = -1;
private Car mCar;
private CarUxRestrictionsManager mUxRManager;
- private TextView mBlockedTitle;
+ private TextView mBlockingText;
+ private TextView mBlockedAppName;
+ private ImageView mBlockedAppIcon;
private Button mExitButton;
+
// Exiting depends on Car connection, which might not be available at the time exit was
// requested (e.g. user presses Exit Button). In that case, we record exiting was requested, and
// Car connection will perform exiting once it is established.
@@ -61,9 +73,12 @@
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_blocking);
- mBlockedTitle = findViewById(R.id.activity_blocked_title);
+ mBlockingText = findViewById(R.id.blocking_text);
+ mBlockedAppName = findViewById(R.id.blocked_app_name);
+ mBlockedAppIcon = findViewById(R.id.blocked_app_icon);
mExitButton = findViewById(R.id.exit);
- mExitButton.setOnClickListener(v -> handleFinish());
+
+ mBlockingText.setText(getString(R.string.activity_blocked_text));
// Listen to the CarUxRestrictions so this blocking activity can be dismissed when the
// restrictions are lifted.
@@ -72,7 +87,7 @@
public void onServiceConnected(ComponentName name, IBinder service) {
try {
if (mExitRequested) {
- handleFinish();
+ handleRestartingTask();
}
mUxRManager = (CarUxRestrictionsManager) mCar.getCarManager(
Car.CAR_UX_RESTRICTION_SERVICE);
@@ -99,25 +114,82 @@
protected void onResume() {
super.onResume();
- // Display message about the current blocked activity, and optionally show an exit button
+ // Display info about the current blocked activity, and optionally show an exit button
// to restart the blocked task (stack of activities) if its root activity is DO.
+ mBlockedTaskId = getIntent().getIntExtra(BLOCKING_INTENT_EXTRA_BLOCKED_TASK_ID,
+ INVALID_TASK_ID);
// blockedActivity is expected to be always passed in as the topmost activity of task.
- String blockedActivity = getIntent().getStringExtra(INTENT_KEY_BLOCKED_ACTIVITY);
- mBlockedTitle.setText(getString(R.string.activity_blocked_string));
- if (Log.isLoggable(CarLog.TAG_AM, Log.DEBUG)) {
- Log.d(CarLog.TAG_AM, "Blocking activity " + blockedActivity);
+ String blockedActivity = getIntent().getStringExtra(
+ BLOCKING_INTENT_EXTRA_BLOCKED_ACTIVITY_NAME);
+ if (!TextUtils.isEmpty(blockedActivity)) {
+ if (Log.isLoggable(CarLog.TAG_AM, Log.DEBUG)) {
+ Log.d(CarLog.TAG_AM, "Blocking activity " + blockedActivity);
+ }
+ // Show application icon and name of blocked activity.
+ Drawable appIcon = findApplicationIcon(blockedActivity);
+ if (appIcon != null) {
+ mBlockedAppIcon.setImageDrawable(appIcon);
+ } else {
+ mBlockedAppIcon.setVisibility(View.GONE);
+ }
+ mBlockedAppName.setText(findHumanReadableLabel(blockedActivity));
}
- // taskId is available as extra if the task can be restarted.
- mBlockedTaskId = getIntent().getIntExtra(EXTRA_BLOCKED_TASK, INVALID_TASK_ID);
+ boolean isRootDO = getIntent().getBooleanExtra(
+ BLOCKING_INTENT_EXTRA_IS_ROOT_ACTIVITY_DO, false);
- mExitButton.setVisibility(mBlockedTaskId == INVALID_TASK_ID ? View.GONE : View.VISIBLE);
- if (Log.isLoggable(CarLog.TAG_AM, Log.DEBUG) && mBlockedTaskId == INVALID_TASK_ID) {
- Log.d(CarLog.TAG_AM, "Blocked task ID is not available. Hiding exit button.");
+ // Display a button to restart task if root task is DO.
+ boolean showButton = mBlockedTaskId != INVALID_TASK_ID && isRootDO;
+ mExitButton.setVisibility(showButton ? View.VISIBLE : View.GONE);
+ mExitButton.setOnClickListener(v -> handleRestartingTask());
+
+ // Show more debug info for non-user build.
+ if (Build.IS_ENG || Build.IS_USERDEBUG) {
+ displayDebugInfo();
}
}
+ private void displayDebugInfo() {
+ String blockedActivity = getIntent().getStringExtra(
+ BLOCKING_INTENT_EXTRA_BLOCKED_ACTIVITY_NAME);
+ String rootActivity = getIntent().getStringExtra(BLOCKING_INTENT_EXTRA_ROOT_ACTIVITY_NAME);
+
+ TextView debugInfo = findViewById(R.id.debug_info);
+ debugInfo.setText(getDebugInfo(blockedActivity, rootActivity));
+
+ // We still want to ensure driving safety for non-user build;
+ // toggle visibility of debug info with this button.
+ Button toggleDebug = findViewById(R.id.toggle_debug_info);
+ toggleDebug.setVisibility(View.VISIBLE);
+ toggleDebug.setOnClickListener(v -> {
+ boolean isDebugVisible = debugInfo.getVisibility() == View.VISIBLE;
+ debugInfo.setVisibility(isDebugVisible ? View.GONE : View.VISIBLE);
+ });
+ }
+
+ private String getDebugInfo(String blockedActivity, String rootActivity) {
+ StringBuilder debug = new StringBuilder();
+
+ ComponentName blocked = ComponentName.unflattenFromString(blockedActivity);
+ debug.append("Blocked activity is ")
+ .append(blocked.getShortClassName())
+ .append("\nBlocked activity package is ")
+ .append(blocked.getPackageName());
+
+ if (rootActivity != null) {
+ ComponentName root = ComponentName.unflattenFromString(rootActivity);
+ // Optionally show root activity info if it differs from the blocked activity.
+ if (!root.equals(blocked)) {
+ debug.append("\n\nRoot activity is ").append(root.getShortClassName());
+ }
+ if (!root.getPackageName().equals(blocked.getPackageName())) {
+ debug.append("\nRoot activity package is ").append(root.getPackageName());
+ }
+ }
+ return debug.toString();
+ }
+
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
@@ -149,7 +221,51 @@
}
}
- private void handleFinish() {
+ // Finds the icon of the application (package) the component belongs to.
+ @Nullable
+ private Drawable findApplicationIcon(String flattenComponentName) {
+ ComponentName componentName = ComponentName.unflattenFromString(flattenComponentName);
+ try {
+ return getPackageManager().getApplicationIcon(componentName.getPackageName());
+ } catch (PackageManager.NameNotFoundException e) {
+ if (Log.isLoggable(CarLog.TAG_AM, Log.INFO)) {
+ Log.i(CarLog.TAG_AM, "Could not find package for component name "
+ + componentName.toString());
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns a human-readable string for {@code flattenComponentName}.
+ *
+ * <p>It first attempts to return the application label for this activity. If that fails,
+ * it will return the last part in the activity name.
+ */
+ private String findHumanReadableLabel(String flattenComponentName) {
+ ComponentName componentName = ComponentName.unflattenFromString(flattenComponentName);
+ String label = null;
+ // Attempt to find application label.
+ try {
+ ApplicationInfo applicationInfo = getPackageManager().getApplicationInfo(
+ componentName.getPackageName(), 0);
+ CharSequence appLabel = getPackageManager().getApplicationLabel(applicationInfo);
+ if (appLabel != null) {
+ label = appLabel.toString();
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ if (Log.isLoggable(CarLog.TAG_AM, Log.INFO)) {
+ Log.i(CarLog.TAG_AM, "Could not find package for component name "
+ + componentName.toString());
+ }
+ }
+ if (TextUtils.isEmpty(label)) {
+ label = componentName.getClass().getSimpleName();
+ }
+ return label;
+ }
+
+ private void handleRestartingTask() {
if (!mCar.isConnected()) {
mExitRequested = true;
return;
@@ -158,9 +274,12 @@
return;
}
- // Lock on self (assuming single instance) to avoid restarting the same task twice.
+ // Lock on self to avoid restarting the same task twice.
synchronized (this) {
try {
+ if (Log.isLoggable(CarLog.TAG_AM, Log.INFO)) {
+ Log.i(CarLog.TAG_AM, "Restarting task " + mBlockedTaskId);
+ }
CarPackageManager carPm = (CarPackageManager)
mCar.getCarManager(Car.PACKAGE_SERVICE);
carPm.restartTask(mBlockedTaskId);
diff --git a/service/src/com/android/car/pm/CarPackageManagerService.java b/service/src/com/android/car/pm/CarPackageManagerService.java
index ed51a78..1c50db9 100644
--- a/service/src/com/android/car/pm/CarPackageManagerService.java
+++ b/service/src/com/android/car/pm/CarPackageManagerService.java
@@ -141,6 +141,32 @@
// To track if we received the boot complete intent.
private boolean mBootLockedIntentRx;
+ /**
+ * Name of blocked activity.
+ *
+ * @hide
+ */
+ public static final String BLOCKING_INTENT_EXTRA_BLOCKED_ACTIVITY_NAME = "blocked_activity";
+ /**
+ * int task id of the blocked task.
+ *
+ * @hide
+ */
+ public static final String BLOCKING_INTENT_EXTRA_BLOCKED_TASK_ID = "blocked_task_id";
+ /**
+ * Name of root activity of blocked task.
+ *
+ * @hide
+ */
+ public static final String BLOCKING_INTENT_EXTRA_ROOT_ACTIVITY_NAME = "root_activity_name";
+ /**
+ * Boolean indicating whether the root activity is distraction-optimized (DO).
+ * Blocking screen should show a button to restart the task if {@code true}.
+ *
+ * @hide
+ */
+ public static final String BLOCKING_INTENT_EXTRA_IS_ROOT_ACTIVITY_DO = "is_root_activity_do";
+
public CarPackageManagerService(Context context,
CarUxRestrictionsManagerService uxRestrictionsService,
SystemActivityMonitoringService systemActivityMonitoringService) {
@@ -935,45 +961,64 @@
" not allowed, will block, number of tasks in stack:" +
topTask.stackInfo.taskIds.length);
}
- StringBuilder blockedActivityLog = new StringBuilder();
- Intent newActivityIntent = new Intent();
- newActivityIntent.setComponent(mActivityBlockingActivity);
- newActivityIntent.putExtra(
- ActivityBlockingActivity.INTENT_KEY_BLOCKED_ACTIVITY,
- topTask.topActivity.flattenToString());
- blockedActivityLog.append("Blocked activity ")
- .append(topTask.topActivity.flattenToShortString())
- .append(". Task id ").append(topTask.taskId);
- // If root activity of blocked task is DO, also pass its task id into blocking activity,
- // which uses the id to display a button for restarting the blocked task.
+ // Figure out the root activity of blocked task.
+ String taskRootActivity = null;
for (int i = 0; i < topTask.stackInfo.taskIds.length; i++) {
// topTask.taskId is the task that should be blocked.
if (topTask.stackInfo.taskIds[i] == topTask.taskId) {
// stackInfo represents an ActivityStack. Its fields taskIds and taskNames
// are 1:1 mapped, where taskNames is the name of root activity in this task.
- String taskRootActivity = topTask.stackInfo.taskNames[i];
-
- ComponentName rootActivityName = ComponentName.unflattenFromString(
- taskRootActivity);
- if (isActivityDistractionOptimized(
- rootActivityName.getPackageName(), rootActivityName.getClassName())) {
- newActivityIntent.putExtra(
- ActivityBlockingActivity.EXTRA_BLOCKED_TASK, topTask.taskId);
- if (Log.isLoggable(CarLog.TAG_PACKAGE, Log.INFO)) {
- Log.i(CarLog.TAG_PACKAGE, "Blocked task " + topTask.taskId
- + " has DO root activity " + taskRootActivity);
- }
- blockedActivityLog.append(". Root DO activity ")
- .append(rootActivityName.flattenToShortString());
- }
+ taskRootActivity = topTask.stackInfo.taskNames[i];
break;
}
}
- addLog(blockedActivityLog.toString());
+
+ boolean isRootDO = false;
+ if (taskRootActivity != null) {
+ ComponentName componentName = ComponentName.unflattenFromString(taskRootActivity);
+ isRootDO = isActivityDistractionOptimized(
+ componentName.getPackageName(), componentName.getClassName());
+ }
+
+ Intent newActivityIntent = createBlockingActivityIntent(
+ mActivityBlockingActivity, topTask.topActivity.flattenToShortString(),
+ topTask.taskId, taskRootActivity, isRootDO);
+
+ // Intent contains all info to debug what is blocked - log into both logcat and dumpsys.
+ String log = "Starting blocking activity with intent: " + newActivityIntent.toUri(0);
+ if (Log.isLoggable(CarLog.TAG_PACKAGE, Log.INFO)) {
+ Log.i(CarLog.TAG_PACKAGE, log);
+ }
+ addLog(log);
mSystemActivityMonitoringService.blockActivity(topTask, newActivityIntent);
}
+ /**
+ * Creates an intent to start blocking activity.
+ *
+ * @param blockingActivity the activity to launch
+ * @param blockedActivity the activity being blocked
+ * @param blockedTaskId the blocked task id, which contains the blocked activity
+ * @param taskRootActivity root activity of the blocked task
+ *
+ * @return an intent to launch the blocking activity.
+ */
+ private static Intent createBlockingActivityIntent(ComponentName blockingActivity,
+ String blockedActivity, int blockedTaskId, String taskRootActivity, boolean isRootDo) {
+ Intent newActivityIntent = new Intent();
+ newActivityIntent.setComponent(blockingActivity);
+ newActivityIntent.putExtra(
+ BLOCKING_INTENT_EXTRA_BLOCKED_ACTIVITY_NAME, blockedActivity);
+ newActivityIntent.putExtra(
+ BLOCKING_INTENT_EXTRA_BLOCKED_TASK_ID, blockedTaskId);
+ newActivityIntent.putExtra(
+ BLOCKING_INTENT_EXTRA_ROOT_ACTIVITY_NAME, taskRootActivity);
+ newActivityIntent.putExtra(
+ BLOCKING_INTENT_EXTRA_IS_ROOT_ACTIVITY_DO, isRootDo);
+ return newActivityIntent;
+ }
+
private void blockTopActivitiesIfNecessary() {
boolean restricted = mUxRestrictionsListener.isRestricted();
if (!restricted) {
diff --git a/service/src/com/android/car/user/CarUserService.java b/service/src/com/android/car/user/CarUserService.java
index eebb9d1..8716aed 100644
--- a/service/src/com/android/car/user/CarUserService.java
+++ b/service/src/com/android/car/user/CarUserService.java
@@ -95,8 +95,7 @@
int currentUser = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
if (currentUser > UserHandle.USER_SYSTEM
&& mCarUserManagerHelper.isPersistentUser(currentUser)) {
- mCarUserManagerHelper.setLastActiveUser(
- currentUser, /* skipGlobalSetting= */ false);
+ mCarUserManagerHelper.setLastActiveUser(currentUser);
}
}
}
diff --git a/tests/EmbeddedKitchenSinkApp/Android.mk b/tests/EmbeddedKitchenSinkApp/Android.mk
index 8239f3c..e6f457e 100644
--- a/tests/EmbeddedKitchenSinkApp/Android.mk
+++ b/tests/EmbeddedKitchenSinkApp/Android.mk
@@ -41,17 +41,29 @@
LOCAL_DEX_PREOPT := false
LOCAL_STATIC_ANDROID_LIBRARIES += \
+ car-service-lib-for-test \
+ car-apps-common \
androidx.car_car \
- car-service-lib-for-test
+ androidx.car_car-cluster
LOCAL_STATIC_JAVA_LIBRARIES += \
android.hidl.base-V1.0-java \
android.hardware.automotive.vehicle-V2.0-java \
vehicle-hal-support-lib \
- com.android.car.keventreader-client
+ com.android.car.keventreader-client \
+ kitchensink-gson
LOCAL_JAVA_LIBRARIES += android.car
include $(BUILD_PACKAGE)
+include $(CLEAR_VARS)
+
+LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES := \
+ kitchensink-gson:libs/gson-2.1.jar
+
+include $(BUILD_MULTI_PREBUILT)
+
+include $(CLEAR_VARS)
+
endif #TARGET_BUILD_PDK
diff --git a/tests/EmbeddedKitchenSinkApp/libs/gson-2.1-sources.jar b/tests/EmbeddedKitchenSinkApp/libs/gson-2.1-sources.jar
new file mode 100644
index 0000000..09396a0
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/libs/gson-2.1-sources.jar
Binary files differ
diff --git a/tests/EmbeddedKitchenSinkApp/libs/gson-2.1.jar b/tests/EmbeddedKitchenSinkApp/libs/gson-2.1.jar
new file mode 100644
index 0000000..83c5c99
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/libs/gson-2.1.jar
Binary files differ
diff --git a/tests/EmbeddedKitchenSinkApp/res/raw/nav_state_data.json b/tests/EmbeddedKitchenSinkApp/res/raw/nav_state_data.json
new file mode 100644
index 0000000..2aa5cd0
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/raw/nav_state_data.json
@@ -0,0 +1,55 @@
+[
+ {
+ "mSteps": [
+ {
+ "mDistance": {
+ "mMeters": 200,
+ "mDisplayValue": "0.2",
+ "mDisplayUnit": { "mValues": [ "KILOMETERS" ] }
+ },
+ "mManeuver": {
+ "mType": { "mValues": [ "DEPART" ] }
+ }
+ }
+ ],
+ "mDestinations": [
+ {
+ "mTitle": "Home",
+ "mAddress": "123 Main st",
+ "mDistance": {
+ "mMeters": 2000,
+ "mDisplayValue": "2",
+ "mDisplayUnit": { "mValues": [ "KILOMETERS" ] }
+ }
+ }
+ ]
+ },
+ {
+ "mSteps": [
+ {
+ "mDistance": {
+ "mMeters": 91,
+ "mDisplayValue": "300",
+ "mDisplayUnit": { "mValues": [ "FEET" ] }
+ },
+ "mManeuver": {
+ "mType": { "mValues": [ "TURN_NORMAL_LEFT" ] }
+ }
+ }
+ ]
+ },
+ {
+ "mSteps": [
+ {
+ "mDistance": {
+ "mMeters": 3218,
+ "mDisplayValue": "2",
+ "mDisplayUnit": { "mValues": [ "MILES" ] }
+ },
+ "mManeuver": {
+ "mType": { "mValues": [ "TURN_NORMAL_RIGHT" ] }
+ }
+ }
+ ]
+ }
+]
\ No newline at end of file
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/cluster/InstrumentClusterFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/cluster/InstrumentClusterFragment.java
index 4f5fc5b..ad4b93a 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/cluster/InstrumentClusterFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/cluster/InstrumentClusterFragment.java
@@ -33,22 +33,38 @@
import android.view.ViewGroup;
import android.widget.Toast;
+import androidx.annotation.IdRes;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.car.cluster.navigation.NavigationState;
import androidx.fragment.app.Fragment;
import com.google.android.car.kitchensink.R;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.Arrays;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.stream.Collectors;
/**
* Contains functions to test instrument cluster API.
*/
public class InstrumentClusterFragment extends Fragment {
- private static final String TAG = InstrumentClusterFragment.class.getSimpleName();
+ private static final String TAG = "Cluster.KitchenSink";
private static final int DISPLAY_IN_CLUSTER_PERMISSION_REQUEST = 1;
private CarNavigationStatusManager mCarNavigationStatusManager;
private CarAppFocusManager mCarAppFocusManager;
private Car mCarApi;
+ private Timer mTimer;
+ private NavigationState[] mNavStateData;
private final ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
@@ -81,6 +97,37 @@
mCarApi.connect();
}
+ /**
+ * Loads sample navigation data from the "nav_state_data.json" file.
+ */
+ @NonNull
+ private NavigationState[] getNavStateData() {
+ try {
+ Gson gson = new GsonBuilder().create();
+ String navStateData = getRawResourceAsString(R.raw.nav_state_data);
+ NavigationState[] navigationState = gson.fromJson(navStateData,
+ NavigationState[].class);
+ return navigationState;
+ } catch (IOException ex) {
+ Log.e(TAG, "Unable to read navigation state data", ex);
+ return new NavigationState[0];
+ }
+ }
+
+ /**
+ * Loads a raw resource as a single string.
+ */
+ @NonNull
+ private String getRawResourceAsString(@IdRes int resId) throws IOException {
+ InputStream inputStream = getResources().openRawResource(resId);
+ BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
+ StringBuilder builder = new StringBuilder();
+ for (String line = null; (line = reader.readLine()) != null; ) {
+ builder.append(line).append("\n");
+ }
+ return builder.toString();
+ }
+
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
@@ -88,7 +135,7 @@
View view = inflater.inflate(R.layout.instrument_cluster, container, false);
view.findViewById(R.id.cluster_start_button).setOnClickListener(v -> initCluster());
- view.findViewById(R.id.cluster_turn_left_button).setOnClickListener(v -> sendTurn());
+ view.findViewById(R.id.cluster_turn_left_button).setOnClickListener(v -> toogleSendTurn());
view.findViewById(R.id.cluster_start_activity).setOnClickListener(v -> startNavActivity());
return view;
@@ -97,7 +144,6 @@
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
initCarApi();
-
super.onCreate(savedInstanceState);
}
@@ -126,12 +172,46 @@
}
}
- private void sendTurn() {
- // TODO(deanh): Make this actually meaningful.
- Bundle bundle = new Bundle();
- bundle.putString("someName", "someValue time=" + System.currentTimeMillis());
+ /**
+ * Enables/disables sending turn-by-turn data through the {@link CarNavigationStatusManager}
+ */
+ private void toogleSendTurn() {
+ // If we haven't yet load the sample navigation state data, do so.
+ if (mNavStateData == null) {
+ mNavStateData = getNavStateData();
+ Log.i(TAG, "Loaded: " + Arrays.asList(mNavStateData)
+ .stream()
+ .map(n -> n.toString())
+ .collect(Collectors.joining(", ")));
+ }
+
+ // Toggle a timer to send update periodically.
+ if (mTimer == null) {
+ mTimer = new Timer();
+ mTimer.schedule(new TimerTask() {
+ private int mPos;
+
+ @Override
+ public void run() {
+ sendTurn(mNavStateData[mPos]);
+ mPos = (mPos + 1) % mNavStateData.length;
+ }
+ }, 0, 1000);
+ } else {
+ mTimer.cancel();
+ mTimer = null;
+ }
+ }
+
+ /**
+ * Sends one update of the navigation state through the {@link CarNavigationStatusManager}
+ */
+ private void sendTurn(@NonNull NavigationState state) {
try {
+ Bundle bundle = new Bundle();
+ bundle.putParcelable("navstate", state.toParcelable());
mCarNavigationStatusManager.sendEvent(1, bundle);
+ Log.i(TAG, "Sending nav state: " + state);
} catch(CarNotConnectedException e) {
Log.e(TAG, "Failed to send turn information.", e);
}
@@ -185,8 +265,6 @@
} catch (CarNotConnectedException e) {
Log.e(TAG, "Failed to get owned focus", e);
}
-
- // TODO(deanh): re-implement this using sendEvent()
}
@Override
diff --git a/tests/carservice_unit_test/src/com/android/car/CarUserManagerHelperTest.java b/tests/carservice_unit_test/src/com/android/car/CarUserManagerHelperTest.java
index 83b1b29..f731bec 100644
--- a/tests/carservice_unit_test/src/com/android/car/CarUserManagerHelperTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/CarUserManagerHelperTest.java
@@ -39,6 +39,7 @@
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
+import android.provider.Settings;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
@@ -91,6 +92,8 @@
doReturn(mActivityManager).when(mContext).getSystemService(Context.ACTIVITY_SERVICE);
doReturn(InstrumentationRegistry.getTargetContext().getResources())
.when(mContext).getResources();
+ doReturn(InstrumentationRegistry.getTargetContext().getContentResolver())
+ .when(mContext).getContentResolver();
doReturn(mContext).when(mContext).getApplicationContext();
mCarUserManagerHelper = new CarUserManagerHelper(mContext);
@@ -776,8 +779,7 @@
UserInfo otherUser2 = createUserInfoForId(lastActiveUserId - 1);
UserInfo otherUser3 = createUserInfoForId(lastActiveUserId);
- mCarUserManagerHelper.setLastActiveUser(
- lastActiveUserId, /* skipGlobalSettings= */ true);
+ setLastActiveUser(lastActiveUserId);
mockGetUsers(mSystemUser, otherUser1, otherUser2, otherUser3);
assertThat(mCarUserManagerHelper.getInitialUser()).isEqualTo(lastActiveUserId);
@@ -791,8 +793,7 @@
UserInfo otherUser1 = createUserInfoForId(lastActiveUserId - 2);
UserInfo otherUser2 = createUserInfoForId(lastActiveUserId - 1);
- mCarUserManagerHelper.setLastActiveUser(
- lastActiveUserId, /* skipGlobalSettings= */ true);
+ setLastActiveUser(lastActiveUserId);
mockGetUsers(mSystemUser, otherUser1, otherUser2);
assertThat(mCarUserManagerHelper.getInitialUser()).isEqualTo(lastActiveUserId - 2);
@@ -842,4 +843,9 @@
}
doReturn(testUsers).when(mUserManager).getUsers(true);
}
+
+ private void setLastActiveUser(int userId) {
+ Settings.Global.putInt(InstrumentationRegistry.getTargetContext().getContentResolver(),
+ Settings.Global.LAST_ACTIVE_USER_ID, userId);
+ }
}
diff --git a/tests/carservice_unit_test/src/com/android/car/user/CarUserServiceTest.java b/tests/carservice_unit_test/src/com/android/car/user/CarUserServiceTest.java
index addb53c..2e3386b 100644
--- a/tests/carservice_unit_test/src/com/android/car/user/CarUserServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/user/CarUserServiceTest.java
@@ -152,8 +152,7 @@
mCarUserService.onReceive(mMockContext, intent);
- verify(mCarUserManagerHelper).setLastActiveUser(
- lastActiveUserId, /* skipGlobalSetting= */ false);
+ verify(mCarUserManagerHelper).setLastActiveUser(lastActiveUserId);
}
/**
diff --git a/user/car-user-lib/src/android/car/userlib/CarUserManagerHelper.java b/user/car-user-lib/src/android/car/userlib/CarUserManagerHelper.java
index fdf3ade..bb3976f 100644
--- a/user/car-user-lib/src/android/car/userlib/CarUserManagerHelper.java
+++ b/user/car-user-lib/src/android/car/userlib/CarUserManagerHelper.java
@@ -84,7 +84,6 @@
private final Context mContext;
private final UserManager mUserManager;
private final ActivityManager mActivityManager;
- private int mLastActiveUser = UserHandle.USER_SYSTEM;
private Bitmap mDefaultGuestUserIcon;
private ArrayList<OnUsersUpdateListener> mUpdateListeners;
private final BroadcastReceiver mUserChangeReceiver = new BroadcastReceiver() {
@@ -164,10 +163,21 @@
* Set last active user.
*
* @param userId last active user id.
- * @param skipGlobalSetting whether to skip set the global settings value.
*/
+ public void setLastActiveUser(int userId) {
+ Settings.Global.putInt(
+ mContext.getContentResolver(), Settings.Global.LAST_ACTIVE_USER_ID, userId);
+ }
+
+ /**
+ * Set last active user.
+ *
+ * @param userId last active user id.
+ * @param skipGlobalSetting whether to skip set the global settings value.
+ * @deprecated Use {@link #setDefaultBootUser(int)} instead.
+ */
+ @Deprecated
public void setLastActiveUser(int userId, boolean skipGlobalSetting) {
- mLastActiveUser = userId;
if (!skipGlobalSetting) {
Settings.Global.putInt(
mContext.getContentResolver(), Settings.Global.LAST_ACTIVE_USER_ID, userId);
@@ -192,9 +202,6 @@
* @return user id of the last active user.
*/
public int getLastActiveUser() {
- if (mLastActiveUser != UserHandle.USER_SYSTEM) {
- return mLastActiveUser;
- }
return Settings.Global.getInt(
mContext.getContentResolver(), Settings.Global.LAST_ACTIVE_USER_ID,
/* default user id= */ UserHandle.USER_SYSTEM);