Add <feature-group> tag and change aapt badging

A <feature-group> represents a set of features required
for an app to be compatible with a device. Multiple
<feature-group> elements represent a logical 'or'
of required features.

Features defined in the old way with <uses-feature> tags
under the <manifest> tag are automatically added to each
feature-group defined.

Defining a <feature-group> means that any default
features are not included (such as android.hardware.touchscreen)
and declared permissions do not imply any features.

Change-Id: I45626f0fdc546e47bcf2aead7ef05ebcca12b023
This commit is contained in:
Adam Lesinski
2014-06-24 09:56:01 -07:00
parent 921ec8056a
commit 2c72b6822d
6 changed files with 316 additions and 220 deletions

View File

@@ -1530,6 +1530,11 @@ public class PackageParser {
XmlUtils.skipCurrentTag(parser);
} else if (tagName.equals("feature-group")) {
// Skip this for now until we know what to do with it.
XmlUtils.skipCurrentTag(parser);
} else if (tagName.equals("uses-sdk")) {
if (SDK_VERSION > 0) {
sa = res.obtainAttributes(attrs,

View File

@@ -1285,6 +1285,19 @@
<attr name="required" format="boolean" />
</declare-styleable>
<!-- The <code>feature-group</code> tag specifies
a set of one or more <code>uses-feature</code> elements that
the application can utilize. An application uses multiple
<code>feature-group</code> sets to indicate that it can support
different combinations of features.
<p>This appears as a child tag of the root
{@link #AndroidManifest manifest} tag. -->
<declare-styleable name="AndroidManifestFeatureGroup">
<!-- The human-readable name of the feature group. -->
<attr name="label" />
</declare-styleable>
<!-- The <code>uses-sdk</code> tag describes the SDK features that the
containing package must be running on to operate correctly.

View File

@@ -0,0 +1,25 @@
#
# Copyright (C) 2014 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES := $(call all-subdir-java-files)
LOCAL_PACKAGE_NAME := UsesFeature2Test
LOCAL_MODULE_TAGS := tests
include $(BUILD_PACKAGE)

View File

@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2014 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.test.usesfeature2">
<uses-sdk android:minSdkVersion="10" android:targetSdkVersion="19" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-feature android:name="android.hardware.sensor.accelerometer" />
<feature-group android:label="@string/minimal">
<uses-feature android:name="android.hardware.dpad" />
<uses-feature android:name="android.hardware.touchscreen.multitouch.distinct" />
</feature-group>
<feature-group android:label="@string/gamepad">
<uses-feature android:name="android.hardware.gamepad" />
</feature-group>
<application android:label="@string/app_title">
<activity android:name="ActivityMain">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2014 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<string name="app_title">Uses Feature 2.0</string>
<string name="minimal">Crippled experience</string>
<string name="gamepad">Gamer experience</string>
</resources>

View File

@@ -4,20 +4,23 @@
// Android Asset Packaging Tool main entry point.
//
#include "ApkBuilder.h"
#include "Main.h"
#include "Bundle.h"
#include "Images.h"
#include "Main.h"
#include "ResourceFilter.h"
#include "ResourceTable.h"
#include "Images.h"
#include "XMLNode.h"
#include <utils/Log.h>
#include <utils/threads.h>
#include <utils/List.h>
#include <utils/Errors.h>
#include <utils/KeyedVector.h>
#include <utils/List.h>
#include <utils/Log.h>
#include <utils/SortedVector.h>
#include <utils/threads.h>
#include <utils/Vector.h>
#include <fcntl.h>
#include <errno.h>
#include <fcntl.h>
using namespace android;
@@ -588,6 +591,106 @@ static void printComponentPresence(const char* componentName) {
printf("provides-component:'%s'\n", componentName);
}
/**
* Represents a feature that has been automatically added due to
* a pre-requisite or some other reason.
*/
struct ImpliedFeature {
/**
* Name of the implied feature.
*/
String8 name;
/**
* List of human-readable reasons for why this feature was implied.
*/
SortedVector<String8> reasons;
};
/**
* Represents a <feature-group> tag in the AndroidManifest.xml
*/
struct FeatureGroup {
/**
* Human readable label
*/
String8 label;
/**
* Explicit features defined in the group
*/
KeyedVector<String8, bool> features;
};
static void addImpliedFeature(KeyedVector<String8, ImpliedFeature>* impliedFeatures,
const char* name, const char* reason) {
String8 name8(name);
ssize_t idx = impliedFeatures->indexOfKey(name8);
if (idx < 0) {
idx = impliedFeatures->add(name8, ImpliedFeature());
impliedFeatures->editValueAt(idx).name = name8;
}
impliedFeatures->editValueAt(idx).reasons.add(String8(reason));
}
static void printFeatureGroup(const FeatureGroup& grp,
const KeyedVector<String8, ImpliedFeature>* impliedFeatures = NULL) {
printf("feature-group: label='%s'\n", grp.label.string());
const size_t numFeatures = grp.features.size();
for (size_t i = 0; i < numFeatures; i++) {
if (!grp.features[i]) {
continue;
}
const String8& featureName = grp.features.keyAt(i);
printf(" uses-feature: name='%s'\n",
ResTable::normalizeForOutput(featureName.string()).string());
}
const size_t numImpliedFeatures =
(impliedFeatures != NULL) ? impliedFeatures->size() : 0;
for (size_t i = 0; i < numImpliedFeatures; i++) {
const ImpliedFeature& impliedFeature = impliedFeatures->valueAt(i);
if (grp.features.indexOfKey(impliedFeature.name) >= 0) {
// The feature is explicitly set, no need to use implied
// definition.
continue;
}
String8 printableFeatureName(ResTable::normalizeForOutput(
impliedFeature.name.string()));
printf(" uses-feature: name='%s'\n", printableFeatureName.string());
printf(" uses-implied-feature: name='%s' reason='",
printableFeatureName.string());
const size_t numReasons = impliedFeature.reasons.size();
for (size_t j = 0; j < numReasons; j++) {
printf("%s", impliedFeature.reasons[j].string());
if (j + 2 < numReasons) {
printf(", ");
} else if (j + 1 < numReasons) {
printf(", and ");
}
}
printf("'\n");
}
}
static void addParentFeatures(FeatureGroup* grp, const String8& name) {
if (name == "android.hardware.camera.autofocus" ||
name == "android.hardware.camera.flash") {
grp->features.add(String8("android.hardware.camera"), true);
} else if (name == "android.hardware.location.gps" ||
name == "android.hardware.location.network") {
grp->features.add(String8("android.hardware.location"), true);
} else if (name == "android.hardware.touchscreen.multitouch") {
grp->features.add(String8("android.hardware.touchscreen"), true);
} else if (name == "android.hardware.touchscreen.multitouch.distinct") {
grp->features.add(String8("android.hardware.touchscreen.multitouch"), true);
grp->features.add(String8("android.hardware.touchscreen"), true);
}
}
/*
* Handle the "dump" command, to extract select data from an archive.
*/
@@ -797,6 +900,7 @@ int doDump(Bundle* bundle)
bool isSearchable = false;
bool withinApplication = false;
bool withinSupportsInput = false;
bool withinFeatureGroup = false;
bool withinReceiver = false;
bool withinService = false;
bool withinProvider = false;
@@ -869,36 +973,7 @@ int doDump(Bundle* bundle)
// some new uses-feature constants in 2.1 and 2.2. In most cases, the
// heuristic is "if an app requests a permission but doesn't explicitly
// request the corresponding <uses-feature>, presume it's there anyway".
bool specCameraFeature = false; // camera-related
bool specCameraAutofocusFeature = false;
bool reqCameraAutofocusFeature = false;
bool reqCameraFlashFeature = false;
bool hasCameraPermission = false;
bool specLocationFeature = false; // location-related
bool specNetworkLocFeature = false;
bool reqNetworkLocFeature = false;
bool specGpsFeature = false;
bool reqGpsFeature = false;
bool hasMockLocPermission = false;
bool hasCoarseLocPermission = false;
bool hasGpsPermission = false;
bool hasGeneralLocPermission = false;
bool specBluetoothFeature = false; // Bluetooth API-related
bool hasBluetoothPermission = false;
bool specMicrophoneFeature = false; // microphone-related
bool hasRecordAudioPermission = false;
bool specWiFiFeature = false;
bool hasWiFiPermission = false;
bool specTelephonyFeature = false; // telephony-related
bool reqTelephonySubFeature = false;
bool hasTelephonyPermission = false;
bool specTouchscreenFeature = false; // touchscreen-related
bool specMultitouchFeature = false;
bool reqDistinctMultitouchFeature = false;
bool specScreenPortraitFeature = false;
bool specScreenLandscapeFeature = false;
bool reqScreenPortraitFeature = false;
bool reqScreenLandscapeFeature = false;
// 2.2 also added some other features that apps can request, but that
// have no corresponding permission, so we cannot implement any
// back-compatibility heuristic for them. The below are thus unnecessary
@@ -926,6 +1001,11 @@ int doDump(Bundle* bundle)
String8 receiverName;
String8 serviceName;
Vector<String8> supportedInput;
FeatureGroup commonFeatures;
Vector<FeatureGroup> featureGroups;
KeyedVector<String8, ImpliedFeature> impliedFeatures;
while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
if (code == ResXMLTree::END_TAG) {
depth--;
@@ -946,6 +1026,7 @@ int doDump(Bundle* bundle)
}
withinApplication = false;
withinSupportsInput = false;
withinFeatureGroup = false;
} else if (depth < 3) {
if (withinActivity && isMainActivity) {
String8 aName(getComponentName(pkg, activityName));
@@ -1210,59 +1291,27 @@ int doDump(Bundle* bundle)
COMPATIBLE_WIDTH_LIMIT_DP_ATTR, NULL, 0);
largestWidthLimitDp = getIntegerAttribute(tree,
LARGEST_WIDTH_LIMIT_DP_ATTR, NULL, 0);
} else if (tag == "feature-group") {
withinFeatureGroup = true;
FeatureGroup group;
group.label = getResolvedAttribute(&res, tree, LABEL_ATTR, &error);
if (error != "") {
fprintf(stderr, "ERROR getting 'android:label' attribute:"
" %s\n", error.string());
goto bail;
}
featureGroups.add(group);
} else if (tag == "uses-feature") {
String8 name = getAttribute(tree, NAME_ATTR, &error);
if (name != "" && error == "") {
int req = getIntegerAttribute(tree,
REQUIRED_ATTR, NULL, 1);
if (name == "android.hardware.camera") {
specCameraFeature = true;
} else if (name == "android.hardware.camera.autofocus") {
// these have no corresponding permission to check for,
// but should imply the foundational camera permission
reqCameraAutofocusFeature = reqCameraAutofocusFeature || req;
specCameraAutofocusFeature = true;
} else if (req && (name == "android.hardware.camera.flash")) {
// these have no corresponding permission to check for,
// but should imply the foundational camera permission
reqCameraFlashFeature = true;
} else if (name == "android.hardware.location") {
specLocationFeature = true;
} else if (name == "android.hardware.location.network") {
specNetworkLocFeature = true;
reqNetworkLocFeature = reqNetworkLocFeature || req;
} else if (name == "android.hardware.location.gps") {
specGpsFeature = true;
reqGpsFeature = reqGpsFeature || req;
} else if (name == "android.hardware.bluetooth") {
specBluetoothFeature = true;
} else if (name == "android.hardware.touchscreen") {
specTouchscreenFeature = true;
} else if (name == "android.hardware.touchscreen.multitouch") {
specMultitouchFeature = true;
} else if (name == "android.hardware.touchscreen.multitouch.distinct") {
reqDistinctMultitouchFeature = reqDistinctMultitouchFeature || req;
} else if (name == "android.hardware.microphone") {
specMicrophoneFeature = true;
} else if (name == "android.hardware.wifi") {
specWiFiFeature = true;
} else if (name == "android.hardware.telephony") {
specTelephonyFeature = true;
} else if (req && (name == "android.hardware.telephony.gsm" ||
name == "android.hardware.telephony.cdma")) {
// these have no corresponding permission to check for,
// but should imply the foundational telephony permission
reqTelephonySubFeature = true;
} else if (name == "android.hardware.screen.portrait") {
specScreenPortraitFeature = true;
} else if (name == "android.hardware.screen.landscape") {
specScreenLandscapeFeature = true;
commonFeatures.features.add(name, req);
if (req) {
addParentFeatures(&commonFeatures, name);
}
printf("uses-feature%s:'%s'\n",
req ? "" : "-not-required",
ResTable::normalizeForOutput(name.string()).string());
} else {
int vers = getIntegerAttribute(tree,
GL_ES_VERSION_ATTR, &error);
@@ -1274,25 +1323,51 @@ int doDump(Bundle* bundle)
String8 name = getAttribute(tree, NAME_ATTR, &error);
if (name != "" && error == "") {
if (name == "android.permission.CAMERA") {
hasCameraPermission = true;
addImpliedFeature(&impliedFeatures, "android.hardware.feature",
String8::format("requested %s permission", name.string())
.string());
} else if (name == "android.permission.ACCESS_FINE_LOCATION") {
hasGpsPermission = true;
addImpliedFeature(&impliedFeatures, "android.hardware.location.gps",
String8::format("requested %s permission", name.string())
.string());
addImpliedFeature(&impliedFeatures, "android.hardware.location",
String8::format("requested %s permission", name.string())
.string());
} else if (name == "android.permission.ACCESS_MOCK_LOCATION") {
hasMockLocPermission = true;
addImpliedFeature(&impliedFeatures, "android.hardware.location",
String8::format("requested %s permission", name.string())
.string());
} else if (name == "android.permission.ACCESS_COARSE_LOCATION") {
hasCoarseLocPermission = true;
addImpliedFeature(&impliedFeatures, "android.hardware.location.network",
String8::format("requested %s permission", name.string())
.string());
addImpliedFeature(&impliedFeatures, "android.hardware.location",
String8::format("requested %s permission", name.string())
.string());
} else if (name == "android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" ||
name == "android.permission.INSTALL_LOCATION_PROVIDER") {
hasGeneralLocPermission = true;
addImpliedFeature(&impliedFeatures, "android.hardware.location",
String8::format("requested %s permission", name.string())
.string());
} else if (name == "android.permission.BLUETOOTH" ||
name == "android.permission.BLUETOOTH_ADMIN") {
hasBluetoothPermission = true;
if (targetSdk > 4) {
addImpliedFeature(&impliedFeatures, "android.hardware.bluetooth",
String8::format("requested %s permission", name.string())
.string());
addImpliedFeature(&impliedFeatures, "android.hardware.bluetooth",
"targetSdkVersion > 4");
}
} else if (name == "android.permission.RECORD_AUDIO") {
hasRecordAudioPermission = true;
addImpliedFeature(&impliedFeatures, "android.hardware.microphone",
String8::format("requested %s permission", name.string())
.string());
} else if (name == "android.permission.ACCESS_WIFI_STATE" ||
name == "android.permission.CHANGE_WIFI_STATE" ||
name == "android.permission.CHANGE_WIFI_MULTICAST_STATE") {
hasWiFiPermission = true;
addImpliedFeature(&impliedFeatures, "android.hardware.wifi",
String8::format("requested %s permission", name.string())
.string());
} else if (name == "android.permission.CALL_PHONE" ||
name == "android.permission.CALL_PRIVILEGED" ||
name == "android.permission.MODIFY_PHONE_STATE" ||
@@ -1304,7 +1379,8 @@ int doDump(Bundle* bundle)
name == "android.permission.SEND_SMS" ||
name == "android.permission.WRITE_APN_SETTINGS" ||
name == "android.permission.WRITE_SMS") {
hasTelephonyPermission = true;
addImpliedFeature(&impliedFeatures, "android.hardware.telephony",
String8("requested a telephony permission").string());
} else if (name == "android.permission.WRITE_EXTERNAL_STORAGE") {
hasWriteExternalStoragePermission = true;
} else if (name == "android.permission.READ_EXTERNAL_STORAGE") {
@@ -1430,10 +1506,12 @@ int doDump(Bundle* bundle)
if (error == "") {
if (orien == 0 || orien == 6 || orien == 8) {
// Requests landscape, sensorLandscape, or reverseLandscape.
reqScreenLandscapeFeature = true;
addImpliedFeature(&impliedFeatures, "android.hardware.screen.landscape",
"one or more activities have specified a landscape orientation");
} else if (orien == 1 || orien == 7 || orien == 9) {
// Requests portrait, sensorPortrait, or reversePortrait.
reqScreenPortraitFeature = true;
addImpliedFeature(&impliedFeatures, "android.hardware.screen.portrait",
"one or more activities have specified a portrait orientation");
}
}
} else if (tag == "uses-library") {
@@ -1560,6 +1638,20 @@ int doDump(Bundle* bundle)
goto bail;
}
}
} else if (withinFeatureGroup && tag == "uses-feature") {
String8 name = getResolvedAttribute(&res, tree, NAME_ATTR, &error);
if (error != "") {
fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
error.string());
goto bail;
}
int required = getIntegerAttribute(tree, REQUIRED_ATTR, NULL, 1);
FeatureGroup& top = featureGroups.editTop();
top.features.add(name, required);
if (required) {
addParentFeatures(&top, name);
}
}
} else if (depth == 4) {
if (tag == "intent-filter") {
@@ -1734,137 +1826,34 @@ int doDump(Bundle* bundle)
}
}
/* The following blocks handle printing "inferred" uses-features, based
* on whether related features or permissions are used by the app.
* Note that the various spec*Feature variables denote whether the
* relevant tag was *present* in the AndroidManfest, not that it was
* present and set to true.
*/
// Camera-related back-compatibility logic
if (!specCameraFeature) {
if (reqCameraFlashFeature) {
// if app requested a sub-feature (autofocus or flash) and didn't
// request the base camera feature, we infer that it meant to
printf("uses-feature:'android.hardware.camera'\n");
printf("uses-implied-feature:'android.hardware.camera'," \
"'requested android.hardware.camera.flash feature'\n");
} else if (reqCameraAutofocusFeature) {
// if app requested a sub-feature (autofocus or flash) and didn't
// request the base camera feature, we infer that it meant to
printf("uses-feature:'android.hardware.camera'\n");
printf("uses-implied-feature:'android.hardware.camera'," \
"'requested android.hardware.camera.autofocus feature'\n");
} else if (hasCameraPermission) {
// if app wants to use camera but didn't request the feature, we infer
// that it meant to, and further that it wants autofocus
// (which was the 1.0 - 1.5 behavior)
printf("uses-feature:'android.hardware.camera'\n");
if (!specCameraAutofocusFeature) {
printf("uses-feature:'android.hardware.camera.autofocus'\n");
printf("uses-implied-feature:'android.hardware.camera.autofocus'," \
"'requested android.permission.CAMERA permission'\n");
addImpliedFeature(&impliedFeatures, "android.hardware.touchscreen",
"default feature for all apps");
const size_t numFeatureGroups = featureGroups.size();
if (numFeatureGroups == 0) {
// If no <feature-group> tags were defined, apply auto-implied features.
printFeatureGroup(commonFeatures, &impliedFeatures);
} else {
// <feature-group> tags are defined, so we ignore implied features and
for (size_t i = 0; i < numFeatureGroups; i++) {
FeatureGroup& grp = featureGroups.editItemAt(i);
// Merge the features defined in the top level (not inside a <feature-group>)
// with this feature group.
const size_t numCommonFeatures = commonFeatures.features.size();
for (size_t j = 0; j < numCommonFeatures; j++) {
if (grp.features.indexOfKey(commonFeatures.features.keyAt(j)) < 0) {
grp.features.add(commonFeatures.features.keyAt(j), commonFeatures.features[j]);
}
}
if (!grp.features.isEmpty()) {
printFeatureGroup(grp);
}
}
}
// Location-related back-compatibility logic
if (!specLocationFeature &&
(hasMockLocPermission || hasCoarseLocPermission || hasGpsPermission ||
hasGeneralLocPermission || reqNetworkLocFeature || reqGpsFeature)) {
// if app either takes a location-related permission or requests one of the
// sub-features, we infer that it also meant to request the base location feature
printf("uses-feature:'android.hardware.location'\n");
printf("uses-implied-feature:'android.hardware.location'," \
"'requested a location access permission'\n");
}
if (!specGpsFeature && hasGpsPermission) {
// if app takes GPS (FINE location) perm but does not request the GPS
// feature, we infer that it meant to
printf("uses-feature:'android.hardware.location.gps'\n");
printf("uses-implied-feature:'android.hardware.location.gps'," \
"'requested android.permission.ACCESS_FINE_LOCATION permission'\n");
}
if (!specNetworkLocFeature && hasCoarseLocPermission) {
// if app takes Network location (COARSE location) perm but does not request the
// network location feature, we infer that it meant to
printf("uses-feature:'android.hardware.location.network'\n");
printf("uses-implied-feature:'android.hardware.location.network'," \
"'requested android.permission.ACCESS_COARSE_LOCATION permission'\n");
}
// Bluetooth-related compatibility logic
if (!specBluetoothFeature && hasBluetoothPermission && (targetSdk > 4)) {
// if app takes a Bluetooth permission but does not request the Bluetooth
// feature, we infer that it meant to
printf("uses-feature:'android.hardware.bluetooth'\n");
printf("uses-implied-feature:'android.hardware.bluetooth'," \
"'requested android.permission.BLUETOOTH or android.permission.BLUETOOTH_ADMIN " \
"permission and targetSdkVersion > 4'\n");
}
// Microphone-related compatibility logic
if (!specMicrophoneFeature && hasRecordAudioPermission) {
// if app takes the record-audio permission but does not request the microphone
// feature, we infer that it meant to
printf("uses-feature:'android.hardware.microphone'\n");
printf("uses-implied-feature:'android.hardware.microphone'," \
"'requested android.permission.RECORD_AUDIO permission'\n");
}
// WiFi-related compatibility logic
if (!specWiFiFeature && hasWiFiPermission) {
// if app takes one of the WiFi permissions but does not request the WiFi
// feature, we infer that it meant to
printf("uses-feature:'android.hardware.wifi'\n");
printf("uses-implied-feature:'android.hardware.wifi'," \
"'requested android.permission.ACCESS_WIFI_STATE, " \
"android.permission.CHANGE_WIFI_STATE, or " \
"android.permission.CHANGE_WIFI_MULTICAST_STATE permission'\n");
}
// Telephony-related compatibility logic
if (!specTelephonyFeature && (hasTelephonyPermission || reqTelephonySubFeature)) {
// if app takes one of the telephony permissions or requests a sub-feature but
// does not request the base telephony feature, we infer that it meant to
printf("uses-feature:'android.hardware.telephony'\n");
printf("uses-implied-feature:'android.hardware.telephony'," \
"'requested a telephony-related permission or feature'\n");
}
// Touchscreen-related back-compatibility logic
if (!specTouchscreenFeature) { // not a typo!
// all apps are presumed to require a touchscreen, unless they explicitly say
// <uses-feature android:name="android.hardware.touchscreen" android:required="false"/>
// Note that specTouchscreenFeature is true if the tag is present, regardless
// of whether its value is true or false, so this is safe
printf("uses-feature:'android.hardware.touchscreen'\n");
printf("uses-implied-feature:'android.hardware.touchscreen'," \
"'assumed you require a touch screen unless explicitly made optional'\n");
}
if (!specMultitouchFeature && reqDistinctMultitouchFeature) {
// if app takes one of the telephony permissions or requests a sub-feature but
// does not request the base telephony feature, we infer that it meant to
printf("uses-feature:'android.hardware.touchscreen.multitouch'\n");
printf("uses-implied-feature:'android.hardware.touchscreen.multitouch'," \
"'requested android.hardware.touchscreen.multitouch.distinct feature'\n");
}
// Landscape/portrait-related compatibility logic
if (!specScreenLandscapeFeature && !specScreenPortraitFeature) {
// If the app has specified any activities in its manifest
// that request a specific orientation, then assume that
// orientation is required.
if (reqScreenLandscapeFeature) {
printf("uses-feature:'android.hardware.screen.landscape'\n");
printf("uses-implied-feature:'android.hardware.screen.landscape'," \
"'one or more activities have specified a landscape orientation'\n");
}
if (reqScreenPortraitFeature) {
printf("uses-feature:'android.hardware.screen.portrait'\n");
printf("uses-implied-feature:'android.hardware.screen.portrait'," \
"'one or more activities have specified a portrait orientation'\n");
}
}
if (hasWidgetReceivers) {
printComponentPresence("app-widget");