Create dialog to show detecting Batmobile device & unlocking

Bug: 136049501
Test: Manually on IHU
Change-Id: I47b55e3032c814623aa5bf5c5c3ec1899c03e98e
This commit is contained in:
Erin Yan
2019-08-14 12:03:12 -07:00
parent c7487391b0
commit 16258e3e98
11 changed files with 489 additions and 10 deletions

View File

@@ -21,4 +21,8 @@
coreApp="true">
<!-- This permission is required to monitor car power state. -->
<uses-permission android:name="android.car.permission.CAR_POWER" />
<!-- This permission is required to get the trusted device list of a user. -->
<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" />
</manifest>

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2019 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
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/unlock_dialog_background_color"/>
<padding
android:bottom="@*android:dimen/car_padding_2"
android:left="@*android:dimen/car_padding_2"
android:right="@*android:dimen/car_padding_2"
android:top="@*android:dimen/car_padding_2"/>
<corners
android:radius="@dimen/unlock_dialog_radius"/>
</shape>

View File

@@ -0,0 +1,72 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2019 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:id="@+id/layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center">
<LinearLayout
android:layout_width="@dimen/unlock_dialog_width"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_gravity="center"
android:orientation="vertical"
android:background="@drawable/unlock_dialog_background"
android:padding="@*android:dimen/car_padding_2">
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ProgressBar
android:layout_gravity="center"
android:layout_width="@dimen/unlock_dialog_progress_bar_size"
android:layout_height="@dimen/unlock_dialog_progress_bar_size" />
<ImageView
android:id="@+id/avatar"
android:layout_gravity="center"
android:layout_width="@dimen/unlock_dialog_avatar_size"
android:layout_height="@dimen/unlock_dialog_avatar_size"
android:scaleType="fitCenter"/>
</FrameLayout>
<TextView
android:id="@+id/user_name"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:text="@string/unlock_dialog_default_user_name"
android:textSize="@*android:dimen/car_body1_size"
android:textColor="@android:color/white"/>
<TextView
android:id="@+id/unlocking_text"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_marginTop="@*android:dimen/car_padding_1"
android:text="@string/unlock_dialog_message_default"
android:textSize="@*android:dimen/car_body4_size"
android:textColor="@color/unlock_dialog_message_text_default"/>
<Button
android:id="@+id/enter_pin_button"
android:layout_marginTop="@*android:dimen/car_padding_1"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:text="@string/unlock_dialog_button_text_pin"
style="@style/UnlockDialogButton"/>
</LinearLayout>
</FrameLayout>

View File

@@ -26,4 +26,9 @@
<color name="car_user_switcher_add_user_background_color">@*android:color/car_dark_blue_grey_600</color>
<color name="car_user_switcher_add_user_add_sign_color">@*android:color/car_body1_light</color>
<!-- colors for unlock dialog -->
<color name="unlock_dialog_background_color">#ff282a2d</color>
<color name="unlock_dialog_message_text_default">@*android:color/car_grey_400</color>
<color name="unlock_dialog_enter_pin_text_color">#ff66b5ff</color>
</resources>

View File

@@ -43,4 +43,10 @@
<!-- 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 the unlock dialog -->
<dimen name="unlock_dialog_width">500dp</dimen>
<dimen name="unlock_dialog_radius">16dp</dimen>
<dimen name="unlock_dialog_avatar_size">100dp</dimen>
<dimen name="unlock_dialog_progress_bar_size">140dp</dimen>
</resources>

View File

@@ -31,5 +31,7 @@
<!--Percentage of the screen height, from the bottom, that a notification panel being peeked
at will result in remaining closed the panel if released-->
<integer name="notification_settle_close_percentage">80</integer>
<!-- The delay before the unlock dialog pops up -->
<integer name="unlock_dialog_delay_ms">3000</integer>
</resources>

View File

@@ -29,4 +29,19 @@
<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>
<!-- Default messages displayed on the unlock dialog before unlock advertising started. [CHAR LIMIT=30]-->
<string name="unlock_dialog_message_default">Waiting\u2026</string>
<!-- Message to inform user that the IHU is looking for trusted device. [CHAR LIMIT=30] -->
<string name="unlock_dialog_message_start">Looking for trusted device\u2026</string>
<!-- Cancel Button text for user who has PIN as security lock. [CHAR LIMIT=30] -->
<string name="unlock_dialog_button_text_pin">Enter PIN instead</string>
<!-- Cancel Button text for user who has Pattern as security lock. [CHAR LIMIT=30] -->
<string name="unlock_dialog_button_text_pattern">Enter Pattern instead</string>
<!-- Cancel Button text for user who has Password as security lock. [CHAR LIMIT=30] -->
<string name="unlock_dialog_button_text_password">Enter Password instead</string>
<!-- Default user name shows on unlock dialog -->
<string name="unlock_dialog_default_user_name">Default Name</string>
<!-- Default title for unlock dialog -->
<string name="unlock_dialog_title">Unlock Dialogue</string>
</resources>

View File

@@ -46,4 +46,12 @@
<item name="android:layout_width">96dp</item>
<item name="android:background">@drawable/nav_button_background</item>
</style>
<style name="UnlockDialogButton">
<item name="android:background">?android:attr/selectableItemBackground</item>
<item name="android:textAlignment">center</item>
<item name="android:textColor">@color/unlock_dialog_enter_pin_text_color</item>
<item name="android:paddingHorizontal">16dp</item>
<item name="android:textAllCaps">false</item>
</style>
</resources>

View File

@@ -26,6 +26,7 @@ import android.car.Car;
import android.car.drivingstate.CarDrivingStateEvent;
import android.car.drivingstate.CarUxRestrictionsManager;
import android.car.hardware.power.CarPowerManager.CarPowerStateListener;
import android.car.trust.CarTrustAgentEnrollmentManager;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.PixelFormat;
@@ -957,8 +958,12 @@ public class CarStatusBar extends StatusBar implements
UserSwitcherController userSwitcherController =
Dependency.get(UserSwitcherController.class);
if (userSwitcherController.useFullscreenUserSwitcher()) {
Car car = Car.createCar(mContext);
CarTrustAgentEnrollmentManager enrollmentManager = (CarTrustAgentEnrollmentManager) car
.getCarManager(Car.CAR_TRUST_AGENT_ENROLLMENT_SERVICE);
mFullscreenUserSwitcher = new FullscreenUserSwitcher(this,
mStatusBarWindow.findViewById(R.id.fullscreen_user_switcher_stub), mContext);
mStatusBarWindow.findViewById(R.id.fullscreen_user_switcher_stub),
enrollmentManager, mContext);
} else {
super.createUserSwitcher();
}

View File

@@ -0,0 +1,240 @@
/*
* Copyright (C) 2019 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.admin.DevicePolicyManager;
import android.bluetooth.BluetoothAdapter;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.PixelFormat;
import android.os.Handler;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.R;
/**
* A helper class displays an unlock dialog and receives broadcast about detecting trusted device
* & unlocking state to show the appropriate message on the dialog.
*/
class CarTrustAgentUnlockDialogHelper extends BroadcastReceiver{
private static final String TAG = CarTrustAgentUnlockDialogHelper.class.getSimpleName();
private final Context mContext;
private final WindowManager mWindowManager;
private final UserManager mUserManager;
private final WindowManager.LayoutParams mParams;
/**
* Not using Dialog because context passed from {@link FullscreenUserSwitcher} is not an
* activity.
*/
private final View mUnlockDialog;
private final TextView mUnlockingText;
private final Button mButton;
private final IntentFilter mFilter;
private int mUid;
private boolean mIsDialogShowing;
private OnHideListener mOnHideListener;
CarTrustAgentUnlockDialogHelper(Context context) {
mContext = context;
mUserManager = mContext.getSystemService(UserManager.class);
mWindowManager = mContext.getSystemService(WindowManager.class);
mParams = createLayoutParams();
mFilter = getIntentFilter();
mParams.packageName = mContext.getPackageName();
mParams.setTitle(mContext.getString(R.string.unlock_dialog_title));
mUnlockDialog = LayoutInflater.from(mContext).inflate(
R.layout.trust_agent_unlock_dialog, null);
mUnlockDialog.setLayoutParams(mParams);
mUnlockingText = mUnlockDialog.findViewById(R.id.unlocking_text);
mButton = mUnlockDialog.findViewById(R.id.enter_pin_button);
mButton.setOnClickListener(v -> {
hideUnlockDialog(/* notifyOnHideListener= */true);
// TODO(b/138250105) Stop unlock advertising
});
BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (bluetoothAdapter != null
&& bluetoothAdapter.getLeState() == BluetoothAdapter.STATE_BLE_ON) {
mUnlockingText.setText(R.string.unlock_dialog_message_start);
}
}
/**
* This filter is listening on:
* {@link BluetoothAdapter#ACTION_BLE_STATE_CHANGED} for starting unlock advertising;
* {@link Intent#ACTION_USER_UNLOCKED} for IHU unlocked
*/
private IntentFilter getIntentFilter() {
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothAdapter.ACTION_BLE_STATE_CHANGED);
filter.addAction(Intent.ACTION_USER_UNLOCKED);
return filter;
}
/**
* Show dialog for the given user
*/
void showUnlockDialog(int uid, OnHideListener listener) {
showUnlockDialogAfterDelay(uid, 0, listener);
}
/**
* Show dialog for the given user after the certain time of delay has elapsed
*
* @param uid the user to unlock
* @param listener listener that listens to dialog hide
*/
void showUnlockDialogAfterDelay(int uid, OnHideListener listener) {
long delayMillis = mContext.getResources().getInteger(R.integer.unlock_dialog_delay_ms);
showUnlockDialogAfterDelay(uid, delayMillis, listener);
}
/**
* Show dialog for the given user after the supplied delay has elapsed
*/
private void showUnlockDialogAfterDelay(int uid, long delayMillis, OnHideListener listener) {
setUid(uid);
mOnHideListener = listener;
if (!mIsDialogShowing) {
logd("Receiver registered");
mContext.registerReceiverAsUser(this, UserHandle.ALL, mFilter,
/* broadcastPermission= */ null,
/* scheduler= */ null);
new Handler().postDelayed(() -> {
if (!mUserManager.isUserUnlocked(uid)) {
logd("Showed unlock dialog for user: " + uid + " after " + delayMillis
+ " delay.");
mWindowManager.addView(mUnlockDialog, mParams);
}
}, delayMillis);
}
mIsDialogShowing = true;
}
private void setUid(int uid) {
mUid = uid;
TextView userName = mUnlockDialog.findViewById(R.id.user_name);
userName.setText(mUserManager.getUserInfo(mUid).name);
ImageView avatar = mUnlockDialog.findViewById(R.id.avatar);
avatar.setImageBitmap(mUserManager.getUserIcon(mUid));
setButtonText();
}
private void hideUnlockDialog(boolean notifyOnHideListener) {
if (!mIsDialogShowing) {
return;
}
mWindowManager.removeView(mUnlockDialog);
logd("Receiver unregistered");
mContext.unregisterReceiver(this);
if (notifyOnHideListener && mOnHideListener != null) {
mOnHideListener.onHide();
}
mIsDialogShowing = false;
}
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action == null) {
return;
}
switch (action) {
case BluetoothAdapter.ACTION_BLE_STATE_CHANGED:
logd("Received ACTION_BLE_STATE_CHANGED");
int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
if (state == BluetoothAdapter.STATE_BLE_ON) {
logd("Received BLE_ON");
mUnlockingText.setText(R.string.unlock_dialog_message_start);
}
break;
case Intent.ACTION_USER_UNLOCKED:
int uid = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
if (uid == mUid) {
logd("IHU unlocked");
hideUnlockDialog(/* notifyOnHideListener= */false);
} else {
Log.e(TAG, "Received ACTION_USER_UNLOCKED for unexpected uid: " + uid);
}
break;
default:
Log.e(TAG, "Encountered unexpected action when attempting to set "
+ "unlock state message: " + action);
}
}
// Set button text based on security lock type
private void setButtonText() {
LockPatternUtils lockPatternUtils = new LockPatternUtils(mContext);
int passwordQuality = lockPatternUtils.getActivePasswordQuality(mUid);
switch (passwordQuality) {
// PIN
case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX:
// Pattern
case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING:
mButton.setText(R.string.unlock_dialog_button_text_pattern);
break;
// Password
case DevicePolicyManager.PASSWORD_QUALITY_MANAGED:
mButton.setText(R.string.unlock_dialog_button_text_password);
break;
default:
Log.e(TAG, "Encountered unexpected security type when attempting to set "
+ "button text:" + passwordQuality);
}
}
private WindowManager.LayoutParams createLayoutParams() {
return new WindowManager.LayoutParams(
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG,
WindowManager.LayoutParams.FLAG_FULLSCREEN
| WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
| WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION,
PixelFormat.TRANSLUCENT
);
}
private void logd(String message) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, message);
}
}
/**
* Listener used to notify when the dialog is hidden
*/
interface OnHideListener {
void onHide();
}
}

View File

@@ -18,29 +18,60 @@ package com.android.systemui.statusbar.car;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.car.trust.CarTrustAgentEnrollmentManager;
import android.car.userlib.CarUserManagerHelper;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.UserInfo;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;
import android.view.View;
import android.view.ViewStub;
import androidx.recyclerview.widget.GridLayoutManager;
import com.android.systemui.R;
import com.android.systemui.statusbar.car.UserGridRecyclerView.UserRecord;
/**
* Manages the fullscreen user switcher.
*/
public class FullscreenUserSwitcher {
private static final String TAG = FullscreenUserSwitcher.class.getSimpleName();
// Because user 0 is headless, user count for single user is 2
private static final int NUMBER_OF_BACKGROUND_USERS = 1;
private final UserGridRecyclerView mUserGridView;
private final View mParent;
private final int mShortAnimDuration;
private final CarStatusBar mStatusBar;
private final Context mContext;
private final UserManager mUserManager;
private final CarTrustAgentEnrollmentManager mEnrollmentManager;
private CarTrustAgentUnlockDialogHelper mUnlockDialogHelper;
private UserGridRecyclerView.UserRecord mSelectedUser;
private CarUserManagerHelper mCarUserManagerHelper;
private final BroadcastReceiver mUserUnlockReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "user 0 is unlocked, SharedPreference is accessible.");
}
showDialogForInitialUser();
mContext.unregisterReceiver(mUserUnlockReceiver);
}
};
public FullscreenUserSwitcher(CarStatusBar statusBar, ViewStub containerStub, Context context) {
public FullscreenUserSwitcher(CarStatusBar statusBar, ViewStub containerStub,
CarTrustAgentEnrollmentManager enrollmentManager, Context context) {
mStatusBar = statusBar;
mParent = containerStub.inflate();
// Hide the user grid by default. It will only be made visible by clicking on a cancel
// button in a bouncer.
hide();
mEnrollmentManager = enrollmentManager;
mContext = context;
View container = mParent.findViewById(R.id.container);
// Initialize user grid.
@@ -50,9 +81,51 @@ public class FullscreenUserSwitcher {
mUserGridView.setLayoutManager(layoutManager);
mUserGridView.buildAdapter();
mUserGridView.setUserSelectionListener(this::onUserSelected);
mCarUserManagerHelper = new CarUserManagerHelper(context);
mUnlockDialogHelper = new CarTrustAgentUnlockDialogHelper(mContext);
mUserManager = mContext.getSystemService(UserManager.class);
mShortAnimDuration = container.getResources()
.getInteger(android.R.integer.config_shortAnimTime);
IntentFilter filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED);
if (mUserManager.isUserUnlocked(UserHandle.USER_SYSTEM)) {
// User0 is unlocked, switched to the initial user
showDialogForInitialUser();
} else {
// listen to USER_UNLOCKED
mContext.registerReceiverAsUser(mUserUnlockReceiver,
UserHandle.getUserHandleForUid(UserHandle.USER_SYSTEM),
filter,
/* broadcastPermission= */ null,
/* scheduler */ null);
}
}
private void showDialogForInitialUser() {
int initialUser = mCarUserManagerHelper.getInitialUser();
UserInfo initialUserInfo = mUserManager.getUserInfo(initialUser);
mSelectedUser = new UserRecord(initialUserInfo,
/* isStartGuestSession= */ false,
/* isAddUser= */ false,
/* isForeground= */ true);
// For single user without trusted device, hide the user switcher.
if (!hasMultipleUsers() && !hasTrustedDevice(initialUser)) {
dismissUserSwitcher();
return;
}
// Show unlock dialog for initial user
if (hasTrustedDevice(initialUser)) {
mUnlockDialogHelper.showUnlockDialogAfterDelay(initialUser,
() -> dismissUserSwitcher());
}
}
/**
* Check if there is only one possible user to login in.
* In a Multi-User system there is always one background user (user 0)
*/
private boolean hasMultipleUsers() {
return mUserManager.getUserCount() > NUMBER_OF_BACKGROUND_USERS + 1;
}
/**
@@ -77,14 +150,33 @@ public class FullscreenUserSwitcher {
}
/**
* Every time user clicks on an item in the switcher, we hide the switcher, either
* gradually or immediately.
* Every time user clicks on an item in the switcher, if the clicked user has no trusted device,
* we hide the switcher, either gradually or immediately.
*
* We dismiss the entire keyguard if user clicked on the foreground user (user we're already
* logged in as).
* If the user has trusted device, we show an unlock dialog to notify user the unlock state.
* When the unlock dialog is dismissed by user, we hide the unlock dialog and the switcher.
*
* We dismiss the entire keyguard when we hide the switcher if user clicked on the foreground
* user (user we're already logged in as).
*/
private void onUserSelected(UserGridRecyclerView.UserRecord record) {
if (record.mIsForeground) {
mSelectedUser = record;
if (hasTrustedDevice(record.mInfo.id)) {
mUnlockDialogHelper.showUnlockDialog(record.mInfo.id, () -> dismissUserSwitcher());
return;
}
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "no trusted device enrolled for uid: " + record.mInfo.id);
}
dismissUserSwitcher();
}
private void dismissUserSwitcher() {
if (mSelectedUser == null) {
Log.e(TAG, "Request to dismiss user switcher, but no user selected");
return;
}
if (mSelectedUser.mIsForeground) {
hide();
mStatusBar.dismissKeyguard();
return;
@@ -106,4 +198,8 @@ public class FullscreenUserSwitcher {
});
}
private boolean hasTrustedDevice(int uid) {
return !mEnrollmentManager.getEnrolledDeviceInfoForUser(uid).isEmpty();
}
}