blob: 1bd820db3f7c08e637813289b93e617ee339cd59 [file] [log] [blame]
/*
* 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);
};
}