Merge "UI modifications for user picker Test: tested on Mojave bug: 75023449" into pi-dev

This commit is contained in:
TreeHugger Robot
2018-04-07 00:40:28 +00:00
committed by Android (Google) Code Review
13 changed files with 454 additions and 550 deletions

View File

@@ -15,40 +15,32 @@
limitations under the License.
-->
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:clipChildren="false"
android:alpha="0"
android:layout_width="wrap_content"
android:layout_height="@dimen/car_fullscreen_user_pod_height"
android:layout_gravity="center_horizontal|bottom" >
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:gravity="fill_horizontal"
android:layout_marginTop="@dimen/car_padding_5"
android:layout_marginStart="@dimen/car_padding_4">
<ImageView android:id="@+id/user_avatar"
android:layout_centerHorizontal="true"
android:layout_marginTop="@dimen/car_fullscreen_user_pod_margin_image_top"
android:layout_width="@dimen/car_fullscreen_user_pod_image_avatar_width"
android:layout_height="@dimen/car_fullscreen_user_pod_image_avatar_height"
android:layout_above="@id/user_name" />
/>
<TextView android:id="@+id/user_name"
android:layout_width="@dimen/car_fullscreen_user_pod_width"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/car_fullscreen_user_pod_margin_name_top"
android:layout_marginBottom="@dimen/car_fullscreen_user_pod_margin_name_bottom"
android:textSize="@dimen/car_fullscreen_user_pod_name_text_size"
android:layout_marginTop="@dimen/car_padding_4"
android:textSize="@dimen/car_body1_size"
android:textColor="@color/qs_user_detail_name"
android:ellipsize="end"
android:singleLine="true"
android:gravity="center_horizontal"
android:layout_above="@id/device_name" />
android:gravity="center"
android:layout_below="@id/user_avatar"/>
<TextView android:id="@+id/device_name"
android:layout_width="@dimen/car_fullscreen_user_pod_width"
android:layout_height="wrap_content"
android:textSize="@dimen/car_fullscreen_user_pod_device_text_size"
android:textColor="@color/qs_user_detail_name"
android:ellipsize="end"
android:singleLine="true"
android:gravity="center_horizontal"
android:layout_alignParentBottom="true" />
</RelativeLayout>

View File

@@ -1,25 +0,0 @@
<?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.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:clipChildren="false"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center" >
<!-- car_fullscreen_user_pods will be dynamically added here. -->
</LinearLayout>

View File

@@ -39,27 +39,34 @@
android:theme="@android:style/Theme"
android:layout_alignParentTop="true"/>
<com.android.systemui.statusbar.car.UserGridView
android:id="@+id/user_grid"
<RelativeLayout
android:id="@+id/fullscreen_user_switcher_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/car_margin"
android:layout_marginRight="@dimen/car_margin"
android:layout_centerInParent="true"/>
android:layout_height="match_parent"
android:layout_marginStart="@dimen/car_margin"
android:layout_marginEnd="@dimen/car_margin">
<com.android.systemui.statusbar.car.PageIndicator
android:id="@+id/user_switcher_page_indicator"
android:layout_width="match_parent"
android:layout_height="@dimen/car_page_indicator_dot_diameter"
android:layout_below="@+id/user_grid" />
<RelativeLayout
android:id="@+id/fullscreen_user_switcher_container_inner"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginEnd="@dimen/car_padding_4">
<com.android.systemui.statusbar.car.UserGridRecyclerView
android:id="@+id/user_grid"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/header"
android:scrollbars="vertical"
android:scrollbarFadeDuration="0"
android:verticalScrollbarPosition="left"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:layout_alignParentRight="true"/>
</RelativeLayout>
</RelativeLayout>
<Button
android:id="@+id/start_driving"
android:layout_width="wrap_content"
android:layout_height="@dimen/car_start_driving_height"
android:text="@string/start_driving"
style="@style/CarUserSwitcher.StartDrivingButton"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true" />
</RelativeLayout>
</FrameLayout>

View File

@@ -30,27 +30,29 @@
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/user_switcher_container"
android:layout_marginStart="@dimen/car_margin"
android:layout_marginEnd="@dimen/car_margin"
android:clipChildren="false"
android:layout_width="match_parent"
android:layout_height="@dimen/car_user_switcher_container_height"
android:layout_gravity="center_horizontal" >
android:layout_height="@dimen/car_user_switcher_container_height">
<com.android.systemui.statusbar.car.UserGridView
android:id="@+id/user_grid"
android:clipChildren="false"
<RelativeLayout
android:id="@+id/fullscreen_user_switcher_container_inner"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/car_margin"
android:layout_marginRight="@dimen/car_margin"
android:layout_above="@id/user_switcher_page_indicator" />
android:layout_height="match_parent"
android:layout_marginEnd="@dimen/car_padding_4">
<com.android.systemui.statusbar.car.PageIndicator
android:id="@+id/user_switcher_page_indicator"
android:layout_width="match_parent"
android:layout_height="@dimen/car_page_indicator_dot_diameter"
android:layout_marginBottom="@dimen/car_page_indicator_margin_bottom"
android:alpha="0"
android:layout_alignParentBottom="true" />
<com.android.systemui.statusbar.car.UserGridRecyclerView
android:id="@+id/user_grid"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scrollbars="vertical"
android:verticalScrollbarPosition="left"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:layout_alignParentRight="true"
android:scrollbarFadeDuration="0"/>
</RelativeLayout>
</RelativeLayout>

View File

@@ -17,18 +17,14 @@
-->
<resources>
<dimen name="car_margin">148dp</dimen>
<dimen name="car_margin_standard">112dp</dimen>
<dimen name="car_fullscreen_user_pod_margin_image_top">24dp</dimen>
<dimen name="car_fullscreen_user_pod_margin_name_top">24dp</dimen>
<dimen name="car_fullscreen_user_pod_margin_name_bottom">20dp</dimen>
<dimen name="car_fullscreen_user_pod_margin_between">24dp</dimen>
<dimen name="car_fullscreen_user_pod_icon_text_size">96dp</dimen>
<dimen name="car_fullscreen_user_pod_image_avatar_width">192dp</dimen>
<dimen name="car_fullscreen_user_pod_image_avatar_height">192dp</dimen>
<dimen name="car_fullscreen_user_pod_width">264dp</dimen>
<!-- TODO replace with car support lib sizes when available -->
<dimen name="car_fullscreen_user_pod_icon_text_size">32sp</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_name_text_size">40sp</dimen> <!-- B1 -->
<dimen name="car_fullscreen_user_pod_device_text_size">@dimen/car_body2_size</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_navigation_button_width">64dp</dimen>
<dimen name="car_navigation_bar_width">760dp</dimen>

View File

@@ -17,4 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<integer name="car_user_switcher_anim_cascade_delay_ms">27</integer>
<!-- Full screen user switcher column number TODO: move to support library-->
<integer name="user_fullscreen_switcher_num_col">3</integer>
</resources>

View File

@@ -17,6 +17,10 @@
*/
-->
<resources>
<string name="unknown_user_label">Unknown</string>
<string name="start_driving">Start Driving</string>
<!-- Name of Guest Profile. [CHAR LIMIT=30] -->
<string name="car_guest">Guest</string>
<!-- Name of Add User Profile. [CHAR LIMIT=30] -->
<string name="car_add_user">Add User</string>
<!-- Default name of the new user created. [CHAR LIMIT=30] -->
<string name="car_new_user">New User</string>
</resources>

View File

@@ -29,7 +29,6 @@ import com.android.systemui.R;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.qs.QSFooter;
import com.android.systemui.qs.QSPanel;
import com.android.systemui.statusbar.car.UserGridView;
import com.android.systemui.statusbar.phone.MultiUserSwitch;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.UserInfoController;

View File

@@ -20,21 +20,20 @@ import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.app.Fragment;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.support.v7.widget.GridLayoutManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.qs.QSFooter;
import com.android.systemui.statusbar.car.PageIndicator;
import com.android.systemui.statusbar.car.UserGridView;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.statusbar.car.UserGridRecyclerView;
import java.util.ArrayList;
import java.util.List;
@@ -45,14 +44,12 @@ import java.util.List;
* status bar, and a static row with access to the user switcher and settings.
*/
public class CarQSFragment extends Fragment implements QS {
private ViewGroup mPanel;
private View mHeader;
private View mUserSwitcherContainer;
private CarQSFooter mFooter;
private View mFooterUserName;
private View mFooterExpandIcon;
private UserGridView mUserGridView;
private PageIndicator mPageIndicator;
private UserGridRecyclerView mUserGridView;
private AnimatorSet mAnimatorSet;
private UserSwitchCallback mUserSwitchCallback;
@@ -65,7 +62,6 @@ public class CarQSFragment extends Fragment implements QS {
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mPanel = (ViewGroup) view;
mHeader = view.findViewById(R.id.header);
mFooter = view.findViewById(R.id.qs_footer);
mFooterUserName = mFooter.findViewById(R.id.user_name);
@@ -75,16 +71,15 @@ public class CarQSFragment extends Fragment implements QS {
updateUserSwitcherHeight(0);
mUserGridView = view.findViewById(R.id.user_grid);
mUserGridView.init(null, Dependency.get(UserSwitcherController.class),
false /* overrideAlpha */);
mPageIndicator = view.findViewById(R.id.user_switcher_page_indicator);
mPageIndicator.setupWithViewPager(mUserGridView);
Context context = getContext();
mUserGridView = mUserSwitcherContainer.findViewById(R.id.user_grid);
GridLayoutManager layoutManager = new GridLayoutManager(context,
context.getResources().getInteger(R.integer.user_fullscreen_switcher_num_col));
mUserGridView.setLayoutManager(layoutManager);
mUserGridView.buildAdapter();
mUserSwitchCallback = new UserSwitchCallback();
mFooter.setUserSwitchCallback(mUserSwitchCallback);
mUserGridView.setUserSwitchCallback(mUserSwitchCallback);
}
@Override
@@ -111,13 +106,11 @@ public class CarQSFragment extends Fragment implements QS {
@Override
public void setHeaderListening(boolean listening) {
mFooter.setListening(listening);
mUserGridView.setListening(listening);
}
@Override
public void setListening(boolean listening) {
mFooter.setListening(listening);
mUserGridView.setListening(listening);
}
@Override
@@ -219,24 +212,6 @@ public class CarQSFragment extends Fragment implements QS {
mShowing = false;
animateHeightChange(false /* opening */);
}
public void resetShowing() {
if (mShowing) {
for (int i = 0; i < mUserGridView.getChildCount(); i++) {
ViewGroup podContainer = (ViewGroup) mUserGridView.getChildAt(i);
// Need to bring the last child to the front to maintain the order in the pod
// container. Why? ¯\_(ツ)_/¯
if (podContainer.getChildCount() > 0) {
podContainer.getChildAt(podContainer.getChildCount() - 1).bringToFront();
}
// The alpha values are default to 0, so if the pods have been refreshed, they
// need to be set to 1 when showing.
for (int j = 0; j < podContainer.getChildCount(); j++) {
podContainer.getChildAt(j).setAlpha(1f);
}
}
}
}
}
private void updateUserSwitcherHeight(int height) {
@@ -260,27 +235,6 @@ public class CarQSFragment extends Fragment implements QS {
});
allAnimators.add(heightAnimator);
// The user grid contains pod containers that each contain a number of pods. Animate
// all pods to avoid any discrepancy/race conditions with possible changes during the
// animation.
int cascadeDelay = getResources().getInteger(
R.integer.car_user_switcher_anim_cascade_delay_ms);
for (int i = 0; i < mUserGridView.getChildCount(); i++) {
ViewGroup podContainer = (ViewGroup) mUserGridView.getChildAt(i);
for (int j = 0; j < podContainer.getChildCount(); j++) {
View pod = podContainer.getChildAt(j);
Animator podAnimator = AnimatorInflater.loadAnimator(getContext(),
opening ? R.anim.car_user_switcher_open_pod_animation
: R.anim.car_user_switcher_close_pod_animation);
// Add the cascading delay between pods
if (opening) {
podAnimator.setStartDelay(podAnimator.getStartDelay() + j * cascadeDelay);
}
podAnimator.setTarget(pod);
allAnimators.add(podAnimator);
}
}
Animator nameAnimator = AnimatorInflater.loadAnimator(getContext(),
opening ? R.anim.car_user_switcher_open_name_animation
: R.anim.car_user_switcher_close_name_animation);
@@ -293,12 +247,6 @@ public class CarQSFragment extends Fragment implements QS {
iconAnimator.setTarget(mFooterExpandIcon);
allAnimators.add(iconAnimator);
Animator pageAnimator = AnimatorInflater.loadAnimator(getContext(),
opening ? R.anim.car_user_switcher_open_pages_animation
: R.anim.car_user_switcher_close_pages_animation);
pageAnimator.setTarget(mPageIndicator);
allAnimators.add(pageAnimator);
mAnimatorSet = new AnimatorSet();
mAnimatorSet.addListener(new AnimatorListenerAdapter() {
@Override

View File

@@ -418,8 +418,7 @@ public class CarStatusBar extends StatusBar implements
Dependency.get(UserSwitcherController.class);
if (userSwitcherController.useFullscreenUserSwitcher()) {
mFullscreenUserSwitcher = new FullscreenUserSwitcher(this,
userSwitcherController,
mStatusBarWindow.findViewById(R.id.fullscreen_user_switcher_stub));
mStatusBarWindow.findViewById(R.id.fullscreen_user_switcher_stub), mContext);
} else {
super.createUserSwitcher();
}

View File

@@ -18,14 +18,15 @@ package com.android.systemui.statusbar.car;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.content.res.Resources;
import android.content.Context;
import android.view.View;
import android.view.ViewStub;
import android.widget.ProgressBar;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import com.android.systemui.R;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.policy.UserSwitcherController;
/**
* Manages the fullscreen user switcher.
@@ -33,36 +34,25 @@ import com.android.systemui.statusbar.policy.UserSwitcherController;
public class FullscreenUserSwitcher {
private final View mContainer;
private final View mParent;
private final UserGridView mUserGridView;
private final UserSwitcherController mUserSwitcherController;
private final UserGridRecyclerView mUserGridView;
private final ProgressBar mSwitchingUsers;
private final int mShortAnimDuration;
private boolean mShowing;
public FullscreenUserSwitcher(StatusBar statusBar,
UserSwitcherController userSwitcherController,
ViewStub containerStub) {
mUserSwitcherController = userSwitcherController;
public FullscreenUserSwitcher(StatusBar statusBar, ViewStub containerStub, Context context) {
mParent = containerStub.inflate();
mContainer = mParent.findViewById(R.id.container);
mUserGridView = mContainer.findViewById(R.id.user_grid);
mUserGridView.init(statusBar, mUserSwitcherController, true /* overrideAlpha */);
mUserGridView.setUserSelectionListener(record -> {
if (!record.isCurrent) {
toggleSwitchInProgress(true);
}
});
mUserGridView.setStatusBar(statusBar);
GridLayoutManager layoutManager = new GridLayoutManager(context,
context.getResources().getInteger(R.integer.user_fullscreen_switcher_num_col));
mUserGridView.setLayoutManager(layoutManager);
mUserGridView.buildAdapter();
mUserGridView.setUserSelectionListener(record -> toggleSwitchInProgress(true));
PageIndicator pageIndicator = mContainer.findViewById(R.id.user_switcher_page_indicator);
pageIndicator.setupWithViewPager(mUserGridView);
Resources res = mContainer.getResources();
mShortAnimDuration = res.getInteger(android.R.integer.config_shortAnimTime);
mContainer.findViewById(R.id.start_driving).setOnClickListener(v -> {
automaticallySelectUser();
});
mShortAnimDuration = mContainer.getResources()
.getInteger(android.R.integer.config_shortAnimTime);
mSwitchingUsers = mParent.findViewById(R.id.switching_users);
}
@@ -115,10 +105,4 @@ public class FullscreenUserSwitcher {
toggleSwitchInProgress(false);
mParent.setVisibility(View.GONE);
}
private void automaticallySelectUser() {
// TODO: Switch according to some policy. This implementation just tries to drop the
// keyguard for the current user.
mUserGridView.showOfflineAuthUi();
}
}

View File

@@ -0,0 +1,360 @@
/*
* 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.systemui.statusbar.car;
import android.app.ActivityManager;
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.Paint;
import android.graphics.Paint.Align;
import android.graphics.drawable.GradientDrawable;
import android.os.AsyncTask;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.ViewHolder;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import com.android.settingslib.users.UserManagerHelper;
import com.android.systemui.R;
import com.android.systemui.qs.car.CarQSFragment;
import com.android.systemui.statusbar.phone.StatusBar;
import java.util.ArrayList;
import java.util.List;
/**
* Displays a GridLayout with icons for the users in the system to allow switching between users.
* One of the uses of this is for the lock screen in auto.
*/
public class UserGridRecyclerView extends RecyclerView implements
UserManagerHelper.OnUsersUpdateListener {
private StatusBar mStatusBar;
private UserSelectionListener mUserSelectionListener;
private UserAdapter mAdapter;
private UserManagerHelper mUserManagerHelper;
private Context mContext;
public UserGridRecyclerView(Context context, AttributeSet attrs) {
super(context, attrs);
super.setHasFixedSize(true);
mContext = context;
mUserManagerHelper = new UserManagerHelper(mContext);
}
/**
* Register listener for any update to the users
*/
@Override
public void onFinishInflate() {
mUserManagerHelper.registerOnUsersUpdateListener(this);
}
/**
* Unregisters listener checking for any change to the users
*/
@Override
public void onDetachedFromWindow() {
mUserManagerHelper.unregisterOnUsersUpdateListener();
}
/**
* Initializes the adapter that populates the grid layout
*
* @return the adapter
*/
public void buildAdapter() {
List<UserRecord> userRecords = createUserRecords(mUserManagerHelper
.getAllUsers());
mAdapter = new UserAdapter(mContext, userRecords);
super.setAdapter(mAdapter);
}
public void setStatusBar(@Nullable StatusBar statusBar) {
mStatusBar = statusBar;
}
private List<UserRecord> createUserRecords(List<UserInfo> userInfoList) {
List<UserRecord> userRecords = new ArrayList<>();
for (UserInfo userInfo : userInfoList) {
boolean isCurrent = false;
if (ActivityManager.getCurrentUser() == userInfo.id) {
isCurrent = true;
}
UserRecord record = new UserRecord(userInfo, false /* isGuest */,
false /* isAddUser */, isCurrent);
userRecords.add(record);
}
// Add guest user record if the current user is not a guest
if (!mUserManagerHelper.isGuestUser()) {
userRecords.add(addGuestUserRecord());
}
// Add add user record if the current user can add users
if (mUserManagerHelper.canAddUsers()) {
userRecords.add(addUserRecord());
}
return userRecords;
}
/**
* Create guest user record
*/
private UserRecord addGuestUserRecord() {
UserInfo userInfo = new UserInfo();
userInfo.name = mContext.getString(R.string.car_guest);
return new UserRecord(userInfo, true /* isGuest */,
false /* isAddUser */, false /* isCurrent */);
}
/**
* Create add user record
*/
private UserRecord addUserRecord() {
UserInfo userInfo = new UserInfo();
userInfo.name = mContext.getString(R.string.car_add_user);
return new UserRecord(userInfo, false /* isGuest */,
true /* isAddUser */, false /* isCurrent */);
}
public void onUserSwitched(int newUserId) {
// Bring up security view after user switch is completed.
post(this::showOfflineAuthUi);
}
public void setUserSelectionListener(UserSelectionListener userSelectionListener) {
mUserSelectionListener = userSelectionListener;
}
void showOfflineAuthUi() {
// TODO: Show keyguard UI in-place.
if (mStatusBar != null) {
mStatusBar.executeRunnableDismissingKeyguard(null/* runnable */, null /* cancelAction */,
true /* dismissShade */, true /* afterKeyguardGone */, true /* deferred */);
}
}
@Override
public void onUsersUpdate() {
mAdapter.clearUsers();
mAdapter.updateUsers(createUserRecords(mUserManagerHelper.getAllUsers()));
mAdapter.notifyDataSetChanged();
}
/**
* Adapter to populate the grid layout with the available user profiles
*/
public final class UserAdapter extends RecyclerView.Adapter<UserAdapter.UserAdapterViewHolder> {
private final Context mContext;
private List<UserRecord> mUsers;
private final int mPodImageAvatarWidth;
private final int mPodImageAvatarHeight;
private final Resources mRes;
private final String mGuestName;
private final String mNewUserName;
public UserAdapter(Context context, List<UserRecord> users) {
mRes = context.getResources();
mContext = context;
updateUsers(users);
mPodImageAvatarWidth = mRes.getDimensionPixelSize(
R.dimen.car_fullscreen_user_pod_image_avatar_width);
mPodImageAvatarHeight = mRes.getDimensionPixelSize(
R.dimen.car_fullscreen_user_pod_image_avatar_height);
mGuestName = mRes.getString(R.string.car_guest);
mNewUserName = mRes.getString(R.string.car_new_user);
}
public void clearUsers() {
mUsers.clear();
}
public void updateUsers(List<UserRecord> users) {
mUsers = users;
}
@Override
public UserAdapterViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(mContext)
.inflate(R.layout.car_fullscreen_user_pod, parent, false);
view.setAlpha(1f);
view.bringToFront();
return new UserAdapterViewHolder(view);
}
@Override
public void onBindViewHolder(UserAdapterViewHolder holder, int position) {
UserRecord userRecord = mUsers.get(position);
holder.mUserAvatarImageView.setImageBitmap(getDefaultUserIcon(userRecord));
holder.mUserNameTextView.setText(userRecord.mInfo.name);
holder.mView.setOnClickListener(v -> {
if (userRecord == null) {
return;
}
// Notify the listener which user was selected
if (mUserSelectionListener != null) {
mUserSelectionListener.onUserSelected(userRecord);
}
// If the user selects Guest, switch to Guest profile
if (userRecord.mIsGuest) {
mUserManagerHelper.switchToGuest(mGuestName);
return;
}
// If the user wants to add a user, start task to add new user
if (userRecord.mIsAddUser) {
new AddNewUserTask().execute(mNewUserName);
return;
}
// If the user doesn't want to be a guest or add a user, switch to the user selected
mUserManagerHelper.switchToUser(userRecord.mInfo);
});
}
private class AddNewUserTask extends AsyncTask<String, Void, UserInfo> {
@Override
protected UserInfo doInBackground(String... userNames) {
return mUserManagerHelper.createNewUser(userNames[0]);
}
@Override
protected void onPreExecute() {
}
@Override
protected void onPostExecute(UserInfo user) {
if (user != null) {
mUserManagerHelper.switchToUser(user);
}
}
}
@Override
public int getItemCount() {
return mUsers.size();
}
/**
* Returns the default user icon. This icon is a circle with a letter in it. The letter is
* the first character in the username.
*
* @param record the profile of the user for which the icon should be created
*/
private Bitmap getDefaultUserIcon(UserRecord record) {
CharSequence displayText;
boolean isAddUserText = false;
if (record.mIsAddUser) {
displayText = "+";
isAddUserText = true;
} else {
displayText = record.mInfo.name.subSequence(0, 1);
}
Bitmap out = Bitmap.createBitmap(mPodImageAvatarWidth, mPodImageAvatarHeight,
Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(out);
// Draw the circle background.
GradientDrawable shape = new GradientDrawable();
shape.setShape(GradientDrawable.RADIAL_GRADIENT);
shape.setGradientRadius(1.0f);
shape.setColor(mContext.getColor(R.color.car_user_switcher_no_user_image_bgcolor));
shape.setBounds(0, 0, mPodImageAvatarWidth, mPodImageAvatarHeight);
shape.draw(canvas);
// Draw the letter in the center.
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(mContext.getColor(R.color.car_user_switcher_no_user_image_fgcolor));
paint.setTextAlign(Align.CENTER);
if (isAddUserText) {
paint.setTextSize(mRes.getDimensionPixelSize(
R.dimen.car_touch_target_size));
} else {
paint.setTextSize(mRes.getDimensionPixelSize(
R.dimen.car_fullscreen_user_pod_icon_text_size));
}
Paint.FontMetricsInt metrics = paint.getFontMetricsInt();
// The Y coordinate is measured by taking half the height of the pod, but that would
// draw the character putting the bottom of the font in the middle of the pod. To
// correct this, half the difference between the top and bottom distance metrics of the
// font gives the offset of the font. Bottom is a positive value, top is negative, so
// the different is actually a sum. The "half" operation is then factored out.
canvas.drawText(displayText.toString(), mPodImageAvatarWidth / 2,
(mPodImageAvatarHeight - (metrics.bottom + metrics.top)) / 2, paint);
return out;
}
public class UserAdapterViewHolder extends RecyclerView.ViewHolder {
public ImageView mUserAvatarImageView;
public TextView mUserNameTextView;
public View mView;
public UserAdapterViewHolder(View view) {
super(view);
mView = view;
mUserAvatarImageView = (ImageView) view.findViewById(R.id.user_avatar);
mUserNameTextView = (TextView) view.findViewById(R.id.user_name);
}
}
}
/**
* Object wrapper class for the userInfo. Use it to distinguish if a profile is a
* guest profile, add user profile, or a current user.
*/
public static final class UserRecord {
public final UserInfo mInfo;
public final boolean mIsGuest;
public final boolean mIsAddUser;
public final boolean mIsCurrent;
public UserRecord(UserInfo userInfo, boolean isGuest, boolean isAddUser,
boolean isCurrent) {
mInfo = userInfo;
mIsGuest = isGuest;
mIsAddUser = isAddUser;
mIsCurrent = isCurrent;
}
}
/**
* Listener used to notify when a user has been selected
*/
interface UserSelectionListener {
void onUserSelected(UserRecord record);
}
}

View File

@@ -1,364 +0,0 @@
/*
* Copyright (C) 2015 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.statusbar.car;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.qs.car.CarQSFragment;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Vector;
/**
* Displays a ViewPager with icons for the users in the system to allow switching between users.
* One of the uses of this is for the lock screen in auto.
*/
public class UserGridView extends ViewPager implements
UserInfoController.OnUserInfoChangedListener {
private StatusBar mStatusBar;
private UserSwitcherController mUserSwitcherController;
private Adapter mAdapter;
private UserSelectionListener mUserSelectionListener;
private UserInfoController mUserInfoController;
private Vector mUserContainers;
private int mContainerWidth;
private boolean mOverrideAlpha;
private CarQSFragment.UserSwitchCallback mUserSwitchCallback;
public UserGridView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void init(StatusBar statusBar, UserSwitcherController userSwitcherController,
boolean overrideAlpha) {
mStatusBar = statusBar;
mUserSwitcherController = userSwitcherController;
mAdapter = new Adapter(mUserSwitcherController);
mUserInfoController = Dependency.get(UserInfoController.class);
mOverrideAlpha = overrideAlpha;
// Whenever the container width changes, the containers must be refreshed. Instead of
// doing an initial refreshContainers() to populate the containers, this listener will
// refresh them on layout change because that affects how the users are split into
// containers. Furthermore, at this point, the container width is unknown, so
// refreshContainers() cannot populate any containers.
addOnLayoutChangeListener(
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
int newWidth = Math.max(left - right, right - left);
if (mContainerWidth != newWidth) {
mContainerWidth = newWidth;
refreshContainers();
}
});
}
private void refreshContainers() {
mUserContainers = new Vector();
Context context = getContext();
LayoutInflater inflater = LayoutInflater.from(context);
for (int i = 0; i < mAdapter.getCount(); i++) {
ViewGroup pods = (ViewGroup) inflater.inflate(
R.layout.car_fullscreen_user_pod_container, null);
int iconsPerPage = mAdapter.getIconsPerPage();
int limit = Math.min(mUserSwitcherController.getUsers().size(), (i + 1) * iconsPerPage);
for (int j = i * iconsPerPage; j < limit; j++) {
View v = mAdapter.makeUserPod(inflater, context, j, pods);
if (mOverrideAlpha) {
v.setAlpha(1f);
}
pods.addView(v);
// This is hacky, but the dividers on the pod container LinearLayout don't seem
// to work for whatever reason. Instead, set a right margin on the pod if it's not
// the right-most pod and there is more than one pod in the container.
if (i < limit - 1 && limit > 1) {
ViewGroup.MarginLayoutParams params =
(ViewGroup.MarginLayoutParams) v.getLayoutParams();
params.setMargins(0, 0, getResources().getDimensionPixelSize(
R.dimen.car_fullscreen_user_pod_margin_between), 0);
v.setLayoutParams(params);
}
}
mUserContainers.add(pods);
}
mAdapter = new Adapter(mUserSwitcherController);
setAdapter(mAdapter);
}
@Override
public void onUserInfoChanged(String name, Drawable picture, String userAccount) {
refreshContainers();
}
public void setUserSwitchCallback(CarQSFragment.UserSwitchCallback callback) {
mUserSwitchCallback = callback;
}
public void onUserSwitched(int newUserId) {
// Bring up security view after user switch is completed.
post(this::showOfflineAuthUi);
}
public void setUserSelectionListener(UserSelectionListener userSelectionListener) {
mUserSelectionListener = userSelectionListener;
}
public void setListening(boolean listening) {
if (listening) {
mUserInfoController.addCallback(this);
} else {
mUserInfoController.removeCallback(this);
}
}
void showOfflineAuthUi() {
// TODO: Show keyguard UI in-place.
mStatusBar.executeRunnableDismissingKeyguard(null, null, true, true, true);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Wrap content doesn't work in ViewPagers, so simulate the behavior in code.
int height = 0;
if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY) {
height = MeasureSpec.getSize(heightMeasureSpec);
} else {
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
child.measure(widthMeasureSpec,
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
height = Math.max(child.getMeasuredHeight(), height);
}
// Respect the AT_MOST request from parent.
if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST) {
height = Math.min(MeasureSpec.getSize(heightMeasureSpec), height);
}
}
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
/**
* This is a ViewPager.PagerAdapter which deletegates the work to a
* UserSwitcherController.BaseUserAdapter. Java doesn't support multiple inheritance so we have
* to use composition instead to achieve the same goal since both the base classes are abstract
* classes and not interfaces.
*/
private final class Adapter extends PagerAdapter {
private final int mPodWidth;
private final int mPodMarginBetween;
private final int mPodImageAvatarWidth;
private final int mPodImageAvatarHeight;
private final WrappedBaseUserAdapter mUserAdapter;
public Adapter(UserSwitcherController controller) {
super();
mUserAdapter = new WrappedBaseUserAdapter(controller, this);
Resources res = getResources();
mPodWidth = res.getDimensionPixelSize(R.dimen.car_fullscreen_user_pod_width);
mPodMarginBetween = res.getDimensionPixelSize(
R.dimen.car_fullscreen_user_pod_margin_between);
mPodImageAvatarWidth = res.getDimensionPixelSize(
R.dimen.car_fullscreen_user_pod_image_avatar_width);
mPodImageAvatarHeight = res.getDimensionPixelSize(
R.dimen.car_fullscreen_user_pod_image_avatar_height);
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View) object);
}
private int getIconsPerPage() {
// We need to know how many pods we need in this page. Each pod has its own width and
// a margin between them. We can then divide the measured width of the parent by the
// sum of pod width and margin to get the number of pods that will completely fit.
// There is one less margin than the number of pods (eg. for 5 pods, there are 4
// margins), so need to add the margin to the measured width to account for that.
return (mContainerWidth + mPodMarginBetween) /
(mPodWidth + mPodMarginBetween);
}
@Override
public void finishUpdate(ViewGroup container) {
if (mUserSwitchCallback != null) {
mUserSwitchCallback.resetShowing();
}
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
if (position < mUserContainers.size()) {
container.addView((View) mUserContainers.get(position));
return mUserContainers.get(position);
} else {
return null;
}
}
/**
* Returns the default user icon. This icon is a circle with a letter in it. The letter is
* the first character in the username.
*
* @param userName the username of the user for which the icon is to be created
*/
private Bitmap getDefaultUserIcon(CharSequence userName) {
CharSequence displayText = userName.subSequence(0, 1);
Bitmap out = Bitmap.createBitmap(mPodImageAvatarWidth, mPodImageAvatarHeight,
Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(out);
// Draw the circle background.
GradientDrawable shape = new GradientDrawable();
shape.setShape(GradientDrawable.RADIAL_GRADIENT);
shape.setGradientRadius(1.0f);
shape.setColor(getContext().getColor(R.color.car_user_switcher_no_user_image_bgcolor));
shape.setBounds(0, 0, mPodImageAvatarWidth, mPodImageAvatarHeight);
shape.draw(canvas);
// Draw the letter in the center.
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(getContext().getColor(R.color.car_user_switcher_no_user_image_fgcolor));
paint.setTextAlign(Align.CENTER);
paint.setTextSize(getResources().getDimensionPixelSize(
R.dimen.car_fullscreen_user_pod_icon_text_size));
Paint.FontMetricsInt metrics = paint.getFontMetricsInt();
// The Y coordinate is measured by taking half the height of the pod, but that would
// draw the character putting the bottom of the font in the middle of the pod. To
// correct this, half the difference between the top and bottom distance metrics of the
// font gives the offset of the font. Bottom is a positive value, top is negative, so
// the different is actually a sum. The "half" operation is then factored out.
canvas.drawText(displayText.toString(), mPodImageAvatarWidth / 2,
(mPodImageAvatarHeight - (metrics.bottom + metrics.top)) / 2, paint);
return out;
}
private View makeUserPod(LayoutInflater inflater, Context context,
int position, ViewGroup parent) {
final UserSwitcherController.UserRecord record = mUserAdapter.getItem(position);
View view = inflater.inflate(R.layout.car_fullscreen_user_pod, parent, false);
TextView nameView = view.findViewById(R.id.user_name);
if (record != null) {
nameView.setText(mUserAdapter.getName(context, record));
view.setActivated(record.isCurrent);
} else {
nameView.setText(context.getString(R.string.unknown_user_label));
}
ImageView iconView = (ImageView) view.findViewById(R.id.user_avatar);
if (record == null || (record.picture == null && !record.isAddUser)) {
iconView.setImageBitmap(getDefaultUserIcon(nameView.getText()));
} else if (record.isAddUser) {
Drawable icon = context.getDrawable(R.drawable.ic_add_circle_qs);
icon.setTint(context.getColor(R.color.car_user_switcher_no_user_image_bgcolor));
iconView.setImageDrawable(icon);
} else {
iconView.setImageBitmap(record.picture);
}
iconView.setOnClickListener(v -> {
if (record == null) {
return;
}
if (mUserSelectionListener != null) {
mUserSelectionListener.onUserSelected(record);
}
if (record.isCurrent) {
showOfflineAuthUi();
} else {
mUserSwitcherController.switchTo(record);
}
});
return view;
}
@Override
public int getCount() {
int iconsPerPage = getIconsPerPage();
if (iconsPerPage == 0) {
return 0;
}
return (int) Math.ceil((double) mUserAdapter.getCount() / getIconsPerPage());
}
public void refresh() {
mUserAdapter.refresh();
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
}
private final class WrappedBaseUserAdapter extends UserSwitcherController.BaseUserAdapter {
private final Adapter mContainer;
public WrappedBaseUserAdapter(UserSwitcherController controller, Adapter container) {
super(controller);
mContainer = container;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
throw new UnsupportedOperationException("unused");
}
@Override
public void notifyDataSetChanged() {
super.notifyDataSetChanged();
mContainer.notifyDataSetChanged();
}
}
interface UserSelectionListener {
void onUserSelected(UserSwitcherController.UserRecord record);
};
}