Android-Android5.1屏幕固定功能(screen pinning)分析
2015-11-09 17:36
811 查看
一、设置中开启屏幕固定:
此功能在设置-安全中开启,不清楚以往的版本中是否支持就有已经有了此功能,但是Android4.4设置中到时没有发现此项。在Android 5.0发现了此项设置。刚一看到此项设置,就心想:“这是什么鬼!”。设置中的代码在SecuritySettings.java和ScreenPinningSettings.java中,代码量不多,Preference XML文件是security_settings_misc.xml:
看到上面代码后,到\android5.1\frameworks\base\core\java\android\provider\Settings.java找到了LOCK_TO_APP_ENABLED,然后就发现这货被hide了,意思就说,在独立应用中是不能去设置此项的值的:
之后,本想查看下这货是怎么写进数据库的,纵所周知,provider settings里面的东西一般都会写进数据库,而settings.db的文件是这里被创建的:
按照介个意思,我想应该是会在这里写进数据库啊,然后就在DatabaseHelper.java搜索LOCK_TO_APP_ENABLED,但是没有找到,只能说,它不是在这里写进数据库的,无奈之下,再度查看ScreenPinningSettings.java中的相关代码:
Settings.System.putInt()方法有如下描述:
由此推断,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绑定到了一块,效果图大致是这样的:
(图1)
意思就说,在显示Recents的时候,如果screen pinning在设置中已开启,那么在Recents 视图中最上面的app 缩略图的右下角会有个图标。点击图标以后会出现如下提示界面:
(图2)
此时点击“知道了”就会固定到Recents中显示的对应应用界面。通过Android5.1 -Recents分析 可知图1中的提示图标是在TaskView,其ID为lock_to_app_fab。既然响应点击事件,就可以在TaskView.java中直接找到onClick()方法:
其中mActionButtonView就是响应点击事件的view。图2显示的view的布局为:screen_pinning_request_text_area.xml,其中Button
ID:screen_pinning_ok_button就是图2中显示的“知道了”。这部分view 在ScreenPinningRequest.java中被inflate。
inflate视图,但是图2中中view是如何显示出来的呢?源码中是通过callback一层层的回调来实现的,前面提到过图1中的view是在TaskView中,TaskView有内部接口,在响应了view的onClick方法以后会调用TaskView类内部的callback:
而TaskStackView视图包含TaskView视图,并实现了TaskView内部的callback,并在此调用自己的callback:
而RecentsView视图又包含TaskStackView视图,并实现TaskStackView的接口,RecentsView在此调用自己callback(onScreenPinningRequest):
到这里,callback回调还没有完,RecentsView的 RecentsViewCallbacks 接口被RecentsActivity实现:
直到这里callback回调才算基本结束,mStatusBar是PhoneStatusBar类的实例对象,其showScreenPinningRequest方法:
ScreenPinningRequest.java的showPrompt()方法:
到这里,图2中的视图在响应了图1视图中的onClick事件以后就显示出来了。
三、屏幕固定实现的功能:
经过上面的分析可知,最终响应Button-screen_pinning_ok_button来实现屏幕固定的功能,代码自然在ScreenPinningRequest.java中:
其中ActivityManagerNative.getDefault() 相当于 ActivityManagerService,所以直接在ActivityManagerService.java中查找startLockTaskModeOnCurrent()方法:
从代码中可看出,此功能的实现都管理activity 的stack 和 task。锁住stack 和 task 不让新的进来,就达到屏幕固定的目的,因为在这种情况下,不能为其他的activity准备stack和task。而取消此模式,有其对应的方法:
四、在独立应用中屏幕固定模式又会怎样:
首先,此功能是否支持在第三方应用里面实现呢?也许会考虑使用ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE)
来看看是否有相关的方法,但是ActivityManager.java中的相关接口都是hide的,不能被第三方应用使用:
虽然ActivityManager类不让使用,但是Activity.java中却提供了相关方法(需要API>=21):
于是在模拟器上测试了一下:
这么看来,此功能算是支持第三方应用开启,并且还提供了一个放来来判断系统是否处于此模式:
而这也带来了思考的问题,当在设置中开启屏幕固定功能,并在Recents上固定某个应用的界面,那么这个应用的界面在onResume的时候是否需要使用isInLockTaskMode来做判断,从而做相应的处理? 这个就要看情况而定吧,我用自己前面瞎写的手电筒应用做测试,如果开启此模式,手电筒会出问题,这个跟我实现手电筒的代码有关系。
问题又来了,在第三方应用中开启屏幕固定功能,提示界面又是如何显示出来的呢?这个就要回到前面提到的PhoneStatusBar.java,前面在Recents界面固定某个应用的界面是RecentsActivity中实现RecentsView内部接口并调用了PhoneStatusBar类中的showScreenPinningRequest(boolean allowCancel)方法。但是PhoneStatusBar类中还重写了一个父类的方法showScreenPinningRequest()。应用独立开启屏幕固定功能就会调用此方法:
此功能在设置-安全中开启,不清楚以往的版本中是否支持就有已经有了此功能,但是Android4.4设置中到时没有发现此项。在Android 5.0发现了此项设置。刚一看到此项设置,就心想:“这是什么鬼!”。设置中的代码在SecuritySettings.java和ScreenPinningSettings.java中,代码量不多,Preference XML文件是security_settings_misc.xml:
if (Settings.System.getInt(getContentResolver(), Settings.System.LOCK_TO_APP_ENABLED, 0) != 0) { root.findPreference(KEY_SCREEN_PINNING).setSummary( getResources().getString(R.string.switch_on_text)); }
看到上面代码后,到\android5.1\frameworks\base\core\java\android\provider\Settings.java找到了LOCK_TO_APP_ENABLED,然后就发现这货被hide了,意思就说,在独立应用中是不能去设置此项的值的:
/** * Whether lock-to-app will be triggered by long-press on recents. * @hide */ 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中的相关代码:
private void setLockToAppEnabled(boolean isEnabled) { Settings.System.putInt(getContentResolver(), Settings.System.LOCK_TO_APP_ENABLED, isEnabled ? 1 : 0); }
Settings.System.putInt()方法有如下描述:
/** * Convenience function for updating a single settings value as an * integer. This will either create a new entry in the table if the * given name does not exist, or modify the value of the existing row * with that name. Note that internally setting values are always * stored as strings, so this function converts the given value to a * string before storing it. * * @param cr The ContentResolver to access. * @param name The name of the setting to modify. * @param value The new value for the setting. * @return true if the value was set, false on database errors */ public static boolean putInt(ContentResolver cr, String name, int value) { return putIntForUser(cr, name, value, UserHandle.myUserId()); }
由此推断,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绑定到了一块,效果图大致是这样的:
(图1)
意思就说,在显示Recents的时候,如果screen pinning在设置中已开启,那么在Recents 视图中最上面的app 缩略图的右下角会有个图标。点击图标以后会出现如下提示界面:
(图2)
此时点击“知道了”就会固定到Recents中显示的对应应用界面。通过Android5.1 -Recents分析 可知图1中的提示图标是在TaskView,其ID为lock_to_app_fab。既然响应点击事件,就可以在TaskView.java中直接找到onClick()方法:
@Override public void onClick(final View v) { final TaskView tv = this; final boolean delayViewClick = (v != this) && (v != mActionButtonView); if (delayViewClick) { // We purposely post the handler delayed to allow for the touch feedback to draw postDelayed(new Runnable() { @Override public void run() { if (Constants.DebugFlags.App.EnableTaskFiltering && v == mHeaderView.mApplicationIcon) { if (mCb != null) { mCb.onTaskViewAppIconClicked(tv); } } else if (v == mHeaderView.mDismissButton) { dismissTask(); } } }, 125); } else { if (v == mActionButtonView) { // Reset the translation of the action button before we animate it out mActionButtonView.setTranslationZ(0f); } if (mCb != null) { mCb.onTaskViewClicked(tv, tv.getTask(), (v == mActionButtonView)); } } }
其中mActionButtonView就是响应点击事件的view。图2显示的view的布局为:screen_pinning_request_text_area.xml,其中Button
ID:screen_pinning_ok_button就是图2中显示的“知道了”。这部分view 在ScreenPinningRequest.java中被inflate。
private void inflateView(boolean isLandscape) { // We only want this landscape orientation on <600dp, so rather than handle // resource overlay for -land and -sw600dp-land, just inflate this // other view for this single case. mLayout = (ViewGroup) View.inflate(getContext(), isLandscape ? R.layout.screen_pinning_request_land_phone : R.layout.screen_pinning_request, null); // Catch touches so they don't trigger cancel/activate, like outside does. mLayout.setClickable(true); ... ... }
inflate视图,但是图2中中view是如何显示出来的呢?源码中是通过callback一层层的回调来实现的,前面提到过图1中的view是在TaskView中,TaskView有内部接口,在响应了view的onClick方法以后会调用TaskView类内部的callback:
if (mCb != null) { mCb.onTaskViewClicked(tv, tv.getTask(), (v == mActionButtonView)); }
而TaskStackView视图包含TaskView视图,并实现了TaskView内部的callback,并在此调用自己的callback:
@Override public void onTaskViewClicked(TaskView tv, Task task, boolean lockToTask) { // Cancel any doze triggers mUIDozeTrigger.stopDozing(); if (mCb != null) { mCb.onTaskViewClicked(this, tv, mStack, task, lockToTask); } }
而RecentsView视图又包含TaskStackView视图,并实现TaskStackView的接口,RecentsView在此调用自己callback(onScreenPinningRequest):
@Override public void onTaskViewClicked(final TaskStackView stackView, final TaskView tv, final TaskStack stack, final Task task, final boolean lockToTask) { // Notify any callbacks of the launching of a new task if (mCb != null) { mCb.onTaskViewClicked(); } ... if (lockToTask) { animStartedListener = new ActivityOptions.OnAnimationStartedListener() { boolean mTriggered = false; @Override public void onAnimationStarted() { if (!mTriggered) { postDelayed(new Runnable() { @Override public void run() { mCb.onScreenPinningRequest(); } }, 350); mTriggered = true; } } }; } ... }
到这里,callback回调还没有完,RecentsView的 RecentsViewCallbacks 接口被RecentsActivity实现:
@Override public void onScreenPinningRequest() { if (mStatusBar != null) { mStatusBar.showScreenPinningRequest(false); } }
直到这里callback回调才算基本结束,mStatusBar是PhoneStatusBar类的实例对象,其showScreenPinningRequest方法:
public void showScreenPinningRequest(boolean allowCancel) { mScreenPinningRequest.showPrompt(allowCancel); }
ScreenPinningRequest.java的showPrompt()方法:
public void showPrompt(boolean allowCancel) { clearPrompt(); mRequestWindow = new RequestWindowView(mContext, allowCancel); mRequestWindow.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE); // show the confirmation WindowManager.LayoutParams lp = getWindowLayoutParams(); mWindowManager.addView(mRequestWindow, lp); }
到这里,图2中的视图在响应了图1视图中的onClick事件以后就显示出来了。
三、屏幕固定实现的功能:
经过上面的分析可知,最终响应Button-screen_pinning_ok_button来实现屏幕固定的功能,代码自然在ScreenPinningRequest.java中:
@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(); }
其中ActivityManagerNative.getDefault() 相当于 ActivityManagerService,所以直接在ActivityManagerService.java中查找startLockTaskModeOnCurrent()方法:
@Override public void startLockTaskModeOnCurrent() throws RemoteException { enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS, "startLockTaskModeOnCurrent"); long ident = Binder.clearCallingIdentity(); try { ActivityRecord r = null; synchronized (this) { r = mStackSupervisor.topRunningActivityLocked(); } startLockTaskMode(r.task); } finally { Binder.restoreCallingIdentity(ident); } } void startLockTaskMode(TaskRecord task) { final String pkg; synchronized (this) { pkg = task.intent.getComponent().getPackageName(); } boolean isSystemInitiated = Binder.getCallingUid() == Process.SYSTEM_UID; if (!isSystemInitiated && !isLockTaskAuthorized(pkg)) { StatusBarManagerInternal statusBarManager = LocalServices.getService( StatusBarManagerInternal.class); if (statusBarManager != null) { statusBarManager.showScreenPinningRequest(); } return; } long ident = Binder.clearCallingIdentity(); try { synchronized (this) { // Since we lost lock on task, make sure it is still there. task = mStackSupervisor.anyTaskForIdLocked(task.taskId); if (task != null) { if (!isSystemInitiated && ((mStackSupervisor.getFocusedStack() == null) || (task != mStackSupervisor.getFocusedStack().topTask()))) { throw new IllegalArgumentException("Invalid task, not in foreground"); } mStackSupervisor.setLockTaskModeLocked(task, !isSystemInitiated, "startLockTask"); } } } finally { Binder.restoreCallingIdentity(ident); } }
从代码中可看出,此功能的实现都管理activity 的stack 和 task。锁住stack 和 task 不让新的进来,就达到屏幕固定的目的,因为在这种情况下,不能为其他的activity准备stack和task。而取消此模式,有其对应的方法:
@Override public void stopLockTaskModeOnCurrent() throws RemoteException { enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS, "stopLockTaskModeOnCurrent"); long ident = Binder.clearCallingIdentity(); try { stopLockTaskMode(); } finally { Binder.restoreCallingIdentity(ident); } }
四、在独立应用中屏幕固定模式又会怎样:
首先,此功能是否支持在第三方应用里面实现呢?也许会考虑使用ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE)
来看看是否有相关的方法,但是ActivityManager.java中的相关接口都是hide的,不能被第三方应用使用:
/** * @hide */ public void startLockTaskMode(int taskId) { try { ActivityManagerNative.getDefault().startLockTaskMode(taskId); } catch (RemoteException e) { } } /** * @hide */ public void stopLockTaskMode() { try { ActivityManagerNative.getDefault().stopLockTaskMode(); } catch (RemoteException e) { } }
虽然ActivityManager类不让使用,但是Activity.java中却提供了相关方法(需要API>=21):
public void startLockTask() { try { ActivityManagerNative.getDefault().startLockTaskMode(mToken); } catch (RemoteException e) { } } public void stopLockTask() { try { ActivityManagerNative.getDefault().stopLockTaskMode(); } catch (RemoteException e) { } }
于是在模拟器上测试了一下:
这么看来,此功能算是支持第三方应用开启,并且还提供了一个放来来判断系统是否处于此模式:
/** * Return whether currently in lock task mode. When in this mode * no new tasks can be created or switched to. * * @see Activity#startLockTask() */ public boolean isInLockTaskMode() { try { return ActivityManagerNative.getDefault().isInLockTaskMode(); } catch (RemoteException e) { return false; } }
而这也带来了思考的问题,当在设置中开启屏幕固定功能,并在Recents上固定某个应用的界面,那么这个应用的界面在onResume的时候是否需要使用isInLockTaskMode来做判断,从而做相应的处理? 这个就要看情况而定吧,我用自己前面瞎写的手电筒应用做测试,如果开启此模式,手电筒会出问题,这个跟我实现手电筒的代码有关系。
问题又来了,在第三方应用中开启屏幕固定功能,提示界面又是如何显示出来的呢?这个就要回到前面提到的PhoneStatusBar.java,前面在Recents界面固定某个应用的界面是RecentsActivity中实现RecentsView内部接口并调用了PhoneStatusBar类中的showScreenPinningRequest(boolean allowCancel)方法。但是PhoneStatusBar类中还重写了一个父类的方法showScreenPinningRequest()。应用独立开启屏幕固定功能就会调用此方法:
@Override public void showScreenPinningRequest() { if (mKeyguardMonitor.isShowing()) { // Don't allow apps to trigger this from keyguard. return; } // Show screen pinning request, since this comes from an app, show 'no thanks', button. showScreenPinningRequest(true); }
相关文章推荐
- MAC OS 下使用Android Studio获取开发版和发布版SHA1证书
- Android 内存分析
- Android Volley框架的使用(三)
- 解决Android开发中,ActiveAndroid和Gson同时使用,对象序列化失败的问题
- 《ArcGIS Runtime SDK for Android开发笔记》——数据制作篇:紧凑型切片制作(Server缓存切片)
- android wear 真机截图
- Android——支付宝SDK调用(移动应用接入支付宝支付,提供安全、便捷的支付能力)
- Android Volley框架的使用(二)
- android中getMeasureWidth()和getWidth()方法的区别
- Android Sensor感应器介绍(三)获取用户移动方向,指南针原理
- android——EventBus(一)
- Android 隐藏软键盘的方式
- Android 自动换行的LinearLayout
- ListView优化为何ViewHolder用static类
- Android Volley 框架的使用(一)
- Android 在 Service 启动 Activity 和 Dialog
- Android图片相关的操作
- android中sharedPreferences的用法
- Android中Parcelable接口用法
- 手把手教SlidingMenu在Android Studio使用