Implement UserSwitchingDialog in SystemUI mounted to OverlayWindow.

Bug: 145132885
Test: Manual + Unit Tests
Change-Id: I9eab89ddd54f9484e9bd79fc867c09ef4a17fe46
This commit is contained in:
kwaky
2020-04-22 11:07:47 -07:00
parent 5bf3f80f20
commit 3911339b66
19 changed files with 549 additions and 257 deletions

View File

@@ -1,37 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 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.
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:fitsSystemWindows="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/user_loading_avatar"
android:layout_width="@dimen/car_fullscreen_user_pod_image_avatar_width"
android:layout_height="@dimen/car_fullscreen_user_pod_image_avatar_height"
android:layout_centerHorizontal="true"/>
<TextView android:id="@+id/user_loading"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/car_padding_4"
android:textSize="@dimen/car_body1_size"
android:textColor="@color/car_body1"
android:layout_below="@id/user_loading_avatar"
android:gravity="center"/>
</RelativeLayout>

View File

@@ -16,14 +16,7 @@
*/
-->
<resources>
<dimen name="car_fullscreen_user_pod_icon_text_size">64sp</dimen>
<dimen name="car_fullscreen_user_pod_width">243dp</dimen>
<dimen name="car_fullscreen_user_pod_height">356dp</dimen>
<dimen name="car_fullscreen_user_pod_image_avatar_width">96dp</dimen>
<dimen name="car_fullscreen_user_pod_image_avatar_height">96dp</dimen>
<dimen name="car_large_avatar_size">96dp</dimen>
<!-- Application Bar -->
<dimen name="car_app_bar_height">80dp</dimen>
<!-- Margin -->

View File

@@ -3638,13 +3638,6 @@
<java-symbol type="color" name="car_card_dark" />
<java-symbol type="dimen" name="car_body1_size" />
<java-symbol type="dimen" name="car_padding_4" />
<java-symbol type="dimen" name="car_fullscreen_user_pod_icon_text_size" />
<java-symbol type="dimen" name="car_fullscreen_user_pod_image_avatar_height" />
<java-symbol type="dimen" name="car_fullscreen_user_pod_image_avatar_width" />
<java-symbol type="dimen" name="car_large_avatar_size" />
<java-symbol type="layout" name="car_user_switching_dialog" />
<java-symbol type="id" name="user_loading_avatar" />
<java-symbol type="id" name="user_loading" />
<java-symbol type="style" name="Theme.DeviceDefault.Light.Dialog.Alert.UserSwitchingDialog" />
<java-symbol type="string" name="battery_saver_description_with_learn_more" />

View File

@@ -25,4 +25,6 @@
<uses-permission android:name="android.car.permission.CAR_ENROLL_TRUST"/>
<!-- This permission is required to get bluetooth broadcast. -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<!-- This permission is required to check the foreground user id. -->
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
</manifest>

View File

@@ -0,0 +1,43 @@
<?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.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:fitsSystemWindows="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:background="@color/car_user_switching_dialog_background_color">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_gravity="center"
android:gravity="center_horizontal">
<ImageView
android:id="@+id/user_loading_avatar"
android:layout_width="@dimen/car_fullscreen_user_pod_image_avatar_width"
android:layout_height="@dimen/car_fullscreen_user_pod_image_avatar_height"/>
<TextView
android:id="@+id/user_loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/car_user_switching_dialog_loading_text_margin_top"
android:textSize="@dimen/car_user_switching_dialog_loading_text_font_size"
android:textColor="@color/car_user_switching_dialog_loading_text_color"
android:layout_below="@id/user_loading_avatar"/>
</LinearLayout>
</FrameLayout>

View File

@@ -39,4 +39,9 @@
android:layout_height="match_parent"
android:layout="@layout/car_fullscreen_user_switcher"/>
<ViewStub android:id="@+id/user_switching_dialog_stub"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout="@layout/car_user_switching_dialog"/>
</FrameLayout>

View File

@@ -55,4 +55,7 @@
<color name="list_divider_color">@*android:color/car_list_divider_light</color>
<color name="car_volume_item_divider_color">@*android:color/car_list_divider</color>
<color name="car_volume_item_background_color">@*android:color/car_card_dark</color>
<color name="car_user_switching_dialog_background_color">@android:color/black</color>
<color name="car_user_switching_dialog_loading_text_color">@*android:color/car_body1</color>
</resources>

View File

@@ -70,11 +70,13 @@
to a constant alpha percent value using the initial alpha. -->
<integer name="config_finalNotificationBackgroundAlpha">100</integer>
<!-- Car System UI's OverlayViewsMediator-->
<!-- Car System UI's OverlayViewsMediator.
Whenever a new class is added, make sure to also add that class to OverlayWindowModule. -->
<string-array name="config_carSystemUIOverlayViewsMediators" translatable="false">
<item>@string/config_notificationPanelViewMediator</item>
<item>com.android.systemui.car.keyguard.CarKeyguardViewMediator</item>
<item>com.android.systemui.car.userswitcher.FullscreenUserSwitcherViewMediator</item>
<item>com.android.systemui.car.userswitcher.UserSwitchTransitionViewMediator</item>
</string-array>
<!--

View File

@@ -15,6 +15,32 @@
~ limitations under the License
-->
<resources>
<!-- Text size for car -->
<dimen name="car_title_size">32sp</dimen>
<dimen name="car_title2_size">32sp</dimen>
<dimen name="car_headline1_size">45sp</dimen>
<dimen name="car_headline2_size">32sp</dimen>
<dimen name="car_headline3_size">24sp</dimen>
<dimen name="car_headline4_size">20sp</dimen>
<dimen name="car_body1_size">32sp</dimen>
<dimen name="car_body2_size">28sp</dimen>
<dimen name="car_body3_size">26sp</dimen>
<dimen name="car_body4_size">24sp</dimen>
<!-- car_body5_size is deprecated -->
<dimen name="car_body5_size">18sp</dimen>
<dimen name="car_label1_size">26sp</dimen>
<dimen name="car_label2_size">64sp</dimen>
<dimen name="car_action1_size">26sp</dimen>
<dimen name="car_action2_size">26sp</dimen>
<!-- Paddings -->
<dimen name="car_padding_0">4dp</dimen>
<dimen name="car_padding_1">8dp</dimen>
<dimen name="car_padding_2">16dp</dimen>
<dimen name="car_padding_3">24dp</dimen>
<dimen name="car_padding_4">32dp</dimen>
<dimen name="car_padding_5">64dp</dimen>
<dimen name="car_padding_6">96dp</dimen>
<!--
Note: status bar height and navigation bar heights are defined
in frameworks/base/core package and thus will have no effect if
@@ -156,4 +182,10 @@
<dimen name="car_user_switcher_container_height">420dp</dimen>
<!-- This must be the negative of car_user_switcher_container_height for the animation. -->
<dimen name="car_user_switcher_container_anim_height">-420dp</dimen>
<!-- dimensions for car user switching dialog -->
<dimen name="car_fullscreen_user_pod_image_avatar_width">96dp</dimen>
<dimen name="car_fullscreen_user_pod_image_avatar_height">96dp</dimen>
<dimen name="car_user_switching_dialog_loading_text_margin_top">@*android:dimen/car_padding_4</dimen>
<dimen name="car_user_switching_dialog_loading_text_font_size">@*android:dimen/car_body1_size</dimen>
</resources>

View File

@@ -15,7 +15,7 @@
~ limitations under the License.
-->
<resources>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- String to represent lowest setting of an HVAC system [CHAR LIMIT=10]-->
<string name="hvac_min_text">Min</string>
<!-- String to represent largest setting of an HVAC system [CHAR LIMIT=10]-->
@@ -34,4 +34,8 @@
<string name="user_add_user_message_setup">When you add a new user, that person needs to set up their space.</string>
<!-- Message to inform user that the newly created user will have permissions to update apps for all other users. [CHAR LIMIT=100] -->
<string name="user_add_user_message_update">Any user can update apps for all other users.</string>
<!-- Message to inform user that the new user profile is loading. [CHAR LIMIT=20] -->
<string name="car_loading_profile">Loading</string>
<!-- Message to inform user that the new user profile is loading with additional information on the previous and the next user. [CHAR LIMIT=100] -->
<string name="car_loading_profile_developer_message">Loading user (from <xliff:g id="from_user" example="10">%1$d</xliff:g> to <xliff:g id="to_user" example="12">%2$d</xliff:g>)</string>
</resources>

View File

@@ -34,16 +34,19 @@ public class FullscreenUserSwitcherViewMediator implements OverlayViewMediator {
private final StatusBarStateController mStatusBarStateController;
private final FullScreenUserSwitcherViewController mFullScreenUserSwitcherViewController;
private final CarKeyguardViewController mCarKeyguardViewController;
private final UserSwitchTransitionViewController mUserSwitchTransitionViewController;
@Inject
public FullscreenUserSwitcherViewMediator(
StatusBarStateController statusBarStateController,
CarKeyguardViewController carKeyguardViewController,
UserSwitchTransitionViewController userSwitchTransitionViewController,
FullScreenUserSwitcherViewController fullScreenUserSwitcherViewController) {
mStatusBarStateController = statusBarStateController;
mFullScreenUserSwitcherViewController = fullScreenUserSwitcherViewController;
mCarKeyguardViewController = carKeyguardViewController;
mUserSwitchTransitionViewController = userSwitchTransitionViewController;
mFullScreenUserSwitcherViewController = fullScreenUserSwitcherViewController;
}
@Override
@@ -74,6 +77,11 @@ public class FullscreenUserSwitcherViewMediator implements OverlayViewMediator {
private void onUserSelected(UserGridRecyclerView.UserRecord record) {
if (record.mType != UserGridRecyclerView.UserRecord.FOREGROUND_USER) {
mCarKeyguardViewController.hideKeyguardToPrepareBouncer();
// If guest user, we cannot use record.mInfo.id and should listen to the User lifecycle
// event instead.
if (record.mType != UserGridRecyclerView.UserRecord.START_GUEST) {
mUserSwitchTransitionViewController.handleShow(record.mInfo.id);
}
}
hide();

View File

@@ -0,0 +1,126 @@
/*
* 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.userswitcher;
import static android.car.settings.CarSettings.Global.ENABLE_USER_SWITCH_DEVELOPER_MESSAGE;
import android.annotation.UserIdInt;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.os.Handler;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.widget.ImageView;
import android.widget.TextView;
import com.android.internal.annotations.GuardedBy;
import com.android.settingslib.drawable.CircleFramedDrawable;
import com.android.systemui.R;
import com.android.systemui.car.window.OverlayViewController;
import com.android.systemui.car.window.OverlayViewGlobalStateController;
import com.android.systemui.dagger.qualifiers.Main;
import javax.inject.Inject;
import javax.inject.Singleton;
/**
* Handles showing and hiding UserSwitchTransitionView that is mounted to SystemUiOverlayWindow.
*/
@Singleton
public class UserSwitchTransitionViewController extends OverlayViewController {
private static final String TAG = "UserSwitchTransitionViewController";
private static final String ENABLE_DEVELOPER_MESSAGE_TRUE = "true";
private final Context mContext;
private final Handler mHandler;
private final Resources mResources;
private final UserManager mUserManager;
@GuardedBy("this")
private boolean mShowing;
private int mPreviousUserId = UserHandle.USER_NULL;
@Inject
public UserSwitchTransitionViewController(
Context context,
@Main Handler handler,
@Main Resources resources,
UserManager userManager,
OverlayViewGlobalStateController overlayViewGlobalStateController) {
super(R.id.user_switching_dialog_stub, overlayViewGlobalStateController);
mContext = context;
mHandler = handler;
mResources = resources;
mUserManager = userManager;
}
/**
* Makes the user switch transition view appear and draws the content inside of it if a user
* that is different from the previous user is provided and if the dialog is not already
* showing.
*/
void handleShow(@UserIdInt int newUserId) {
if (mPreviousUserId == newUserId || mShowing) return;
mShowing = true;
mHandler.post(() -> {
start();
populateDialog(mPreviousUserId, newUserId);
// next time a new user is selected, this current new user will be the previous user.
mPreviousUserId = newUserId;
});
}
void handleHide() {
if (!mShowing) return;
mShowing = false;
mHandler.post(this::stop);
}
private void populateDialog(@UserIdInt int previousUserId, @UserIdInt int newUserId) {
drawUserIcon(newUserId);
populateLoadingText(previousUserId, newUserId);
}
private void drawUserIcon(int newUserId) {
Bitmap bitmap = mUserManager.getUserIcon(newUserId);
if (bitmap != null) {
CircleFramedDrawable drawable = CircleFramedDrawable.getInstance(mContext, bitmap);
((ImageView) getLayout().findViewById(R.id.user_loading_avatar))
.setImageDrawable(drawable);
}
}
private void populateLoadingText(@UserIdInt int previousUserId, @UserIdInt int newUserId) {
TextView msgView = getLayout().findViewById(R.id.user_loading);
boolean showInfo = ENABLE_DEVELOPER_MESSAGE_TRUE.equals(
Settings.Global.getString(mContext.getContentResolver(),
ENABLE_USER_SWITCH_DEVELOPER_MESSAGE));
if (showInfo && mPreviousUserId != UserHandle.USER_NULL) {
msgView.setText(
mResources.getString(R.string.car_loading_profile_developer_message,
previousUserId, newUserId));
} else {
msgView.setText(mResources.getString(R.string.car_loading_profile));
}
}
}

View File

@@ -0,0 +1,85 @@
/*
* 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.userswitcher;
import android.app.ActivityManager;
import android.car.Car;
import android.car.user.CarUserManager;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.car.CarServiceProvider;
import com.android.systemui.car.window.OverlayViewMediator;
import javax.inject.Inject;
/**
* Registers listeners that subscribe to events that show or hide CarUserSwitchingDialog that is
* mounted to SystemUiOverlayWindow.
*/
public class UserSwitchTransitionViewMediator implements OverlayViewMediator,
CarUserManager.UserSwitchUiCallback {
private static final String TAG = "UserSwitchTransitionViewMediator";
private final CarServiceProvider mCarServiceProvider;
private final UserSwitchTransitionViewController mUserSwitchTransitionViewController;
@Inject
public UserSwitchTransitionViewMediator(
CarServiceProvider carServiceProvider,
UserSwitchTransitionViewController userSwitchTransitionViewController) {
mCarServiceProvider = carServiceProvider;
mUserSwitchTransitionViewController = userSwitchTransitionViewController;
}
@Override
public void registerListeners() {
mCarServiceProvider.addListener(car -> {
CarUserManager carUserManager =
(CarUserManager) car.getCarManager(Car.CAR_USER_SERVICE);
if (carUserManager != null) {
carUserManager.setUserSwitchUiCallback(this);
carUserManager.addListener(Runnable::run, this::handleUserLifecycleEvent);
} else {
Log.e(TAG, "registerListeners: CarUserManager could not be obtained.");
}
});
}
@Override
public void setupOverlayContentViewControllers() {
// no-op.
}
@Override
public void showUserSwitchDialog(int userId) {
mUserSwitchTransitionViewController.handleShow(userId);
}
@VisibleForTesting
void handleUserLifecycleEvent(CarUserManager.UserLifecycleEvent event) {
if (event.getEventType() == CarUserManager.USER_LIFECYCLE_EVENT_TYPE_STARTING
&& ActivityManager.getCurrentUser() == event.getUserId()) {
mUserSwitchTransitionViewController.handleShow(event.getUserId());
}
if (event.getEventType() == CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING) {
mUserSwitchTransitionViewController.handleHide();
}
}
}

View File

@@ -23,6 +23,9 @@ public interface OverlayViewMediator {
/**
* Register listeners that could use ContentVisibilityAdjuster to show/hide content.
*
* Note that we do not unregister listeners because SystemUI components are expected to live
* for the lifecycle of the device.
*/
void registerListeners();

View File

@@ -21,6 +21,7 @@ import com.android.systemui.car.notification.BottomNotificationPanelViewMediator
import com.android.systemui.car.notification.NotificationPanelViewMediator;
import com.android.systemui.car.notification.TopNotificationPanelViewMediator;
import com.android.systemui.car.userswitcher.FullscreenUserSwitcherViewMediator;
import com.android.systemui.car.userswitcher.UserSwitchTransitionViewMediator;
import dagger.Binds;
import dagger.Module;
@@ -67,4 +68,11 @@ public abstract class OverlayWindowModule {
@ClassKey(FullscreenUserSwitcherViewMediator.class)
public abstract OverlayViewMediator bindFullscreenUserSwitcherViewsMediator(
FullscreenUserSwitcherViewMediator overlayViewsMediator);
/** Injects CarUserSwitchingDialogMediator. */
@Binds
@IntoMap
@ClassKey(UserSwitchTransitionViewMediator.class)
public abstract OverlayViewMediator bindUserSwitchTransitionViewMediator(
UserSwitchTransitionViewMediator userSwitchTransitionViewMediator);
}

View File

@@ -0,0 +1,145 @@
/*
* 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.userswitcher;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import android.content.Context;
import android.content.res.Resources;
import android.os.Handler;
import android.os.UserManager;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableResources;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.car.window.OverlayViewGlobalStateController;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@SmallTest
public class UserSwitchTransitionViewControllerTest extends SysuiTestCase {
private static final int TEST_USER_1 = 100;
private static final int TEST_USER_2 = 110;
private TestableUserSwitchTransitionViewController mCarUserSwitchingDialogController;
private TestableResources mTestableResources;
@Mock
private OverlayViewGlobalStateController mOverlayViewGlobalStateController;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mTestableResources = mContext.getOrCreateTestableResources();
mCarUserSwitchingDialogController = new TestableUserSwitchTransitionViewController(
mContext,
Handler.getMain(),
mTestableResources.getResources(),
(UserManager) mContext.getSystemService(Context.USER_SERVICE),
mOverlayViewGlobalStateController
);
mCarUserSwitchingDialogController.inflate((ViewGroup) LayoutInflater.from(mContext).inflate(
R.layout.sysui_overlay_window, /* root= */ null));
}
@Test
public void onHandleShow_newUserSelected_showsDialog() {
mCarUserSwitchingDialogController.handleShow(/* currentUserId= */ TEST_USER_1);
verify(mOverlayViewGlobalStateController).showView(eq(mCarUserSwitchingDialogController),
any());
}
@Test
public void onHandleShow_alreadyShowing_ignoresRequest() {
mCarUserSwitchingDialogController.handleShow(/* currentUserId= */ TEST_USER_1);
mCarUserSwitchingDialogController.handleShow(/* currentUserId= */ TEST_USER_2);
// Verify that the request was processed only once.
verify(mOverlayViewGlobalStateController).showView(eq(mCarUserSwitchingDialogController),
any());
}
@Test
public void onHandleShow_sameUserSelected_ignoresRequest() {
mCarUserSwitchingDialogController.handleShow(/* currentUserId= */ TEST_USER_1);
mCarUserSwitchingDialogController.handleHide();
mCarUserSwitchingDialogController.handleShow(/* currentUserId= */ TEST_USER_1);
// Verify that the request was processed only once.
verify(mOverlayViewGlobalStateController).showView(eq(mCarUserSwitchingDialogController),
any());
}
@Test
public void onHide_currentlyShowing_hidesDialog() {
mCarUserSwitchingDialogController.handleShow(/* currentUserId= */ TEST_USER_1);
mCarUserSwitchingDialogController.handleHide();
verify(mOverlayViewGlobalStateController).hideView(eq(mCarUserSwitchingDialogController),
any());
}
@Test
public void onHide_notShowing_ignoresRequest() {
mCarUserSwitchingDialogController.handleShow(/* currentUserId= */ TEST_USER_1);
mCarUserSwitchingDialogController.handleHide();
mCarUserSwitchingDialogController.handleHide();
// Verify that the request was processed only once.
verify(mOverlayViewGlobalStateController).hideView(eq(mCarUserSwitchingDialogController),
any());
}
private final class TestableUserSwitchTransitionViewController extends
UserSwitchTransitionViewController {
private final Handler mHandler;
TestableUserSwitchTransitionViewController(Context context, Handler handler,
Resources resources, UserManager userManager,
OverlayViewGlobalStateController overlayViewGlobalStateController) {
super(context, handler, resources, userManager, overlayViewGlobalStateController);
mHandler = handler;
}
@Override
public void handleShow(int currentUserId) {
super.handleShow(currentUserId);
waitForIdleSync(mHandler);
}
@Override
public void handleHide() {
super.handleHide();
waitForIdleSync(mHandler);
}
}
}

View File

@@ -0,0 +1,76 @@
/*
* 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.userswitcher;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.car.user.CarUserManager;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import com.android.systemui.car.CarServiceProvider;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@SmallTest
public class UserSwitchTransitionViewMediatorTest {
private static final int TEST_USER = 100;
private UserSwitchTransitionViewMediator mUserSwitchTransitionViewMediator;
@Mock
private CarServiceProvider mCarServiceProvider;
@Mock
private UserSwitchTransitionViewController mUserSwitchTransitionViewController;
@Mock
private CarUserManager.UserLifecycleEvent mUserLifecycleEvent;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mUserSwitchTransitionViewMediator = new UserSwitchTransitionViewMediator(
mCarServiceProvider, mUserSwitchTransitionViewController);
}
@Test
public void onUserLifecycleEvent_userStarting_callsHandleShow() {
when(mUserLifecycleEvent.getEventType()).thenReturn(
CarUserManager.USER_LIFECYCLE_EVENT_TYPE_STARTING);
when(mUserLifecycleEvent.getUserId()).thenReturn(TEST_USER);
mUserSwitchTransitionViewMediator.handleUserLifecycleEvent(mUserLifecycleEvent);
verify(mUserSwitchTransitionViewController).handleShow(TEST_USER);
}
@Test
public void onUserLifecycleEvent_userSwitching_callsHandleHide() {
when(mUserLifecycleEvent.getEventType()).thenReturn(
CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING);
mUserSwitchTransitionViewMediator.handleUserLifecycleEvent(mUserLifecycleEvent);
verify(mUserSwitchTransitionViewController).handleHide();
}
}

View File

@@ -1,193 +0,0 @@
/*
* Copyright (C) 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.
*/
package com.android.server.am;
import android.content.Context;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.os.UserManager;
import android.provider.Settings;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowInsets;
import android.widget.ImageView;
import android.widget.TextView;
import com.android.internal.R;
/**
* Dialog to show when a user switch it about to happen for the car. The intent is to snapshot the
* screen immediately after the dialog shows so that the user is informed that something is
* happening in the background rather than just freeze the screen and not know if the user-switch
* affordance was being handled.
*/
final class CarUserSwitchingDialog extends UserSwitchingDialog {
private static final String TAG = "ActivityManagerCarUserSwitchingDialog";
private View mView;
public CarUserSwitchingDialog(ActivityManagerService service, Context context, UserInfo oldUser,
UserInfo newUser, boolean aboveSystem, String switchingFromSystemUserMessage,
String switchingToSystemUserMessage) {
super(service, context, oldUser, newUser, aboveSystem, switchingFromSystemUserMessage,
switchingToSystemUserMessage);
}
@Override
void inflateContent() {
// Set up the dialog contents
setCancelable(false);
Resources res = getContext().getResources();
// Custom view due to alignment and font size requirements
getContext().setTheme(R.style.Theme_DeviceDefault_Light_Dialog_Alert_UserSwitchingDialog);
mView = LayoutInflater.from(getContext()).inflate(
R.layout.car_user_switching_dialog,
null);
UserManager userManager =
(UserManager) getContext().getSystemService(Context.USER_SERVICE);
Bitmap bitmap = userManager.getUserIcon(mNewUser.id);
if (bitmap != null) {
CircleFramedDrawable drawable = CircleFramedDrawable.getInstance(bitmap,
res.getDimension(R.dimen.car_fullscreen_user_pod_image_avatar_height));
((ImageView) mView.findViewById(R.id.user_loading_avatar))
.setImageDrawable(drawable);
}
TextView msgView = mView.findViewById(R.id.user_loading);
// TODO(b/145132885): use constant from CarSettings
boolean showInfo = "true".equals(Settings.Global.getString(
getContext().getContentResolver(),
"android.car.ENABLE_USER_SWITCH_DEVELOPER_MESSAGE"));
if (showInfo) {
msgView.setText(res.getString(R.string.car_loading_profile) + " user\n(from "
+ mOldUser.id + " to " + mNewUser.id + ")");
} else {
msgView.setText(res.getString(R.string.car_loading_profile));
}
setView(mView);
}
@Override
public void show() {
super.show();
hideNavigationBar();
}
private void hideNavigationBar() {
mView.getWindowInsetsController().hide(WindowInsets.Type.navigationBars());
}
/**
* Converts the user icon to a circularly clipped one. This is used in the User Picker and
* Settings.
*/
static class CircleFramedDrawable extends Drawable {
private final Bitmap mBitmap;
private final int mSize;
private final Paint mPaint;
private float mScale;
private Rect mSrcRect;
private RectF mDstRect;
public static CircleFramedDrawable getInstance(Bitmap icon, float iconSize) {
CircleFramedDrawable instance = new CircleFramedDrawable(icon, (int) iconSize);
return instance;
}
public CircleFramedDrawable(Bitmap icon, int size) {
super();
mSize = size;
mBitmap = Bitmap.createBitmap(mSize, mSize, Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas(mBitmap);
final int width = icon.getWidth();
final int height = icon.getHeight();
final int square = Math.min(width, height);
final Rect cropRect = new Rect((width - square) / 2, (height - square) / 2,
square, square);
final RectF circleRect = new RectF(0f, 0f, mSize, mSize);
final Path fillPath = new Path();
fillPath.addArc(circleRect, 0f, 360f);
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
// opaque circle
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(Color.BLACK);
mPaint.setStyle(Paint.Style.FILL);
canvas.drawPath(fillPath, mPaint);
// mask in the icon where the bitmap is opaque
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
canvas.drawBitmap(icon, cropRect, circleRect, mPaint);
// prepare paint for frame drawing
mPaint.setXfermode(null);
mScale = 1f;
mSrcRect = new Rect(0, 0, mSize, mSize);
mDstRect = new RectF(0, 0, mSize, mSize);
}
@Override
public void draw(Canvas canvas) {
final float inside = mScale * mSize;
final float pad = (mSize - inside) / 2f;
mDstRect.set(pad, pad, mSize - pad, mSize - pad);
canvas.drawBitmap(mBitmap, mSrcRect, mDstRect, null);
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
@Override
public void setAlpha(int alpha) {
// Needed to implement abstract method. Do nothing.
}
@Override
public void setColorFilter(ColorFilter colorFilter) {
// Needed to implement abstract method. Do nothing.
}
}
}

View File

@@ -2736,19 +2736,13 @@ class UserController implements Handler.Callback {
void showUserSwitchingDialog(UserInfo fromUser, UserInfo toUser,
String switchingFromSystemUserMessage, String switchingToSystemUserMessage) {
Dialog d;
if (!mService.mContext.getPackageManager()
.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
d = new UserSwitchingDialog(mService, mService.mContext, fromUser, toUser,
true /* above system */, switchingFromSystemUserMessage,
switchingToSystemUserMessage);
} else {
d = new CarUserSwitchingDialog(mService, mService.mContext, fromUser, toUser,
true /* above system */, switchingFromSystemUserMessage,
switchingToSystemUserMessage);
final Dialog d = new UserSwitchingDialog(mService, mService.mContext, fromUser,
toUser, true /* above system */, switchingFromSystemUserMessage,
switchingToSystemUserMessage);
d.show();
}
d.show();
}
void reportGlobalUsageEventLocked(int event) {