diff options
| author | 2016-12-20 14:50:13 +0000 | |
|---|---|---|
| committer | 2017-01-11 14:13:03 +0000 | |
| commit | 3fef1f284390a2ff7a58e0dcd56cb90bf83d2017 (patch) | |
| tree | 7e228c0844a22a67238b25d66edbc4d8be5461c2 | |
| parent | 54402aab14deb9f21b0a9b451f80113ab9821b04 (diff) | |
Bounce work challenge through a WorkLockActivity
This stops us from depending on Settings for keeping the work profile
secure. Instead that is delegated to a smaller Activity inside SystemUI
which has just two jobs:
1) Don't let anyone see the content that's supposed to be locked.
2) Start ConfirmCredentialsActivity (still lives in Settings for now)
Bug: 31001762
Test: //cts/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest#testResetPasswordFbe
Test: //tests/PoApi/src/com/google/android/afwtest/poapi/WorkChallengeTest
Change-Id: If43820b683007a60a37edf32fb65b442a8fb709b
6 files changed, 246 insertions, 41 deletions
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/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/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 49c4995bf4dd..7682f9196b66 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -11837,20 +11837,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..aeab7be1c37e 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -763,43 +763,51 @@ 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.mActivityStarter.startTaskLockedActivity(task); + } } } + } finally { + mWindowManager.continueSurfaceLayout(); } - return false; } void setNextTaskIdForUserLocked(int taskId, int userId) { diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java index 09af9412da64..5f04d7fbc3de 100644 --- a/services/core/java/com/android/server/am/ActivityStarter.java +++ b/services/core/java/com/android/server/am/ActivityStarter.java @@ -660,6 +660,27 @@ class ActivityStarter { UserHandle.CURRENT); } + void startTaskLockedActivity(final TaskRecord task) { + final ActivityRecord activityRecord = task.topRunningActivityLocked(); + if (activityRecord == null) { + Slog.w(TAG, "Unable to find activity record to start lock activity for task: " + task); + return; + } + + final KeyguardManager km = (KeyguardManager) mService.mContext + .getSystemService(Context.KEYGUARD_SERVICE); + Intent intent = new Intent(KeyguardManager.ACTION_CONFIRM_DEVICE_CREDENTIAL_WITH_USER); + intent.setPackage("com.android.systemui"); + intent.putExtra(Intent.EXTRA_TASK_ID, task.lastTaskDescription); + intent.putExtra(Intent.EXTRA_USER_ID, task.userId); + intent.addFlags(FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS | FLAG_ACTIVITY_REORDER_TO_FRONT + | FLAG_ACTIVITY_SINGLE_TOP); + final ActivityOptions options = ActivityOptions.makeBasic(); + options.setLaunchTaskId(task.taskId); + options.setTaskOverlay(true); + mService.mContext.startActivityAsUser(intent, options.toBundle(), UserHandle.CURRENT); + } + final int startActivityMayWait(IApplicationThread caller, int callingUid, String callingPackage, Intent intent, String resolvedType, IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, |