Merge "Add background protection when work biometrics is shown" into tm-dev

This commit is contained in:
Alex Johnston
2022-05-17 16:35:37 +00:00
committed by Android (Google) Code Review
16 changed files with 129 additions and 115 deletions

View File

@@ -137,7 +137,7 @@ oneway interface ITaskStackListener {
* activities inside it belong to a managed profile user, and that user has just
* been locked.
*/
void onTaskProfileLocked(int taskId, int userId);
void onTaskProfileLocked(in ActivityManager.RunningTaskInfo taskInfo);
/**
* Called when a task snapshot got updated.

View File

@@ -155,7 +155,7 @@ public abstract class TaskStackListener extends ITaskStackListener.Stub {
@Override
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public void onTaskProfileLocked(int taskId, int userId) throws RemoteException {
public void onTaskProfileLocked(RunningTaskInfo taskInfo) throws RemoteException {
}
@Override

View File

@@ -38,7 +38,7 @@ public interface TaskStackListenerCallback {
default void onTaskStackChanged() { }
default void onTaskProfileLocked(int taskId, int userId) { }
default void onTaskProfileLocked(RunningTaskInfo taskInfo) { }
default void onTaskDisplayChanged(int taskId, int newDisplayId) { }

View File

@@ -150,8 +150,8 @@ public class TaskStackListenerImpl extends TaskStackListener implements Handler.
}
@Override
public void onTaskProfileLocked(int taskId, int userId) {
mMainHandler.obtainMessage(ON_TASK_PROFILE_LOCKED, taskId, userId).sendToTarget();
public void onTaskProfileLocked(ActivityManager.RunningTaskInfo taskInfo) {
mMainHandler.obtainMessage(ON_TASK_PROFILE_LOCKED, taskInfo).sendToTarget();
}
@Override
@@ -341,8 +341,10 @@ public class TaskStackListenerImpl extends TaskStackListener implements Handler.
break;
}
case ON_TASK_PROFILE_LOCKED: {
final ActivityManager.RunningTaskInfo
info = (ActivityManager.RunningTaskInfo) msg.obj;
for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
mTaskStackListeners.get(i).onTaskProfileLocked(msg.arg1, msg.arg2);
mTaskStackListeners.get(i).onTaskProfileLocked(info);
}
break;
}

View File

@@ -109,9 +109,10 @@ public class TaskStackListenerImplTest {
@Test
public void testOnTaskProfileLocked() {
mImpl.onTaskProfileLocked(1, 2);
verify(mCallback).onTaskProfileLocked(eq(1), eq(2));
verify(mOtherCallback).onTaskProfileLocked(eq(1), eq(2));
ActivityManager.RunningTaskInfo info = mock(ActivityManager.RunningTaskInfo.class);
mImpl.onTaskProfileLocked(info);
verify(mCallback).onTaskProfileLocked(eq(info));
verify(mOtherCallback).onTaskProfileLocked(eq(info));
}
@Test

View File

@@ -723,7 +723,7 @@
android:excludeFromRecents="true"
android:stateNotNeeded="true"
android:resumeWhilePausing="true"
android:theme="@android:style/Theme.Translucent.NoTitleBar.Fullscreen">
android:theme="@style/Theme.AppCompat.DayNight.NoActionBar">
<intent-filter>
<action android:name="android.app.action.CONFIRM_DEVICE_CREDENTIAL_WITH_USER" />
<category android:name="android.intent.category.DEFAULT" />

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2022 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:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<ImageView
android:id="@+id/icon"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_marginBottom="160dp"/>
</LinearLayout>

View File

@@ -61,7 +61,7 @@ public interface TaskStackChangeListener {
onActivityLaunchOnSecondaryDisplayRerouted();
}
default void onTaskProfileLocked(int taskId, int userId) { }
default void onTaskProfileLocked(RunningTaskInfo taskInfo) { }
default void onTaskCreated(int taskId, ComponentName componentName) { }
default void onTaskRemoved(int taskId) { }
default void onTaskMovedToFront(int taskId) { }

View File

@@ -211,8 +211,8 @@ public class TaskStackChangeListeners {
}
@Override
public void onTaskProfileLocked(int taskId, int userId) {
mHandler.obtainMessage(ON_TASK_PROFILE_LOCKED, taskId, userId).sendToTarget();
public void onTaskProfileLocked(RunningTaskInfo taskInfo) {
mHandler.obtainMessage(ON_TASK_PROFILE_LOCKED, taskInfo).sendToTarget();
}
@Override
@@ -357,8 +357,9 @@ public class TaskStackChangeListeners {
break;
}
case ON_TASK_PROFILE_LOCKED: {
final RunningTaskInfo info = (RunningTaskInfo) msg.obj;
for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
mTaskStackListeners.get(i).onTaskProfileLocked(msg.arg1, msg.arg2);
mTaskStackListeners.get(i).onTaskProfileLocked(info);
}
break;
}

View File

@@ -17,23 +17,22 @@
package com.android.systemui.keyguard;
import static android.app.ActivityManager.TaskDescription;
import static android.app.admin.DevicePolicyResources.Strings.SystemUi.WORK_LOCK_ACCESSIBILITY;
import android.annotation.ColorInt;
import android.annotation.UserIdInt;
import android.app.Activity;
import android.app.ActivityOptions;
import android.app.KeyguardManager;
import android.app.PendingIntent;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Color;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.UserHandle;
import android.view.View;
import android.os.UserManager;
import android.widget.ImageView;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
@@ -52,12 +51,6 @@ import javax.inject.Inject;
public class WorkLockActivity extends Activity {
private static final String TAG = "WorkLockActivity";
/**
* Contains a {@link TaskDescription} for the activity being covered.
*/
static final String EXTRA_TASK_DESCRIPTION =
"com.android.systemui.keyguard.extra.TASK_DESCRIPTION";
private static final int REQUEST_CODE_CONFIRM_CREDENTIALS = 1;
/**
@@ -65,12 +58,17 @@ public class WorkLockActivity extends Activity {
* @see KeyguardManager
*/
private KeyguardManager mKgm;
private UserManager mUserManager;
private PackageManager mPackageManager;
private final BroadcastDispatcher mBroadcastDispatcher;
@Inject
public WorkLockActivity(BroadcastDispatcher broadcastDispatcher) {
public WorkLockActivity(BroadcastDispatcher broadcastDispatcher, UserManager userManager,
PackageManager packageManager) {
super();
mBroadcastDispatcher = broadcastDispatcher;
mUserManager = userManager;
mPackageManager = packageManager;
}
@Override
@@ -91,15 +89,28 @@ public class WorkLockActivity extends Activity {
// Draw captions overlaid on the content view, so the whole window is one solid color.
setOverlayWithDecorCaptionEnabled(true);
// Blank out the activity. When it is on-screen it will look like a Recents thumbnail with
// redaction switched on.
final DevicePolicyManager dpm = getSystemService(DevicePolicyManager.class);
String contentDescription = dpm.getResources().getString(
WORK_LOCK_ACCESSIBILITY, () -> getString(R.string.accessibility_desc_work_lock));
final View blankView = new View(this);
blankView.setContentDescription(contentDescription);
blankView.setBackgroundColor(getPrimaryColor());
setContentView(blankView);
// Add background protection that contains a badged icon of the app being opened.
setContentView(R.layout.auth_biometric_background);
Drawable badgedIcon = getBadgedIcon();
if (badgedIcon != null) {
((ImageView) findViewById(R.id.icon)).setImageDrawable(badgedIcon);
}
}
@VisibleForTesting
protected Drawable getBadgedIcon() {
String packageName = getIntent().getStringExtra(Intent.EXTRA_PACKAGE_NAME);
if (!packageName.isEmpty()) {
try {
return mUserManager.getBadgedIconForUser(mPackageManager.getApplicationIcon(
mPackageManager.getApplicationInfoAsUser(packageName,
PackageManager.ApplicationInfoFlags.of(0), getTargetUserId())),
UserHandle.of(getTargetUserId()));
} catch (PackageManager.NameNotFoundException e) {
// Unable to set the badged icon, show the background protection without an icon.
}
}
return null;
}
/**
@@ -208,19 +219,4 @@ public class WorkLockActivity extends Activity {
final int getTargetUserId() {
return getIntent().getIntExtra(Intent.EXTRA_USER_ID, UserHandle.myUserId());
}
@VisibleForTesting
@ColorInt
final int getPrimaryColor() {
final TaskDescription taskDescription = (TaskDescription)
getIntent().getExtra(EXTRA_TASK_DESCRIPTION);
if (taskDescription != null && Color.alpha(taskDescription.getPrimaryColor()) == 255) {
return taskDescription.getPrimaryColor();
} else {
// No task description. Use an organization color set by the policy controller.
final DevicePolicyManager devicePolicyManager = (DevicePolicyManager)
getSystemService(Context.DEVICE_POLICY_SERVICE);
return devicePolicyManager.getOrganizationColorForUser(getTargetUserId());
}
}
}

View File

@@ -52,22 +52,17 @@ public class WorkLockActivityController {
tscl.registerTaskStackListener(mLockListener);
}
private void startWorkChallengeInTask(int taskId, int userId) {
ActivityManager.TaskDescription taskDescription = null;
try {
taskDescription = mIatm.getTaskDescription(taskId);
} catch (RemoteException e) {
Log.w(TAG, "Failed to get description for task=" + taskId);
}
private void startWorkChallengeInTask(ActivityManager.RunningTaskInfo info) {
String packageName = info.baseActivity != null ? info.baseActivity.getPackageName() : "";
Intent intent = new Intent(KeyguardManager.ACTION_CONFIRM_DEVICE_CREDENTIAL_WITH_USER)
.setComponent(new ComponentName(mContext, WorkLockActivity.class))
.putExtra(Intent.EXTRA_USER_ID, userId)
.putExtra(WorkLockActivity.EXTRA_TASK_DESCRIPTION, taskDescription)
.putExtra(Intent.EXTRA_USER_ID, info.userId)
.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName)
.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT
| Intent.FLAG_ACTIVITY_CLEAR_TOP);
final ActivityOptions options = ActivityOptions.makeBasic();
options.setLaunchTaskId(taskId);
options.setLaunchTaskId(info.taskId);
options.setTaskOverlay(true, false /* canResume */);
final int result = startActivityAsUser(intent, options.toBundle(), UserHandle.USER_CURRENT);
@@ -77,9 +72,9 @@ public class WorkLockActivityController {
// Starting the activity inside the task failed. We can't be sure why, so to be
// safe just remove the whole task if it still exists.
try {
mIatm.removeTask(taskId);
mIatm.removeTask(info.taskId);
} catch (RemoteException e) {
Log.w(TAG, "Failed to get description for task=" + taskId);
Log.w(TAG, "Failed to get description for task=" + info.taskId);
}
}
}
@@ -112,8 +107,8 @@ public class WorkLockActivityController {
private final TaskStackChangeListener mLockListener = new TaskStackChangeListener() {
@Override
public void onTaskProfileLocked(int taskId, int userId) {
startWorkChallengeInTask(taskId, userId);
public void onTaskProfileLocked(ActivityManager.RunningTaskInfo info) {
startWorkChallengeInTask(info);
}
};
}

View File

@@ -60,6 +60,13 @@ import org.mockito.MockitoAnnotations;
public class WorkLockActivityControllerTest extends SysuiTestCase {
private static final int USER_ID = 333;
private static final int TASK_ID = 444;
private static final ActivityManager.RunningTaskInfo TASK_INFO =
new ActivityManager.RunningTaskInfo();
static {
TASK_INFO.userId = USER_ID;
TASK_INFO.taskId = TASK_ID;
}
private @Mock Context mContext;
private @Mock TaskStackChangeListeners mTaskStackChangeListeners;
@@ -91,7 +98,7 @@ public class WorkLockActivityControllerTest extends SysuiTestCase {
setActivityStartCode(TASK_ID, true /*taskOverlay*/, ActivityManager.START_SUCCESS);
// And the controller receives a message saying the profile is locked,
mTaskStackListener.onTaskProfileLocked(TASK_ID, USER_ID);
mTaskStackListener.onTaskProfileLocked(TASK_INFO);
// The overlay should start and the task the activity started in should not be removed.
verifyStartActivity(TASK_ID, true /*taskOverlay*/);
@@ -104,7 +111,7 @@ public class WorkLockActivityControllerTest extends SysuiTestCase {
setActivityStartCode(TASK_ID, true /*taskOverlay*/, ActivityManager.START_CLASS_NOT_FOUND);
// And the controller receives a message saying the profile is locked,
mTaskStackListener.onTaskProfileLocked(TASK_ID, USER_ID);
mTaskStackListener.onTaskProfileLocked(TASK_INFO);
// The task the activity started in should be removed to prevent the locked task from
// being shown.

View File

@@ -16,8 +16,6 @@
package com.android.systemui.keyguard;
import static android.app.ActivityManager.TaskDescription;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.eq;
@@ -25,14 +23,15 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.annotation.ColorInt;
import android.annotation.UserIdInt;
import android.app.KeyguardManager;
import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.os.Looper;
import android.os.UserHandle;
import android.os.UserManager;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -53,18 +52,21 @@ import org.mockito.MockitoAnnotations;
@RunWith(AndroidJUnit4.class)
public class WorkLockActivityTest extends SysuiTestCase {
private static final @UserIdInt int USER_ID = 270;
private static final String TASK_LABEL = "task label";
private static final String CALLING_PACKAGE_NAME = "com.android.test";
private @Mock DevicePolicyManager mDevicePolicyManager;
private @Mock KeyguardManager mKeyguardManager;
private @Mock UserManager mUserManager;
private @Mock PackageManager mPackageManager;
private @Mock Context mContext;
private @Mock BroadcastDispatcher mBroadcastDispatcher;
private @Mock Drawable mDrawable;
private @Mock Drawable mBadgedDrawable;
private WorkLockActivity mActivity;
private static class WorkLockActivityTestable extends WorkLockActivity {
WorkLockActivityTestable(Context baseContext, BroadcastDispatcher broadcastDispatcher) {
super(broadcastDispatcher);
WorkLockActivityTestable(Context baseContext, BroadcastDispatcher broadcastDispatcher,
UserManager userManager, PackageManager packageManager) {
super(broadcastDispatcher, userManager, packageManager);
attachBaseContext(baseContext);
}
}
@@ -73,46 +75,26 @@ public class WorkLockActivityTest extends SysuiTestCase {
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
when(mContext.getSystemService(eq(Context.DEVICE_POLICY_SERVICE)))
.thenReturn(mDevicePolicyManager);
when(mContext.getSystemService(eq(Context.KEYGUARD_SERVICE)))
.thenReturn(mKeyguardManager);
if (Looper.myLooper() == null) {
Looper.prepare();
}
mActivity = new WorkLockActivityTestable(mContext, mBroadcastDispatcher);
mActivity = new WorkLockActivityTestable(mContext, mBroadcastDispatcher, mUserManager,
mPackageManager);
}
@Test
public void testBackgroundAlwaysOpaque() throws Exception {
final @ColorInt int orgColor = Color.rgb(250, 199, 67);
when(mDevicePolicyManager.getOrganizationColorForUser(eq(USER_ID))).thenReturn(orgColor);
final @ColorInt int opaqueColor= Color.rgb(164, 198, 57);
final @ColorInt int transparentColor = Color.argb(0, 0, 0, 0);
TaskDescription opaque = new TaskDescription(null, null, opaqueColor);
TaskDescription transparent = new TaskDescription(null, null, transparentColor);
// When a task description is provided with a suitable (opaque) primaryColor, it should be
// used as the scrim's background color.
public void testGetBadgedIcon() throws Exception {
ApplicationInfo info = new ApplicationInfo();
when(mPackageManager.getApplicationInfoAsUser(eq(CALLING_PACKAGE_NAME), any(),
eq(USER_ID))).thenReturn(info);
when(mPackageManager.getApplicationIcon(eq(info))).thenReturn(mDrawable);
when(mUserManager.getBadgedIconForUser(any(), eq(UserHandle.of(USER_ID)))).thenReturn(
mBadgedDrawable);
mActivity.setIntent(new Intent()
.putExtra(Intent.EXTRA_USER_ID, USER_ID)
.putExtra(WorkLockActivity.EXTRA_TASK_DESCRIPTION, opaque));
assertEquals(opaqueColor, mActivity.getPrimaryColor());
.putExtra(Intent.EXTRA_PACKAGE_NAME, CALLING_PACKAGE_NAME));
// When a task description is provided but has no primaryColor / the primaryColor is
// transparent, the organization color should be used instead.
mActivity.setIntent(new Intent()
.putExtra(Intent.EXTRA_USER_ID, USER_ID)
.putExtra(WorkLockActivity.EXTRA_TASK_DESCRIPTION, transparent));
assertEquals(orgColor, mActivity.getPrimaryColor());
// When no task description is provided at all, it should be treated like a transparent
// description and the organization color shown instead.
mActivity.setIntent(new Intent()
.putExtra(Intent.EXTRA_USER_ID, USER_ID));
assertEquals(orgColor, mActivity.getPrimaryColor());
assertEquals(mBadgedDrawable, mActivity.getBadgedIcon());
}
@Test

View File

@@ -3231,7 +3231,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
if (task.getActivity(activity -> !activity.finishing && activity.mUserId == userId)
!= null) {
mService.getTaskChangeNotificationController().notifyTaskProfileLocked(
task.mTaskId, userId);
task.getTaskInfo());
}
}, true /* traverseTopToBottom */);
}

View File

@@ -144,7 +144,7 @@ class TaskChangeNotificationController {
};
private final TaskStackConsumer mNotifyTaskProfileLocked = (l, m) -> {
l.onTaskProfileLocked(m.arg1, m.arg2);
l.onTaskProfileLocked((RunningTaskInfo) m.obj);
};
private final TaskStackConsumer mNotifyTaskSnapshotChanged = (l, m) -> {
@@ -467,9 +467,9 @@ class TaskChangeNotificationController {
* Notify listeners that the task has been put in a locked state because one or more of the
* activities inside it belong to a managed profile user that has been locked.
*/
void notifyTaskProfileLocked(int taskId, int userId) {
final Message msg = mHandler.obtainMessage(NOTIFY_TASK_PROFILE_LOCKED_LISTENERS_MSG, taskId,
userId);
void notifyTaskProfileLocked(ActivityManager.RunningTaskInfo taskInfo) {
final Message msg = mHandler.obtainMessage(NOTIFY_TASK_PROFILE_LOCKED_LISTENERS_MSG,
taskInfo);
forAllLocalListeners(mNotifyTaskProfileLocked, msg);
msg.sendToTarget();
}

View File

@@ -1097,7 +1097,7 @@ public class RootWindowContainerTests extends WindowTestsBase {
TaskChangeNotificationController controller = mAtm.getTaskChangeNotificationController();
spyOn(controller);
mWm.mRoot.lockAllProfileTasks(profileUserId);
verify(controller).notifyTaskProfileLocked(eq(task.mTaskId), eq(profileUserId));
verify(controller).notifyTaskProfileLocked(any());
// Create the work lock activity on top of the task
final ActivityRecord workLockActivity = new ActivityBuilder(mAtm).setTask(task).build();
@@ -1107,7 +1107,7 @@ public class RootWindowContainerTests extends WindowTestsBase {
// Make sure the listener won't be notified again.
clearInvocations(controller);
mWm.mRoot.lockAllProfileTasks(profileUserId);
verify(controller, never()).notifyTaskProfileLocked(anyInt(), anyInt());
verify(controller, never()).notifyTaskProfileLocked(any());
}
/**