android6.0 固定屏幕功能

来源:互联网 发布:艾珺的淘宝店铺 编辑:程序博客网 时间:2024/06/05 17:03

可能大家看到这个标题不知道是什么东西,我先说明下,android6.0在设置->安全->屏幕固定开启后,然后再长按home键出现最近的几个Activity可以选择一个图钉按钮就开启了屏幕固定功能。

屏幕固定开启后,屏幕只能固定在设定的Task上的Activity切换。


一、设置固定屏幕

我们先来看SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java的代码,这段代码就是长按home键出现几个Activity,然后按了图钉的那个按钮。在这里直接调用了AMS的startLockTaskModeOnCurrent函数。

    @Override    public void onClick(View v) {        if (v.getId() == R.id.screen_pinning_ok_button || mRequestWindow == v) {            try {                ActivityManagerNative.getDefault().startLockTaskModeOnCurrent();            } catch (RemoteException e) {}        }        clearPrompt();    }

我们来看AMS的startLockTaskModeOnCurrent函数,先调用ActivityStackSupervisor的topRunningActivityLocked获取最前面的Activity,然后调用startLockTaskModeLocked函数,参数是TaskRecord。

    public void startLockTaskModeOnCurrent() throws RemoteException {        enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS,                "startLockTaskModeOnCurrent");        long ident = Binder.clearCallingIdentity();        try {            synchronized (this) {                ActivityRecord r = mStackSupervisor.topRunningActivityLocked();                if (r != null) {                    startLockTaskModeLocked(r.task);                }            }        } finally {            Binder.restoreCallingIdentity(ident);        }    }
我们再来看topRunningActivityLocked函数,先从mFocusedStack中获取最前面的Activity。如果没有再遍历所有的mStacks获取。

    ActivityRecord topRunningActivityLocked() {        final ActivityStack focusedStack = mFocusedStack;        ActivityRecord r = focusedStack.topRunningActivityLocked(null);        if (r != null) {            return r;        }        // Return to the home stack.        final ArrayList<ActivityStack> stacks = mHomeStack.mStacks;        for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {            final ActivityStack stack = stacks.get(stackNdx);            if (stack != focusedStack && isFrontStack(stack)) {                r = stack.topRunningActivityLocked(null);                if (r != null) {                    return r;                }            }        }        return null;    }

在startLockTaskModeLocked函数中主要是调用了ActivityStackSupervisor的setLockTaskModeLocked函数,下面我们来看这个函数,我们的task不为null,第一次mLockTaskModeTasks为空,会发送一个LOCK_TASK_START_MSG消息

    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();                    resumeTopActivitiesLocked();                    return;                }            }            if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK,                    "setLockTaskModeLocked: No tasks to unlock. Callers=" + Debug.getCallers(4));            return;        }        // 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);//加入到mLockModeTasks中        if (task.mLockTaskUid == -1) {            task.mLockTaskUid = task.effectiveUid;        }        if (andResume) {            findTaskToMoveToFrontLocked(task, 0, null, reason);//把task放最前面            resumeTopActivitiesLocked();//显示新的Activity        }    }

我们再来看消息处理,在消息处理中主要调用了WMS的disableKeyguard函数。

                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;



二、固定屏幕后Activity启动流程

在固定屏幕后,如果我们启动其他TaskRecord的Activity是不能启动的,我们来看下这个原理。在startActivityUncheckedLocked函数中会调用isLockTaskModeViolation函数来判断是否进一步的Activity的启动流程,我们来看下这个函数,调用getLockedTaskLocked来看mLockTaskModeTasks(就是锁定屏幕的那些Task),如果当前的task就是当前正在固定屏幕的task,直接return false就是可以继续启动Activity的流程,而如果不是,我们需要看task的mLockTaskAuth变量。

    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;        }    }

我们再来看TaskRecord的setLockedTaskAuth函数,在新建一个TaskRecord的时候会调用setIntent函数,而setIntent函数又是在TaskRecord的构造函数中调用的。我们来看这个函数mLockTaskAuth的值是根据mLockTaskMode来定的,而mLockTaskMode又是ActivityInfo传入的,这个值是在PKMS解析AndroidManifest.xml的时候构造的,默认就是LOCK_TASK_LAUNCH_MODE_DEFAULT,而当没有白名单mLockTaskAuth最后就是LOCK_TASK_AUTH_PINNABLE。

    void setLockTaskAuth() {        if (!mPrivileged &&                (mLockTaskMode == LOCK_TASK_LAUNCH_MODE_ALWAYS ||                        mLockTaskMode == LOCK_TASK_LAUNCH_MODE_NEVER)) {            // Non-priv apps are not allowed to use always or never, fall back to default            mLockTaskMode = LOCK_TASK_LAUNCH_MODE_DEFAULT;        }        switch (mLockTaskMode) {            case LOCK_TASK_LAUNCH_MODE_DEFAULT:                mLockTaskAuth = isLockTaskWhitelistedLocked() ?                    LOCK_TASK_AUTH_WHITELISTED : LOCK_TASK_AUTH_PINNABLE;                break;            case LOCK_TASK_LAUNCH_MODE_NEVER:                mLockTaskAuth = LOCK_TASK_AUTH_DONT_LOCK;                break;            case LOCK_TASK_LAUNCH_MODE_ALWAYS:                mLockTaskAuth = LOCK_TASK_AUTH_LAUNCHABLE_PRIV;                break;            case LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED:                mLockTaskAuth = isLockTaskWhitelistedLocked() ?                        LOCK_TASK_AUTH_LAUNCHABLE : LOCK_TASK_AUTH_PINNABLE;                break;        }        if (DEBUG_LOCKTASK) Slog.d(TAG_LOCKTASK, "setLockTaskAuth: task=" + this +                " mLockTaskAuth=" + lockTaskAuthToString());    }

我们再来看isLockTaskModeViolation函数如下代码,现在是task的mLockTaskAuth 是LOCK_TASK_AUTH_PINNABLE,而当前处于固定屏幕,所以mLockTaskModeTasks不为null,最后返回true。那Activity启动流程就不能走下去了,那就是代表启动普通的Activity会被阻止。

            case LOCK_TASK_AUTH_PINNABLE:                // Pinnable tasks can't be launched on top of locktask tasks.                return !mLockTaskModeTasks.isEmpty();


三、取消固定屏幕

最后我们再来看看取消固定屏幕,取消屏幕会在PhoneStatusBar中取消,但是一定是要有虚拟键,原生就是这么设定的。最后调用了AMS的stopLockTaskModeOnCurrent函数。这个函数主要是调用了stopLockTaskMode函数,这个函数中主要是调用了ActivityStackSupervisor的setLockTaskModeLocked函数,之前在固定屏幕时也是调用了这个函数,但是这里我们仔细看,其第一个参数为null。

    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;        // 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.        if (getLockTaskModeState() == ActivityManager.LOCK_TASK_MODE_LOCKED &&                callingUid != lockTaskUid                && (lockTaskUid != 0                    || (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);            }        } finally {            Binder.restoreCallingIdentity(ident);        }    }

我们来看下这个函数,如果为空,现在调用getLockedTaskLocked获取当前固定屏幕的TaskRecord,然后调用removeLockedTaskLocked去除这个TaskRecord,如果还不为null,调用resumeTopActivitiesLocked启动下个Activity(一般也就是下个屏幕锁定的TaskRecord的Activity)。

如果为空了,直接返回。但是在我们下次启动普通的Activity的时候就恢复正常了,因为mLockTaskModeTasks已经为空了。

    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();                    resumeTopActivitiesLocked();                    return;                }            }            if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK,                    "setLockTaskModeLocked: No tasks to unlock. Callers=" + Debug.getCallers(4));            return;        }


四、没有虚拟键如何取消屏幕固定

前面说过如果没有虚拟键就不能取消屏幕固定了,我们说下几种方式

1.使用am命令 am task lock stop可以调用am的stopLockTaskMode函数

2.另一种我们可以在Activity.java中修改代码,比较长按返回键调用AMS的stopLockTaskMode方法,下面就是实现,Activity本身提供了stopLockTask就是调用了AMS的stopLockTaskMode方法

    public boolean onKeyLongPress(int keyCode, KeyEvent event) {        if (keyCode == KeyEvent.KEYCODE_BACK) {            stopLockTask();            }        return false;    }

3.直接在Settings中对这项进行置灰处理

在SecuritySettings会读取security_settings_misc.xml文件然后加入相关perference,这其中就会有如下是屏幕固定相关的

        <PreferenceScreen                android:key="screen_pinning_settings"                android:title="@string/screen_pinning_title"                android:summary="@string/switch_off_text"                android:fragment="com.android.settings.ScreenPinningSettings"/>

我们可以在SecuritySettings读取该文件之后,调用WMS的hasNavigationBar来看有没有虚拟键(没有虚拟按键到时候不能取消屏幕固定),如果没有直接把Settings中这项置灰。

        // Append the rest of the settings        addPreferencesFromResource(R.xml.security_settings_misc);        IWindowManager windowManager = WindowManagerGlobal.getWindowManagerService();        try {            boolean is_screen_pining = windowManager.hasNavigationBar();            root.findPreference(KEY_SCREEN_PINNING).setEnabled(is_screen_pining);        } catch(RemoteException e) {            Log.e("SecuritySettings", "get window service remoteException.");        }






1 0
原创粉丝点击