简介
how-to-use-screen-pinning-android
SystemUI
PhoneStatusBar
public void showScreenPinningRequest(int taskId, boolean allowCancel) {
mScreenPinningRequest.showPrompt(taskId, allowCancel);
}
ScreenPinningRequest
@Override
public void onClick(View v) {
if (v.getId() == R.id.screen_pinning_ok_button || mRequestWindow == v) {
try {
ActivityManagerNative.getDefault().startSystemLockTaskMode(taskId);
} catch (RemoteException e) {}
}
clearPrompt();
}
开启屏幕固定
AMS
@Override
public void startSystemLockTaskMode(int taskId) throws RemoteException {
enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "startSystemLockTaskMode");
// This makes inner call to look as if it was initiated by system.
long ident = Binder.clearCallingIdentity();
try {
synchronized (this) {
startLockTaskMode(taskId);
}
} finally {
Binder.restoreCallingIdentity(ident);
}
}
@Override
public void startLockTaskMode(int taskId) {
synchronized (this) {
final TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId);
if (task != null) {
startLockTaskModeLocked(task);
}
}
}
void startLockTaskModeLocked(TaskRecord task) {
if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, "startLockTaskModeLocked: " + task);
if (task.mLockTaskAuth == LOCK_TASK_AUTH_DONT_LOCK) {
return;
}
// isSystemInitiated is used to distinguish between locked and pinned mode, as pinned mode
// is initiated by system after the pinning request was shown and locked mode is initiated
// by an authorized app directly
final int callingUid = Binder.getCallingUid();
boolean isSystemInitiated = callingUid == Process.SYSTEM_UID;
long ident = Binder.clearCallingIdentity();
try {
if (!isSystemInitiated) {
task.mLockTaskUid = callingUid;
if (task.mLockTaskAuth == LOCK_TASK_AUTH_PINNABLE) {
// startLockTask() called by app and task mode is lockTaskModeDefault.
if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, "Mode default, asking user");
StatusBarManagerInternal statusBarManager =
LocalServices.getService(StatusBarManagerInternal.class);
if (statusBarManager != null) {
statusBarManager.showScreenPinningRequest(task.taskId);
}
return;
}
final ActivityStack stack = mStackSupervisor.getFocusedStack();
if (stack == null || task != stack.topTask()) {
throw new IllegalArgumentException("Invalid task, not in foreground");
}
}
if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, isSystemInitiated ? "Locking pinned" :
"Locking fully");
mStackSupervisor.setLockTaskModeLocked(task, isSystemInitiated ?
ActivityManager.LOCK_TASK_MODE_PINNED :
ActivityManager.LOCK_TASK_MODE_LOCKED,
"startLockTask", true);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
ActivityStackSupervisor
void setLockTaskModeLocked(TaskRecord task, int lockTaskModeState, String reason,
boolean andResume) {
// Should have already been checked, but do it again.
if (task.mLockTaskAuth == LOCK_TASK_AUTH_DONT_LOCK) {
if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK,
"setLockTaskModeLocked: Can't lock due to auth");
return;
}
if (isLockTaskModeViolation(task)) {
Slog.e(TAG_LOCKTASK, "setLockTaskMode: Attempt to start an unauthorized lock task.");
return;
}
if (mLockTaskModeTasks.isEmpty()) {
// First locktask.
final Message lockTaskMsg = Message.obtain();
lockTaskMsg.obj = task.intent.getComponent().getPackageName();
lockTaskMsg.arg1 = task.userId;
lockTaskMsg.what = LOCK_TASK_START_MSG;
lockTaskMsg.arg2 = lockTaskModeState;
mHandler.sendMessage(lockTaskMsg);
}
// Add it or move it to the top.
if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, "setLockTaskModeLocked: Locking to " + task +
" Callers=" + Debug.getCallers(4));
mLockTaskModeTasks.remove(task);
mLockTaskModeTasks.add(task);
if (task.mLockTaskUid == -1) {
task.mLockTaskUid = task.effectiveUid;
}
if (andResume) {
findTaskToMoveToFrontLocked(task, 0, null, reason,
lockTaskModeState != LOCK_TASK_MODE_NONE);
resumeFocusedStackTopActivityLocked();
} else if (lockTaskModeState != LOCK_TASK_MODE_NONE) {
handleNonResizableTaskIfNeeded(task, INVALID_STACK_ID, task.stack.mStackId,
true /* forceNonResizable */);
}
}
private final class ActivityStackSupervisorHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case LOCK_TASK_START_MSG: {
// When lock task starts, we disable the status bars.
try {
if (mLockTaskNotify == null) {
mLockTaskNotify = new LockTaskNotify(mService.mContext);
}
mLockTaskNotify.show(true);
mLockTaskModeState = msg.arg2;
if (getStatusBarService() != null) {
int flags = 0;
if (mLockTaskModeState == LOCK_TASK_MODE_LOCKED) {
flags = StatusBarManager.DISABLE_MASK
& (~StatusBarManager.DISABLE_BACK);
} else if (mLockTaskModeState == LOCK_TASK_MODE_PINNED) {
flags = StatusBarManager.DISABLE_MASK
& (~StatusBarManager.DISABLE_BACK)
& (~StatusBarManager.DISABLE_HOME)
& (~StatusBarManager.DISABLE_RECENT);
}
getStatusBarService().disable(flags, mToken,
mService.mContext.getPackageName());
}
mWindowManager.disableKeyguard(mToken, LOCK_TASK_TAG);
if (getDevicePolicyManager() != null) {
getDevicePolicyManager().notifyLockTaskModeChanged(true,
(String)msg.obj, msg.arg1);
}
} catch (RemoteException ex) {
throw new RuntimeException(ex);
}
} break;
}
}
}
关闭屏幕固定
AMS
/**
* This API should be called by SystemUI only when user perform certain action to dismiss
* lock task mode. We should only dismiss pinned lock task mode in this case.
*/
@Override
public void stopSystemLockTaskMode() throws RemoteException {
if (mStackSupervisor.getLockTaskModeState() == ActivityManager.LOCK_TASK_MODE_PINNED) {
stopLockTaskMode();
} else {
mStackSupervisor.showLockTaskToast();
}
}
@Override
public void stopLockTaskMode() {
final TaskRecord lockTask = mStackSupervisor.getLockedTaskLocked();
if (lockTask == null) {
// Our work here is done.
return;
}
final int callingUid = Binder.getCallingUid();
final int lockTaskUid = lockTask.mLockTaskUid;
final int lockTaskModeState = mStackSupervisor.getLockTaskModeState();
if (lockTaskModeState == ActivityManager.LOCK_TASK_MODE_NONE) {
// Done.
return;
} else {
// Ensure the same caller for startLockTaskMode and stopLockTaskMode.
// It is possible lockTaskMode was started by the system process because
// android:lockTaskMode is set to a locking value in the application manifest
// instead of the app calling startLockTaskMode. In this case
// {@link TaskRecord.mLockTaskUid} will be 0, so we compare the callingUid to the
// {@link TaskRecord.effectiveUid} instead. Also caller with
// {@link MANAGE_ACTIVITY_STACKS} can stop any lock task.
if (checkCallingPermission(MANAGE_ACTIVITY_STACKS) != PERMISSION_GRANTED
&& callingUid != lockTaskUid
&& (lockTaskUid != 0 || callingUid != lockTask.effectiveUid)) {
throw new SecurityException("Invalid uid, expected " + lockTaskUid
+ " callingUid=" + callingUid + " effectiveUid=" + lockTask.effectiveUid);
}
}
long ident = Binder.clearCallingIdentity();
try {
Log.d(TAG, "stopLockTaskMode");
// Stop lock task
synchronized (this) {
mStackSupervisor.setLockTaskModeLocked(null, ActivityManager.LOCK_TASK_MODE_NONE,
"stopLockTask", true);
}
TelecomManager tm = (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
if (tm != null) {
tm.showInCallScreen(false);
}
} finally {
Binder.restoreCallingIdentity(ident);
}
}
ActivityStackSupervisor
void setLockTaskModeLocked(TaskRecord task, int lockTaskModeState, String reason,
boolean andResume) {
if (task == null) {
// Take out of lock task mode if necessary
final TaskRecord lockedTask = getLockedTaskLocked();
if (lockedTask != null) {
removeLockedTaskLocked(lockedTask);
if (!mLockTaskModeTasks.isEmpty()) {
// There are locked tasks remaining, can only finish this task, not unlock it.
if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK,
"setLockTaskModeLocked: Tasks remaining, can't unlock");
lockedTask.performClearTaskLocked();
resumeFocusedStackTopActivityLocked();
return;
}
}
if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK,
"setLockTaskModeLocked: No tasks to unlock. Callers=" + Debug.getCallers(4));
return;
}
-------------------------------------------------------------------
}
void removeLockedTaskLocked(final TaskRecord task) {
if (!mLockTaskModeTasks.remove(task)) {
return;
}
if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, "removeLockedTaskLocked: removed " + task);
if (mLockTaskModeTasks.isEmpty()) {
// Last one.
if (DEBUG_LOCKTASK) Slog.d(TAG_LOCKTASK, "removeLockedTask: task=" + task +
" last task, reverting locktask mode. Callers=" + Debug.getCallers(3));
final Message lockTaskMsg = Message.obtain();
lockTaskMsg.arg1 = task.userId;
lockTaskMsg.what = LOCK_TASK_END_MSG;
mHandler.sendMessage(lockTaskMsg);
}
}
private final class ActivityStackSupervisorHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case LOCK_TASK_END_MSG: {
// When lock task ends, we enable the status bars.
try {
if (getStatusBarService() != null) {
getStatusBarService().disable(StatusBarManager.DISABLE_NONE, mToken,
mService.mContext.getPackageName());
}
mWindowManager.reenableKeyguard(mToken);
if (getDevicePolicyManager() != null) {
getDevicePolicyManager().notifyLockTaskModeChanged(false, null,
msg.arg1);
}
if (mLockTaskNotify == null) {
mLockTaskNotify = new LockTaskNotify(mService.mContext);
}
mLockTaskNotify.show(false);
try {
boolean shouldLockKeyguard = Settings.Secure.getInt(
mService.mContext.getContentResolver(),
Settings.Secure.LOCK_TO_APP_EXIT_LOCKED) != 0;
if (mLockTaskModeState == LOCK_TASK_MODE_PINNED && shouldLockKeyguard) {
mWindowManager.lockNow(null);
mWindowManager.dismissKeyguard();
new LockPatternUtils(mService.mContext)
.requireCredentialEntry(UserHandle.USER_ALL);
}
} catch (SettingNotFoundException e) {
// No setting, don't lock.
}
} catch (RemoteException ex) {
throw new RuntimeException(ex);
} finally {
mLockTaskModeState = LOCK_TASK_MODE_NONE;
}
} break;
}
}
}
屏幕拦截逻辑
ActivityStackSupervisor
/** Can't be put in lockTask mode. */
final static int LOCK_TASK_AUTH_DONT_LOCK = 0;
/** Can enter app pinning with user approval. Can never start over existing lockTask task. */
final static int LOCK_TASK_AUTH_PINNABLE = 1;
/** Starts in LOCK_TASK_MODE_LOCKED automatically. Can start over existing lockTask task. */
final static int LOCK_TASK_AUTH_LAUNCHABLE = 2;
/** Can enter lockTask without user approval. Can start over existing lockTask task. */
final static int LOCK_TASK_AUTH_WHITELISTED = 3;
/** Priv-app that starts in LOCK_TASK_MODE_LOCKED automatically. Can start over existing
* lockTask task. */
final static int LOCK_TASK_AUTH_LAUNCHABLE_PRIV = 4;
int mLockTaskAuth = LOCK_TASK_AUTH_PINNABLE;
int mLockTaskUid = -1; // The uid of the application that called startLockTask().
boolean isLockTaskModeViolation(TaskRecord task) {
return isLockTaskModeViolation(task, false);
}
boolean isLockTaskModeViolation(TaskRecord task, boolean isNewClearTask) {
if (getLockedTaskLocked() == task && !isNewClearTask) {
return false;
}
final int lockTaskAuth = task.mLockTaskAuth;
switch (lockTaskAuth) {
case LOCK_TASK_AUTH_DONT_LOCK:
return !mLockTaskModeTasks.isEmpty();
case LOCK_TASK_AUTH_LAUNCHABLE_PRIV:
case LOCK_TASK_AUTH_LAUNCHABLE:
case LOCK_TASK_AUTH_WHITELISTED:
return false;
case LOCK_TASK_AUTH_PINNABLE:
// Pinnable tasks can't be launched on top of locktask tasks.
return !mLockTaskModeTasks.isEmpty();
default:
Slog.w(TAG, "isLockTaskModeViolation: invalid lockTaskAuth value=" + lockTaskAuth);
return true;
}
}
ActivityStarter
private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord,
IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask) {
// Should this be considered a new task?
if (mStartActivity.resultTo == null && mInTask == null && !mAddingToTask
&& (mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) {
newTask = true;
setTaskFromReuseOrCreateNewTask(taskToAffiliate);
if (mSupervisor.isLockTaskModeViolation(mStartActivity.task)) {
Slog.e(TAG, "Attempted Lock Task Mode violation mStartActivity=" + mStartActivity);
return START_RETURN_LOCK_TASK_MODE_VIOLATION;
}
if (!mMovedOtherTask) {
// If stack id is specified in activity options, usually it means that activity is
// launched not from currently focused stack (e.g. from SysUI or from shell) - in
// that case we check the target stack.
updateTaskReturnToType(mStartActivity.task, mLaunchFlags,
preferredLaunchStackId != INVALID_STACK_ID ? mTargetStack : topStack);
}
} else if (mSourceRecord != null) {
if (mSupervisor.isLockTaskModeViolation(mSourceRecord.task)) {
Slog.e(TAG, "Attempted Lock Task Mode violation mStartActivity=" + mStartActivity);
return START_RETURN_LOCK_TASK_MODE_VIOLATION;
}
final int result = setTaskFromSourceRecord();
if (result != START_SUCCESS) {
return result;
}
} else if (mInTask != null) {
// The caller is asking that the new activity be started in an explicit
// task it has provided to us.
if (mSupervisor.isLockTaskModeViolation(mInTask)) {
Slog.e(TAG, "Attempted Lock Task Mode violation mStartActivity=" + mStartActivity);
return START_RETURN_LOCK_TASK_MODE_VIOLATION;
}
final int result = setTaskFromInTask();
if (result != START_SUCCESS) {
return result;
}
} else {
// This not being started from an existing activity, and not part of a new task...
// just put it in the top task, though these days this case should never happen.
setTaskToCurrentTopOrCreateNewTask();
}
mTargetStack.startActivityLocked(mStartActivity, newTask, mKeepCurTransition, mOptions);
}
dumpsys activity
$ adb shell dumpsys activity activities
mFocusedActivity: ActivityRecord{3d76e47 u0 com.sunmi.toolbox/.laboratory.LabActivity t125}
mFocusedStack=ActivityStack{abd4410 stackId=1, 6 tasks} mLastFocusedStack=ActivityStack{abd4410 stackId=1, 6 tasks}
mSleepTimeout=false
mCurTaskIdForUser={0=128}
mUserStackInFront={}
mActivityContainers={0=ActivtyContainer{0}A, 1=ActivtyContainer{1}A}
mLockTaskModeState=PINNED mLockTaskPackages (userId:packages)=
0:[com.android.settings, com.sunmi.toolbox]
mLockTaskModeTasks[TaskRecord{842d833 #124 A=com.sunmi.superpermissiontest U=0 StackId=1 sz=1}]
白名单
DevicePolicyManagerService
/**
* Sets which packages may enter lock task mode.
*
* <p>This function can only be called by the device owner or alternatively by the profile owner
* in case the user is affiliated.
*
* @param packages The list of packages allowed to enter lock task mode.
*/
@Override
public void setLockTaskPackages(ComponentName who, String[] packages)
throws SecurityException {
Preconditions.checkNotNull(who, "ComponentName is null");
synchronized (this) {
ActiveAdmin deviceOwner = getActiveAdminWithPolicyForUidLocked(
who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER, mInjector.binderGetCallingUid());
ActiveAdmin profileOwner = getActiveAdminWithPolicyForUidLocked(
who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, mInjector.binderGetCallingUid());
if (deviceOwner != null || (profileOwner != null && isAffiliatedUser())) {
int userHandle = mInjector.userHandleGetCallingUserId();
setLockTaskPackagesLocked(userHandle, new ArrayList<>(Arrays.asList(packages)));
} else {
throw new SecurityException("Admin " + who +
" is neither the device owner or affiliated user's profile owner.");
}
}
}
private void setLockTaskPackagesLocked(int userHandle, List<String> packages) {
DevicePolicyData policy = getUserData(userHandle);
policy.mLockTaskPackages = packages;
// Store the settings persistently.
saveSettingsLocked(userHandle);
updateLockTaskPackagesLocked(packages, userHandle);
}
/**
* This function returns the list of components allowed to start the task lock mode.
*/
@Override
public String[] getLockTaskPackages(ComponentName who) {
Preconditions.checkNotNull(who, "ComponentName is null");
synchronized (this) {
getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
int userHandle = mInjector.binderGetCallingUserHandle().getIdentifier();
final List<String> packages = getLockTaskPackagesLocked(userHandle);
return packages.toArray(new String[packages.size()]);
}
}
private List<String> getLockTaskPackagesLocked(int userHandle) {
final DevicePolicyData policy = getUserData(userHandle);
return policy.mLockTaskPackages;
}
AMS
/**
* List of packages whitelisted by DevicePolicyManager for locktask. Indexed by userId.
*/
SparseArray<String[]> mLockTaskPackages = new SparseArray<>();
@Override
public void updateLockTaskPackages(int userId, String[] packages) {
final int callingUid = Binder.getCallingUid();
if (callingUid != 0 && callingUid != Process.SYSTEM_UID) {
enforceCallingPermission(android.Manifest.permission.UPDATE_LOCK_TASK_PACKAGES,
"updateLockTaskPackages()");
}
synchronized (this) {
if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, "Whitelisting " + userId + ":" +
Arrays.toString(packages));
mLockTaskPackages.put(userId, packages);
mStackSupervisor.onLockTaskPackagesUpdatedLocked();
}
}
ActivityStackSupervisor
void onLockTaskPackagesUpdatedLocked() {
boolean didSomething = false;
for (int taskNdx = mLockTaskModeTasks.size() - 1; taskNdx >= 0; --taskNdx) {
final TaskRecord lockedTask = mLockTaskModeTasks.get(taskNdx);
final boolean wasWhitelisted =
(lockedTask.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE) ||
(lockedTask.mLockTaskAuth == LOCK_TASK_AUTH_WHITELISTED);
lockedTask.setLockTaskAuth();
final boolean isWhitelisted =
(lockedTask.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE) ||
(lockedTask.mLockTaskAuth == LOCK_TASK_AUTH_WHITELISTED);
if (wasWhitelisted && !isWhitelisted) {
// Lost whitelisting authorization. End it now.
if (DEBUG_LOCKTASK) Slog.d(TAG_LOCKTASK, "onLockTaskPackagesUpdated: removing " +
lockedTask + " mLockTaskAuth=" + lockedTask.lockTaskAuthToString());
removeLockedTaskLocked(lockedTask);
lockedTask.performClearTaskLocked();
didSomething = true;
}
}
for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
final ActivityStack stack = stacks.get(stackNdx);
stack.onLockTaskPackagesUpdatedLocked();
}
}
final ActivityRecord r = topRunningActivityLocked();
final TaskRecord task = r != null ? r.task : null;
if (mLockTaskModeTasks.isEmpty() && task != null
&& task.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE) {
// This task must have just been authorized.
if (DEBUG_LOCKTASK) Slog.d(TAG_LOCKTASK,
"onLockTaskPackagesUpdated: starting new locktask task=" + task);
setLockTaskModeLocked(task, ActivityManager.LOCK_TASK_MODE_LOCKED, "package updated",
false);
didSomething = true;
}
if (didSomething) {
resumeFocusedStackTopActivityLocked();
}
}