有时候在开发中,为了版权问题,有的应用虽然在应用商城(google play store)中可以搜到,但是因为没有给OEM 方授权,故在OEM的Device 中不能使用(预制以及在商城下载)此应用。例如,吊炸天的Netflix。以前我总是想不通,能上架应用商城的应用,把不得用户多多下载。可是为啥人家Netflix 就这么叼呢。唉,这个不是本文重点。
要解决这个问题,需要提前了解 android 中的uses-feature 属性。具体可以参考这篇文章—AndroidManifest配置之uses-feature
AndroidManifest中的uses-feature配置用来声明一个app在运行时所依赖的外部的硬件或软件特征(feature),uses-feature还提供了一个required属性配置,表示此项依赖的软硬件特征是否是必须的,当它设置为true表示此app运行时必须使用此项特征,如果没有则无法工作,如果它设置为false,表示应用在运行时需要用到这些特征,但如果没有,应用可能会有一部分功能会受到影响,但大部分功能还是可以正常工作。例如一个拍照app,它使用时必须开启设备的摄像头,在没有摄像头的机器上任何功能都无法使用,这就需要通过uses-feature来声明该应用需要摄像头,并将required设置为true。再比如一个支付app,它支持扫码支付的功能,这项功能同样需要开启设备的摄像头,因此需要通过uses-feature声明该应用需要摄像头,但如果一个设备没有摄像头,仅意味着扫码支付的功能无法使用,其他支付方式仍然可以使用,这时就可以设置required属性为false,表明此项feature的需求不是必须的。
uses-feature只是对外提供了这样一组信息,表明它所依赖的软硬件特征,这个信息通常是给应用市场使用的,应用市场会读取app的uses-feature设置,然后只给那些满足这组软硬件特征的设备分发这个app。
Android系统在应用安装时并不会使用这里的信息。例如一个app在uses-feature中声明了需要摄像头,且required为true,那么应用市场在分发app时就不会将该app分发给那些没有摄像头的设备上,一个没有摄像头的设备也不能通过应用市场搜索和下载到该app。但是如果用户通过其他渠道(例如官网)下载该app对应的apk文件到某个设备上,在该设备上安装此apk时,系统并不会检查uses-feature所声明的软硬件特征是否满足,如果该设备没有摄像头,同样可以安装该app。
不仅如此,Android系统在应用运行时也不会使用这里的信息,所以uses-feature声明对应用的运行也不会产生影响(除非应用自己在代码中去判断某项feature是否满足)。这点和uses-permission是不一样的,Android系统会读取应用的uses-permission,并对应用的运行产生影响。同样以摄像头为例,如果应用某项功能需要用到摄像头,无论uses-feature如何声明,都不会影响到摄像头是否能够使用。虽然如果设备没有摄像头这个硬件,这项功能就无法使用,但是这和uses-feature声明无关。但如果没有通过uses-permission声明摄像头的权限,那么即使有摄像头硬件,也是无法使用摄像头的。
这里的因果关系是这样的:由于应用没有这项feature就无法工作,所以需要在AndroidManifest中配置uses-feature,让应用市场在分发app时自动过滤掉那些不支持的设备,不是由于配置了uses-feature,导致应用在没有这项feature的设备上无法工作。
当用户打开Google Play浏览或查找应用时,Google Play会对用户可见的app进行过滤,用户在Google Play上只能够看到和下载到那些能够和用户设备相兼容的app。根据uses-feature声明的软硬件特征来过滤app是其中的一种方式。
当开发者上传apk文件到Google Play后,Google Play会读取apk文件中AndroidManifest,根据uses-feature,uses-sdk,uses-permission,uses-library等配置信息来评估此app所需要的features,然后根据这些信息生成一份元数据和该apk版本关联起来,保存到数据库中。
当用户打开设备上的Google Play应用时,Google Play应用会通过getSystemAvailableFeatures()获取到当前设备所支持的软硬件特征,然后将此列表发送给Google Play,之后当用户在这台设备上通过Google Play浏览或查找应用时,Google Play会将每个应用所需要的feature和用户设备支持的feature做比较。只有那些所有需要的feature在当前设备上都支持的app才会显示出来,如果一个app所需要的某个feature不能被当前设备满足,那么在这台设备上就不会显示这个app。
———————————————————————————————————————————
知道了 uses-feature的作用后,我们就可以来过滤Netflix了,可是有一个问题,就是我们该使用那一个uses-feature 来过滤Netflix呢。Netflix 专门有一个特殊的uses-feature。来标识其。
即:nrdp.modelgroup
这里可以看看Netflix 的AndroidManifest.xml 中的配置:
The Netflix Ready Device Platform (NRDP) is a software development kit used by internal and external partners to integrate Netflix into CE (consumer electronics) devices. The technologies, features, tools, and documentation in the NRDP are continually improved to enable the best possible Netflix experience while simplifying and accelerating the partner integration process.
其实大致意思就是NRDP是Netflix 提供的一种机制。不用纠结。
好了,知道了以上,那么我们就可以在我们的系统中做一些操作,删除掉支持nrdp.modelgroup 属性use-feature即可。删除后,google play store 就检测到我们的设备不支持nrdp.modelgroup,则也就搜索不到Netflix了。这里提供两种方法:
device\OEM\device.mk
# Permission files
PRODUCT_COPY_FILES += \
$(DEVICE_FOLDER)/configs/tv_features.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/tv_features.xml \
$(DEVICE_FOLDER)/configs/libs_permissions.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/libs_permissions.xml
通过device.mk中可以找到tv_features.xml 的定义
device\OEM\config\tv_features.xml
// 删除掉此属性即可
可以在解析tv_feature.xml 的代码中做一些操作。
frameworks / base/core/java/com/android/server/SystemConfig.java
private void readPermissionsFromXml(File permFile, int permissionFlag) {
FileReader permReader = null;
try {
permReader = new FileReader(permFile);
} catch (FileNotFoundException e) {
Slog.w(TAG, "Couldn't find or open permissions file " + permFile);
return;
}
final boolean lowRam = ActivityManager.isLowRamDeviceStatic();
try {
XmlPullParser parser = Xml.newPullParser();
parser.setInput(permReader);
int type;
while ((type=parser.next()) != parser.START_TAG
&& type != parser.END_DOCUMENT) {
;
}
if (type != parser.START_TAG) {
throw new XmlPullParserException("No start tag found");
}
if (!parser.getName().equals("permissions") && !parser.getName().equals("config")) {
throw new XmlPullParserException("Unexpected start tag in " + permFile
+ ": found " + parser.getName() + ", expected 'permissions' or 'config'");
}
boolean allowAll = permissionFlag == ALLOW_ALL;
boolean allowLibs = (permissionFlag & ALLOW_LIBS) != 0;
boolean allowFeatures = (permissionFlag & ALLOW_FEATURES) != 0;
boolean allowPermissions = (permissionFlag & ALLOW_PERMISSIONS) != 0;
boolean allowAppConfigs = (permissionFlag & ALLOW_APP_CONFIGS) != 0;
boolean allowPrivappPermissions = (permissionFlag & ALLOW_PRIVAPP_PERMISSIONS) != 0;
boolean allowOemPermissions = (permissionFlag & ALLOW_OEM_PERMISSIONS) != 0;
boolean allowApiWhitelisting = (permissionFlag & ALLOW_HIDDENAPI_WHITELISTING) != 0;
while (true) {
XmlUtils.nextElement(parser);
if (parser.getEventType() == XmlPullParser.END_DOCUMENT) {
break;
}
String name = parser.getName();
if ("group".equals(name) && allowAll) {
String gidStr = parser.getAttributeValue(null, "gid");
if (gidStr != null) {
int gid = android.os.Process.getGidForName(gidStr);
mGlobalGids = appendInt(mGlobalGids, gid);
} else {
Slog.w(TAG, " without gid in " + permFile + " at "
+ parser.getPositionDescription());
}
XmlUtils.skipCurrentTag(parser);
continue;
} else if ("permission".equals(name) && allowPermissions) {
String perm = parser.getAttributeValue(null, "name");
if (perm == null) {
Slog.w(TAG, " without name in " + permFile + " at "
+ parser.getPositionDescription());
XmlUtils.skipCurrentTag(parser);
continue;
}
perm = perm.intern();
readPermission(parser, perm);
} else if ("assign-permission".equals(name) && allowPermissions) {
String perm = parser.getAttributeValue(null, "name");
if (perm == null) {
Slog.w(TAG, " without name in " + permFile + " at "
+ parser.getPositionDescription());
XmlUtils.skipCurrentTag(parser);
continue;
}
String uidStr = parser.getAttributeValue(null, "uid");
if (uidStr == null) {
Slog.w(TAG, " without uid in " + permFile + " at "
+ parser.getPositionDescription());
XmlUtils.skipCurrentTag(parser);
continue;
}
int uid = Process.getUidForName(uidStr);
if (uid < 0) {
Slog.w(TAG, " with unknown uid \""
+ uidStr + " in " + permFile + " at "
+ parser.getPositionDescription());
XmlUtils.skipCurrentTag(parser);
continue;
}
perm = perm.intern();
ArraySet perms = mSystemPermissions.get(uid);
if (perms == null) {
perms = new ArraySet();
mSystemPermissions.put(uid, perms);
}
perms.add(perm);
XmlUtils.skipCurrentTag(parser);
} else if ("library".equals(name) && allowLibs) {
String lname = parser.getAttributeValue(null, "name");
String lfile = parser.getAttributeValue(null, "file");
if (lname == null) {
Slog.w(TAG, " without name in " + permFile + " at "
+ parser.getPositionDescription());
} else if (lfile == null) {
Slog.w(TAG, " without file in " + permFile + " at "
+ parser.getPositionDescription());
} else {
//Log.i(TAG, "Got library " + lname + " in " + lfile);
mSharedLibraries.put(lname, lfile);
}
XmlUtils.skipCurrentTag(parser);
continue;
} else if ("feature".equals(name) && allowFeatures) {
String fname = parser.getAttributeValue(null, "name");
int fversion = XmlUtils.readIntAttribute(parser, "version", 0);
boolean allowed;
if (!lowRam) {
allowed = true;
} else {
String notLowRam = parser.getAttributeValue(null, "notLowRam");
allowed = !"true".equals(notLowRam);
}
if (fname == null) {
Slog.w(TAG, " without name in " + permFile + " at "
+ parser.getPositionDescription());
} else if (allowed) {
if("nrdp.modelgroup".equals(fname) ) // 如果xml 中包含此属性,则skip 掉此tag的属性。
{
Slog.w(TAG, "ConfigData ignore Google play store Netflix.");
XmlUtils.skipCurrentTag(parser);
continue;
}
addFeature(fname, fversion);
}
XmlUtils.skipCurrentTag(parser);
continue;
到此在google play store中就搜索不到Netflix应用了。