Added user icon and (device | profile) owner info on KitchenSink USER screen.
Also created a UserAvatarView on car-admin-ui-lib, so it properly
shows the managed user badge when needed.
Test: manual verification
Bug: 175214581
Bug: 176262528
Change-Id: I6c412c909830bbbc0cea366a175579721d2630ab
diff --git a/car-admin-ui-lib/Android.bp b/car-admin-ui-lib/Android.bp
index 930278a..3b68619 100644
--- a/car-admin-ui-lib/Android.bp
+++ b/car-admin-ui-lib/Android.bp
@@ -23,5 +23,10 @@
srcs: [
"src/**/*.java",
],
- resource_dirs: ["src/main/res"],
+ libs: [
+ "SettingsLib"
+ ],
+ resource_dirs: [
+ "src/main/res"
+ ],
}
diff --git a/car-admin-ui-lib/src/main/java/com/android/car/admin/ui/UserAvatarView.java b/car-admin-ui-lib/src/main/java/com/android/car/admin/ui/UserAvatarView.java
new file mode 100644
index 0000000..a011e24
--- /dev/null
+++ b/car-admin-ui-lib/src/main/java/com/android/car/admin/ui/UserAvatarView.java
@@ -0,0 +1,138 @@
+/*
+ * 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.car.admin.ui;
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.View;
+
+import com.android.settingslib.drawable.UserIconDrawable;
+
+// TODO(b/176262528): copied from com.android.systemui, ideally it should be provided by a common
+// library like SettingsLib. If not, then this whole project / package should be renamed to
+// "car-user-ui-lib", not "car-admin-ui-lib".
+
+/**
+ * A view that displays a user image cropped to a circle with an optional frame.
+ */
+public class UserAvatarView extends View {
+
+ private final UserIconDrawable mDrawable = new UserIconDrawable();
+
+ public UserAvatarView(Context context, AttributeSet attrs,
+ int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, R.styleable.UserAvatarView, defStyleAttr, defStyleRes);
+ final int N = a.getIndexCount();
+ for (int i = 0; i < N; i++) {
+ int attr = a.getIndex(i);
+ if (attr == R.styleable.UserAvatarView_avatarPadding) {
+ setAvatarPadding(a.getDimension(attr, 0));
+ } else if (attr == R.styleable.UserAvatarView_frameWidth) {
+ setFrameWidth(a.getDimension(attr, 0));
+ } else if (attr == R.styleable.UserAvatarView_framePadding) {
+ setFramePadding(a.getDimension(attr, 0));
+ } else if (attr == R.styleable.UserAvatarView_frameColor) {
+ setFrameColor(a.getColorStateList(attr));
+ } else if (attr == R.styleable.UserAvatarView_badgeDiameter) {
+ setBadgeDiameter(a.getDimension(attr, 0));
+ } else if (attr == R.styleable.UserAvatarView_badgeMargin) {
+ setBadgeMargin(a.getDimension(attr, 0));
+ }
+ else {
+ setBadgeDiameter(a.getDimension(attr, 0));
+ }
+ }
+ a.recycle();
+ setBackground(mDrawable);
+ }
+
+ public UserAvatarView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public UserAvatarView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public UserAvatarView(Context context) {
+ this(context, null);
+ }
+
+ @Override
+ public void setActivated(boolean activated) {
+ super.setActivated(activated);
+ mDrawable.invalidateSelf();
+ }
+
+ public void setFrameColor(ColorStateList color) {
+ mDrawable.setFrameColor(color);
+ }
+
+ public void setFrameWidth(float frameWidth) {
+ mDrawable.setFrameWidth(frameWidth);
+ }
+
+ public void setFramePadding(float framePadding) {
+ mDrawable.setFramePadding(framePadding);
+ }
+
+ public void setAvatarPadding(float avatarPadding) {
+ mDrawable.setPadding(avatarPadding);
+ }
+
+ public void setBadgeDiameter(float diameter) {
+ mDrawable.setBadgeRadius(diameter * 0.5f);
+ }
+
+ public void setBadgeMargin(float margin) {
+ mDrawable.setBadgeMargin(margin);
+ }
+
+ public void setAvatar(Bitmap avatar) {
+ mDrawable.setIcon(avatar);
+ mDrawable.setBadge(null);
+ }
+
+ public void setAvatarWithBadge(Bitmap avatar, int userId) {
+ mDrawable.setIcon(avatar);
+ mDrawable.setBadgeIfManagedUser(getContext(), userId);
+ }
+
+ public void setDrawable(Drawable d) {
+ if (d instanceof UserIconDrawable) {
+ throw new RuntimeException("Recursively adding UserIconDrawable");
+ }
+ mDrawable.setIconDrawable(d);
+ mDrawable.setBadge(null);
+ }
+
+ public void setDrawableWithBadge(Drawable d, int userId) {
+ if (d instanceof UserIconDrawable) {
+ throw new RuntimeException("Recursively adding UserIconDrawable");
+ }
+ mDrawable.setIconDrawable(d);
+ mDrawable.setBadgeIfManagedUser(getContext(), userId);
+ }
+}
diff --git a/car-admin-ui-lib/src/main/res/values/attrs.xml b/car-admin-ui-lib/src/main/res/values/attrs.xml
new file mode 100644
index 0000000..f41c690
--- /dev/null
+++ b/car-admin-ui-lib/src/main/res/values/attrs.xml
@@ -0,0 +1,30 @@
+<?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.
+-->
+<resources>
+<!-- TODO(b/176262528): copy-and-pasted from frameworks/base/packages/SystemUI/res/values/attrs.xml
+ if moved to a proper library, whole file could be removed -->
+ <attr name="frameColor" format="color" />
+ <declare-styleable name="UserAvatarView">
+ <attr name="avatarPadding" format="dimension" />
+ <attr name="frameWidth" format="dimension" />
+ <attr name="framePadding" format="dimension" />
+ <!-- {@deprecated Use a statelist in frameColor instead.} -->
+ <attr name="activeFrameColor" format="color" />
+ <attr name="frameColor" />
+ <attr name="badgeDiameter" format="dimension" />
+ <attr name="badgeMargin" format="dimension" />
+ </declare-styleable>
+</resources>
\ No newline at end of file
diff --git a/tests/EmbeddedKitchenSinkApp/Android.bp b/tests/EmbeddedKitchenSinkApp/Android.bp
index 7bd0245..926221e 100644
--- a/tests/EmbeddedKitchenSinkApp/Android.bp
+++ b/tests/EmbeddedKitchenSinkApp/Android.bp
@@ -39,6 +39,7 @@
"car-service-test-static-lib",
"com.google.android.material_material",
"androidx.appcompat_appcompat",
+ "car-admin-ui-lib",
"car-ui-lib",
"android.hidl.base-V1.0-java",
"android.hardware.automotive.vehicle-V2.0-java",
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/user_info_view.xml b/tests/EmbeddedKitchenSinkApp/res/layout/user_info_view.xml
index e27dac8..0ea3f8b 100644
--- a/tests/EmbeddedKitchenSinkApp/res/layout/user_info_view.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/user_info_view.xml
@@ -18,54 +18,78 @@
NOTE: This layout is meant to be used by the UserInfoView widget only
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:admin_ui="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:orientation="vertical" >
+ android:orientation="horizontal" >
+ <FrameLayout
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_marginRight="30dp">
+ <com.android.car.admin.ui.UserAvatarView
+ android:id="@+id/user_avatar"
+ android:layout_width="108dp"
+ android:layout_height="108dp"
+ admin_ui:badgeDiameter="36dp"
+ admin_ui:badgeMargin="2dp"
+ />
+ </FrameLayout>
+
<LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal" >
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" android:text="ID: "/>
- <EditText
- android:id="@+id/user_id"
- android:enabled="false"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" android:text=""/>
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" android:text="Name: "/>
- <EditText
- android:id="@+id/user_name"
- android:enabled="false"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" android:text=""/>
- </LinearLayout>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal" >
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" android:text="Type: "/>
- <EditText
- android:id="@+id/user_type"
- android:enabled="false"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" android:text=""/>
- </LinearLayout>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal" >
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" android:text="Flags: "/>
- <EditText
- android:id="@+id/user_flags"
- android:enabled="false"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" android:text=""/>
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:orientation="vertical" >
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" >
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" android:text="ID: "/>
+ <EditText
+ android:id="@+id/user_id"
+ android:enabled="false"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" android:text=""/>
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" android:text="Name: "/>
+ <EditText
+ android:id="@+id/user_name"
+ android:enabled="false"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" android:text=""/>
+ <TextView
+ android:id="@+id/managed"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:visibility="gone"/>
+ </LinearLayout>
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" >
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" android:text="Type: "/>
+ <EditText
+ android:id="@+id/user_type"
+ android:enabled="false"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" android:text=""/>
+ </LinearLayout>
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" >
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" android:text="Flags: "/>
+ <EditText
+ android:id="@+id/user_flags"
+ android:enabled="false"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" android:text=""/>
+ </LinearLayout>
</LinearLayout>
</LinearLayout>
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/users/UserInfoView.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/users/UserInfoView.java
index 7087b6c..6135bef 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/users/UserInfoView.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/users/UserInfoView.java
@@ -13,15 +13,26 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package com.google.android.car.kitchensink.users;
import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
import android.content.Context;
import android.content.pm.UserInfo;
+import android.graphics.Bitmap;
+import android.os.UserHandle;
+import android.os.UserManager;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.EditText;
import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.car.admin.ui.UserAvatarView;
+import com.android.internal.util.UserIcons;
import com.google.android.car.kitchensink.R;
@@ -32,38 +43,74 @@
private static final String TAG = UserInfoView.class.getSimpleName();
+ private UserAvatarView mUserAvatarView;
private EditText mUserIdEditText;
private EditText mUserNameEditText;
private EditText mUserTypeEditText;
private EditText mUserFlagsEditText;
+ private TextView mManagedTextView;
public UserInfoView(Context context, AttributeSet attrs) {
super(context, attrs);
inflate(context, R.layout.user_info_view, this);
+ mUserAvatarView = findViewById(R.id.user_avatar);
mUserIdEditText = findViewById(R.id.user_id);
mUserNameEditText = findViewById(R.id.user_name);
mUserTypeEditText = findViewById(R.id.user_type);
mUserFlagsEditText = findViewById(R.id.user_flags);
+ mManagedTextView = findViewById(R.id.managed);
}
/**
* Initializes the widget with the given user.
*/
public void update(@NonNull UserInfo user) {
- Log.v(TAG, "initializing with " + user);
- String userId, userName, userType, userFlags;
- if (user == null) {
- userId = userName = userType = userFlags = "N/A";
- } else {
- userId = String.valueOf(user.id);
- userName = user.name;
- userType = user.userType;
- userFlags = UserInfo.flagsToString(user.flags);
+ Log.v(TAG, "initializing with " + user.toFullString());
+
+ setUserIcon(user.id);
+ mUserIdEditText.setText(String.valueOf(user.id));
+ mUserNameEditText.setText(user.name);
+ mUserTypeEditText.setText(user.userType);
+ mUserFlagsEditText.setText(UserInfo.flagsToString(user.flags));
+ setManagedStatus(user.id);
+ }
+
+ private void setUserIcon(@UserIdInt int userId) {
+ UserManager um = UserManager.get(getContext());
+ Bitmap icon = um.getUserIcon(userId);
+ if (icon == null) {
+ Log.v(TAG, "No icon for user " + userId); // Happens on system user
+ icon = UserIcons.convertToBitmap(
+ UserIcons.getDefaultUserIcon(getResources(), userId, /* light= */ false));
}
- mUserIdEditText.setText(userId);
- mUserNameEditText.setText(userName);
- mUserTypeEditText.setText(userType);
- mUserFlagsEditText.setText(userFlags);
+ mUserAvatarView.setAvatarWithBadge(icon, userId);
+ }
+
+ private void setManagedStatus(@UserIdInt int userId) {
+ // Check managed status
+ DevicePolicyManager dpm = getContext().getSystemService(DevicePolicyManager.class);
+ if (dpm == null) return;
+
+ UserHandle deviceOwner = dpm.getDeviceOwnerUser();
+ Log.v(TAG, "Device owner is " + deviceOwner);
+
+ if (deviceOwner != null && deviceOwner.getIdentifier() == userId) {
+ setManagedText("DeviceOwner");
+ return;
+ }
+
+ ComponentName profileOwner = dpm.getProfileOwnerAsUser(userId);
+ if (profileOwner != null) {
+ Log.v(TAG, "User " + userId + " has profile owner: " + profileOwner);
+ setManagedText("ProfileOwner");
+ return;
+ }
+ mManagedTextView.setVisibility(GONE);
+ }
+
+ private void setManagedText(@NonNull CharSequence text) {
+ mManagedTextView.setText(text);
+ mManagedTextView.setVisibility(VISIBLE);
}
}