Add username to status bar and update layout

Update the status bar with the new UX designs:
- Remove driver and passenger HVAC views
- Add driver icon (placeholder) and username aligned to start of bar
- Move system icons aligned to end of bar (previously next to clock)
- Add AM/PM to clock

HVAC will be moved into the navigation bar in a separate change.

Test: manual - change to guest, change username, change to long username (truncates before clock view)
Test: atest :carsysui-presubmit
Bug: 162598687
Change-Id: I5b9095adce7fee0970e8b15d307f01eb4adf2b40
This commit is contained in:
Julia Kawano
2020-08-17 23:24:16 +00:00
committed by Julia Kawano
parent cf259d5288
commit 224c81c6da
9 changed files with 371 additions and 74 deletions

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2020 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:width="@dimen/system_bar_icon_drawing_size"
android:height="@dimen/system_bar_icon_drawing_size"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M12,12c2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4 -4,1.79 -4,4 1.79,4 4,4zM12,14c-2.67,0 -8,1.34 -8,4v2h16v-2c0,-2.66 -5.33,-4 -8,-4z"
android:fillColor="@color/system_bar_user_icon_color"/>
</vector>

View File

@@ -31,39 +31,44 @@
android:layoutDirection="ltr">
<FrameLayout
android:id="@+id/left_hvac_container"
android:id="@+id/user_name_container"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentStart="true"
android:layout_toStartOf="@+id/clock_container"
>
<com.android.systemui.car.navigationbar.CarNavigationButton
android:id="@+id/hvacleft"
android:id="@+id/user_name"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@null"
systemui:broadcast="true"
systemui:intent="intent:#Intent;action=android.car.intent.action.TOGGLE_HVAC_CONTROLS;end"
/>
<com.android.systemui.car.hvac.AnimatedTemperatureView
android:id="@+id/lefttext"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:paddingStart="@*android:dimen/car_padding_4"
android:paddingEnd="16dp"
android:gravity="center_vertical|start"
android:minEms="4"
android:textAppearance="@style/TextAppearance.CarStatus"
systemui:hvacAreaId="49"
systemui:hvacMaxText="@string/hvac_max_text"
systemui:hvacMaxValue="@dimen/hvac_max_value"
systemui:hvacMinText="@string/hvac_min_text"
systemui:hvacMinValue="@dimen/hvac_min_value"
systemui:hvacPivotOffset="60dp"
systemui:hvacPropertyId="358614275"
systemui:hvacTempFormat="%.0f\u00B0"
/>
systemui:icon="@null"
systemui:intent="intent:#Intent;component=com.android.car.settings/.users.UserSwitcherActivity;launchFlags=0x24000000;end"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:gravity="center_vertical"
>
<ImageView
android:id="@+id/user_avatar"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:src="@drawable/car_ic_user_icon"
android:paddingLeft="@dimen/system_bar_user_icon_padding"
android:paddingRight="@dimen/system_bar_user_icon_padding"
/>
<TextView
android:id="@+id/user_name_text"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:textAppearance="@style/TextAppearance.SystemBar.Username"
android:maxLines="1"
/>
</LinearLayout>
</com.android.systemui.car.navigationbar.CarNavigationButton>
</FrameLayout>
<FrameLayout
@@ -86,7 +91,8 @@
android:layout_gravity="center"
android:elevation="5dp"
android:singleLine="true"
android:textAppearance="@style/TextAppearance.StatusBar.Clock"
android:textAppearance="@style/TextAppearance.SystemBar.Clock"
systemui:amPmStyle="normal"
/>
</FrameLayout>
@@ -94,10 +100,9 @@
android:id="@+id/system_icon_area"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_centerHorizontal="true"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_toEndOf="@+id/clock_container"
android:paddingStart="@*android:dimen/car_padding_1"
android:paddingEnd="@*android:dimen/car_padding_1"
android:gravity="center_vertical"
android:orientation="horizontal"
>
@@ -107,46 +112,9 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:paddingStart="4dp"
android:gravity="center_vertical"
/>
</LinearLayout>
<FrameLayout
android:id="@+id/right_hvac_container"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentEnd="true"
>
<com.android.systemui.car.navigationbar.CarNavigationButton
android:id="@+id/hvacright"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@null"
systemui:broadcast="true"
systemui:intent="intent:#Intent;action=android.car.intent.action.TOGGLE_HVAC_CONTROLS;end"
/>
<com.android.systemui.car.hvac.AnimatedTemperatureView
android:id="@+id/righttext"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:paddingStart="16dp"
android:paddingEnd="@*android:dimen/car_padding_4"
android:gravity="center_vertical|end"
android:minEms="4"
android:textAppearance="@style/TextAppearance.CarStatus"
systemui:hvacAreaId="68"
systemui:hvacMaxText="@string/hvac_max_text"
systemui:hvacMaxValue="@dimen/hvac_max_value"
systemui:hvacMinText="@string/hvac_min_text"
systemui:hvacMinValue="@dimen/hvac_min_value"
systemui:hvacPivotOffset="60dp"
systemui:hvacPropertyId="358614275"
systemui:hvacTempFormat="%.0f\u00B0"
/>
</FrameLayout>
</RelativeLayout>
</com.android.systemui.car.navigationbar.CarNavigationBarView>

View File

@@ -31,6 +31,8 @@
<color name="docked_divider_background">@*android:color/car_grey_50</color>
<color name="system_bar_background_opaque">#ff172026</color>
<!-- colors for status bar -->
<color name="system_bar_user_icon_color">#ffffff</color>
<color name="status_bar_background_color">#33000000</color>
<drawable name="system_bar_background">@color/status_bar_background_color</drawable>

View File

@@ -46,8 +46,9 @@
in frameworks/base/core package and thus will have no effect if
set here. See car_product overlay for car specific defaults-->
<dimen name="status_bar_icon_drawing_size_dark">36dp</dimen>
<dimen name="status_bar_icon_drawing_size">36dp</dimen>
<dimen name="system_bar_user_icon_padding">16dp</dimen>
<dimen name="system_bar_icon_drawing_size_dark">36dp</dimen>
<dimen name="system_bar_icon_drawing_size">36dp</dimen>
<!-- The amount by which to scale up the status bar icons. -->
<item name="status_bar_icon_scale_factor" format="float" type="dimen">1.75</item>

View File

@@ -25,11 +25,16 @@
<item name="android:padding">22dp</item>
</style>
<style name="TextAppearance.StatusBar.Clock"
parent="@*android:style/TextAppearance.StatusBar.Icon">
<item name="android:textSize">42sp</item>
<item name="android:fontFamily">sans-serif-regular</item>
<item name="android:textColor">@*android:color/car_grey_50</item>
<style name="TextAppearance.SystemBar.Clock"
parent="@*android:style/TextAppearance.StatusBar.Icon">
<item name="android:textSize">@dimen/car_body1_size</item>
<item name="android:textColor">@*android:color/car_headline3</item>
</style>
<style name="TextAppearance.SystemBar.Username"
parent="@android:style/TextAppearance.DeviceDefault">
<item name="android:textSize">@dimen/car_body3_size</item>
<item name="android:textColor">@*android:color/car_headline3</item>
</style>
<style name="TextAppearance.CarStatus" parent="@android:style/TextAppearance.DeviceDefault">

View File

@@ -23,6 +23,7 @@ import android.view.ViewGroup;
import androidx.annotation.Nullable;
import com.android.systemui.car.hvac.HvacController;
import com.android.systemui.car.statusbar.UserNameViewController;
import com.android.systemui.dagger.SysUISingleton;
import javax.inject.Inject;
@@ -38,6 +39,7 @@ public class CarNavigationBarController {
private final ButtonSelectionStateController mButtonSelectionStateController;
private final ButtonRoleHolderController mButtonRoleHolderController;
private final Lazy<HvacController> mHvacControllerLazy;
private final Lazy<UserNameViewController> mUserNameViewControllerLazy;
private boolean mShowTop;
private boolean mShowBottom;
@@ -60,12 +62,14 @@ public class CarNavigationBarController {
NavigationBarViewFactory navigationBarViewFactory,
ButtonSelectionStateController buttonSelectionStateController,
Lazy<HvacController> hvacControllerLazy,
Lazy<UserNameViewController> userNameViewControllerLazy,
ButtonRoleHolderController buttonRoleHolderController,
SystemBarConfigs systemBarConfigs) {
mContext = context;
mNavigationBarViewFactory = navigationBarViewFactory;
mButtonSelectionStateController = buttonSelectionStateController;
mHvacControllerLazy = hvacControllerLazy;
mUserNameViewControllerLazy = userNameViewControllerLazy;
mButtonRoleHolderController = buttonRoleHolderController;
// Read configuration.
@@ -109,6 +113,7 @@ public class CarNavigationBarController {
mHvacControllerLazy.get().removeAllComponents();
mButtonSelectionStateController.removeAll();
mButtonRoleHolderController.removeAll();
mUserNameViewControllerLazy.get().removeAll();
}
/** Gets the top window if configured to do so. */
@@ -218,6 +223,7 @@ public class CarNavigationBarController {
mButtonSelectionStateController.addAllButtonsWithSelectionState(view);
mButtonRoleHolderController.addAllButtonsWithRoleName(view);
mHvacControllerLazy.get().addTemperatureViewToController(view);
mUserNameViewControllerLazy.get().addUserNameView(view);
}
/** Sets a touch listener for the top navigation bar. */

View File

@@ -0,0 +1,132 @@
/*
* Copyright (C) 2020 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.systemui.car.statusbar;
import android.car.Car;
import android.car.user.CarUserManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.UserInfo;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import com.android.systemui.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.car.CarDeviceProvisionedController;
import com.android.systemui.car.CarServiceProvider;
import com.android.systemui.dagger.SysUISingleton;
import javax.inject.Inject;
/**
* Controls a TextView with the current driver's username
*/
@SysUISingleton
public class UserNameViewController {
private static final String TAG = "UserNameViewController";
private Context mContext;
private UserManager mUserManager;
private CarUserManager mCarUserManager;
private CarServiceProvider mCarServiceProvider;
private CarDeviceProvisionedController mCarDeviceProvisionedController;
private BroadcastDispatcher mBroadcastDispatcher;
private TextView mUserNameView;
private final BroadcastReceiver mUserUpdateReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
updateUser(mCarDeviceProvisionedController.getCurrentUser());
}
};
private final CarUserManager.UserLifecycleListener mUserLifecycleListener =
new CarUserManager.UserLifecycleListener() {
@Override
public void onEvent(CarUserManager.UserLifecycleEvent event) {
if (event.getEventType()
== CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING) {
updateUser(event.getUserId());
}
}
};
@Inject
public UserNameViewController(Context context, CarServiceProvider carServiceProvider,
UserManager userManager, BroadcastDispatcher broadcastDispatcher,
CarDeviceProvisionedController carDeviceProvisionedController) {
mContext = context;
mCarServiceProvider = carServiceProvider;
mUserManager = userManager;
mBroadcastDispatcher = broadcastDispatcher;
mCarDeviceProvisionedController = carDeviceProvisionedController;
}
/**
* Find the {@link TextView} for the driver's user name from a view and if found set it with the
* current driver's user name.
*/
public void addUserNameView(View v) {
TextView userNameView = v.findViewById(R.id.user_name_text);
if (userNameView != null) {
if (mUserNameView == null) {
registerForUserChangeEvents();
}
mUserNameView = userNameView;
updateUser(mCarDeviceProvisionedController.getCurrentUser());
}
}
/**
* Clean up the controller and unregister receiver.
*/
public void removeAll() {
mBroadcastDispatcher.unregisterReceiver(mUserUpdateReceiver);
if (mCarUserManager != null) {
mCarUserManager.removeListener(mUserLifecycleListener);
}
}
private void registerForUserChangeEvents() {
// Register for user switching
mCarServiceProvider.addListener(car -> {
mCarUserManager = (CarUserManager) car.getCarManager(Car.CAR_USER_SERVICE);
if (mCarUserManager != null) {
mCarUserManager.addListener(Runnable::run, mUserLifecycleListener);
} else {
Log.e(TAG, "CarUserManager could not be obtained.");
}
});
// Also register for user info changing
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_USER_INFO_CHANGED);
mBroadcastDispatcher.registerReceiver(mUserUpdateReceiver, filter, /* executor= */ null,
UserHandle.ALL);
}
private void updateUser(int userId) {
if (mUserNameView != null) {
UserInfo currentUserInfo = mUserManager.getUserInfo(userId);
mUserNameView.setText(currentUserInfo.name);
}
}
}

View File

@@ -33,6 +33,7 @@ import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.car.CarSystemUiTest;
import com.android.systemui.car.hvac.HvacController;
import com.android.systemui.car.statusbar.UserNameViewController;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.statusbar.phone.StatusBarIconController;
@@ -58,6 +59,8 @@ public class CarNavigationBarControllerTest extends SysuiTestCase {
private ButtonRoleHolderController mButtonRoleHolderController;
@Mock
private HvacController mHvacController;
@Mock
private UserNameViewController mUserNameViewController;
@Before
public void setUp() throws Exception {
@@ -73,7 +76,7 @@ public class CarNavigationBarControllerTest extends SysuiTestCase {
private CarNavigationBarController createNavigationBarController() {
return new CarNavigationBarController(mContext, mNavigationBarViewFactory,
mButtonSelectionStateController, () -> mHvacController,
mButtonRoleHolderController,
() -> mUserNameViewController, mButtonRoleHolderController,
new SystemBarConfigs(mTestableResources.getResources()));
}

View File

@@ -0,0 +1,155 @@
/*
* Copyright (C) 2020 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.systemui.car.statusbar;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import android.car.Car;
import android.car.user.CarUserManager;
import android.content.BroadcastReceiver;
import android.content.Intent;
import android.content.pm.UserInfo;
import android.os.UserManager;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.View;
import android.widget.TextView;
import androidx.test.filters.SmallTest;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.car.CarDeviceProvisionedController;
import com.android.systemui.car.CarServiceProvider;
import com.android.systemui.car.CarSystemUiTest;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@CarSystemUiTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@SmallTest
public class UserNameViewControllerTest extends SysuiTestCase {
private final UserInfo mUserInfo1 = new UserInfo(/* id= */ 0, "Test User Name", /* flags= */ 0);
private final UserInfo mUserInfo2 = new UserInfo(/* id= */ 1, "Another User", /* flags= */ 0);
private TextView mTextView;
private UserNameViewController mUserNameViewController;
@Mock
private Car mCar;
@Mock
private CarUserManager mCarUserManager;
@Mock
private UserManager mUserManager;
@Mock
private CarDeviceProvisionedController mCarDeviceProvisionedController;
@Mock
private BroadcastDispatcher mBroadcastDispatcher;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
when(mUserManager.getUserInfo(mUserInfo1.id)).thenReturn(mUserInfo1);
when(mUserManager.getUserInfo(mUserInfo2.id)).thenReturn(mUserInfo2);
when(mCar.isConnected()).thenReturn(true);
when(mCar.getCarManager(Car.CAR_USER_SERVICE)).thenReturn(mCarUserManager);
CarServiceProvider carServiceProvider = new CarServiceProvider(mContext, mCar);
mUserNameViewController = new UserNameViewController(getContext(), carServiceProvider,
mUserManager, mBroadcastDispatcher, mCarDeviceProvisionedController);
mTextView = new TextView(getContext());
mTextView.setId(R.id.user_name_text);
}
@Test
public void addUserNameViewToController_updatesUserNameView() {
when(mCarDeviceProvisionedController.getCurrentUser()).thenReturn(mUserInfo1.id);
mUserNameViewController.addUserNameView(mTextView);
assertEquals(mTextView.getText(), mUserInfo1.name);
}
@Test
public void addUserNameViewToController_withNoTextView_doesNotUpdate() {
View nullView = new View(getContext());
mUserNameViewController.addUserNameView(nullView);
assertEquals(mTextView.getText(), "");
verifyZeroInteractions(mCarDeviceProvisionedController);
verifyZeroInteractions(mCarUserManager);
verifyZeroInteractions(mUserManager);
}
@Test
public void userLifecycleListener_onUserSwitchLifecycleEvent_updatesUserNameView() {
ArgumentCaptor<CarUserManager.UserLifecycleListener> userLifecycleListenerArgumentCaptor =
ArgumentCaptor.forClass(CarUserManager.UserLifecycleListener.class);
when(mCarDeviceProvisionedController.getCurrentUser()).thenReturn(mUserInfo1.id);
// Add the initial TextView, which registers the UserLifecycleListener
mUserNameViewController.addUserNameView(mTextView);
assertEquals(mTextView.getText(), mUserInfo1.name);
verify(mCarUserManager).addListener(any(), userLifecycleListenerArgumentCaptor.capture());
CarUserManager.UserLifecycleEvent event = new CarUserManager.UserLifecycleEvent(
CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING, /* from= */ mUserInfo1.id,
/* to= */ mUserInfo2.id);
userLifecycleListenerArgumentCaptor.getValue().onEvent(event);
assertEquals(mTextView.getText(), mUserInfo2.name);
}
@Test
public void userInfoChangedBroadcast_withoutInitializingUserNameView_doesNothing() {
getContext().sendBroadcast(new Intent(Intent.ACTION_USER_INFO_CHANGED));
assertEquals(mTextView.getText(), "");
verifyZeroInteractions(mCarDeviceProvisionedController);
}
@Test
public void userInfoChangedBroadcast_withUserNameViewInitialized_updatesUserNameView() {
ArgumentCaptor<BroadcastReceiver> broadcastReceiverArgumentCaptor = ArgumentCaptor.forClass(
BroadcastReceiver.class);
when(mCarDeviceProvisionedController.getCurrentUser()).thenReturn(mUserInfo1.id);
mUserNameViewController.addUserNameView(mTextView);
assertEquals(mTextView.getText(), mUserInfo1.name);
verify(mBroadcastDispatcher).registerReceiver(broadcastReceiverArgumentCaptor.capture(),
any(), any(), any());
reset(mCarDeviceProvisionedController);
when(mCarDeviceProvisionedController.getCurrentUser()).thenReturn(mUserInfo2.id);
broadcastReceiverArgumentCaptor.getValue().onReceive(getContext(),
new Intent(Intent.ACTION_USER_INFO_CHANGED));
assertEquals(mTextView.getText(), mUserInfo2.name);
verify(mCarDeviceProvisionedController).getCurrentUser();
}
}