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<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.");
}

感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!

(0)

相关推荐

  • Android判断NavigationBar是否显示的方法(获取屏幕真实的高度)

    有些时候,我们需要知道当前手机上是否显示了NavigationBar,也就是屏幕底部的虚拟按键. 比如截屏的时候,要获取屏幕的高度,必须包括NavigationBar的高度. 试过网上的多种方法,但是对那种可以通过手势来显示/隐藏的NavigationBar没办法,最后终于找到了一个好办法,看代码: public boolean isNavigationBarShow(){ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_M

  • Android编程实现将tab选项卡放在屏幕底部的方法

    本文实例讲述了Android编程实现将tab选项卡放在屏幕底部的方法.分享给大家供大家参考,具体如下: 今天写Tab的时候由于TAB的跳转问题去查资料,倒反而发现更有趣的问题,就是如何将TAB放置在屏幕的底端.有点类似IPhone里的布局了,呵呵-(其实后来发现这个应该不是用TAB做的,而是ButtonBar做出来的吧,或者是他重写了TAB?总之不是简单地将TAB放置底端了). 要放置底端,那么Android自带的例程是不可以做到的(例程参看development-ApiDemo).先需要写一个

  • Android编程实现ActionBar的home图标动画切换效果

    本文实例讲述了Android编程实现ActionBar的home图标动画切换效果.分享给大家供大家参考,具体如下: Material Design中一个重要特性是侧滑菜单 展开/关闭 时,ActionBar上的home图标也动画切换.本例要实现的正是这个效果,如图所示: 实现这个效果仅需几步: 1.首先,该页面的布局是一个DrawerLayout,代码如下: <android.support.v4.widget.DrawerLayout xmlns:android="http://sche

  • Android ToolBar整合实例使用方法详解

    最近做项目中遇到ToolBar因为不同的界面toobar不同为了描述统一的风格.相信大家也非常清楚,大多数ToolBar包括以下几个方面 左标题 左边题颜色 左标题图标等 标题 标题颜色 右标题 右标题颜色 右标题图标 ToolBar标题 ToolBar颜色 ToolBar图标 ToolBar子标题 ToolBar子标题 ToolBar子标题颜色 再看一下淘宝以及其他appToolBar样式界面 下面看下我自定义的CustomeToolBar继承原生ToolBar package com.ldm

  • Android UI控件RatingBar实现自定义星星评分效果

    本文实例为大家分享了Android RatingBar星星评分效果的具体代码,供大家参考,具体内容如下 继承关系 AppCompatRatingBar 效果图 xml <RatingBar style="@android:style/Widget.DeviceDefault.RatingBar.Small" android:layout_width="wrap_content" android:layout_height="wrap_content&

  • Android编程实现将ButtonBar放在屏幕底部的方法

    本文实例讲述了Android编程实现将ButtonBar放在屏幕底部的方法.分享给大家供大家参考,具体如下: 前面一篇<Android编程实现将tab选项卡放在屏幕底部的方法>提到ButtonBar的方式写底部button,试了试,看起来外观貌似比Tab好看,不过恐怕没有Tab管理Activity方便吧,毕竟一 个Tab就是一个Activity,但是这样用Button的话,却并不如此,所以这样的涉及可能虽然好看点,但是管理起来却是相当麻烦.那么暂且把对 activity的管理放在一边,只看界面

  • Android编程之ProgressBar圆形进度条颜色设置方法

    本文实例讲述了Android ProgressBar圆形进度条颜色设置方法.分享给大家供大家参考,具体如下: 你是不是还在为设置进度条的颜色而烦恼呢--别着急,且看如下如何解决. ProgressBar分圆形进度条和水平进度条 我这里就分享下如何设置圆形进度条的颜色吧,希望对大家会有帮助. 源码如下: 布局文件代码: <ProgressBar android:id="@+id/progressbar" android:layout_width="wrap_content

  • Android Animation实战之屏幕底部弹出PopupWindow

    Android动画的一个实战内容,从屏幕底部滑动弹出PopupWindow. 相信这种效果大家在很多APP上都遇到过,比如需要拍照或者从SD卡选择图片,再比如需要分享某些东西时,大多会采用这么一种效果: 那这种效果如何实现呢? 我们仿写一个这种效果的实例吧: 1)我们首先定义一下,弹出窗口的页面布局组件:take_photo_pop.xml <?xml version="1.0" encoding="utf-8"?> <RelativeLayout

  • Android自定义谷歌风格ProgressBar

    本文实例为大家分享了谷歌风格ProgressBar的具体代码,供大家参考,具体内容如下 具体代码 package zms.demo.colorprogress; import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.draw

  • android 选项卡(TabHost)如何放置在屏幕的底部

    今天写Tab的时候由于TAB的跳转问题去查资料,倒反而发现更有趣的问题,就是如何将TAB放置在屏幕的底端. 复制代码 代码如下: <?xml version="1.0" encoding="utf-8"?> <TabHost xmlns:android="http://schemas.android.com/apk/res/android" android:id="@android:id/tabhost" a

  • Android动态修改ToolBar的Menu菜单示例

    Android动态修改ToolBar的Menu菜单 效果图 实现 实现很简单,就是一个具有3个Action的Menu,在我们滑动到不同状态的时候,把对应的Action隐藏了. 开始上货 Menu Menu下添加3个Item <menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xml

  • Android仿淘宝view滑动至屏幕顶部会一直停留在顶部的位置

    在刚刚完成的项目中,在一个页面中,用户体验师提出引用户操作的入住按钮要一直保留在页面当中,不管页面能滚动多长都得停留在页面的可视区域.最终实现效果如下图所示:   如图中的红色框中的view始终会停留在页面中,如果滑动至页面的顶部,会一直保留在顶部. 下面来说下具体的实现思路: 思路:其实整个页面当中一共有两个视觉效果一样的View,通过滑动的位置来进行View的隐藏和显示来达到这种效果.整个页面的在上下滑动的过程中可以总结为两个状态,状态A(如图1所示),view2在可视区域内时,view1不

随机推荐