Android之Lollipop DevicePolicyManager学习(下)

转载:http://blog.csdn.net/guiyu_1985/article/details/42968781

3.      如何在主账户与被管理者账户之间做数据通信。

a)        什么是userID

刚才提到,Lollipop用来区分主账户与被管理账户的其实是一个int型数值userID。

从UserHandler.class可以看到,这个userID是通过对uid作整除得到的:

public static final int PER_USER_RANGE =100000;

/** *Returns the user id for a given uid. *@hide */  public static final int getUserId(int uid) {  if (MU_ENABLED) {  return uid / PER_USER_RANGE;  }else {  return 0;  }  
}  
所以100000以内的uid对应的userID都是0,而超过这个数值的再取其整除结果。注意,这个只是Google为了辨识主账户与被管理账户所做的设计,并不是Unix底层带上来的参数。


而这个userID的作用刚才也提到了。在service进程对应的方法里会进行参数校验,一般来说,只有系统应用才能调用一些涉及到其他profile的方法。

b)        两个账户之前通信的先决条件

由于Profile之间数据通信的相互隔离,导致任何一个Profile中的消息发送只能被自己Profile中的组件所捕获。这样一来,虽然从根本上解决了两个Profile之间因为数据交流所可能产生的隐私暴露的问题,但是也为我们的数据共享带来了不便。

 

当然,Google也考虑了这方面的问题,通过一个授权处理方法addCrossProfileIntentFilter(),指定一个用于处理对应消息的Intentfilter,既可以让被管理者账户的消息可以透传到主账户,也可以在被管理者账户中接收到主账户的消息。


其中的参数FLAG_MANAGED_CAN_ACCESS_PARENT对应前者, FLAG_PARENT_CAN_ACCESS_MANAGED 对应后者

c)        验证可行的通信方式

Android常见的组件之间通信的方式无外乎Intent,通过Intent我们可以启动Activity,Service或者是进行Broadcast等。

但是在两个Profile之间进行组件的启动,我只成功尝试了startActivity一种……

 

先说startService。Android5.0之后,Google对于startService限制更加严格,已经不允许以隐式Intent的方式启动一个service,不管它是不是本进程的。虽然我在建立Intent对象的同时既指定了service class,也指定了对应的action,但是通过这个action建立的intentfilter仍然无法像Activity那样被其他Profile对应的Service组件捕获。


而Broadcast也有同样的问题,无论是静态注册的还是动态注册,都无法接收到其他Profile发出的广播信息。

这个实在非常奇怪,如果有人找到了解决的办法务必给我留言,多谢。

 

至于说通过startActivity的方式来透传消息,有人可能认为这会造成设计上的不美观,因为跳转到其他Profile相关应用都会首先展现一个Activity。这个其实可以解决,在Manifest中对这个跳转用的activity做一些调整:

    <activity  android:name=".ui.PackageEnabledActivity"  <strong>android:theme="@android:style/Theme.NoDisplay</strong>">  <intent-filter>  …  </intent-filter>  </activity>  

就可以了,所显示的Activity完全被隐藏。之后通过这个Activity在启动此应用所在的Profile的其他组件,就没有任何的问题。

当两边的通信方式确立了之后,可能还存在一个有趣的问题,那就是如何只让某些Intent透传到其他Profile而不被本Profile的同名组件所捕获

说起来有点绕,举个简单的例子就明白了。我们现在知道,当android系统中已经建立被管理者账户时,一些应用既可以存在于主账户侧,又可以在被管理账户中有一个同名的拷贝。那么问题来了,这些应用发给自身某些组件的消息,比如说启动某个Activity的Intent,如果被允许透传的话,两边Profile的同名应用都会接收到这个Intent,而且会启动可以处理该Intent的应用列表,就像这样:

那么有没有办法只让这个消息传到其他的Profile中,而本Profile的组件不做处理?

 

其实很好解决,不需要而且也不可能通过Intent的标志位来处理,因为这是完全相同的两个镜像应用。解决这个问题的办法是禁用当前Profile中的这个组件就可以了:

public static void disableCurrentProfileComponent(Context context, Class component, PackageManagerpm) {  final ComponentName activity = newComponentName(context, component);  pm.setComponentEnabledSetting(activity,  PackageManager.COMPONENT_ENABLED_STATE_DISABLED,  PackageManager.DONT_KILL_APP); 

禁用了当前Profile的这个组件,那么自然消息只能被对面Profile的同名组件来处理。

 

PS:当然,还有一个更简单的方法,就是利用PackageManager.SKIP_CURRENT_PROFILE标志位来禁止在本Profile内的使用,譬如:

    pm.addCrossProfileIntentFilter(callEmergency,managedProfileUserId, parentUserId,  PackageManager.SKIP_CURRENT_PROFILE);  

d)        账户之间的大量数据传输

解决了两个Profile之间消息传输的方式之后,最后来看如何携带大量数据。


这个问题其实不难解决,因为即使Profile之间数据区相互独立,但是Intent本身是可以通过Bundle来携带键值对的。只要Intent能够传过去,自然也能在对应的Activity组件中解析出Bundle数据来。


但是一旦要透传某些文件类的数据,比如说图片或音乐,或者说Profile双方需要共同维护一个数据库,比如一个联系人库。这个时候,单靠Bundle就很难完成工作。


以,Profile之间的数据交互不能仅限于键值对的方式,以往的文件类型和数据库类型的共享仍然要走通才可以。

File类型的数据共享

Google的帮助文档中提到了用于共享数据文件的方法,这是通过FileProvider库提供的方法来完成的操作。具体的思路就是:

1)  将待传输的文件ContentUri通过FileProvider.getUriForFile()取出来。

2)  把ContentUri与Type通过setDataAndType()加载到Intent中。

3)  一定要在Intent中加上这个Flag——Intent.FLAG_GRANT_READ_URI_PERMISSION,这个Flag决定了Receiver是否具有这个Uri的临时访问权限。这点非常重要。

4)  startActivity成功之后,通过getFileDescriptor()方法得到待传输文件的文件描述符,之后解析出这个文件即可。


File类型文件传输的难点并不是如何从Uri中解析文件,而在于Intent传输过程。我查阅的大量资料中都建议在文件的ContentUri获取之后,通过grantUriPermission()赋予其对应的读写权限,但是这个方法是不成的,只有在Intent中加上对应Flag才行。


数据库类型共享

虽然在Google的帮助文档中没有说明不同的Profile可以共享ContentProvider,但是通过文件类型的数据共享可以看出,从原理上说ContentProvider也应是可以共享的,因为FileProvider正式ContentP的一个子类。


关于ContentProvider的共享我走了点弯路,先把解决问题的要点说出来:

使用ContentProvider时我们都会维护一个static常量CONTENT_URI,这个常量一般是由几部分拼成的:

    //Content Url  lic static final Uri CONTENT_URI =Uri.parse("content://" + AUTHORITY + "/item");  

通常,需要使用数据库的其他组件直接解析这个Uri就能得到db文件的确切地址,使用对应的方法就能读写数据库文件。


但是在跨Profile操作时不能这么做。因为如果直接解析这个常量,得到的只是db文件的相对存储地址而已,比如说同样将数据库保存在应用内部,主Profile可能是/data/data/companyName/databases/*.db,但在被管理Profile里,则变成了/data/user/11/companyName/databases/*.db。


所以即使我们知道db文件的ContentUri,也必须通过Intent携带上述临时访问权限(Intent.FLAG_GRANT_READ_URI_PERMISSION)发到其他Profile的组件中去。在对方的环境里解析出正确的db地址来。


至于ContentProvider其他的共享细节与FileProvider无异。只是query数据的时候,记得使用我们Intent携带的Uri而不要用static常量直接解析。

 

到此为止,AP与MP之间的通信可以由我们自己完全控制,哪些消息可以通过,哪些消息会被禁止都由我们自己来界定。接下来说说被管理者账户中的那些应用都可以做哪些操作。

4.      如何对MP账户中的应用进行限制

安装于MP账户中的应用,可以从两个方面进行限制。

 

一个是账户使用者层面的限制。DevicePlicyManager类提供了一组用来限制被管理者账户某些功能的方法addUserRestriction()/clearUserRestriction(),通过给定的key来限制对应账户的某些功能。


值得注意的是,这原本不是什么新功能,为了改善JB多用户功能的体验Google在4.3就添加了这个Restrict Profile功能。但是当时的情形是,平板的使用者在主账户中对访客账户做某些限制,当平板的使用者切换到一个访客账户时,这些功能就不能再被使用了。而现在的情况是,被管理者账户与主账户同处于一个Launch里,可以对被管理者账户进行限制但不应该影响到主账户的同样功能。

这个功能比较坑,以限制拨打电话功能为例。如果我不希望访客账户或者被管理者账户的应用拨打电话,那么势必要在MP账户下通过以下方法禁止拨电话功能:

<p>myDeviceManaged.addUserRestriction(myDeviceName,UserManager. DISALLOW_OUTGOING_CALLS)</p>  

注意到Android检查这个disallow标志是在CallActivity的processOutgoingCallIntent方法中进行的:

    privatevoid processOutgoingCallIntent(Intent intent) {  ….  if(userManager.hasUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS)  &&!TelephonyUtil.shouldProcessAsEmergency(this, handle)) {  // Only emergency calls are allowedfor users with the DISALLOW_OUTGOING_CALLS  // restriction.  …  }  }  

唤起这个Activity的是Intent.ACTION_CALL,而Google在CrossProfileIntentFiltersHelper中自作主张的为ACTION_CALL添加了SKIP_CURRENT_PROFILE的条件:

    publicstatic void setFilters(PackageManager pm, int parentUserId, intmanagedProfileUserId) {  …  IntentFilter callVoicemail = new IntentFilter();  callVoicemail.addAction(Intent.ACTION_DIAL);  callVoicemail.addAction(Intent.ACTION_CALL);  callVoicemail.addAction(Intent.ACTION_VIEW);  callVoicemail.addCategory(Intent.CATEGORY_DEFAULT);  callVoicemail.addCategory(Intent.CATEGORY_BROWSABLE);  callVoicemail.addDataScheme("voicemail");  pm.addCrossProfileIntentFilter(callVoicemail, managedProfileUserId,parentUserId,  PackageManager.SKIP_CURRENT_PROFILE);  …  IntentFilter smsMms = new IntentFilter();  smsMms.addAction(Intent.ACTION_VIEW);  smsMms.addAction(Intent.ACTION_SENDTO);  smsMms.addCategory(Intent.CATEGORY_DEFAULT);  smsMms.addCategory(Intent.CATEGORY_BROWSABLE);  smsMms.addDataScheme("sms");  smsMms.addDataScheme("smsto");  smsMms.addDataScheme("mms");  smsMms.addDataScheme("mmsto");  pm.addCrossProfileIntentFilter(smsMms, managedProfileUserId,parentUserId,  PackageManager.SKIP_CURRENT_PROFILE);  …  }  
导致这个Activity实际上调用的是AP账户中的那个,而我们所做的限制在AP中并不生效。


最终的结论就是,对账户所做的限制,也只有在本账户内执行的有效,实际调用主账户完成的操作并不能实现。

 

另一个则是应用层面的限制。DevicePolicyManager类同样提供了一组用来限制被管理者账户中具体应用的某些功能的方法setApplicationRestrictions()/getApplicationRestrictions(),该方法是通过指定具体的应用包名,以及一组用于限制应用功能的Bundle串来限制具体的应用功能。


可以看到UserManagerService的实现方法:

    public voidsetApplicationRestrictions(String packageName, Bundle restrictions,  int userId) {  if(UserHandle.getCallingUserId() != userId  || !UserHandle.isSameApp(Binder.getCallingUid(),getUidForPackage(packageName))) {  checkManageUsersPermission("Only system can set restrictions forother users/apps");  }  synchronized(mPackagesLock) {  if (restrictions == null|| restrictions.isEmpty()) {  cleanAppRestrictionsForPackage(packageName, userId);  } else {  // Write therestrictions to XML  writeApplicationRestrictionsLocked(packageName, restrictions, userId);  }  }  if(isPackageInstalled(packageName, userId)) {  // Notify package ofchanges via an intent - only sent to explicitly registered receivers.  Intent changeIntent =new Intent(Intent.ACTION_APPLICATION_RESTRICTIONS_CHANGED);  changeIntent.setPackage(packageName);  changeIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);  mContext.sendBroadcastAsUser(changeIntent, new UserHandle(userId));  }  

Google将限制的功能以及对应包名注册到一个xml文件中,然后重新启动以限制功能的方式重新唤起这个组件,这个组件在启动之后会载入用以限制功能的xml,实现限制具体功能的目的。


这个功能出发点本身是非常好的,因为作为被管理者账户中的某个单独应用,很可能存在某些特定的功能需求,比如说不允许使用某些应用特定功能(例如内购),或者是必须打开默认的访问页面等。这些功能的实现都有赖于具体的限制方法。但实际上,这个功能又比较难以完成。原因有两个。


首先,用于限制应用具体功能的Bundle字串是如何获取的。根据Google官方的参考demoBasicManagedProfile可以了解到,Google的系统应用Chrome是如何进行定制的,但是反过来作为非系统层面的开发人员,你该如何获取Google系统应用具体支持的定制功能串呢?在没有官方文档的前提下,我想只能通过反编译这些应用,通过源码才能找到具体的功能字串名,以及该如何修改这些功能的方法。


再者,Google系统应用之所以能够通过这类Bundle键值对修改具体的功能,前提是它已经预留好了接口给开发者,让我们能够通过setApplicationRestrictions()方法修改具体的应用。如果是没有预留这些接口的第三方应用,则根本不可能完成这类功能。

所以如果希望对MP账户中应用进行限制,目前看起来行之有效的只有对Google的系统应用进行具体功能限制,而对第三方应用而言,只能在账户层面上做一些限制而已。

 

最后欢迎所有希望了解DevicePolicyManager的人给我留言,我们可以一块讨论并学习这部分功能。

参考代码与本项目源码

1.      参考代码

Google官方demo:

BasicManagedProfile

https://github.com/googlesamples/android-BasicManagedProfile.git

 

AppRestrictionEnforcer

https://github.com/googlesamples/android-AppRestrictionEnforcer.git

 

AppRestrictionSchema

https://github.com/googlesamples/android-AppRestrictionSchema.git

2.      本人测试代码

DevicePolicyTest

https://github.com/guiyu/DevicePolicyTest.git



/********************************************转载请注明来源***********************************/


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/294104.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

详解:从Greenplum、Hadoop到现在的阿里大数据技术

对于企业来说&#xff0c;但是到底云计算是什么呢&#xff1f;相信很多企业都有这样的困惑&#xff0c;让我们一起回到这个原始的起点探讨究竟什么是云计算&#xff1f;云计算对于企业而言到底意味什么&#xff1f;云计算的三条发展路径及三种落地形态 当回到最初的起点再审视云…

技术分享 | 【构建服务端SDK】之连接中心统一调用SDK

源宝导读&#xff1a;微服务架构与传统的单体式方案的最大不同是微服务将应用的核心功能拆分成多项服务。每项服务可以单独构建和部署。服务之间需要互相通信。假设服务间每次通信都需要在调用方编码操作&#xff0c;那么必定会增加很大的工作量&#xff0c;并且造成代码冗余并…

Tcp连接的七次握手浅析

连接的三次握手 客户端向服务器发送SYN请求 服务器发送ACK回应请求&#xff0c;并同时发送一个SYN的请求给客户端 客户端回应ACK应答 关闭的四次握手 对于关闭流程&#xff0c;一共有三种情况&#xff1a;客户端主动关闭&#xff0c;服务器端主动关闭&#xff0c;客户端和服务器…

VS2022安装教程和使用说明来了

我看很多小伙伴已经开始迫不及待的安装VS2022了&#xff0c;虽然我也安装了VS2022&#xff0c;但是我依旧使用VS2019。因为我觉得适合我的才是最好的&#xff0c;并非是最新的&#xff0c;所以大家在使用的时候&#xff0c;根据实际需求选择开发工具&#xff0c;不要一味追求最…

华为交换机RRPP配置实验

在工作中遇到了H3C和HW的RRPP配置&#xff0c;以下就以华为模拟器再作一次实验。大家共同来论讨论遇到的问题。 【理论基础】RRPP具体的理论见配置手册下面只点几个容易出错的地方1、作为RRPP环的接口要关闭STP2、两个重要的命令&#xff1a;control-vlan vlan-id命令&#xff…

Android之AIDL服务

AIDL服务 服务&#xff08;Service&#xff09;是android系统中非常重要的组件。Service可以脱离应用程序运行。也就是说&#xff0c;应用程序只起到一个启动Service的作用。一但Service被启动&#xff0c;就算应用程序关闭&#xff0c;Service仍然会在后台运行。 andro…

男人的那些统一话术......

1 当面试官来租你的房子▼2 好家伙&#xff08;via.dy油画艺术&#xff09;▼3 学到了&#xff08;素材来源网络&#xff0c;侵删&#xff09;▼4 一杯奶茶能加多少料▼5 原来我们如此优秀&#xff01;&#xff08;素材来源网络&#xff0c;侵删&#xff09;▼6 幼儿园里卧…

后端开发者开发前端必会的工具(一):样式调试篇

又来为大家分享干货了&#xff0c;今天主要是分享一点关于后端工程师开发前端比较苦恼的一个问题《如何去调试前端&#xff1f;》&#xff0c;我相信这是所有后端开发者比较困惑的&#xff0c;如果有这个困惑的&#xff0c;记得关注“程序员晓晓”公众号&#xff0c;并给我留言…

adb android源码分析,Android源码分析(十六)----adb shell 命令进行OTA升级

一: 进入shell命令界面adb shell二&#xff1a;创建目录/cache/recoverymkdir /cache/recovery 如果系统中已有此目录&#xff0c;则会提示已存在。三: 修改文件夹权限chmod -R 777 /cache/recovery四: 把ota文件路径写入/cache/recovery/command文件中echo "--update_pac…

如何使用cURL获得请求和响应时间?

✎ 码甲说 hello&#xff0c;老伙计们&#xff0c;又有半个多月没见了&#xff0c;今天给大家分享一个干货编程小技巧&#xff0c;上至架构师、下至开发者、运维男、QA&#xff0c; 得此利器&#xff0c;事半功倍。cURL在我的眼里&#xff0c;就是一个httpClient手办&#xff…

ASP.NET MVC CheckBoxFor为什么会生成hidden input控件

自己开发的公众号&#xff0c;可以领取淘宝内部优惠券 Html.CheckBoxFor(m > m.Bool) 使用CheckBoxFor方法得到的html代码会是下面这个样子 <input checked"checked" data-val"true" data-val-required"Bool 字段是必需的。" id"Bool…

android 远程调试工具,Android远程调试的探索与实现

文章来源&#xff1a;美团点评技术团队作为移动开发者&#xff0c;最头疼的莫过于遇到产品上线以后出现了bug&#xff0c;但是本地开发环境又无法复现的情况。常见的调查线上棘手问题方式大概如下&#xff1a;方法优点缺点联系用户安装已添加测试日志的APK方便定位问题需要用户…

.NET 6新特性试用 | 自动生成高性能日志记录代码

前言要想记录日志&#xff0c;常用的方式是访问ILogger实例提供的日志记录方法&#xff1a;private readonly ILogger<WeatherForecastController> _logger;public WeatherForecastController(ILogger<WeatherForecastController> logger) {_logger logger; }[Htt…

3150 Pibonacci数 - Wikioi

题目描述 Description   你可能听说过的Fibonacci数和圆周率Pi。   如果你让这两个概念合并&#xff0c;一个新的深奥的概念应运而生&#xff1a;Pibonacci数。   这些数可以被定义为对于x>0&#xff1a;     如果0<x<4&#xff0c;则P(x) 1 …

Oracle Enterprises Manager 12C安装

前言 随着时代的进步与发展&#xff0c;Oracle官方于2012年12月1日起正式公布不再为Oracle10g版本提供免费的技术支持服务&#xff0c;而另一款新产品12C也即将面试&#xff0c;C即cloud&#xff0c;伴随着云计算的脚步&#xff0c;他终于粉墨登场了&#xff0c;熊熊第一时间下…