diff options
| author | 2017-01-11 17:40:58 +0000 | |
|---|---|---|
| committer | 2017-01-11 17:41:01 +0000 | |
| commit | 3101b3ad21b52be9ef06400b33016cf6c973382f (patch) | |
| tree | a346d411fb11ed01d14d8cc3a0bf2d43f8eab967 | |
| parent | 48ee82ad5b315c6df3d12d1dd647e83cc4fdb224 (diff) | |
| parent | c41f6ec877309d9c73168ef8cdc277f82eb0d42e (diff) | |
Merge changes I3fd28e69,If43820b6
* changes:
Lock work tasks from SystemUI instead of ActivityStarter
Bounce work challenge through a WorkLockActivity
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(); + } } |