android6.0 固定屏幕功能
來源:程序員人生 發布時間:2016-11-19 14:45:53 閱讀次數:2525次
可能大家看到這個標題不知道是甚么東西,我先說明下,android6.0在設置->安全->屏幕固定開啟后,然后再長按home鍵出現最近的幾個Activity可以選擇1個圖釘按鈕就開啟了屏幕固定功能。
屏幕固定開啟后,屏幕只能固定在設定的Task上的Activity切換。
1、設置固定屏幕
我們先來看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,第1次mLockTaskModeTasks為空,會發送1個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 == ⑴) {
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;
2、固定屏幕后Activity啟動流程
在固定屏幕后,如果我們啟動其他TaskRecord的Activity是不能啟動的,我們來看下這個原理。在startActivityUncheckedLocked函數中會調用isLockTaskModeViolation函數來判斷是不是進1步的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函數,在新建1個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();
3、取消固定屏幕
最后我們再來看看取消固定屏幕,取消屏幕會在PhoneStatusBar中取消,但是1定是要有虛擬鍵,原生就是這么設定的。最后調用了AMS的stopLockTaskModeOnCurrent函數。這個函數主要是調用了stopLockTaskMode函數,這個函數中主要是調用了ActivityStackSupervisor的setLockTaskModeLocked函數,之前在固定屏幕時也是調用了這個函數,但是這里我們仔細看,其第1個參數為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(1般也就是下個屏幕鎖定的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;
}
4、沒有虛擬鍵如何取消屏幕固定
前面說過如果沒有虛擬鍵就不能取消屏幕固定了,我們說下幾種方式
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.");
}
生活不易,碼農辛苦
如果您覺得本網站對您的學習有所幫助,可以手機掃描二維碼進行捐贈