欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android6.0 固定屏幕功能实现方法及实例

程序员文章站 2024-02-03 10:52:52
android 固定屏幕功能 可能大家看到这个标题不知道是什么东西,我先说明下,android6.0在设置->安全->屏幕固定开启后,然后再长按home键出现...

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

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