am 1038bf1d: am 94499248: Merge "Add <feature-group> tag and change aapt badging" into lmp-dev
* commit '1038bf1d7a592c74bcc48d729ccea1f56d8fce1d':
Add <feature-group> tag and change aapt badging
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 9866200..db87cf7 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -1530,6 +1530,11 @@
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,
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 7311a60..c268d97 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -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.
diff --git a/tests/UsesFeature2Test/Android.mk b/tests/UsesFeature2Test/Android.mk
new file mode 100644
index 0000000..cc784d7
--- /dev/null
+++ b/tests/UsesFeature2Test/Android.mk
@@ -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)
diff --git a/tests/UsesFeature2Test/AndroidManifest.xml b/tests/UsesFeature2Test/AndroidManifest.xml
new file mode 100644
index 0000000..724d186
--- /dev/null
+++ b/tests/UsesFeature2Test/AndroidManifest.xml
@@ -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>
diff --git a/tests/UsesFeature2Test/res/values/values.xml b/tests/UsesFeature2Test/res/values/values.xml
new file mode 100644
index 0000000..2ee9107
--- /dev/null
+++ b/tests/UsesFeature2Test/res/values/values.xml
@@ -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>
diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp
index 5fefab6..ac1ae70 100644
--- a/tools/aapt/Command.cpp
+++ b/tools/aapt/Command.cpp
@@ -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 @@
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 @@
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 @@
// 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 @@
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 @@
}
withinApplication = false;
withinSupportsInput = false;
+ withinFeatureGroup = false;
} else if (depth < 3) {
if (withinActivity && isMainActivity) {
String8 aName(getComponentName(pkg, activityName));
@@ -1210,59 +1291,27 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
}
- /* 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");