Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> android6.0 固定屏幕功能

android6.0 固定屏幕功能

編輯:關於Android編程

可能大家看到這個標題不知道是什麼東西,我先說明下,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 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,這其中就會有如下是屏幕固定相關的

 

        

 

我們可以在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. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved