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

Android-Android5.1屏幕固定功能(screen pinning)分析

程序员文章站 2022-05-23 14:21:37
...

转载自 https://blog.csdn.net/u013656135/article/details/49741659

 一、设置中开启屏幕固定:  

    此功能在设置-安全中开启,不清楚以往的版本中是否支持就有已经有了此功能,但是Android4.4设置中到时没有发现此项。在Android 5.0发现了此项设置。刚一看到此项设置,就心想:“这是什么鬼!”。设置中的代码在SecuritySettings.java和ScreenPinningSettings.java中,代码量不多,Preference XML文件是security_settings_misc.xml:                

 
  1. if (Settings.System.getInt(getContentResolver(),

  2. Settings.System.LOCK_TO_APP_ENABLED, 0) != 0) {

  3. root.findPreference(KEY_SCREEN_PINNING).setSummary(

  4. getResources().getString(R.string.switch_on_text));

  5. }

    看到上面代码后,到\android5.1\frameworks\base\core\java\android\provider\Settings.java找到了LOCK_TO_APP_ENABLED,然后就发现这货被hide了,意思就说,在独立应用中是不能去设置此项的值的:    

 
  1. /**

  2. * Whether lock-to-app will be triggered by long-press on recents.

  3. * @hide

  4. */

  5. public static final String LOCK_TO_APP_ENABLED = "lock_to_app_enabled";

    之后,本想查看下这货是怎么写进数据库的,纵所周知,provider settings里面的东西一般都会写进数据库,而settings.db的文件是这里被创建的:            

\android5.1\frameworks\base\packages\SettingsProvider\src\com\android\providers\settings\DatabaseHelper.java

     按照介个意思,我想应该是会在这里写进数据库啊,然后就在DatabaseHelper.java搜索LOCK_TO_APP_ENABLED,但是没有找到,只能说,它不是在这里写进数据库的,无奈之下,再度查看ScreenPinningSettings.java中的相关代码:      

 
  1. private void setLockToAppEnabled(boolean isEnabled) {

  2. Settings.System.putInt(getContentResolver(), Settings.System.LOCK_TO_APP_ENABLED,

  3. isEnabled ? 1 : 0);

  4. }

      Settings.System.putInt()方法有如下描述:      

 
  1. /**

  2. * Convenience function for updating a single settings value as an

  3. * integer. This will either create a new entry in the table if the

  4. * given name does not exist, or modify the value of the existing row

  5. * with that name. Note that internally setting values are always

  6. * stored as strings, so this function converts the given value to a

  7. * string before storing it.

  8. *

  9. * @param cr The ContentResolver to access.

  10. * @param name The name of the setting to modify.

  11. * @param value The new value for the setting.

  12. * @return true if the value was set, false on database errors

  13. */

  14. public static boolean putInt(ContentResolver cr, String name, int value) {

  15. return putIntForUser(cr, name, value, UserHandle.myUserId());

  16. }

     由此推断,settings.db数据库system table中本没有lock_to_app_enabled此项,而在开启screen pinning后,会向此表中写入lock_to_app_enabled的数据:    

     settings.db 在手机中的位置:/data/data/com.android.providers.settings/database/settings.db (需要root)。

    

    二、屏幕固定开启后视图的显示:

           在Android5.1 -Recents分析 中曾提到过screen pinning。从代码上看,screen pinning和 Recents绑定到了一块,效果图大致是这样的:

             Android-Android5.1屏幕固定功能(screen pinning)分析(图1)

    意思就说,在显示Recents的时候,如果screen pinning在设置中已开启,那么在Recents 视图中最上面的app 缩略图的右下角会有个图标。点击图标以后会出现如下提示界面:

      Android-Android5.1屏幕固定功能(screen pinning)分析(图2)

    此时点击“知道了”就会固定到Recents中显示的对应应用界面。通过Android5.1 -Recents分析 可知图1中的提示图标是在TaskView,其ID为lock_to_app_fab。既然响应点击事件,就可以在TaskView.java中直接找到onClick()方法:  

 
  1. @Override

  2. public void onClick(final View v) {

  3. final TaskView tv = this;

  4. final boolean delayViewClick = (v != this) && (v != mActionButtonView);

  5. if (delayViewClick) {

  6. // We purposely post the handler delayed to allow for the touch feedback to draw

  7. postDelayed(new Runnable() {

  8. @Override

  9. public void run() {

  10. if (Constants.DebugFlags.App.EnableTaskFiltering && v == mHeaderView.mApplicationIcon) {

  11. if (mCb != null) {

  12. mCb.onTaskViewAppIconClicked(tv);

  13. }

  14. } else if (v == mHeaderView.mDismissButton) {

  15. dismissTask();

  16. }

  17. }

  18. }, 125);

  19. } else {

  20. if (v == mActionButtonView) {

  21. // Reset the translation of the action button before we animate it out

  22. mActionButtonView.setTranslationZ(0f);

  23. }

  24. if (mCb != null) {

  25. mCb.onTaskViewClicked(tv, tv.getTask(), (v == mActionButtonView));

  26. }

  27. }

  28. }

         其中mActionButtonView就是响应点击事件的view。图2显示的view的布局为:screen_pinning_request_text_area.xml,其中Button ID:screen_pinning_ok_button就是图2中显示的“知道了”。这部分view 在ScreenPinningRequest.java中被inflate。  

 
  1. private void inflateView(boolean isLandscape) {

  2. // We only want this landscape orientation on <600dp, so rather than handle

  3. // resource overlay for -land and -sw600dp-land, just inflate this

  4. // other view for this single case.

  5. mLayout = (ViewGroup) View.inflate(getContext(), isLandscape

  6. ? R.layout.screen_pinning_request_land_phone : R.layout.screen_pinning_request,

  7. null);

  8. // Catch touches so they don't trigger cancel/activate, like outside does.

  9. mLayout.setClickable(true);

  10. ...

  11. ...

  12. }

  inflate视图,但是图2中中view是如何显示出来的呢?源码中是通过callback一层层的回调来实现的,前面提到过图1中的view是在TaskView中,TaskView有内部接口,在响应了view的onClick方法以后会调用TaskView类内部的callback:          

 
  1. if (mCb != null) {

  2. mCb.onTaskViewClicked(tv, tv.getTask(), (v == mActionButtonView));

  3. }

  而TaskStackView视图包含TaskView视图,并实现了TaskView内部的callback,并在此调用自己的callback:   

 
  1. @Override

  2. public void onTaskViewClicked(TaskView tv, Task task, boolean lockToTask) {

  3. // Cancel any doze triggers

  4. mUIDozeTrigger.stopDozing();

  5. if (mCb != null) {

  6. mCb.onTaskViewClicked(this, tv, mStack, task, lockToTask);

  7. }

  8. }

  而RecentsView视图又包含TaskStackView视图,并实现TaskStackView的接口,RecentsView在此调用自己callback(onScreenPinningRequest):  

 
  1. @Override

  2. public void onTaskViewClicked(final TaskStackView stackView, final TaskView tv,

  3. final TaskStack stack, final Task task, final boolean lockToTask) {

  4. // Notify any callbacks of the launching of a new task

  5. if (mCb != null) {

  6. mCb.onTaskViewClicked();

  7. }

  8. ...

  9. if (lockToTask) {

  10. animStartedListener = new ActivityOptions.OnAnimationStartedListener() {

  11. boolean mTriggered = false;

  12. @Override

  13. public void onAnimationStarted() {

  14. if (!mTriggered) {

  15. postDelayed(new Runnable() {

  16. @Override

  17. public void run() {

  18. mCb.onScreenPinningRequest();

  19. }

  20. }, 350);

  21. mTriggered = true;

  22. }

  23. }

  24. };

  25. }

  26. ...

  27.  
  28. }

  到这里,callback回调还没有完,RecentsView的 RecentsViewCallbacks 接口被RecentsActivity实现: 

 
  1. @Override

  2. public void onScreenPinningRequest() {

  3. if (mStatusBar != null) {

  4. mStatusBar.showScreenPinningRequest(false);

  5. }

  6. }

    直到这里callback回调才算基本结束,mStatusBar是PhoneStatusBar类的实例对象,其showScreenPinningRequest方法:    

 
  1. public void showScreenPinningRequest(boolean allowCancel) {

  2. mScreenPinningRequest.showPrompt(allowCancel);

  3. }

    ScreenPinningRequest.java的showPrompt()方法:    

 
  1. public void showPrompt(boolean allowCancel) {

  2. clearPrompt();

  3. mRequestWindow = new RequestWindowView(mContext, allowCancel);

  4. mRequestWindow.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);

  5. // show the confirmation

  6. WindowManager.LayoutParams lp = getWindowLayoutParams();

  7. mWindowManager.addView(mRequestWindow, lp);

  8. }

    到这里,图2中的视图在响应了图1视图中的onClick事件以后就显示出来了。

 

三、屏幕固定实现的功能:

    经过上面的分析可知,最终响应Button-screen_pinning_ok_button来实现屏幕固定的功能,代码自然在ScreenPinningRequest.java中:   

 
  1. @Override

  2. public void onClick(View v) {

  3. if (v.getId() == R.id.screen_pinning_ok_button || mRequestWindow == v) {

  4. try {

  5. ActivityManagerNative.getDefault().startLockTaskModeOnCurrent();

  6. } catch (RemoteException e) {}

  7. }

  8. clearPrompt();

  9. }

    其中ActivityManagerNative.getDefault() 相当于 ActivityManagerService,所以直接在ActivityManagerService.java中查找startLockTaskModeOnCurrent()方法:   

 
  1. @Override

  2. public void startLockTaskModeOnCurrent() throws RemoteException {

  3. enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS,

  4. "startLockTaskModeOnCurrent");

  5. long ident = Binder.clearCallingIdentity();

  6. try {

  7. ActivityRecord r = null;

  8. synchronized (this) {

  9. r = mStackSupervisor.topRunningActivityLocked();

  10. }

  11. startLockTaskMode(r.task);

  12. } finally {

  13. Binder.restoreCallingIdentity(ident);

  14. }

  15. }

  16. void startLockTaskMode(TaskRecord task) {

  17. final String pkg;

  18. synchronized (this) {

  19. pkg = task.intent.getComponent().getPackageName();

  20. }

  21. boolean isSystemInitiated = Binder.getCallingUid() == Process.SYSTEM_UID;

  22. if (!isSystemInitiated && !isLockTaskAuthorized(pkg)) {

  23. StatusBarManagerInternal statusBarManager = LocalServices.getService(

  24. StatusBarManagerInternal.class);

  25. if (statusBarManager != null) {

  26. statusBarManager.showScreenPinningRequest();

  27. }

  28. return;

  29. }

  30. long ident = Binder.clearCallingIdentity();

  31. try {

  32. synchronized (this) {

  33. // Since we lost lock on task, make sure it is still there.

  34. task = mStackSupervisor.anyTaskForIdLocked(task.taskId);

  35. if (task != null) {

  36. if (!isSystemInitiated

  37. && ((mStackSupervisor.getFocusedStack() == null)

  38. || (task != mStackSupervisor.getFocusedStack().topTask()))) {

  39. throw new IllegalArgumentException("Invalid task, not in foreground");

  40. }

  41. mStackSupervisor.setLockTaskModeLocked(task, !isSystemInitiated,

  42. "startLockTask");

  43. }

  44. }

  45. } finally {

  46. Binder.restoreCallingIdentity(ident);

  47. }

  48. }

    从代码中可看出,此功能的实现都管理activity 的stack 和 task。锁住stack 和 task 不让新的进来,就达到屏幕固定的目的,因为在这种情况下,不能为其他的activity准备stack和task。而取消此模式,有其对应的方法: 

 
  1. @Override

  2. public void stopLockTaskModeOnCurrent() throws RemoteException {

  3. enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS,

  4. "stopLockTaskModeOnCurrent");

  5. long ident = Binder.clearCallingIdentity();

  6. try {

  7. stopLockTaskMode();

  8. } finally {

  9. Binder.restoreCallingIdentity(ident);

  10. }

  11. }

 

四、在独立应用中屏幕固定模式又会怎样:

    首先,此功能是否支持在第三方应用里面实现呢?也许会考虑使用ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE)

    来看看是否有相关的方法,但是ActivityManager.java中的相关接口都是hide的,不能被第三方应用使用:    

 
  1. /**

  2. * @hide

  3. */

  4. public void startLockTaskMode(int taskId) {

  5. try {

  6. ActivityManagerNative.getDefault().startLockTaskMode(taskId);

  7. } catch (RemoteException e) {

  8. }

  9. }

  10. /**

  11. * @hide

  12. */

  13. public void stopLockTaskMode() {

  14. try {

  15. ActivityManagerNative.getDefault().stopLockTaskMode();

  16. } catch (RemoteException e) {

  17. }

  18. }

    虽然ActivityManager类不让使用,但是Activity.java中却提供了相关方法(需要API>=21):  

 
  1. public void startLockTask() {

  2. try {

  3. ActivityManagerNative.getDefault().startLockTaskMode(mToken);

  4. } catch (RemoteException e) {

  5. }

  6. }

  7.  
  8. public void stopLockTask() {

  9. try {

  10. ActivityManagerNative.getDefault().stopLockTaskMode();

  11. } catch (RemoteException e) {

  12. }

  13. }

    于是在模拟器上测试了一下:

    Android-Android5.1屏幕固定功能(screen pinning)分析

    这么看来,此功能算是支持第三方应用开启,并且还提供了一个放来来判断系统是否处于此模式:     

 
  1. /**

  2. * Return whether currently in lock task mode. When in this mode

  3. * no new tasks can be created or switched to.

  4. *

  5. * @see Activity#startLockTask()

  6. */

  7. public boolean isInLockTaskMode() {

  8. try {

  9. return ActivityManagerNative.getDefault().isInLockTaskMode();

  10. } catch (RemoteException e) {

  11. return false;

  12. }

  13. }

 

    而这也带来了思考的问题,当在设置中开启屏幕固定功能,并在Recents上固定某个应用的界面,那么这个应用的界面在onResume的时候是否需要使用isInLockTaskMode来做判断,从而做相应的处理? 这个就要看情况而定吧,我用自己前面瞎写的手电筒应用做测试,如果开启此模式,手电筒会出问题,这个跟我实现手电筒的代码有关系。

    问题又来了,在第三方应用中开启屏幕固定功能,提示界面又是如何显示出来的呢?这个就要回到前面提到的PhoneStatusBar.java,前面在Recents界面固定某个应用的界面是RecentsActivity中实现RecentsView内部接口并调用了PhoneStatusBar类中的showScreenPinningRequest(boolean allowCancel)方法。但是PhoneStatusBar类中还重写了一个父类的方法showScreenPinningRequest()。应用独立开启屏幕固定功能就会调用此方法:  

 
  1. @Override

  2. public void showScreenPinningRequest() {

  3. if (mKeyguardMonitor.isShowing()) {

  4. // Don't allow apps to trigger this from keyguard.

  5. return;

  6. }

  7. // Show screen pinning request, since this comes from an app, show 'no thanks', button.

  8. showScreenPinningRequest(true);

  9. }