summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Robin Lee <rgl@google.com> 2017-01-11 17:40:58 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2017-01-11 17:41:01 +0000
commit3101b3ad21b52be9ef06400b33016cf6c973382f (patch)
treea346d411fb11ed01d14d8cc3a0bf2d43f8eab967
parent48ee82ad5b315c6df3d12d1dd647e83cc4fdb224 (diff)
parentc41f6ec877309d9c73168ef8cdc277f82eb0d42e (diff)
Merge changes I3fd28e69,If43820b6
* changes: Lock work tasks from SystemUI instead of ActivityStarter Bounce work challenge through a WorkLockActivity
-rw-r--r--core/java/android/app/ITaskStackListener.aidl7
-rw-r--r--core/java/android/app/TaskStackListener.java4
-rw-r--r--packages/SystemUI/AndroidManifest.xml15
-rw-r--r--packages/SystemUI/res/values/strings.xml2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java159
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java61
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java13
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java16
-rw-r--r--services/core/java/com/android/server/am/ActivityStackSupervisor.java75
-rw-r--r--services/core/java/com/android/server/am/TaskChangeNotificationController.java13
11 files changed, 332 insertions, 41 deletions
diff --git a/core/java/android/app/ITaskStackListener.aidl b/core/java/android/app/ITaskStackListener.aidl
index e454ae170e28..ef997c90166e 100644
--- a/core/java/android/app/ITaskStackListener.aidl
+++ b/core/java/android/app/ITaskStackListener.aidl
@@ -95,4 +95,11 @@ oneway interface ITaskStackListener {
* perform relevant animations before the window disappears.
*/
void onTaskRemovalStarted(int taskId);
+
+ /**
+ * Called when the task has been put in a locked state because one or more of the
+ * activities inside it belong to a managed profile user, and that user has just
+ * been locked.
+ */
+ void onTaskProfileLocked(int taskId, int userId);
}
diff --git a/core/java/android/app/TaskStackListener.java b/core/java/android/app/TaskStackListener.java
index 063955224e22..ad5e69b5cbd4 100644
--- a/core/java/android/app/TaskStackListener.java
+++ b/core/java/android/app/TaskStackListener.java
@@ -74,4 +74,8 @@ public abstract class TaskStackListener extends ITaskStackListener.Stub {
public void onActivityRequestedOrientationChanged(int taskId, int requestedOrientation)
throws RemoteException {
}
+
+ @Override
+ public void onTaskProfileLocked(int taskId, int userId) {
+ }
}
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 5574753bee6c..62f6136026df 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -483,6 +483,21 @@
android:exported="true"
android:enabled="@bool/config_enableKeyguardService" />
+ <activity android:name=".keyguard.WorkLockActivity"
+ android:label="@string/accessibility_desc_work_lock"
+ android:permission="android.permission.MANAGE_USERS"
+ android:exported="false"
+ android:launchMode="singleTop"
+ android:excludeFromRecents="true"
+ android:stateNotNeeded="true"
+ android:resumeWhilePausing="true"
+ android:theme="@android:style/Theme.Black.NoTitleBar">
+ <intent-filter>
+ <action android:name="android.app.action.CONFIRM_DEVICE_CREDENTIAL_WITH_USER" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
<activity android:name=".Somnambulator"
android:label="@string/start_dreams"
android:icon="@mipmap/ic_launcher_dreams"
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 34a0397eca8b..05963ac9ff30 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -460,6 +460,8 @@
<string name="accessibility_desc_settings">Settings</string>
<!-- Content description for the recent apps panel (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_desc_recent_apps">Overview.</string>
+ <!-- Content description for the graphic shown instead of an activity window while the activity is locked (not shown on the screen). [CHAR LIMIT=NONE] -->
+ <string name="accessibility_desc_work_lock">Work lock screen</string>
<!-- Content description for the close button in the zen mode panel introduction message. [CHAR LIMIT=NONE] -->
<string name="accessibility_desc_close">Close</string>
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 8e5db97dd27d..ce89aab30911 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -326,6 +326,12 @@ public class KeyguardViewMediator extends SystemUI {
*/
private boolean mPendingLock;
+ /**
+ * Controller for showing individual "work challenge" lock screen windows inside managed profile
+ * tasks when the current user has been unlocked but the profile is still locked.
+ */
+ private WorkLockActivityController mWorkLockController;
+
private boolean mLockLater;
private boolean mWakeAndUnlocking;
@@ -708,6 +714,8 @@ public class KeyguardViewMediator extends SystemUI {
mHideAnimation = AnimationUtils.loadAnimation(mContext,
com.android.internal.R.anim.lock_screen_behind_enter);
+
+ mWorkLockController = new WorkLockActivityController(mContext);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java
new file mode 100644
index 000000000000..12cf6fa2db20
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java
@@ -0,0 +1,159 @@
+/*
+ * 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
+ */
+
+package com.android.systemui.keyguard;
+
+import static android.app.ActivityManager.TaskDescription;
+
+import android.annotation.ColorInt;
+import android.annotation.UserIdInt;
+import android.app.Activity;
+import android.app.ActivityManager;
+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.os.Bundle;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Log;
+import android.view.View;
+
+/**
+ * Bouncer between work activities and the activity used to confirm credentials before unlocking
+ * a managed profile.
+ * <p>
+ * Shows a solid color when started, based on the organization color of the user it is supposed to
+ * be blocking. Once focused, it switches to a screen to confirm credentials and auto-dismisses if
+ * credentials are accepted.
+ */
+public class WorkLockActivity extends Activity {
+ private static final String TAG = "WorkLockActivity";
+
+ /**
+ * ID of the locked user that this activity blocks access to.
+ */
+ @UserIdInt
+ private int mUserId;
+
+ /**
+ * {@see KeyguardManager}
+ */
+ private KeyguardManager mKgm;
+
+ /**
+ * {@see DevicePolicyManager}
+ */
+ private DevicePolicyManager mDpm;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mUserId = getIntent().getIntExtra(Intent.EXTRA_USER_ID, UserHandle.myUserId());
+ mDpm = (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
+ mKgm = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
+
+ final IntentFilter lockFilter = new IntentFilter();
+ lockFilter.addAction(Intent.ACTION_DEVICE_LOCKED_CHANGED);
+ registerReceiverAsUser(mLockEventReceiver, UserHandle.ALL, lockFilter,
+ /* permission */ null, /* scheduler */ null);
+
+ // Once the receiver is registered, check whether anything happened between now and the time
+ // when this activity was launched. If it did and the user is unlocked now, just quit.
+ if (!mKgm.isDeviceLocked(mUserId)) {
+ finish();
+ return;
+ }
+
+ // Get the organization color; this is a 24-bit integer provided by a DPC, guaranteed to
+ // be completely opaque.
+ final @ColorInt int color = mDpm.getOrganizationColorForUser(mUserId);
+
+ // Draw captions overlaid on the content view, so the whole window is one solid color.
+ setOverlayWithDecorCaptionEnabled(true);
+
+ // Match task description to the task stack we are replacing so it's still recognizably the
+ // original task stack with the same icon and title text.
+ setTaskDescription(new TaskDescription(null, null, color));
+
+ // Blank out the activity. When it is on-screen it will look like a Recents thumbnail with
+ // redaction switched on.
+ final View blankView = new View(this);
+ blankView.setBackgroundColor(color);
+ setContentView(blankView);
+
+ // Respond to input events by showing the prompt to confirm credentials.
+ blankView.setOnClickListener((View v) -> {
+ showConfirmCredentialActivity();
+ });
+ }
+
+ @Override
+ public void onDestroy() {
+ unregisterReceiver(mLockEventReceiver);
+ super.onDestroy();
+ }
+
+ @Override
+ public void onBackPressed() {
+ // Ignore back presses.
+ return;
+ }
+
+ private final BroadcastReceiver mLockEventReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, mUserId);
+ if (userId == mUserId && !mKgm.isDeviceLocked(mUserId)) {
+ finish();
+ }
+ }
+ };
+
+ private void showConfirmCredentialActivity() {
+ if (isFinishing() || !mKgm.isDeviceLocked(mUserId)) {
+ // Don't show the confirm credentials screen if we are already unlocked / unlocking.
+ return;
+ }
+
+ final Intent credential = mKgm.createConfirmDeviceCredentialIntent(null, null, mUserId);
+ if (credential == null) {
+ return;
+ }
+
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchTaskId(getTaskId());
+
+ // Bring this activity back to the foreground after confirming credentials.
+ final PendingIntent target = PendingIntent.getActivity(this, /* request */ -1, getIntent(),
+ PendingIntent.FLAG_CANCEL_CURRENT |
+ PendingIntent.FLAG_ONE_SHOT |
+ PendingIntent.FLAG_IMMUTABLE, options.toBundle());
+
+ credential.putExtra(Intent.EXTRA_INTENT, target.getIntentSender());
+ try {
+ ActivityManager.getService().startConfirmDeviceCredentialIntent(credential);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to start confirm credential intent", e);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java
new file mode 100644
index 000000000000..22fceffd127f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java
@@ -0,0 +1,61 @@
+/*
+ * 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
+ */
+
+package com.android.systemui.keyguard;
+
+import android.app.Activity;
+import android.app.ActivityOptions;
+import android.app.KeyguardManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.UserHandle;
+
+import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.misc.SystemServicesProxy;
+import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
+
+public class WorkLockActivityController {
+ private final Context mContext;
+
+ public WorkLockActivityController(Context context) {
+ mContext = context;
+ EventBus.getDefault().register(this);
+ SystemServicesProxy.getInstance(context).registerTaskStackListener(mLockListener);
+ }
+
+ private void startWorkChallengeInTask(int taskId, int userId) {
+ Intent intent = new Intent(KeyguardManager.ACTION_CONFIRM_DEVICE_CREDENTIAL_WITH_USER)
+ .setComponent(new ComponentName(mContext, WorkLockActivity.class))
+ .putExtra(Intent.EXTRA_USER_ID, userId)
+ .addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
+ | Intent.FLAG_ACTIVITY_REORDER_TO_FRONT
+ | Intent.FLAG_ACTIVITY_SINGLE_TOP);
+
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchTaskId(taskId);
+ options.setTaskOverlay(true);
+ mContext.startActivityAsUser(intent, options.toBundle(), UserHandle.CURRENT);
+ }
+
+ private final TaskStackListener mLockListener = new TaskStackListener() {
+ @Override
+ public void onTaskProfileLocked(int taskId, int userId) {
+ startWorkChallengeInTask(taskId, userId);
+ }
+ };
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index ddffea2446a4..05df63467c02 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -154,6 +154,7 @@ public class SystemServicesProxy {
public void onPinnedStackAnimationEnded() { }
public void onActivityForcedResizable(String packageName, int taskId) { }
public void onActivityDismissingDockedStack() { }
+ public void onTaskProfileLocked(int taskId, int userId) { }
}
/**
@@ -197,6 +198,11 @@ public class SystemServicesProxy {
public void onActivityDismissingDockedStack() throws RemoteException {
mHandler.sendEmptyMessage(H.ON_ACTIVITY_DISMISSING_DOCKED_STACK);
}
+
+ @Override
+ public void onTaskProfileLocked(int taskId, int userId) {
+ mHandler.obtainMessage(H.ON_TASK_PROFILE_LOCKED, taskId, userId).sendToTarget();
+ }
};
/**
@@ -1155,6 +1161,7 @@ public class SystemServicesProxy {
private static final int ON_PINNED_STACK_ANIMATION_ENDED = 4;
private static final int ON_ACTIVITY_FORCED_RESIZABLE = 5;
private static final int ON_ACTIVITY_DISMISSING_DOCKED_STACK = 6;
+ private static final int ON_TASK_PROFILE_LOCKED = 7;
@Override
public void handleMessage(Message msg) {
@@ -1196,6 +1203,12 @@ public class SystemServicesProxy {
}
break;
}
+ case ON_TASK_PROFILE_LOCKED: {
+ for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
+ mTaskStackListeners.get(i).onTaskProfileLocked(msg.arg1, msg.arg2);
+ }
+ break;
+ }
}
}
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 3ab30f234bf0..3b2041e499ab 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -11839,20 +11839,20 @@ public class ActivityManagerService extends IActivityManager.Stub
}
synchronized (this) {
- if (mStackSupervisor.isUserLockedProfile(userId)) {
- final long ident = Binder.clearCallingIdentity();
- try {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ if (mUserController.shouldConfirmCredentials(userId)) {
final int currentUserId = mUserController.getCurrentUserIdLocked();
- if (mUserController.isLockScreenDisabled(currentUserId)) {
- // If there is no device lock, we will show the profile's credential page.
- mActivityStarter.showConfirmDeviceCredential(userId);
+ if (!mKeyguardController.isKeyguardLocked()) {
+ // If the device is not locked, we will prompt for credentials immediately.
+ mStackSupervisor.lockAllProfileTasks(userId);
} else {
// Showing launcher to avoid user entering credential twice.
startHomeActivityLocked(currentUserId, "notifyLockedProfile");
}
- } finally {
- Binder.restoreCallingIdentity(ident);
}
+ } finally {
+ Binder.restoreCallingIdentity(ident);
}
}
}
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 8bd7c9010181..a93524903b29 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -763,43 +763,52 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
}
/**
- * TODO: Handle freefom mode.
- * @return true when credential confirmation is needed for the user and there is any
- * activity started by the user in any visible stack.
+ * Detects whether we should show a lock screen in front of this task for a locked user.
+ * <p>
+ * We'll do this if either of the following holds:
+ * <ul>
+ * <li>The top activity explicitly belongs to {@param userId}.</li>
+ * <li>The top activity returns a result to an activity belonging to {@param userId}.</li>
+ * </ul>
+ *
+ * @return {@code true} if the top activity looks like it belongs to {@param userId}.
*/
- boolean isUserLockedProfile(@UserIdInt int userId) {
- if (!mService.mUserController.shouldConfirmCredentials(userId)) {
- return false;
- }
- final ActivityStack fullScreenStack = getStack(FULLSCREEN_WORKSPACE_STACK_ID);
- final ActivityStack dockedStack = getStack(DOCKED_STACK_ID);
- final ActivityStack[] activityStacks = new ActivityStack[] {fullScreenStack, dockedStack};
- for (final ActivityStack activityStack : activityStacks) {
- if (activityStack == null) {
- continue;
- }
- if (activityStack.topRunningActivityLocked() == null) {
- continue;
- }
- if (activityStack.getStackVisibilityLocked(null) == STACK_INVISIBLE) {
- continue;
- }
- if (activityStack.isDockedStack() && mIsDockMinimized) {
- continue;
- }
- final TaskRecord topTask = activityStack.topTask();
- if (topTask == null) {
- continue;
- }
- // To handle the case that work app is in the task but just is not the top one.
- for (int i = topTask.mActivities.size() - 1; i >= 0; i--) {
- final ActivityRecord activityRecord = topTask.mActivities.get(i);
- if (activityRecord.userId == userId) {
- return true;
+ private boolean taskTopActivityIsUser(TaskRecord task, @UserIdInt int userId) {
+ // To handle the case that work app is in the task but just is not the top one.
+ final ActivityRecord activityRecord = task.getTopActivity();
+ final ActivityRecord resultTo = (activityRecord != null ? activityRecord.resultTo : null);
+
+ return (activityRecord != null && activityRecord.userId == userId)
+ || (resultTo != null && resultTo.userId == userId);
+ }
+
+ /**
+ * Find all visible task stacks containing {@param userId} and intercept them with an activity
+ * to block out the contents and possibly start a credential-confirming intent.
+ *
+ * @param userId user handle for the locked managed profile.
+ */
+ void lockAllProfileTasks(@UserIdInt int userId) {
+ mWindowManager.deferSurfaceLayout();
+ try {
+ final List<ActivityStack> stacks = getStacks();
+ for (int stackNdx = stacks.size() - 1; stackNdx >= 0; stackNdx--) {
+ final List<TaskRecord> tasks = stacks.get(stackNdx).getAllTasks();
+ for (int taskNdx = tasks.size() - 1; taskNdx >= 0; taskNdx--) {
+ final TaskRecord task = tasks.get(taskNdx);
+
+ // Check the task for a top activity belonging to userId, or returning a result
+ // to an activity belonging to userId. Example case: a document picker for
+ // personal files, opened by a work app, should still get locked.
+ if (taskTopActivityIsUser(task, userId)) {
+ mService.mTaskChangeNotificationController.notifyTaskProfileLocked(
+ task.taskId, userId);
+ }
}
}
+ } finally {
+ mWindowManager.continueSurfaceLayout();
}
- return false;
}
void setNextTaskIdForUserLocked(int taskId, int userId) {
diff --git a/services/core/java/com/android/server/am/TaskChangeNotificationController.java b/services/core/java/com/android/server/am/TaskChangeNotificationController.java
index fd248c6c807a..fbdbb1b2443a 100644
--- a/services/core/java/com/android/server/am/TaskChangeNotificationController.java
+++ b/services/core/java/com/android/server/am/TaskChangeNotificationController.java
@@ -39,6 +39,7 @@ class TaskChangeNotificationController {
static final int NOTIFY_TASK_DESCRIPTION_CHANGED_LISTENERS_MSG = 11;
static final int NOTIFY_ACTIVITY_REQUESTED_ORIENTATION_CHANGED_LISTENERS = 12;
static final int NOTIFY_TASK_REMOVAL_STARTED_LISTENERS = 13;
+ static final int NOTIFY_TASK_PROFILE_LOCKED_LISTENERS_MSG = 14;
// Delay in notifying task stack change listeners (in millis)
static final int NOTIFY_TASK_STACK_CHANGE_LISTENERS_DELAY = 100;
@@ -110,6 +111,9 @@ class TaskChangeNotificationController {
case NOTIFY_ACTIVITY_DISMISSING_DOCKED_STACK_MSG:
forAllListeners((listener) -> listener.onActivityDismissingDockedStack());
break;
+ case NOTIFY_TASK_PROFILE_LOCKED_LISTENERS_MSG:
+ forAllListeners((listener) -> listener.onTaskProfileLocked(msg.arg1, msg.arg2));
+ break;
}
}
}
@@ -228,4 +232,13 @@ class TaskChangeNotificationController {
mHandler.obtainMessage(NOTIFY_TASK_REMOVAL_STARTED_LISTENERS, taskId, 0 /* unused */)
.sendToTarget();
}
+
+ /**
+ * 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) {
+ mHandler.obtainMessage(NOTIFY_TASK_PROFILE_LOCKED_LISTENERS_MSG, taskId, userId)
+ .sendToTarget();
+ }
}