diff options
author | Adam Lesinski <adamlesinski@google.com> | 2014-05-12 18:12:34 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2014-05-12 18:12:35 +0000 |
commit | f0009776203b4e5b705f575990d3fa466a10a1b3 (patch) | |
tree | adce2f25011a4201e57d85b8a82faa3a483247a4 | |
parent | 1cb088c1281deec042bdf00e0f2d227f7e5cd718 (diff) | |
parent | fab50875b98e8274ac8ee44b38ba42521bbbf1f9 (diff) | |
download | frameworks_base-f0009776203b4e5b705f575990d3fa466a10a1b3.zip frameworks_base-f0009776203b4e5b705f575990d3fa466a10a1b3.tar.gz frameworks_base-f0009776203b4e5b705f575990d3fa466a10a1b3.tar.bz2 |
Merge "Add support for building split APKs"
37 files changed, 2543 insertions, 1931 deletions
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp index 098753b..6aad5fb 100644 --- a/libs/androidfw/ResourceTypes.cpp +++ b/libs/androidfw/ResourceTypes.cpp @@ -2452,15 +2452,19 @@ String8 ResTable_config::toString() const { if (mcc != 0) { if (res.size() > 0) res.append("-"); - res.appendFormat("%dmcc", dtohs(mcc)); + res.appendFormat("mcc%d", dtohs(mcc)); } if (mnc != 0) { if (res.size() > 0) res.append("-"); - res.appendFormat("%dmnc", dtohs(mnc)); + res.appendFormat("mnc%d", dtohs(mnc)); } + char localeStr[RESTABLE_MAX_LOCALE_LEN]; getBcp47Locale(localeStr); - res.append(localeStr); + if (strlen(localeStr) > 0) { + if (res.size() > 0) res.append("-"); + res.append(localeStr); + } if ((screenLayout&MASK_LAYOUTDIR) != 0) { if (res.size() > 0) res.append("-"); @@ -2627,6 +2631,20 @@ String8 ResTable_config::toString() const { break; } } + if ((inputFlags&MASK_KEYSHIDDEN) != 0) { + if (res.size() > 0) res.append("-"); + switch (inputFlags&MASK_KEYSHIDDEN) { + case ResTable_config::KEYSHIDDEN_NO: + res.append("keysexposed"); + break; + case ResTable_config::KEYSHIDDEN_YES: + res.append("keyshidden"); + break; + case ResTable_config::KEYSHIDDEN_SOFT: + res.append("keyssoft"); + break; + } + } if (keyboard != KEYBOARD_ANY) { if (res.size() > 0) res.append("-"); switch (keyboard) { @@ -2644,17 +2662,18 @@ String8 ResTable_config::toString() const { break; } } - if ((inputFlags&MASK_KEYSHIDDEN) != 0) { + if ((inputFlags&MASK_NAVHIDDEN) != 0) { if (res.size() > 0) res.append("-"); - switch (inputFlags&MASK_KEYSHIDDEN) { - case ResTable_config::KEYSHIDDEN_NO: - res.append("keysexposed"); + switch (inputFlags&MASK_NAVHIDDEN) { + case ResTable_config::NAVHIDDEN_NO: + res.append("navexposed"); break; - case ResTable_config::KEYSHIDDEN_YES: - res.append("keyshidden"); + case ResTable_config::NAVHIDDEN_YES: + res.append("navhidden"); break; - case ResTable_config::KEYSHIDDEN_SOFT: - res.append("keyssoft"); + default: + res.appendFormat("inputFlagsNavHidden=%d", + dtohs(inputFlags&MASK_NAVHIDDEN)); break; } } @@ -2678,21 +2697,6 @@ String8 ResTable_config::toString() const { break; } } - if ((inputFlags&MASK_NAVHIDDEN) != 0) { - if (res.size() > 0) res.append("-"); - switch (inputFlags&MASK_NAVHIDDEN) { - case ResTable_config::NAVHIDDEN_NO: - res.append("navsexposed"); - break; - case ResTable_config::NAVHIDDEN_YES: - res.append("navhidden"); - break; - default: - res.appendFormat("inputFlagsNavHidden=%d", - dtohs(inputFlags&MASK_NAVHIDDEN)); - break; - } - } if (screenSize != 0) { if (res.size() > 0) res.append("-"); res.appendFormat("%dx%d", dtohs(screenWidth), dtohs(screenHeight)); @@ -5503,7 +5507,25 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg, if (package == NULL) { return (mError=NO_MEMORY); } - + + if (idmap_id == 0) { + err = package->typeStrings.setTo(base+dtohl(pkg->typeStrings), + header->dataEnd-(base+dtohl(pkg->typeStrings))); + if (err != NO_ERROR) { + delete group; + delete package; + return (mError=err); + } + + err = package->keyStrings.setTo(base+dtohl(pkg->keyStrings), + header->dataEnd-(base+dtohl(pkg->keyStrings))); + if (err != NO_ERROR) { + delete group; + delete package; + return (mError=err); + } + } + if (id == 0) { // This is a library so assign an ID id = mNextPackageId++; @@ -5521,21 +5543,6 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg, return (mError=NO_MEMORY); } - err = package->typeStrings.setTo(base+dtohl(pkg->typeStrings), - header->dataEnd-(base+dtohl(pkg->typeStrings))); - if (err != NO_ERROR) { - delete group; - delete package; - return (mError=err); - } - err = package->keyStrings.setTo(base+dtohl(pkg->keyStrings), - header->dataEnd-(base+dtohl(pkg->keyStrings))); - if (err != NO_ERROR) { - delete group; - delete package; - return (mError=err); - } - //printf("Adding new package id %d at index %d\n", id, idx); err = mPackageGroups.add(group); if (err < NO_ERROR) { diff --git a/tests/Split/Android.mk b/tests/Split/Android.mk new file mode 100644 index 0000000..7884d4d --- /dev/null +++ b/tests/Split/Android.mk @@ -0,0 +1,28 @@ +# +# 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 := Split + +LOCAL_AAPT_FLAGS := --split fr,de +LOCAL_AAPT_FLAGS += -v + +LOCAL_MODULE_TAGS := tests + +include $(BUILD_PACKAGE) diff --git a/tests/Split/AndroidManifest.xml b/tests/Split/AndroidManifest.xml new file mode 100644 index 0000000..a4956a7 --- /dev/null +++ b/tests/Split/AndroidManifest.xml @@ -0,0 +1,27 @@ +<?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.example.split"> + <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/Split/assets/blah.txt b/tests/Split/assets/blah.txt new file mode 100644 index 0000000..1b37e40 --- /dev/null +++ b/tests/Split/assets/blah.txt @@ -0,0 +1 @@ +This is some useful info. diff --git a/tests/Split/assets/statement.xml b/tests/Split/assets/statement.xml new file mode 100644 index 0000000..91750d1 --- /dev/null +++ b/tests/Split/assets/statement.xml @@ -0,0 +1,19 @@ +<?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. +--> + +<statement> + <value>Hello</value> +</statement> diff --git a/tests/Split/res/layout-fr-sw600dp/main.xml b/tests/Split/res/layout-fr-sw600dp/main.xml new file mode 100644 index 0000000..2461c8c --- /dev/null +++ b/tests/Split/res/layout-fr-sw600dp/main.xml @@ -0,0 +1,23 @@ +<?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. +--> + +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <FrameLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content"/> +</FrameLayout> diff --git a/tests/Split/res/layout/main.xml b/tests/Split/res/layout/main.xml new file mode 100644 index 0000000..36992a2 --- /dev/null +++ b/tests/Split/res/layout/main.xml @@ -0,0 +1,19 @@ +<?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. +--> + +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"/> diff --git a/tests/Split/res/values-de/values.xml b/tests/Split/res/values-de/values.xml new file mode 100644 index 0000000..26d0507 --- /dev/null +++ b/tests/Split/res/values-de/values.xml @@ -0,0 +1,19 @@ +<?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="test">Achtung!</string> +</resources> diff --git a/tests/Split/res/values-fr/values.xml b/tests/Split/res/values-fr/values.xml new file mode 100644 index 0000000..16532da --- /dev/null +++ b/tests/Split/res/values-fr/values.xml @@ -0,0 +1,25 @@ +<?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">APK Divisé</string> + <string name="test">Bonjour, Monde!</string> + <string name="blah">Bleh..</string> + <string-array name="lotsofstrings"> + <item>Hé là</item> + <item>Au revoir</item> + </string-array> +</resources> diff --git a/tests/Split/res/values-sw600dp/values.xml b/tests/Split/res/values-sw600dp/values.xml new file mode 100644 index 0000000..a8329bb --- /dev/null +++ b/tests/Split/res/values-sw600dp/values.xml @@ -0,0 +1,19 @@ +<?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> + <dimen name="width">230dp</dimen> +</resources> diff --git a/tests/Split/res/values/values.xml b/tests/Split/res/values/values.xml new file mode 100644 index 0000000..68edc77 --- /dev/null +++ b/tests/Split/res/values/values.xml @@ -0,0 +1,47 @@ +<?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">Split APK</string> + <string name="test">Hello, World!</string> + <string name="boom">Boom!</string> + <string name="blah">Blah...</string> + <string-array name="lotsofstrings"> + <item>Hello there</item> + <item>Good bye</item> + </string-array> + + <plurals name="plur"> + <item quantity="zero">I no haz :(</item> + <item quantity="one">I haz 1!1! :)</item> + <item quantity="many">I haz ALL!</item> + </plurals> + + <bool name="que">true</bool> + <color name="green">#00FF00</color> + <dimen name="width">23dp</dimen> + <item type="id" name="identifier" /> + <integer name="number">123</integer> + <integer-array name="numList"> + <item>1234</item> + </integer-array> + + <array name="ary"> + <item>@string/test</item> + <item>@string/boom</item> + <item>25dp</item> + </array> +</resources> diff --git a/tests/Split/src/java/com/android/example/split/ActivityMain.java b/tests/Split/src/java/com/android/example/split/ActivityMain.java new file mode 100644 index 0000000..a15fb3c --- /dev/null +++ b/tests/Split/src/java/com/android/example/split/ActivityMain.java @@ -0,0 +1,31 @@ +/* + * 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. + */ + +package com.android.example.split; + +import android.app.Activity; +import android.os.Bundle; +import android.widget.TextView; + +public class ActivityMain extends Activity { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + TextView text = new TextView(this); + text.setText(R.string.test); + setContentView(text); + } +} diff --git a/tools/aapt/AaptAssets.cpp b/tools/aapt/AaptAssets.cpp index e0dab78..12d5389 100644 --- a/tools/aapt/AaptAssets.cpp +++ b/tools/aapt/AaptAssets.cpp @@ -3,8 +3,10 @@ // #include "AaptAssets.h" -#include "ResourceFilter.h" +#include "AaptConfig.h" +#include "AaptUtil.h" #include "Main.h" +#include "ResourceFilter.h" #include <utils/misc.h> #include <utils/SortedVector.h> @@ -14,7 +16,6 @@ #include <errno.h> static const char* kDefaultLocale = "default"; -static const char* kWildcardName = "any"; static const char* kAssetDir = "assets"; static const char* kResourceDir = "res"; static const char* kValuesDir = "values"; @@ -149,24 +150,6 @@ static bool isHidden(const char *root, const char *path) // ========================================================================= // ========================================================================= -/* static */ void AaptLocaleValue::splitAndLowerCase(const char* const chars, - Vector<String8>* parts, const char separator) { - const char *p = chars; - const char *q; - while (NULL != (q = strchr(p, separator))) { - String8 val(p, q - p); - val.toLower(); - parts->add(val); - p = q+1; - } - - if (p < chars + strlen(chars)) { - String8 val(p); - val.toLower(); - parts->add(val); - } -} - /* static */ inline bool isAlpha(const String8& string) { const size_t length = string.length(); @@ -230,8 +213,7 @@ void AaptLocaleValue::setVariant(const char* variantChars) { bool AaptLocaleValue::initFromFilterString(const String8& str) { // A locale (as specified in the filter) is an underscore separated name such // as "en_US", "en_Latn_US", or "en_US_POSIX". - Vector<String8> parts; - splitAndLowerCase(str.string(), &parts, '_'); + Vector<String8> parts = AaptUtil::splitAndLowerCase(str, '_'); const int numTags = parts.size(); bool valid = false; @@ -301,8 +283,7 @@ int AaptLocaleValue::initFromDirName(const Vector<String8>& parts, const int sta if (part[0] == 'b' && part[1] == '+') { // This is a "modified" BCP-47 language tag. Same semantics as BCP-47 tags, // except that the separator is "+" and not "-". - Vector<String8> subtags; - AaptLocaleValue::splitAndLowerCase(part.string(), &subtags, '+'); + Vector<String8> subtags = AaptUtil::splitAndLowerCase(part, '+'); subtags.removeItemsAt(0); if (subtags.size() == 1) { setLanguage(subtags[0]); @@ -438,1349 +419,46 @@ void AaptLocaleValue::writeTo(ResTable_config* out) const { } } - -/* static */ bool -AaptGroupEntry::parseFilterNamePart(const String8& part, int* axis, AxisValue* value) -{ - ResTable_config config; - memset(&config, 0, sizeof(ResTable_config)); - - // IMSI - MCC - if (getMccName(part.string(), &config)) { - *axis = AXIS_MCC; - value->intValue = config.mcc; - return true; - } - - // IMSI - MNC - if (getMncName(part.string(), &config)) { - *axis = AXIS_MNC; - value->intValue = config.mnc; - return true; - } - - // locale - language - if (value->localeValue.initFromFilterString(part)) { - *axis = AXIS_LOCALE; - return true; - } - - // layout direction - if (getLayoutDirectionName(part.string(), &config)) { - *axis = AXIS_LAYOUTDIR; - value->intValue = (config.screenLayout&ResTable_config::MASK_LAYOUTDIR); - return true; - } - - // smallest screen dp width - if (getSmallestScreenWidthDpName(part.string(), &config)) { - *axis = AXIS_SMALLESTSCREENWIDTHDP; - value->intValue = config.smallestScreenWidthDp; - return true; - } - - // screen dp width - if (getScreenWidthDpName(part.string(), &config)) { - *axis = AXIS_SCREENWIDTHDP; - value->intValue = config.screenWidthDp; - return true; - } - - // screen dp height - if (getScreenHeightDpName(part.string(), &config)) { - *axis = AXIS_SCREENHEIGHTDP; - value->intValue = config.screenHeightDp; - return true; - } - - // screen layout size - if (getScreenLayoutSizeName(part.string(), &config)) { - *axis = AXIS_SCREENLAYOUTSIZE; - value->intValue = (config.screenLayout&ResTable_config::MASK_SCREENSIZE); - return true; - } - - // screen layout long - if (getScreenLayoutLongName(part.string(), &config)) { - *axis = AXIS_SCREENLAYOUTLONG; - value->intValue = (config.screenLayout&ResTable_config::MASK_SCREENLONG); - return true; - } - - // orientation - if (getOrientationName(part.string(), &config)) { - *axis = AXIS_ORIENTATION; - value->intValue = config.orientation; - return true; - } - - // ui mode type - if (getUiModeTypeName(part.string(), &config)) { - *axis = AXIS_UIMODETYPE; - value->intValue = (config.uiMode&ResTable_config::MASK_UI_MODE_TYPE); - return true; - } - - // ui mode night - if (getUiModeNightName(part.string(), &config)) { - *axis = AXIS_UIMODENIGHT; - value->intValue = (config.uiMode&ResTable_config::MASK_UI_MODE_NIGHT); - return true; - } - - // density - if (getDensityName(part.string(), &config)) { - *axis = AXIS_DENSITY; - value->intValue = config.density; - return true; - } - - // touchscreen - if (getTouchscreenName(part.string(), &config)) { - *axis = AXIS_TOUCHSCREEN; - value->intValue = config.touchscreen; - return true; - } - - // keyboard hidden - if (getKeysHiddenName(part.string(), &config)) { - *axis = AXIS_KEYSHIDDEN; - value->intValue = config.inputFlags; - return true; - } - - // keyboard - if (getKeyboardName(part.string(), &config)) { - *axis = AXIS_KEYBOARD; - value->intValue = config.keyboard; - return true; - } - - // navigation hidden - if (getNavHiddenName(part.string(), &config)) { - *axis = AXIS_NAVHIDDEN; - value->intValue = config.inputFlags; - return 0; - } - - // navigation - if (getNavigationName(part.string(), &config)) { - *axis = AXIS_NAVIGATION; - value->intValue = config.navigation; - return true; - } - - // screen size - if (getScreenSizeName(part.string(), &config)) { - *axis = AXIS_SCREENSIZE; - value->intValue = config.screenSize; - return true; - } - - // version - if (getVersionName(part.string(), &config)) { - *axis = AXIS_VERSION; - value->intValue = config.version; - return true; - } - - return false; -} - -AxisValue -AaptGroupEntry::getConfigValueForAxis(const ResTable_config& config, int axis) -{ - AxisValue value; - switch (axis) { - case AXIS_MCC: - value.intValue = config.mcc; - break; - case AXIS_MNC: - value.intValue = config.mnc; - break; - case AXIS_LOCALE: - value.localeValue.initFromResTable(config); - break; - case AXIS_LAYOUTDIR: - value.intValue = config.screenLayout&ResTable_config::MASK_LAYOUTDIR; - break; - case AXIS_SCREENLAYOUTSIZE: - value.intValue = config.screenLayout&ResTable_config::MASK_SCREENSIZE; - break; - case AXIS_ORIENTATION: - value.intValue = config.orientation; - break; - case AXIS_UIMODETYPE: - value.intValue = (config.uiMode&ResTable_config::MASK_UI_MODE_TYPE); - break; - case AXIS_UIMODENIGHT: - value.intValue = (config.uiMode&ResTable_config::MASK_UI_MODE_NIGHT); - break; - case AXIS_DENSITY: - value.intValue = config.density; - break; - case AXIS_TOUCHSCREEN: - value.intValue = config.touchscreen; - break; - case AXIS_KEYSHIDDEN: - value.intValue = config.inputFlags; - break; - case AXIS_KEYBOARD: - value.intValue = config.keyboard; - break; - case AXIS_NAVIGATION: - value.intValue = config.navigation; - break; - case AXIS_SCREENSIZE: - value.intValue = config.screenSize; - break; - case AXIS_SMALLESTSCREENWIDTHDP: - value.intValue = config.smallestScreenWidthDp; - break; - case AXIS_SCREENWIDTHDP: - value.intValue = config.screenWidthDp; - break; - case AXIS_SCREENHEIGHTDP: - value.intValue = config.screenHeightDp; - break; - case AXIS_VERSION: - value.intValue = config.version; - break; - } - - return value; -} - -bool -AaptGroupEntry::configSameExcept(const ResTable_config& config, - const ResTable_config& otherConfig, int axis) -{ - for (int i=AXIS_START; i<=AXIS_END; i++) { - if (i == axis) { - continue; - } - if (getConfigValueForAxis(config, i) != getConfigValueForAxis(otherConfig, i)) { - return false; - } - } - return true; -} - bool AaptGroupEntry::initFromDirName(const char* dir, String8* resType) { - mParamsChanged = true; - - Vector<String8> parts; - AaptLocaleValue::splitAndLowerCase(dir, &parts, '-'); - - String8 mcc, mnc, layoutsize, layoutlong, orient, den; - String8 touch, key, keysHidden, nav, navHidden, size, layoutDir, vers; - String8 uiModeType, uiModeNight, smallestwidthdp, widthdp, heightdp; - - AaptLocaleValue locale; - int numLocaleComponents = 0; - - const int N = parts.size(); - int index = 0; - String8 part = parts[index]; - - // resource type - if (!isValidResourceType(part)) { - return false; - } - *resType = part; - - index++; - if (index == N) { - goto success; - } - part = parts[index]; - - // imsi - mcc - if (getMccName(part.string())) { - mcc = part; - - index++; - if (index == N) { - goto success; - } - part = parts[index]; - } else { - //printf("not mcc: %s\n", part.string()); - } - - // imsi - mnc - if (getMncName(part.string())) { - mnc = part; - - index++; - if (index == N) { - goto success; - } - part = parts[index]; + const char* q = strchr(dir, '-'); + size_t typeLen; + if (q != NULL) { + typeLen = q - dir; } else { - //printf("not mnc: %s\n", part.string()); + typeLen = strlen(dir); } - index = locale.initFromDirName(parts, index); - if (index == -1) { + String8 type(dir, typeLen); + if (!isValidResourceType(type)) { return false; } - if (index >= N){ - goto success; - } - - part = parts[index]; - if (getLayoutDirectionName(part.string())) { - layoutDir = part; - - index++; - if (index == N) { - goto success; - } - part = parts[index]; - } else { - //printf("not layout direction: %s\n", part.string()); - } - - if (getSmallestScreenWidthDpName(part.string())) { - smallestwidthdp = part; - - index++; - if (index == N) { - goto success; - } - part = parts[index]; - } else { - //printf("not smallest screen width dp: %s\n", part.string()); - } - - if (getScreenWidthDpName(part.string())) { - widthdp = part; - - index++; - if (index == N) { - goto success; - } - part = parts[index]; - } else { - //printf("not screen width dp: %s\n", part.string()); - } - - if (getScreenHeightDpName(part.string())) { - heightdp = part; - - index++; - if (index == N) { - goto success; - } - part = parts[index]; - } else { - //printf("not screen height dp: %s\n", part.string()); - } - - if (getScreenLayoutSizeName(part.string())) { - layoutsize = part; - - index++; - if (index == N) { - goto success; - } - part = parts[index]; - } else { - //printf("not screen layout size: %s\n", part.string()); - } - - if (getScreenLayoutLongName(part.string())) { - layoutlong = part; - - index++; - if (index == N) { - goto success; - } - part = parts[index]; - } else { - //printf("not screen layout long: %s\n", part.string()); - } - - // orientation - if (getOrientationName(part.string())) { - orient = part; - - index++; - if (index == N) { - goto success; - } - part = parts[index]; - } else { - //printf("not orientation: %s\n", part.string()); - } - - // ui mode type - if (getUiModeTypeName(part.string())) { - uiModeType = part; - - index++; - if (index == N) { - goto success; - } - part = parts[index]; - } else { - //printf("not ui mode type: %s\n", part.string()); - } - - // ui mode night - if (getUiModeNightName(part.string())) { - uiModeNight = part; - - index++; - if (index == N) { - goto success; - } - part = parts[index]; - } else { - //printf("not ui mode night: %s\n", part.string()); - } - - // density - if (getDensityName(part.string())) { - den = part; - - index++; - if (index == N) { - goto success; - } - part = parts[index]; - } else { - //printf("not density: %s\n", part.string()); - } - - // touchscreen - if (getTouchscreenName(part.string())) { - touch = part; - - index++; - if (index == N) { - goto success; - } - part = parts[index]; - } else { - //printf("not touchscreen: %s\n", part.string()); - } - - // keyboard hidden - if (getKeysHiddenName(part.string())) { - keysHidden = part; - - index++; - if (index == N) { - goto success; - } - part = parts[index]; - } else { - //printf("not keysHidden: %s\n", part.string()); - } - // keyboard - if (getKeyboardName(part.string())) { - key = part; - - index++; - if (index == N) { - goto success; - } - part = parts[index]; - } else { - //printf("not keyboard: %s\n", part.string()); - } - - // navigation hidden - if (getNavHiddenName(part.string())) { - navHidden = part; - - index++; - if (index == N) { - goto success; - } - part = parts[index]; - } else { - //printf("not navHidden: %s\n", part.string()); - } - - if (getNavigationName(part.string())) { - nav = part; - - index++; - if (index == N) { - goto success; - } - part = parts[index]; - } else { - //printf("not navigation: %s\n", part.string()); - } - - if (getScreenSizeName(part.string())) { - size = part; - - index++; - if (index == N) { - goto success; - } - part = parts[index]; - } else { - //printf("not screen size: %s\n", part.string()); - } - - if (getVersionName(part.string())) { - vers = part; - - index++; - if (index == N) { - goto success; + if (q != NULL) { + if (!AaptConfig::parse(String8(q + 1), &mParams)) { + return false; } - part = parts[index]; - } else { - //printf("not version: %s\n", part.string()); } - // if there are extra parts, it doesn't match - return false; - -success: - this->mcc = mcc; - this->mnc = mnc; - this->locale = locale; - this->screenLayoutSize = layoutsize; - this->screenLayoutLong = layoutlong; - this->smallestScreenWidthDp = smallestwidthdp; - this->screenWidthDp = widthdp; - this->screenHeightDp = heightdp; - this->orientation = orient; - this->uiModeType = uiModeType; - this->uiModeNight = uiModeNight; - this->density = den; - this->touchscreen = touch; - this->keysHidden = keysHidden; - this->keyboard = key; - this->navHidden = navHidden; - this->navigation = nav; - this->screenSize = size; - this->layoutDirection = layoutDir; - this->version = vers; - - // what is this anyway? - this->vendor = ""; - + *resType = type; return true; } String8 -AaptGroupEntry::toString() const -{ - String8 s = this->mcc; - s += ","; - s += this->mnc; - s += ","; - s += locale.toDirName(); - s += ","; - s += layoutDirection; - s += ","; - s += smallestScreenWidthDp; - s += ","; - s += screenWidthDp; - s += ","; - s += screenHeightDp; - s += ","; - s += screenLayoutSize; - s += ","; - s += screenLayoutLong; - s += ","; - s += this->orientation; - s += ","; - s += uiModeType; - s += ","; - s += uiModeNight; - s += ","; - s += density; - s += ","; - s += touchscreen; - s += ","; - s += keysHidden; - s += ","; - s += keyboard; - s += ","; - s += navHidden; - s += ","; - s += navigation; - s += ","; - s += screenSize; - s += ","; - s += version; - return s; -} - -String8 AaptGroupEntry::toDirName(const String8& resType) const { String8 s = resType; - if (this->mcc != "") { - if (s.length() > 0) { - s += "-"; - } - s += mcc; - } - if (this->mnc != "") { - if (s.length() > 0) { - s += "-"; - } - s += mnc; - } - - const String8 localeComponent = locale.toDirName(); - if (localeComponent != "") { - if (s.length() > 0) { - s += "-"; - } - s += localeComponent; - } - - if (this->layoutDirection != "") { - if (s.length() > 0) { - s += "-"; - } - s += layoutDirection; - } - if (this->smallestScreenWidthDp != "") { - if (s.length() > 0) { - s += "-"; - } - s += smallestScreenWidthDp; - } - if (this->screenWidthDp != "") { - if (s.length() > 0) { - s += "-"; - } - s += screenWidthDp; - } - if (this->screenHeightDp != "") { - if (s.length() > 0) { - s += "-"; - } - s += screenHeightDp; - } - if (this->screenLayoutSize != "") { - if (s.length() > 0) { - s += "-"; - } - s += screenLayoutSize; - } - if (this->screenLayoutLong != "") { - if (s.length() > 0) { - s += "-"; - } - s += screenLayoutLong; - } - if (this->orientation != "") { - if (s.length() > 0) { - s += "-"; - } - s += orientation; - } - if (this->uiModeType != "") { - if (s.length() > 0) { - s += "-"; - } - s += uiModeType; - } - if (this->uiModeNight != "") { - if (s.length() > 0) { - s += "-"; - } - s += uiModeNight; - } - if (this->density != "") { - if (s.length() > 0) { - s += "-"; - } - s += density; - } - if (this->touchscreen != "") { - if (s.length() > 0) { - s += "-"; - } - s += touchscreen; - } - if (this->keysHidden != "") { - if (s.length() > 0) { - s += "-"; - } - s += keysHidden; - } - if (this->keyboard != "") { - if (s.length() > 0) { - s += "-"; - } - s += keyboard; - } - if (this->navHidden != "") { + String8 params = mParams.toString(); + if (params.length() > 0) { if (s.length() > 0) { s += "-"; } - s += navHidden; + s += params; } - if (this->navigation != "") { - if (s.length() > 0) { - s += "-"; - } - s += navigation; - } - if (this->screenSize != "") { - if (s.length() > 0) { - s += "-"; - } - s += screenSize; - } - if (this->version != "") { - if (s.length() > 0) { - s += "-"; - } - s += version; - } - return s; } -bool AaptGroupEntry::getMccName(const char* name, - ResTable_config* out) -{ - if (strcmp(name, kWildcardName) == 0) { - if (out) out->mcc = 0; - return true; - } - const char* c = name; - if (tolower(*c) != 'm') return false; - c++; - if (tolower(*c) != 'c') return false; - c++; - if (tolower(*c) != 'c') return false; - c++; - - const char* val = c; - - while (*c >= '0' && *c <= '9') { - c++; - } - if (*c != 0) return false; - if (c-val != 3) return false; - - int d = atoi(val); - if (d != 0) { - if (out) out->mcc = d; - return true; - } - - return false; -} - -bool AaptGroupEntry::getMncName(const char* name, - ResTable_config* out) -{ - if (strcmp(name, kWildcardName) == 0) { - if (out) out->mcc = 0; - return true; - } - const char* c = name; - if (tolower(*c) != 'm') return false; - c++; - if (tolower(*c) != 'n') return false; - c++; - if (tolower(*c) != 'c') return false; - c++; - - const char* val = c; - - while (*c >= '0' && *c <= '9') { - c++; - } - if (*c != 0) return false; - if (c-val == 0 || c-val > 3) return false; - - if (out) { - out->mnc = atoi(val); - if (out->mnc == 0) { - out->mnc = ACONFIGURATION_MNC_ZERO; - } - } - - return true; -} - -bool AaptGroupEntry::getLayoutDirectionName(const char* name, ResTable_config* out) -{ - if (strcmp(name, kWildcardName) == 0) { - if (out) out->screenLayout = - (out->screenLayout&~ResTable_config::MASK_LAYOUTDIR) - | ResTable_config::LAYOUTDIR_ANY; - return true; - } else if (strcmp(name, "ldltr") == 0) { - if (out) out->screenLayout = - (out->screenLayout&~ResTable_config::MASK_LAYOUTDIR) - | ResTable_config::LAYOUTDIR_LTR; - return true; - } else if (strcmp(name, "ldrtl") == 0) { - if (out) out->screenLayout = - (out->screenLayout&~ResTable_config::MASK_LAYOUTDIR) - | ResTable_config::LAYOUTDIR_RTL; - return true; - } - - return false; -} - -bool AaptGroupEntry::getScreenLayoutSizeName(const char* name, - ResTable_config* out) -{ - if (strcmp(name, kWildcardName) == 0) { - if (out) out->screenLayout = - (out->screenLayout&~ResTable_config::MASK_SCREENSIZE) - | ResTable_config::SCREENSIZE_ANY; - return true; - } else if (strcmp(name, "small") == 0) { - if (out) out->screenLayout = - (out->screenLayout&~ResTable_config::MASK_SCREENSIZE) - | ResTable_config::SCREENSIZE_SMALL; - return true; - } else if (strcmp(name, "normal") == 0) { - if (out) out->screenLayout = - (out->screenLayout&~ResTable_config::MASK_SCREENSIZE) - | ResTable_config::SCREENSIZE_NORMAL; - return true; - } else if (strcmp(name, "large") == 0) { - if (out) out->screenLayout = - (out->screenLayout&~ResTable_config::MASK_SCREENSIZE) - | ResTable_config::SCREENSIZE_LARGE; - return true; - } else if (strcmp(name, "xlarge") == 0) { - if (out) out->screenLayout = - (out->screenLayout&~ResTable_config::MASK_SCREENSIZE) - | ResTable_config::SCREENSIZE_XLARGE; - return true; - } - - return false; -} - -bool AaptGroupEntry::getScreenLayoutLongName(const char* name, - ResTable_config* out) -{ - if (strcmp(name, kWildcardName) == 0) { - if (out) out->screenLayout = - (out->screenLayout&~ResTable_config::MASK_SCREENLONG) - | ResTable_config::SCREENLONG_ANY; - return true; - } else if (strcmp(name, "long") == 0) { - if (out) out->screenLayout = - (out->screenLayout&~ResTable_config::MASK_SCREENLONG) - | ResTable_config::SCREENLONG_YES; - return true; - } else if (strcmp(name, "notlong") == 0) { - if (out) out->screenLayout = - (out->screenLayout&~ResTable_config::MASK_SCREENLONG) - | ResTable_config::SCREENLONG_NO; - return true; - } - - return false; -} - -bool AaptGroupEntry::getOrientationName(const char* name, - ResTable_config* out) -{ - if (strcmp(name, kWildcardName) == 0) { - if (out) out->orientation = out->ORIENTATION_ANY; - return true; - } else if (strcmp(name, "port") == 0) { - if (out) out->orientation = out->ORIENTATION_PORT; - return true; - } else if (strcmp(name, "land") == 0) { - if (out) out->orientation = out->ORIENTATION_LAND; - return true; - } else if (strcmp(name, "square") == 0) { - if (out) out->orientation = out->ORIENTATION_SQUARE; - return true; - } - - return false; -} - -bool AaptGroupEntry::getUiModeTypeName(const char* name, - ResTable_config* out) -{ - if (strcmp(name, kWildcardName) == 0) { - if (out) out->uiMode = - (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE) - | ResTable_config::UI_MODE_TYPE_ANY; - return true; - } else if (strcmp(name, "desk") == 0) { - if (out) out->uiMode = - (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE) - | ResTable_config::UI_MODE_TYPE_DESK; - return true; - } else if (strcmp(name, "car") == 0) { - if (out) out->uiMode = - (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE) - | ResTable_config::UI_MODE_TYPE_CAR; - return true; - } else if (strcmp(name, "television") == 0) { - if (out) out->uiMode = - (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE) - | ResTable_config::UI_MODE_TYPE_TELEVISION; - return true; - } else if (strcmp(name, "appliance") == 0) { - if (out) out->uiMode = - (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE) - | ResTable_config::UI_MODE_TYPE_APPLIANCE; - return true; - } else if (strcmp(name, "watch") == 0) { - if (out) out->uiMode = - (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE) - | ResTable_config::UI_MODE_TYPE_WATCH; - return true; - } - - return false; -} - -bool AaptGroupEntry::getUiModeNightName(const char* name, - ResTable_config* out) -{ - if (strcmp(name, kWildcardName) == 0) { - if (out) out->uiMode = - (out->uiMode&~ResTable_config::MASK_UI_MODE_NIGHT) - | ResTable_config::UI_MODE_NIGHT_ANY; - return true; - } else if (strcmp(name, "night") == 0) { - if (out) out->uiMode = - (out->uiMode&~ResTable_config::MASK_UI_MODE_NIGHT) - | ResTable_config::UI_MODE_NIGHT_YES; - return true; - } else if (strcmp(name, "notnight") == 0) { - if (out) out->uiMode = - (out->uiMode&~ResTable_config::MASK_UI_MODE_NIGHT) - | ResTable_config::UI_MODE_NIGHT_NO; - return true; - } - - return false; -} - -bool AaptGroupEntry::getDensityName(const char* name, - ResTable_config* out) -{ - if (strcmp(name, kWildcardName) == 0) { - if (out) out->density = ResTable_config::DENSITY_DEFAULT; - return true; - } - - if (strcmp(name, "nodpi") == 0) { - if (out) out->density = ResTable_config::DENSITY_NONE; - return true; - } - - if (strcmp(name, "ldpi") == 0) { - if (out) out->density = ResTable_config::DENSITY_LOW; - return true; - } - - if (strcmp(name, "mdpi") == 0) { - if (out) out->density = ResTable_config::DENSITY_MEDIUM; - return true; - } - - if (strcmp(name, "tvdpi") == 0) { - if (out) out->density = ResTable_config::DENSITY_TV; - return true; - } - - if (strcmp(name, "hdpi") == 0) { - if (out) out->density = ResTable_config::DENSITY_HIGH; - return true; - } - - if (strcmp(name, "xhdpi") == 0) { - if (out) out->density = ResTable_config::DENSITY_XHIGH; - return true; - } - - if (strcmp(name, "xxhdpi") == 0) { - if (out) out->density = ResTable_config::DENSITY_XXHIGH; - return true; - } - - if (strcmp(name, "xxxhdpi") == 0) { - if (out) out->density = ResTable_config::DENSITY_XXXHIGH; - return true; - } - - char* c = (char*)name; - while (*c >= '0' && *c <= '9') { - c++; - } - - // check that we have 'dpi' after the last digit. - if (toupper(c[0]) != 'D' || - toupper(c[1]) != 'P' || - toupper(c[2]) != 'I' || - c[3] != 0) { - return false; - } - - // temporarily replace the first letter with \0 to - // use atoi. - char tmp = c[0]; - c[0] = '\0'; - - int d = atoi(name); - c[0] = tmp; - - if (d != 0) { - if (out) out->density = d; - return true; - } - - return false; -} - -bool AaptGroupEntry::getTouchscreenName(const char* name, - ResTable_config* out) -{ - if (strcmp(name, kWildcardName) == 0) { - if (out) out->touchscreen = out->TOUCHSCREEN_ANY; - return true; - } else if (strcmp(name, "notouch") == 0) { - if (out) out->touchscreen = out->TOUCHSCREEN_NOTOUCH; - return true; - } else if (strcmp(name, "stylus") == 0) { - if (out) out->touchscreen = out->TOUCHSCREEN_STYLUS; - return true; - } else if (strcmp(name, "finger") == 0) { - if (out) out->touchscreen = out->TOUCHSCREEN_FINGER; - return true; - } - - return false; -} - -bool AaptGroupEntry::getKeysHiddenName(const char* name, - ResTable_config* out) -{ - uint8_t mask = 0; - uint8_t value = 0; - if (strcmp(name, kWildcardName) == 0) { - mask = ResTable_config::MASK_KEYSHIDDEN; - value = ResTable_config::KEYSHIDDEN_ANY; - } else if (strcmp(name, "keysexposed") == 0) { - mask = ResTable_config::MASK_KEYSHIDDEN; - value = ResTable_config::KEYSHIDDEN_NO; - } else if (strcmp(name, "keyshidden") == 0) { - mask = ResTable_config::MASK_KEYSHIDDEN; - value = ResTable_config::KEYSHIDDEN_YES; - } else if (strcmp(name, "keyssoft") == 0) { - mask = ResTable_config::MASK_KEYSHIDDEN; - value = ResTable_config::KEYSHIDDEN_SOFT; - } - - if (mask != 0) { - if (out) out->inputFlags = (out->inputFlags&~mask) | value; - return true; - } - - return false; -} - -bool AaptGroupEntry::getKeyboardName(const char* name, - ResTable_config* out) -{ - if (strcmp(name, kWildcardName) == 0) { - if (out) out->keyboard = out->KEYBOARD_ANY; - return true; - } else if (strcmp(name, "nokeys") == 0) { - if (out) out->keyboard = out->KEYBOARD_NOKEYS; - return true; - } else if (strcmp(name, "qwerty") == 0) { - if (out) out->keyboard = out->KEYBOARD_QWERTY; - return true; - } else if (strcmp(name, "12key") == 0) { - if (out) out->keyboard = out->KEYBOARD_12KEY; - return true; - } - - return false; -} - -bool AaptGroupEntry::getNavHiddenName(const char* name, - ResTable_config* out) -{ - uint8_t mask = 0; - uint8_t value = 0; - if (strcmp(name, kWildcardName) == 0) { - mask = ResTable_config::MASK_NAVHIDDEN; - value = ResTable_config::NAVHIDDEN_ANY; - } else if (strcmp(name, "navexposed") == 0) { - mask = ResTable_config::MASK_NAVHIDDEN; - value = ResTable_config::NAVHIDDEN_NO; - } else if (strcmp(name, "navhidden") == 0) { - mask = ResTable_config::MASK_NAVHIDDEN; - value = ResTable_config::NAVHIDDEN_YES; - } - - if (mask != 0) { - if (out) out->inputFlags = (out->inputFlags&~mask) | value; - return true; - } - - return false; -} - -bool AaptGroupEntry::getNavigationName(const char* name, - ResTable_config* out) -{ - if (strcmp(name, kWildcardName) == 0) { - if (out) out->navigation = out->NAVIGATION_ANY; - return true; - } else if (strcmp(name, "nonav") == 0) { - if (out) out->navigation = out->NAVIGATION_NONAV; - return true; - } else if (strcmp(name, "dpad") == 0) { - if (out) out->navigation = out->NAVIGATION_DPAD; - return true; - } else if (strcmp(name, "trackball") == 0) { - if (out) out->navigation = out->NAVIGATION_TRACKBALL; - return true; - } else if (strcmp(name, "wheel") == 0) { - if (out) out->navigation = out->NAVIGATION_WHEEL; - return true; - } - - return false; -} - -bool AaptGroupEntry::getScreenSizeName(const char* name, ResTable_config* out) -{ - if (strcmp(name, kWildcardName) == 0) { - if (out) { - out->screenWidth = out->SCREENWIDTH_ANY; - out->screenHeight = out->SCREENHEIGHT_ANY; - } - return true; - } - - const char* x = name; - while (*x >= '0' && *x <= '9') x++; - if (x == name || *x != 'x') return false; - String8 xName(name, x-name); - x++; - - const char* y = x; - while (*y >= '0' && *y <= '9') y++; - if (y == name || *y != 0) return false; - String8 yName(x, y-x); - - uint16_t w = (uint16_t)atoi(xName.string()); - uint16_t h = (uint16_t)atoi(yName.string()); - if (w < h) { - return false; - } - - if (out) { - out->screenWidth = w; - out->screenHeight = h; - } - - return true; -} - -bool AaptGroupEntry::getSmallestScreenWidthDpName(const char* name, ResTable_config* out) -{ - if (strcmp(name, kWildcardName) == 0) { - if (out) { - out->smallestScreenWidthDp = out->SCREENWIDTH_ANY; - } - return true; - } - - if (*name != 's') return false; - name++; - if (*name != 'w') return false; - name++; - const char* x = name; - while (*x >= '0' && *x <= '9') x++; - if (x == name || x[0] != 'd' || x[1] != 'p' || x[2] != 0) return false; - String8 xName(name, x-name); - - if (out) { - out->smallestScreenWidthDp = (uint16_t)atoi(xName.string()); - } - - return true; -} - -bool AaptGroupEntry::getScreenWidthDpName(const char* name, ResTable_config* out) -{ - if (strcmp(name, kWildcardName) == 0) { - if (out) { - out->screenWidthDp = out->SCREENWIDTH_ANY; - } - return true; - } - - if (*name != 'w') return false; - name++; - const char* x = name; - while (*x >= '0' && *x <= '9') x++; - if (x == name || x[0] != 'd' || x[1] != 'p' || x[2] != 0) return false; - String8 xName(name, x-name); - - if (out) { - out->screenWidthDp = (uint16_t)atoi(xName.string()); - } - - return true; -} - -bool AaptGroupEntry::getScreenHeightDpName(const char* name, ResTable_config* out) -{ - if (strcmp(name, kWildcardName) == 0) { - if (out) { - out->screenHeightDp = out->SCREENWIDTH_ANY; - } - return true; - } - - if (*name != 'h') return false; - name++; - const char* x = name; - while (*x >= '0' && *x <= '9') x++; - if (x == name || x[0] != 'd' || x[1] != 'p' || x[2] != 0) return false; - String8 xName(name, x-name); - - if (out) { - out->screenHeightDp = (uint16_t)atoi(xName.string()); - } - - return true; -} - -bool AaptGroupEntry::getVersionName(const char* name, ResTable_config* out) -{ - if (strcmp(name, kWildcardName) == 0) { - if (out) { - out->sdkVersion = out->SDKVERSION_ANY; - out->minorVersion = out->MINORVERSION_ANY; - } - return true; - } - - if (*name != 'v') { - return false; - } - - name++; - const char* s = name; - while (*s >= '0' && *s <= '9') s++; - if (s == name || *s != 0) return false; - String8 sdkName(name, s-name); - - if (out) { - out->sdkVersion = (uint16_t)atoi(sdkName.string()); - out->minorVersion = 0; - } - - return true; -} - -int AaptGroupEntry::compare(const AaptGroupEntry& o) const -{ - int v = mcc.compare(o.mcc); - if (v == 0) v = mnc.compare(o.mnc); - if (v == 0) v = locale.compare(o.locale); - if (v == 0) v = layoutDirection.compare(o.layoutDirection); - if (v == 0) v = vendor.compare(o.vendor); - if (v == 0) v = smallestScreenWidthDp.compare(o.smallestScreenWidthDp); - if (v == 0) v = screenWidthDp.compare(o.screenWidthDp); - if (v == 0) v = screenHeightDp.compare(o.screenHeightDp); - if (v == 0) v = screenLayoutSize.compare(o.screenLayoutSize); - if (v == 0) v = screenLayoutLong.compare(o.screenLayoutLong); - if (v == 0) v = orientation.compare(o.orientation); - if (v == 0) v = uiModeType.compare(o.uiModeType); - if (v == 0) v = uiModeNight.compare(o.uiModeNight); - if (v == 0) v = density.compare(o.density); - if (v == 0) v = touchscreen.compare(o.touchscreen); - if (v == 0) v = keysHidden.compare(o.keysHidden); - if (v == 0) v = keyboard.compare(o.keyboard); - if (v == 0) v = navHidden.compare(o.navHidden); - if (v == 0) v = navigation.compare(o.navigation); - if (v == 0) v = screenSize.compare(o.screenSize); - if (v == 0) v = version.compare(o.version); - return v; -} - -const ResTable_config AaptGroupEntry::toParams() const -{ - if (!mParamsChanged) { - return mParams; - } - - mParamsChanged = false; - ResTable_config& params = mParams; - memset(¶ms, 0, sizeof(ResTable_config)); - getMccName(mcc.string(), ¶ms); - getMncName(mnc.string(), ¶ms); - locale.writeTo(¶ms); - getLayoutDirectionName(layoutDirection.string(), ¶ms); - getSmallestScreenWidthDpName(smallestScreenWidthDp.string(), ¶ms); - getScreenWidthDpName(screenWidthDp.string(), ¶ms); - getScreenHeightDpName(screenHeightDp.string(), ¶ms); - getScreenLayoutSizeName(screenLayoutSize.string(), ¶ms); - getScreenLayoutLongName(screenLayoutLong.string(), ¶ms); - getOrientationName(orientation.string(), ¶ms); - getUiModeTypeName(uiModeType.string(), ¶ms); - getUiModeNightName(uiModeNight.string(), ¶ms); - getDensityName(density.string(), ¶ms); - getTouchscreenName(touchscreen.string(), ¶ms); - getKeysHiddenName(keysHidden.string(), ¶ms); - getKeyboardName(keyboard.string(), ¶ms); - getNavHiddenName(navHidden.string(), ¶ms); - getNavigationName(navigation.string(), ¶ms); - getScreenSizeName(screenSize.string(), ¶ms); - getVersionName(version.string(), ¶ms); - - // Fix up version number based on specified parameters. - int minSdk = 0; - if (params.smallestScreenWidthDp != ResTable_config::SCREENWIDTH_ANY - || params.screenWidthDp != ResTable_config::SCREENWIDTH_ANY - || params.screenHeightDp != ResTable_config::SCREENHEIGHT_ANY) { - minSdk = SDK_HONEYCOMB_MR2; - } else if ((params.uiMode&ResTable_config::MASK_UI_MODE_TYPE) - != ResTable_config::UI_MODE_TYPE_ANY - || (params.uiMode&ResTable_config::MASK_UI_MODE_NIGHT) - != ResTable_config::UI_MODE_NIGHT_ANY) { - minSdk = SDK_FROYO; - } else if ((params.screenLayout&ResTable_config::MASK_SCREENSIZE) - != ResTable_config::SCREENSIZE_ANY - || (params.screenLayout&ResTable_config::MASK_SCREENLONG) - != ResTable_config::SCREENLONG_ANY - || params.density != ResTable_config::DENSITY_DEFAULT) { - minSdk = SDK_DONUT; - } - - if (minSdk > params.sdkVersion) { - params.sdkVersion = minSdk; - } - - return params; -} // ========================================================================= // ========================================================================= @@ -2229,9 +907,7 @@ AaptAssets::AaptAssets() : AaptDir(String8(), String8()), mHavePrivateSymbols(false), mChanged(false), mHaveIncludedAssets(false), - mRes(NULL) -{ -} + mRes(NULL) {} const SortedVector<AaptGroupEntry>& AaptAssets::getGroupEntries() const { if (mChanged) { @@ -2506,7 +1182,7 @@ ssize_t AaptAssets::slurpResourceTree(Bundle* bundle, const String8& srcDir) String8 resType; bool b = group.initFromDirName(entry->d_name, &resType); if (!b) { - fprintf(stderr, "invalid resource directory name: %s/%s\n", srcDir.string(), + fprintf(stderr, "invalid resource directory name: %s %s\n", srcDir.string(), entry->d_name); err = -1; continue; @@ -2654,30 +1330,35 @@ bail: status_t AaptAssets::filter(Bundle* bundle) { - ResourceFilter reqFilter; + WeakResourceFilter reqFilter; status_t err = reqFilter.parse(bundle->getConfigurations()); if (err != NO_ERROR) { return err; } - ResourceFilter prefFilter; - err = prefFilter.parse(bundle->getPreferredConfigurations()); - if (err != NO_ERROR) { - return err; + uint32_t preferredDensity = 0; + if (bundle->getPreferredDensity().size() > 0) { + ResTable_config preferredConfig; + if (!AaptConfig::parseDensity(bundle->getPreferredDensity().string(), &preferredConfig)) { + fprintf(stderr, "Error parsing preferred density: %s\n", + bundle->getPreferredDensity().string()); + return UNKNOWN_ERROR; + } + preferredDensity = preferredConfig.density; } - if (reqFilter.isEmpty() && prefFilter.isEmpty()) { + if (reqFilter.isEmpty() && preferredDensity == 0) { return NO_ERROR; } if (bundle->getVerbose()) { if (!reqFilter.isEmpty()) { printf("Applying required filter: %s\n", - bundle->getConfigurations()); + bundle->getConfigurations().string()); } - if (!prefFilter.isEmpty()) { - printf("Applying preferred filter: %s\n", - bundle->getPreferredConfigurations()); + if (preferredDensity > 0) { + printf("Applying preferred density filter: %s\n", + bundle->getPreferredDensity().string()); } } @@ -2734,88 +1415,70 @@ status_t AaptAssets::filter(Bundle* bundle) } // Quick check: no preferred filters, nothing more to do. - if (prefFilter.isEmpty()) { + if (preferredDensity == 0) { continue; } // Get the preferred density if there is one. We do not match exactly for density. // If our preferred density is hdpi but we only have mdpi and xhdpi resources, we // pick xhdpi. - uint32_t preferredDensity = 0; - const SortedVector<AxisValue>* preferredConfigs = prefFilter.configsForAxis(AXIS_DENSITY); - if (preferredConfigs != NULL && preferredConfigs->size() > 0) { - preferredDensity = (*preferredConfigs)[0].intValue; - } + for (size_t k=0; k<grp->getFiles().size(); k++) { + sp<AaptFile> file = grp->getFiles().valueAt(k); + if (k == 0 && grp->getFiles().size() == 1) { + // If this is the only file left, we need to keep it. + // Otherwise the resource IDs we are using will be inconsistent + // with what we get when not stripping. Sucky, but at least + // for now we can rely on the back-end doing another filtering + // pass to take this out and leave us with this resource name + // containing no entries. + continue; + } + if (file->getPath().getPathExtension() == ".xml") { + // We can't remove .xml files at this point, because when + // we parse them they may add identifier resources, so + // removing them can cause our resource identifiers to + // become inconsistent. + continue; + } + const ResTable_config& config(file->getGroupEntry().toParams()); + if (config.density != 0 && config.density != preferredDensity) { + // This is a resource we would prefer not to have. Check + // to see if have a similar variation that we would like + // to have and, if so, we can drop it. + uint32_t bestDensity = config.density; + + for (size_t m=0; m<grp->getFiles().size(); m++) { + if (m == k) { + continue; + } - // Now deal with preferred configurations. - for (int axis=AXIS_START; axis<=AXIS_END; axis++) { - for (size_t k=0; k<grp->getFiles().size(); k++) { - sp<AaptFile> file = grp->getFiles().valueAt(k); - if (k == 0 && grp->getFiles().size() == 1) { - // If this is the only file left, we need to keep it. - // Otherwise the resource IDs we are using will be inconsistent - // with what we get when not stripping. Sucky, but at least - // for now we can rely on the back-end doing another filtering - // pass to take this out and leave us with this resource name - // containing no entries. - continue; - } - if (file->getPath().getPathExtension() == ".xml") { - // We can't remove .xml files at this point, because when - // we parse them they may add identifier resources, so - // removing them can cause our resource identifiers to - // become inconsistent. - continue; - } - const ResTable_config& config(file->getGroupEntry().toParams()); - if (!prefFilter.match(axis, config)) { - // This is a resource we would prefer not to have. Check - // to see if have a similar variation that we would like - // to have and, if so, we can drop it. - - uint32_t bestDensity = config.density; - - for (size_t m=0; m<grp->getFiles().size(); m++) { - if (m == k) continue; - sp<AaptFile> mfile = grp->getFiles().valueAt(m); - const ResTable_config& mconfig(mfile->getGroupEntry().toParams()); - if (AaptGroupEntry::configSameExcept(config, mconfig, axis)) { - if (axis == AXIS_DENSITY && preferredDensity > 0) { - // See if there is a better density resource - if (mconfig.density < bestDensity && - mconfig.density > preferredDensity && - bestDensity > preferredDensity) { - // This density is between our best density and - // the preferred density, therefore it is better. - bestDensity = mconfig.density; - } else if (mconfig.density > bestDensity && - bestDensity < preferredDensity) { - // This density is better than our best density and - // our best density was smaller than our preferred - // density, so it is better. - bestDensity = mconfig.density; - } - } else if (prefFilter.match(axis, mconfig)) { - if (bundle->getVerbose()) { - printf("Pruning unneeded resource: %s\n", - file->getPrintableSource().string()); - } - grp->removeFile(k); - k--; - break; - } + sp<AaptFile> mfile = grp->getFiles().valueAt(m); + const ResTable_config& mconfig(mfile->getGroupEntry().toParams()); + if (AaptConfig::isSameExcept(config, mconfig, ResTable_config::CONFIG_DENSITY)) { + // See if there is a better density resource + if (mconfig.density < bestDensity && + mconfig.density > preferredDensity && + bestDensity > preferredDensity) { + // This density is between our best density and + // the preferred density, therefore it is better. + bestDensity = mconfig.density; + } else if (mconfig.density > bestDensity && + bestDensity < preferredDensity) { + // This density is better than our best density and + // our best density was smaller than our preferred + // density, so it is better. + bestDensity = mconfig.density; } } + } - if (axis == AXIS_DENSITY && preferredDensity > 0 && - bestDensity != config.density) { - if (bundle->getVerbose()) { - printf("Pruning unneeded resource: %s\n", - file->getPrintableSource().string()); - } - grp->removeFile(k); - k--; + if (bestDensity != config.density) { + if (bundle->getVerbose()) { + printf("Pruning unneeded resource: %s\n", + file->getPrintableSource().string()); } + grp->removeFile(k); + k--; } } } diff --git a/tools/aapt/AaptAssets.h b/tools/aapt/AaptAssets.h index 82dda5f..0c2576a 100644 --- a/tools/aapt/AaptAssets.h +++ b/tools/aapt/AaptAssets.h @@ -6,22 +6,24 @@ #ifndef __AAPT_ASSETS_H #define __AAPT_ASSETS_H -#include <stdlib.h> #include <androidfw/AssetManager.h> #include <androidfw/ResourceTypes.h> +#include <stdlib.h> +#include <set> #include <utils/KeyedVector.h> #include <utils/RefBase.h> #include <utils/SortedVector.h> #include <utils/String8.h> #include <utils/Vector.h> -#include "ZipFile.h" +#include "AaptConfig.h" #include "Bundle.h" +#include "ConfigDescription.h" #include "SourcePos.h" +#include "ZipFile.h" using namespace android; - extern const char * const gDefaultIgnoreAssets; extern const char * gUserIgnoreAssets; @@ -82,9 +84,6 @@ struct AaptLocaleValue { return memcmp(this, &other, sizeof(AaptLocaleValue)); } - static void splitAndLowerCase(const char* const chars, Vector<String8>* parts, - const char separator); - inline bool operator<(const AaptLocaleValue& o) const { return compare(o) < 0; } inline bool operator<=(const AaptLocaleValue& o) const { return compare(o) <= 0; } inline bool operator==(const AaptLocaleValue& o) const { return compare(o) == 0; } @@ -98,31 +97,6 @@ private: void setVariant(const char* variant); }; -struct AxisValue { - // Used for all axes except AXIS_LOCALE, which is represented - // as a AaptLocaleValue value. - int intValue; - AaptLocaleValue localeValue; - - AxisValue() : intValue(0) { - } - - inline int compare(const AxisValue &other) const { - if (intValue != other.intValue) { - return intValue - other.intValue; - } - - return localeValue.compare(other.localeValue); - } - - inline bool operator<(const AxisValue& o) const { return compare(o) < 0; } - inline bool operator<=(const AxisValue& o) const { return compare(o) <= 0; } - inline bool operator==(const AxisValue& o) const { return compare(o) == 0; } - inline bool operator!=(const AxisValue& o) const { return compare(o) != 0; } - inline bool operator>=(const AxisValue& o) const { return compare(o) >= 0; } - inline bool operator>(const AxisValue& o) const { return compare(o) > 0; } -}; - /** * This structure contains a specific variation of a single file out * of all the variations it can have that we can have. @@ -130,23 +104,11 @@ struct AxisValue { struct AaptGroupEntry { public: - AaptGroupEntry() : mParamsChanged(true) { - memset(&mParams, 0, sizeof(ResTable_config)); - } - bool initFromDirName(const char* dir, String8* resType); - static bool parseFilterNamePart(const String8& part, int* axis, AxisValue* value); - - static AxisValue getConfigValueForAxis(const ResTable_config& config, int axis); - - static bool configSameExcept(const ResTable_config& config, - const ResTable_config& otherConfig, int axis); - - int compare(const AaptGroupEntry& o) const; - - const ResTable_config toParams() const; + inline const ConfigDescription& toParams() const { return mParams; } + inline int compare(const AaptGroupEntry& o) const { return mParams.compareLogical(o.mParams); } inline bool operator<(const AaptGroupEntry& o) const { return compare(o) < 0; } inline bool operator<=(const AaptGroupEntry& o) const { return compare(o) <= 0; } inline bool operator==(const AaptGroupEntry& o) const { return compare(o) == 0; } @@ -154,56 +116,13 @@ public: inline bool operator>=(const AaptGroupEntry& o) const { return compare(o) >= 0; } inline bool operator>(const AaptGroupEntry& o) const { return compare(o) > 0; } - String8 toString() const; + String8 toString() const { return mParams.toString(); } String8 toDirName(const String8& resType) const; - const String8& getVersionString() const { return version; } + const String8 getVersionString() const { return AaptConfig::getVersion(mParams); } private: - static bool getMccName(const char* name, ResTable_config* out = NULL); - static bool getMncName(const char* name, ResTable_config* out = NULL); - static bool getScreenLayoutSizeName(const char* name, ResTable_config* out = NULL); - static bool getScreenLayoutLongName(const char* name, ResTable_config* out = NULL); - static bool getOrientationName(const char* name, ResTable_config* out = NULL); - static bool getUiModeTypeName(const char* name, ResTable_config* out = NULL); - static bool getUiModeNightName(const char* name, ResTable_config* out = NULL); - static bool getDensityName(const char* name, ResTable_config* out = NULL); - static bool getTouchscreenName(const char* name, ResTable_config* out = NULL); - static bool getKeysHiddenName(const char* name, ResTable_config* out = NULL); - static bool getKeyboardName(const char* name, ResTable_config* out = NULL); - static bool getNavigationName(const char* name, ResTable_config* out = NULL); - static bool getNavHiddenName(const char* name, ResTable_config* out = NULL); - static bool getScreenSizeName(const char* name, ResTable_config* out = NULL); - static bool getSmallestScreenWidthDpName(const char* name, ResTable_config* out = NULL); - static bool getScreenWidthDpName(const char* name, ResTable_config* out = NULL); - static bool getScreenHeightDpName(const char* name, ResTable_config* out = NULL); - static bool getLayoutDirectionName(const char* name, ResTable_config* out = NULL); - static bool getVersionName(const char* name, ResTable_config* out = NULL); - - String8 mcc; - String8 mnc; - AaptLocaleValue locale; - String8 vendor; - String8 smallestScreenWidthDp; - String8 screenWidthDp; - String8 screenHeightDp; - String8 screenLayoutSize; - String8 screenLayoutLong; - String8 orientation; - String8 uiModeType; - String8 uiModeNight; - String8 density; - String8 touchscreen; - String8 keysHidden; - String8 keyboard; - String8 navHidden; - String8 navigation; - String8 screenSize; - String8 layoutDirection; - String8 version; - - mutable bool mParamsChanged; - mutable ResTable_config mParams; + ConfigDescription mParams; }; inline int compare_type(const AaptGroupEntry& lhs, const AaptGroupEntry& rhs) diff --git a/tools/aapt/AaptConfig.cpp b/tools/aapt/AaptConfig.cpp new file mode 100644 index 0000000..69a9c7f --- /dev/null +++ b/tools/aapt/AaptConfig.cpp @@ -0,0 +1,790 @@ +/* + * 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. + */ + +#include <androidfw/ResourceTypes.h> +#include <ctype.h> + +#include "AaptConfig.h" +#include "AaptAssets.h" +#include "AaptUtil.h" +#include "ResourceFilter.h" + +using android::String8; +using android::Vector; +using android::ResTable_config; + +namespace AaptConfig { + +static const char* kWildcardName = "any"; + +bool parse(const String8& str, ConfigDescription* out) { + Vector<String8> parts = AaptUtil::splitAndLowerCase(str, '-'); + + ConfigDescription config; + AaptLocaleValue locale; + ssize_t index = 0; + ssize_t localeIndex = 0; + const ssize_t N = parts.size(); + const char* part = parts[index].string(); + + if (str.length() == 0) { + goto success; + } + + if (parseMcc(part, &config)) { + index++; + if (index == N) { + goto success; + } + part = parts[index].string(); + } + + if (parseMnc(part, &config)) { + index++; + if (index == N) { + goto success; + } + part = parts[index].string(); + } + + // Locale spans a few '-' separators, so we let it + // control the index. + localeIndex = locale.initFromDirName(parts, index); + if (localeIndex < 0) { + return false; + } else if (localeIndex > index) { + locale.writeTo(&config); + index = localeIndex; + if (index >= N) { + goto success; + } + part = parts[index].string(); + } + + if (parseLayoutDirection(part, &config)) { + index++; + if (index == N) { + goto success; + } + part = parts[index].string(); + } + + if (parseSmallestScreenWidthDp(part, &config)) { + index++; + if (index == N) { + goto success; + } + part = parts[index].string(); + } + + if (parseScreenWidthDp(part, &config)) { + index++; + if (index == N) { + goto success; + } + part = parts[index].string(); + } + + if (parseScreenHeightDp(part, &config)) { + index++; + if (index == N) { + goto success; + } + part = parts[index].string(); + } + + if (parseScreenLayoutSize(part, &config)) { + index++; + if (index == N) { + goto success; + } + part = parts[index].string(); + } + + if (parseScreenLayoutLong(part, &config)) { + index++; + if (index == N) { + goto success; + } + part = parts[index].string(); + } + + if (parseOrientation(part, &config)) { + index++; + if (index == N) { + goto success; + } + part = parts[index].string(); + } + + if (parseUiModeType(part, &config)) { + index++; + if (index == N) { + goto success; + } + part = parts[index].string(); + } + + if (parseUiModeNight(part, &config)) { + index++; + if (index == N) { + goto success; + } + part = parts[index].string(); + } + + if (parseDensity(part, &config)) { + index++; + if (index == N) { + goto success; + } + part = parts[index].string(); + } + + if (parseTouchscreen(part, &config)) { + index++; + if (index == N) { + goto success; + } + part = parts[index].string(); + } + + if (parseKeysHidden(part, &config)) { + index++; + if (index == N) { + goto success; + } + part = parts[index].string(); + } + + if (parseKeyboard(part, &config)) { + index++; + if (index == N) { + goto success; + } + part = parts[index].string(); + } + + if (parseNavHidden(part, &config)) { + index++; + if (index == N) { + goto success; + } + part = parts[index].string(); + } + + if (parseNavigation(part, &config)) { + index++; + if (index == N) { + goto success; + } + part = parts[index].string(); + } + + if (parseScreenSize(part, &config)) { + index++; + if (index == N) { + goto success; + } + part = parts[index].string(); + } + + if (parseVersion(part, &config)) { + index++; + if (index == N) { + goto success; + } + part = parts[index].string(); + } + + // Unrecognized. + return false; + +success: + if (out != NULL) { + applyVersionForCompatibility(&config); + *out = config; + } + return true; +} + +bool parseCommaSeparatedList(const String8& str, std::set<ConfigDescription>* outSet) { + Vector<String8> parts = AaptUtil::splitAndLowerCase(str, ','); + const size_t N = parts.size(); + for (size_t i = 0; i < N; i++) { + ConfigDescription config; + if (!parse(parts[i], &config)) { + return false; + } + outSet->insert(config); + } + return true; +} + +void applyVersionForCompatibility(ConfigDescription* config) { + if (config == NULL) { + return; + } + + uint16_t minSdk = 0; + if (config->smallestScreenWidthDp != ResTable_config::SCREENWIDTH_ANY + || config->screenWidthDp != ResTable_config::SCREENWIDTH_ANY + || config->screenHeightDp != ResTable_config::SCREENHEIGHT_ANY) { + minSdk = SDK_HONEYCOMB_MR2; + } else if ((config->uiMode & ResTable_config::MASK_UI_MODE_TYPE) + != ResTable_config::UI_MODE_TYPE_ANY + || (config->uiMode & ResTable_config::MASK_UI_MODE_NIGHT) + != ResTable_config::UI_MODE_NIGHT_ANY) { + minSdk = SDK_FROYO; + } else if ((config->screenLayout & ResTable_config::MASK_SCREENSIZE) + != ResTable_config::SCREENSIZE_ANY + || (config->screenLayout & ResTable_config::MASK_SCREENLONG) + != ResTable_config::SCREENLONG_ANY + || config->density != ResTable_config::DENSITY_DEFAULT) { + minSdk = SDK_DONUT; + } + + if (minSdk > config->sdkVersion) { + config->sdkVersion = minSdk; + } +} + +bool parseMcc(const char* name, ResTable_config* out) { + if (strcmp(name, kWildcardName) == 0) { + if (out) out->mcc = 0; + return true; + } + const char* c = name; + if (tolower(*c) != 'm') return false; + c++; + if (tolower(*c) != 'c') return false; + c++; + if (tolower(*c) != 'c') return false; + c++; + + const char* val = c; + + while (*c >= '0' && *c <= '9') { + c++; + } + if (*c != 0) return false; + if (c-val != 3) return false; + + int d = atoi(val); + if (d != 0) { + if (out) out->mcc = d; + return true; + } + + return false; +} + +bool parseMnc(const char* name, ResTable_config* out) { + if (strcmp(name, kWildcardName) == 0) { + if (out) out->mcc = 0; + return true; + } + const char* c = name; + if (tolower(*c) != 'm') return false; + c++; + if (tolower(*c) != 'n') return false; + c++; + if (tolower(*c) != 'c') return false; + c++; + + const char* val = c; + + while (*c >= '0' && *c <= '9') { + c++; + } + if (*c != 0) return false; + if (c-val == 0 || c-val > 3) return false; + + if (out) { + out->mnc = atoi(val); + if (out->mnc == 0) { + out->mnc = ACONFIGURATION_MNC_ZERO; + } + } + + return true; +} + +bool parseLayoutDirection(const char* name, ResTable_config* out) { + if (strcmp(name, kWildcardName) == 0) { + if (out) out->screenLayout = + (out->screenLayout&~ResTable_config::MASK_LAYOUTDIR) + | ResTable_config::LAYOUTDIR_ANY; + return true; + } else if (strcmp(name, "ldltr") == 0) { + if (out) out->screenLayout = + (out->screenLayout&~ResTable_config::MASK_LAYOUTDIR) + | ResTable_config::LAYOUTDIR_LTR; + return true; + } else if (strcmp(name, "ldrtl") == 0) { + if (out) out->screenLayout = + (out->screenLayout&~ResTable_config::MASK_LAYOUTDIR) + | ResTable_config::LAYOUTDIR_RTL; + return true; + } + + return false; +} + +bool parseScreenLayoutSize(const char* name, ResTable_config* out) { + if (strcmp(name, kWildcardName) == 0) { + if (out) out->screenLayout = + (out->screenLayout&~ResTable_config::MASK_SCREENSIZE) + | ResTable_config::SCREENSIZE_ANY; + return true; + } else if (strcmp(name, "small") == 0) { + if (out) out->screenLayout = + (out->screenLayout&~ResTable_config::MASK_SCREENSIZE) + | ResTable_config::SCREENSIZE_SMALL; + return true; + } else if (strcmp(name, "normal") == 0) { + if (out) out->screenLayout = + (out->screenLayout&~ResTable_config::MASK_SCREENSIZE) + | ResTable_config::SCREENSIZE_NORMAL; + return true; + } else if (strcmp(name, "large") == 0) { + if (out) out->screenLayout = + (out->screenLayout&~ResTable_config::MASK_SCREENSIZE) + | ResTable_config::SCREENSIZE_LARGE; + return true; + } else if (strcmp(name, "xlarge") == 0) { + if (out) out->screenLayout = + (out->screenLayout&~ResTable_config::MASK_SCREENSIZE) + | ResTable_config::SCREENSIZE_XLARGE; + return true; + } + + return false; +} + +bool parseScreenLayoutLong(const char* name, ResTable_config* out) { + if (strcmp(name, kWildcardName) == 0) { + if (out) out->screenLayout = + (out->screenLayout&~ResTable_config::MASK_SCREENLONG) + | ResTable_config::SCREENLONG_ANY; + return true; + } else if (strcmp(name, "long") == 0) { + if (out) out->screenLayout = + (out->screenLayout&~ResTable_config::MASK_SCREENLONG) + | ResTable_config::SCREENLONG_YES; + return true; + } else if (strcmp(name, "notlong") == 0) { + if (out) out->screenLayout = + (out->screenLayout&~ResTable_config::MASK_SCREENLONG) + | ResTable_config::SCREENLONG_NO; + return true; + } + + return false; +} + +bool parseOrientation(const char* name, ResTable_config* out) { + if (strcmp(name, kWildcardName) == 0) { + if (out) out->orientation = out->ORIENTATION_ANY; + return true; + } else if (strcmp(name, "port") == 0) { + if (out) out->orientation = out->ORIENTATION_PORT; + return true; + } else if (strcmp(name, "land") == 0) { + if (out) out->orientation = out->ORIENTATION_LAND; + return true; + } else if (strcmp(name, "square") == 0) { + if (out) out->orientation = out->ORIENTATION_SQUARE; + return true; + } + + return false; +} + +bool parseUiModeType(const char* name, ResTable_config* out) { + if (strcmp(name, kWildcardName) == 0) { + if (out) out->uiMode = + (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE) + | ResTable_config::UI_MODE_TYPE_ANY; + return true; + } else if (strcmp(name, "desk") == 0) { + if (out) out->uiMode = + (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE) + | ResTable_config::UI_MODE_TYPE_DESK; + return true; + } else if (strcmp(name, "car") == 0) { + if (out) out->uiMode = + (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE) + | ResTable_config::UI_MODE_TYPE_CAR; + return true; + } else if (strcmp(name, "television") == 0) { + if (out) out->uiMode = + (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE) + | ResTable_config::UI_MODE_TYPE_TELEVISION; + return true; + } else if (strcmp(name, "appliance") == 0) { + if (out) out->uiMode = + (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE) + | ResTable_config::UI_MODE_TYPE_APPLIANCE; + return true; + } else if (strcmp(name, "watch") == 0) { + if (out) out->uiMode = + (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE) + | ResTable_config::UI_MODE_TYPE_WATCH; + return true; + } + + return false; +} + +bool parseUiModeNight(const char* name, ResTable_config* out) { + if (strcmp(name, kWildcardName) == 0) { + if (out) out->uiMode = + (out->uiMode&~ResTable_config::MASK_UI_MODE_NIGHT) + | ResTable_config::UI_MODE_NIGHT_ANY; + return true; + } else if (strcmp(name, "night") == 0) { + if (out) out->uiMode = + (out->uiMode&~ResTable_config::MASK_UI_MODE_NIGHT) + | ResTable_config::UI_MODE_NIGHT_YES; + return true; + } else if (strcmp(name, "notnight") == 0) { + if (out) out->uiMode = + (out->uiMode&~ResTable_config::MASK_UI_MODE_NIGHT) + | ResTable_config::UI_MODE_NIGHT_NO; + return true; + } + + return false; +} + +bool parseDensity(const char* name, ResTable_config* out) { + if (strcmp(name, kWildcardName) == 0) { + if (out) out->density = ResTable_config::DENSITY_DEFAULT; + return true; + } + + if (strcmp(name, "nodpi") == 0) { + if (out) out->density = ResTable_config::DENSITY_NONE; + return true; + } + + if (strcmp(name, "ldpi") == 0) { + if (out) out->density = ResTable_config::DENSITY_LOW; + return true; + } + + if (strcmp(name, "mdpi") == 0) { + if (out) out->density = ResTable_config::DENSITY_MEDIUM; + return true; + } + + if (strcmp(name, "tvdpi") == 0) { + if (out) out->density = ResTable_config::DENSITY_TV; + return true; + } + + if (strcmp(name, "hdpi") == 0) { + if (out) out->density = ResTable_config::DENSITY_HIGH; + return true; + } + + if (strcmp(name, "xhdpi") == 0) { + if (out) out->density = ResTable_config::DENSITY_XHIGH; + return true; + } + + if (strcmp(name, "xxhdpi") == 0) { + if (out) out->density = ResTable_config::DENSITY_XXHIGH; + return true; + } + + if (strcmp(name, "xxxhdpi") == 0) { + if (out) out->density = ResTable_config::DENSITY_XXXHIGH; + return true; + } + + char* c = (char*)name; + while (*c >= '0' && *c <= '9') { + c++; + } + + // check that we have 'dpi' after the last digit. + if (toupper(c[0]) != 'D' || + toupper(c[1]) != 'P' || + toupper(c[2]) != 'I' || + c[3] != 0) { + return false; + } + + // temporarily replace the first letter with \0 to + // use atoi. + char tmp = c[0]; + c[0] = '\0'; + + int d = atoi(name); + c[0] = tmp; + + if (d != 0) { + if (out) out->density = d; + return true; + } + + return false; +} + +bool parseTouchscreen(const char* name, ResTable_config* out) { + if (strcmp(name, kWildcardName) == 0) { + if (out) out->touchscreen = out->TOUCHSCREEN_ANY; + return true; + } else if (strcmp(name, "notouch") == 0) { + if (out) out->touchscreen = out->TOUCHSCREEN_NOTOUCH; + return true; + } else if (strcmp(name, "stylus") == 0) { + if (out) out->touchscreen = out->TOUCHSCREEN_STYLUS; + return true; + } else if (strcmp(name, "finger") == 0) { + if (out) out->touchscreen = out->TOUCHSCREEN_FINGER; + return true; + } + + return false; +} + +bool parseKeysHidden(const char* name, ResTable_config* out) { + uint8_t mask = 0; + uint8_t value = 0; + if (strcmp(name, kWildcardName) == 0) { + mask = ResTable_config::MASK_KEYSHIDDEN; + value = ResTable_config::KEYSHIDDEN_ANY; + } else if (strcmp(name, "keysexposed") == 0) { + mask = ResTable_config::MASK_KEYSHIDDEN; + value = ResTable_config::KEYSHIDDEN_NO; + } else if (strcmp(name, "keyshidden") == 0) { + mask = ResTable_config::MASK_KEYSHIDDEN; + value = ResTable_config::KEYSHIDDEN_YES; + } else if (strcmp(name, "keyssoft") == 0) { + mask = ResTable_config::MASK_KEYSHIDDEN; + value = ResTable_config::KEYSHIDDEN_SOFT; + } + + if (mask != 0) { + if (out) out->inputFlags = (out->inputFlags&~mask) | value; + return true; + } + + return false; +} + +bool parseKeyboard(const char* name, ResTable_config* out) { + if (strcmp(name, kWildcardName) == 0) { + if (out) out->keyboard = out->KEYBOARD_ANY; + return true; + } else if (strcmp(name, "nokeys") == 0) { + if (out) out->keyboard = out->KEYBOARD_NOKEYS; + return true; + } else if (strcmp(name, "qwerty") == 0) { + if (out) out->keyboard = out->KEYBOARD_QWERTY; + return true; + } else if (strcmp(name, "12key") == 0) { + if (out) out->keyboard = out->KEYBOARD_12KEY; + return true; + } + + return false; +} + +bool parseNavHidden(const char* name, ResTable_config* out) { + uint8_t mask = 0; + uint8_t value = 0; + if (strcmp(name, kWildcardName) == 0) { + mask = ResTable_config::MASK_NAVHIDDEN; + value = ResTable_config::NAVHIDDEN_ANY; + } else if (strcmp(name, "navexposed") == 0) { + mask = ResTable_config::MASK_NAVHIDDEN; + value = ResTable_config::NAVHIDDEN_NO; + } else if (strcmp(name, "navhidden") == 0) { + mask = ResTable_config::MASK_NAVHIDDEN; + value = ResTable_config::NAVHIDDEN_YES; + } + + if (mask != 0) { + if (out) out->inputFlags = (out->inputFlags&~mask) | value; + return true; + } + + return false; +} + +bool parseNavigation(const char* name, ResTable_config* out) { + if (strcmp(name, kWildcardName) == 0) { + if (out) out->navigation = out->NAVIGATION_ANY; + return true; + } else if (strcmp(name, "nonav") == 0) { + if (out) out->navigation = out->NAVIGATION_NONAV; + return true; + } else if (strcmp(name, "dpad") == 0) { + if (out) out->navigation = out->NAVIGATION_DPAD; + return true; + } else if (strcmp(name, "trackball") == 0) { + if (out) out->navigation = out->NAVIGATION_TRACKBALL; + return true; + } else if (strcmp(name, "wheel") == 0) { + if (out) out->navigation = out->NAVIGATION_WHEEL; + return true; + } + + return false; +} + +bool parseScreenSize(const char* name, ResTable_config* out) { + if (strcmp(name, kWildcardName) == 0) { + if (out) { + out->screenWidth = out->SCREENWIDTH_ANY; + out->screenHeight = out->SCREENHEIGHT_ANY; + } + return true; + } + + const char* x = name; + while (*x >= '0' && *x <= '9') x++; + if (x == name || *x != 'x') return false; + String8 xName(name, x-name); + x++; + + const char* y = x; + while (*y >= '0' && *y <= '9') y++; + if (y == name || *y != 0) return false; + String8 yName(x, y-x); + + uint16_t w = (uint16_t)atoi(xName.string()); + uint16_t h = (uint16_t)atoi(yName.string()); + if (w < h) { + return false; + } + + if (out) { + out->screenWidth = w; + out->screenHeight = h; + } + + return true; +} + +bool parseSmallestScreenWidthDp(const char* name, ResTable_config* out) { + if (strcmp(name, kWildcardName) == 0) { + if (out) { + out->smallestScreenWidthDp = out->SCREENWIDTH_ANY; + } + return true; + } + + if (*name != 's') return false; + name++; + if (*name != 'w') return false; + name++; + const char* x = name; + while (*x >= '0' && *x <= '9') x++; + if (x == name || x[0] != 'd' || x[1] != 'p' || x[2] != 0) return false; + String8 xName(name, x-name); + + if (out) { + out->smallestScreenWidthDp = (uint16_t)atoi(xName.string()); + } + + return true; +} + +bool parseScreenWidthDp(const char* name, ResTable_config* out) { + if (strcmp(name, kWildcardName) == 0) { + if (out) { + out->screenWidthDp = out->SCREENWIDTH_ANY; + } + return true; + } + + if (*name != 'w') return false; + name++; + const char* x = name; + while (*x >= '0' && *x <= '9') x++; + if (x == name || x[0] != 'd' || x[1] != 'p' || x[2] != 0) return false; + String8 xName(name, x-name); + + if (out) { + out->screenWidthDp = (uint16_t)atoi(xName.string()); + } + + return true; +} + +bool parseScreenHeightDp(const char* name, ResTable_config* out) { + if (strcmp(name, kWildcardName) == 0) { + if (out) { + out->screenHeightDp = out->SCREENWIDTH_ANY; + } + return true; + } + + if (*name != 'h') return false; + name++; + const char* x = name; + while (*x >= '0' && *x <= '9') x++; + if (x == name || x[0] != 'd' || x[1] != 'p' || x[2] != 0) return false; + String8 xName(name, x-name); + + if (out) { + out->screenHeightDp = (uint16_t)atoi(xName.string()); + } + + return true; +} + +bool parseVersion(const char* name, ResTable_config* out) { + if (strcmp(name, kWildcardName) == 0) { + if (out) { + out->sdkVersion = out->SDKVERSION_ANY; + out->minorVersion = out->MINORVERSION_ANY; + } + return true; + } + + if (*name != 'v') { + return false; + } + + name++; + const char* s = name; + while (*s >= '0' && *s <= '9') s++; + if (s == name || *s != 0) return false; + String8 sdkName(name, s-name); + + if (out) { + out->sdkVersion = (uint16_t)atoi(sdkName.string()); + out->minorVersion = 0; + } + + return true; +} + +String8 getVersion(const ResTable_config& config) { + return String8::format("v%u", config.sdkVersion); +} + +bool isSameExcept(const ResTable_config& a, const ResTable_config& b, int axisMask) { + return a.diff(b) == axisMask; +} + +} // namespace AaptConfig diff --git a/tools/aapt/AaptConfig.h b/tools/aapt/AaptConfig.h new file mode 100644 index 0000000..2963539 --- /dev/null +++ b/tools/aapt/AaptConfig.h @@ -0,0 +1,85 @@ +/* + * 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. + */ + +#ifndef __AAPT_CONFIG_H +#define __AAPT_CONFIG_H + +#include <set> +#include <utils/String8.h> + +#include "ConfigDescription.h" + +/** + * Utility methods for dealing with configurations. + */ +namespace AaptConfig { + +/** + * Parse a string of the form 'fr-sw600dp-land' and fill in the + * given ResTable_config with resulting configuration parameters. + * + * The resulting configuration has the appropriate sdkVersion defined + * for backwards compatibility. + */ +bool parse(const android::String8& str, ConfigDescription* out = NULL); + +/** + * Parse a comma separated list of configuration strings. Duplicate configurations + * will be removed. + * + * Example input: "fr,de-land,fr-sw600dp-land" + */ +bool parseCommaSeparatedList(const android::String8& str, std::set<ConfigDescription>* outSet); + +/** + * If the configuration uses an axis that was added after + * the original Android release, make sure the SDK version + * is set accordingly. + */ +void applyVersionForCompatibility(ConfigDescription* config); + +// Individual axis +bool parseMcc(const char* str, android::ResTable_config* out = NULL); +bool parseMnc(const char* str, android::ResTable_config* out = NULL); +bool parseLayoutDirection(const char* str, android::ResTable_config* out = NULL); +bool parseSmallestScreenWidthDp(const char* str, android::ResTable_config* out = NULL); +bool parseScreenWidthDp(const char* str, android::ResTable_config* out = NULL); +bool parseScreenHeightDp(const char* str, android::ResTable_config* out = NULL); +bool parseScreenLayoutSize(const char* str, android::ResTable_config* out = NULL); +bool parseScreenLayoutLong(const char* str, android::ResTable_config* out = NULL); +bool parseOrientation(const char* str, android::ResTable_config* out = NULL); +bool parseUiModeType(const char* str, android::ResTable_config* out = NULL); +bool parseUiModeNight(const char* str, android::ResTable_config* out = NULL); +bool parseDensity(const char* str, android::ResTable_config* out = NULL); +bool parseTouchscreen(const char* str, android::ResTable_config* out = NULL); +bool parseKeysHidden(const char* str, android::ResTable_config* out = NULL); +bool parseKeyboard(const char* str, android::ResTable_config* out = NULL); +bool parseNavHidden(const char* str, android::ResTable_config* out = NULL); +bool parseNavigation(const char* str, android::ResTable_config* out = NULL); +bool parseScreenSize(const char* str, android::ResTable_config* out = NULL); +bool parseVersion(const char* str, android::ResTable_config* out = NULL); + +android::String8 getVersion(const android::ResTable_config& config); + +/** + * Returns true if the two configurations only differ by the specified axis. + * The axis mask is a bitmask of CONFIG_* constants. + */ +bool isSameExcept(const android::ResTable_config& a, const android::ResTable_config& b, int configMask); + +} // namespace AaptConfig + +#endif // __AAPT_CONFIG_H diff --git a/tools/aapt/AaptUtil.cpp b/tools/aapt/AaptUtil.cpp new file mode 100644 index 0000000..293e144 --- /dev/null +++ b/tools/aapt/AaptUtil.cpp @@ -0,0 +1,64 @@ +/* + * 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. + */ + +#include "AaptUtil.h" + +using android::Vector; +using android::String8; + +namespace AaptUtil { + +Vector<String8> split(const String8& str, const char sep) { + Vector<String8> parts; + const char* p = str.string(); + const char* q; + + while (true) { + q = strchr(p, sep); + if (q == NULL) { + parts.add(String8(p, strlen(p))); + return parts; + } + + parts.add(String8(p, q-p)); + p = q + 1; + } + return parts; +} + +Vector<String8> splitAndLowerCase(const String8& str, const char sep) { + Vector<String8> parts; + const char* p = str.string(); + const char* q; + + while (true) { + q = strchr(p, sep); + if (q == NULL) { + String8 val(p, strlen(p)); + val.toLower(); + parts.add(val); + return parts; + } + + String8 val(p, q-p); + val.toLower(); + parts.add(val); + p = q + 1; + } + return parts; +} + +} // namespace AaptUtil diff --git a/tools/aapt/AaptUtil.h b/tools/aapt/AaptUtil.h new file mode 100644 index 0000000..47a704a --- /dev/null +++ b/tools/aapt/AaptUtil.h @@ -0,0 +1,30 @@ +/* + * 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. + */ + +#ifndef __AAPT_UTIL_H +#define __AAPT_UTIL_H + +#include <utils/String8.h> +#include <utils/Vector.h> + +namespace AaptUtil { + +android::Vector<android::String8> split(const android::String8& str, const char sep); +android::Vector<android::String8> splitAndLowerCase(const android::String8& str, const char sep); + +} // namespace AaptUtil + +#endif // __AAPT_UTIL_H diff --git a/tools/aapt/Android.mk b/tools/aapt/Android.mk index 806f8ff..700afa1 100644 --- a/tools/aapt/Android.mk +++ b/tools/aapt/Android.mk @@ -1,104 +1,168 @@ -# -# Copyright 2006 The Android Open Source Project # -# Android Asset Packaging Tool +# 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. # # This tool is prebuilt if we're doing an app-only build. ifeq ($(TARGET_BUILD_APPS)$(filter true,$(TARGET_BUILD_PDK)),) +# ========================================================== +# Setup some common variables for the different build +# targets here. +# ========================================================== +LOCAL_PATH:= $(call my-dir) -aapt_src_files := \ - AaptAssets.cpp \ - Command.cpp \ - CrunchCache.cpp \ - FileFinder.cpp \ - Main.cpp \ - Package.cpp \ - StringPool.cpp \ - XMLNode.cpp \ - ResourceFilter.cpp \ - ResourceIdCache.cpp \ - ResourceTable.cpp \ - Images.cpp \ - Resource.cpp \ +aaptMain := Main.cpp +aaptSources := \ + AaptAssets.cpp \ + AaptConfig.cpp \ + AaptUtil.cpp \ + ApkBuilder.cpp \ + Command.cpp \ + CrunchCache.cpp \ + FileFinder.cpp \ + Package.cpp \ + StringPool.cpp \ + XMLNode.cpp \ + ResourceFilter.cpp \ + ResourceIdCache.cpp \ + ResourceTable.cpp \ + Images.cpp \ + Resource.cpp \ pseudolocalize.cpp \ SourcePos.cpp \ - WorkQueue.cpp \ + WorkQueue.cpp \ ZipEntry.cpp \ ZipFile.cpp \ - qsort_r_compat.c + qsort_r_compat.c + +aaptTests := \ + tests/AaptConfig_test.cpp \ + tests/AaptGroupEntry_test.cpp \ + tests/ResourceFilter_test.cpp + +aaptCIncludes := \ + external/libpng \ + external/zlib + +aaptHostLdLibs := +aaptHostStaticLibs := \ + libandroidfw \ + libpng \ + liblog \ + libutils \ + libcutils \ + libexpat \ + libziparchive-host -LOCAL_PATH:= $(call my-dir) +ifeq ($(HOST_OS),linux) + aaptHostLdLibs += -lrt -ldl -lpthread +endif + +# Statically link libz for MinGW (Win SDK under Linux), +# and dynamically link for all others. +ifneq ($(strip $(USE_MINGW)),) + aaptHostStaticLibs += libz +else + aaptHostLdLibs += -lz +endif + + +# ========================================================== +# Build the host static library: libaapt +# ========================================================== include $(CLEAR_VARS) -LOCAL_SRC_FILES := $(aapt_src_files) +LOCAL_MODULE := libaapt + +LOCAL_SRC_FILES := $(aaptSources) +LOCAL_C_INCLUDES += $(aaptCIncludes) LOCAL_CFLAGS += -Wno-format-y2k +LOCAL_CFLAGS += -DSTATIC_ANDROIDFW_FOR_TOOLS ifeq (darwin,$(HOST_OS)) LOCAL_CFLAGS += -D_DARWIN_UNLIMITED_STREAMS endif -LOCAL_CFLAGS += -DSTATIC_ANDROIDFW_FOR_TOOLS +include $(BUILD_HOST_STATIC_LIBRARY) -LOCAL_C_INCLUDES += external/libpng -LOCAL_C_INCLUDES += external/zlib -LOCAL_STATIC_LIBRARIES := \ - libandroidfw \ - libutils \ - libcutils \ - libexpat \ - libpng \ - liblog \ - libziparchive-host +# ========================================================== +# Build the host executable: aapt +# ========================================================== +include $(CLEAR_VARS) -ifeq ($(HOST_OS),linux) -LOCAL_LDLIBS += -lrt -ldl -lpthread -endif +LOCAL_MODULE := aapt -# Statically link libz for MinGW (Win SDK under Linux), -# and dynamically link for all others. -ifneq ($(strip $(USE_MINGW)),) - LOCAL_STATIC_LIBRARIES += libz -else - LOCAL_LDLIBS += -lz -endif +LOCAL_SRC_FILES := $(aaptMain) -LOCAL_MODULE := aapt +LOCAL_STATIC_LIBRARIES += \ + libaapt \ + $(aaptHostStaticLibs) +LOCAL_LDLIBS += $(aaptHostLdLibs) include $(BUILD_HOST_EXECUTABLE) -# aapt for running on the device -# ========================================================= -ifneq ($(SDK_ONLY),true) + +# ========================================================== +# Build the host tests: libaapt_tests +# ========================================================== include $(CLEAR_VARS) -LOCAL_SRC_FILES := $(aapt_src_files) +LOCAL_MODULE := libaapt_tests -LOCAL_MODULE := aapt +LOCAL_SRC_FILES += $(aaptTests) +LOCAL_C_INCLUDES += $(LOCAL_PATH) -LOCAL_C_INCLUDES += bionic -LOCAL_C_INCLUDES += bionic/libstdc++/include -LOCAL_C_INCLUDES += external/stlport/stlport -LOCAL_C_INCLUDES += external/libpng -LOCAL_C_INCLUDES += external/zlib +LOCAL_STATIC_LIBRARIES += \ + libaapt \ + $(aaptHostStaticLibs) +LOCAL_LDLIBS += $(aaptHostLdLibs) -LOCAL_CFLAGS += -Wno-non-virtual-dtor +include $(BUILD_HOST_NATIVE_TEST) + + +# ========================================================== +# Build the device executable: aapt +# ========================================================== +ifneq ($(SDK_ONLY),true) +include $(CLEAR_VARS) + +LOCAL_MODULE := aapt + +LOCAL_SRC_FILES := $(aaptSources) $(aaptMain) +LOCAL_C_INCLUDES += \ + $(aaptCIncludes) \ + bionic \ + external/stlport/stlport LOCAL_SHARED_LIBRARIES := \ - libandroidfw \ - libutils \ - libcutils \ - libpng \ - liblog \ - libz + libandroidfw \ + libutils \ + libcutils \ + libpng \ + liblog \ + libz LOCAL_STATIC_LIBRARIES := \ - libstlport_static \ - libexpat_static + libstlport_static \ + libexpat_static + +LOCAL_CPPFLAGS += -Wno-non-virtual-dtor include $(BUILD_EXECUTABLE) -endif + +endif # Not SDK_ONLY endif # No TARGET_BUILD_APPS or TARGET_BUILD_PDK diff --git a/tools/aapt/ApkBuilder.cpp b/tools/aapt/ApkBuilder.cpp new file mode 100644 index 0000000..12f6040 --- /dev/null +++ b/tools/aapt/ApkBuilder.cpp @@ -0,0 +1,111 @@ +/* + * 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. + */ + +#include "AaptAssets.h" +#include "ApkBuilder.h" + +using namespace android; + +ApkBuilder::ApkBuilder(const sp<WeakResourceFilter>& configFilter) + : mConfigFilter(configFilter) + , mDefaultFilter(new AndResourceFilter()) { + // Add the default split, which is present for all APKs. + mDefaultFilter->addFilter(mConfigFilter); + mSplits.add(new ApkSplit(std::set<ConfigDescription>(), mDefaultFilter, true)); +} + +status_t ApkBuilder::createSplitForConfigs(const std::set<ConfigDescription>& configs) { + const size_t N = mSplits.size(); + for (size_t i = 0; i < N; i++) { + const std::set<ConfigDescription>& splitConfigs = mSplits[i]->getConfigs(); + std::set<ConfigDescription>::const_iterator iter = configs.begin(); + for (; iter != configs.end(); iter++) { + if (splitConfigs.count(*iter) > 0) { + // Can't have overlapping configurations. + fprintf(stderr, "ERROR: Split configuration '%s' is already defined " + "in another split.\n", iter->toString().string()); + return ALREADY_EXISTS; + } + } + } + + sp<StrongResourceFilter> splitFilter = new StrongResourceFilter(configs); + + // Add the inverse filter of this split filter to the base apk filter so it will + // omit resources that belong in this split. + mDefaultFilter->addFilter(new InverseResourceFilter(splitFilter)); + + // Now add the apk-wide config filter to our split filter. + sp<AndResourceFilter> filter = new AndResourceFilter(); + filter->addFilter(splitFilter); + filter->addFilter(mConfigFilter); + mSplits.add(new ApkSplit(configs, filter)); + return NO_ERROR; +} + +status_t ApkBuilder::addEntry(const String8& path, const sp<AaptFile>& file) { + const size_t N = mSplits.size(); + for (size_t i = 0; i < N; i++) { + if (mSplits[i]->matches(file)) { + return mSplits.editItemAt(i)->addEntry(path, file); + } + } + // Entry can be dropped if it doesn't match any split. This will only happen + // if the enry doesn't mConfigFilter. + return NO_ERROR; +} + +void ApkBuilder::print() const { + fprintf(stderr, "APK Builder\n"); + fprintf(stderr, "-----------\n"); + const size_t N = mSplits.size(); + for (size_t i = 0; i < N; i++) { + mSplits[i]->print(); + fprintf(stderr, "\n"); + } +} + +ApkSplit::ApkSplit(const std::set<ConfigDescription>& configs, const sp<ResourceFilter>& filter, bool isBase) + : mConfigs(configs), mFilter(filter), mIsBase(isBase) { + std::set<ConfigDescription>::const_iterator iter = configs.begin(); + for (; iter != configs.end(); iter++) { + if (mName.size() > 0) { + mName.append(","); + mDirName.append("_"); + } + + String8 configStr = iter->toString(); + mName.append(configStr); + mDirName.append(configStr); + } +} + +status_t ApkSplit::addEntry(const String8& path, const sp<AaptFile>& file) { + if (!mFiles.insert(OutputEntry(path, file)).second) { + // Duplicate file. + return ALREADY_EXISTS; + } + return NO_ERROR; +} + +void ApkSplit::print() const { + fprintf(stderr, "APK Split '%s'\n", mName.string()); + + std::set<OutputEntry>::const_iterator iter = mFiles.begin(); + for (; iter != mFiles.end(); iter++) { + fprintf(stderr, " %s (%s)\n", iter->getPath().string(), iter->getFile()->getSourceFile().string()); + } +} diff --git a/tools/aapt/ApkBuilder.h b/tools/aapt/ApkBuilder.h new file mode 100644 index 0000000..a4b7d4a --- /dev/null +++ b/tools/aapt/ApkBuilder.h @@ -0,0 +1,113 @@ +/* + * 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. + */ + +#ifndef __APK_BUILDER_H +#define __APK_BUILDER_H + +#include <set> +#include <utils/Errors.h> +#include <utils/String8.h> +#include <utils/StrongPointer.h> +#include <utils/Vector.h> + +#include "ConfigDescription.h" +#include "OutputSet.h" +#include "ResourceFilter.h" + +class ApkSplit; +class AaptFile; + +class ApkBuilder : public android::RefBase { +public: + ApkBuilder(const sp<WeakResourceFilter>& configFilter); + + /** + * Tells the builder to generate a separate APK for resources that + * match the configurations specified. Split APKs can not have + * overlapping resources. + * + * NOTE: All splits should be set up before any files are added. + */ + android::status_t createSplitForConfigs(const std::set<ConfigDescription>& configs); + + /** + * Adds a file to be written to the final APK. It's name must not collide + * with that of any files previously added. When a Split APK is being + * generated, duplicates can exist as long as they are in different splits + * (resources.arsc, AndroidManifest.xml). + */ + android::status_t addEntry(const String8& path, const android::sp<AaptFile>& file); + + android::Vector<sp<ApkSplit> >& getSplits() { + return mSplits; + } + + void print() const; + +private: + android::sp<ResourceFilter> mConfigFilter; + android::sp<AndResourceFilter> mDefaultFilter; + android::Vector<sp<ApkSplit> > mSplits; +}; + +class ApkSplit : public OutputSet { +public: + android::status_t addEntry(const String8& path, const android::sp<AaptFile>& file); + + const std::set<OutputEntry>& getEntries() const { + return mFiles; + } + + const std::set<ConfigDescription>& getConfigs() const { + return mConfigs; + } + + bool matches(const sp<AaptFile>& file) const { + return mFilter->match(file->getGroupEntry().toParams()); + } + + sp<ResourceFilter> getResourceFilter() const { + return mFilter; + } + + const android::String8& getPrintableName() const { + return mName; + } + + const android::String8& getDirectorySafeName() const { + return mDirName; + } + + bool isBase() const { + return mIsBase; + } + + void print() const; + +private: + friend class ApkBuilder; + + ApkSplit(const std::set<ConfigDescription>& configs, const android::sp<ResourceFilter>& filter, bool isBase=false); + + std::set<ConfigDescription> mConfigs; + const sp<ResourceFilter> mFilter; + const bool mIsBase; + String8 mName; + String8 mDirName; + std::set<OutputEntry> mFiles; +}; + +#endif // __APK_BUILDER_H diff --git a/tools/aapt/Bundle.h b/tools/aapt/Bundle.h index ebe1bed..ceb52a0 100644 --- a/tools/aapt/Bundle.h +++ b/tools/aapt/Bundle.h @@ -151,10 +151,12 @@ public: void setPublicOutputFile(const char* file) { mPublicOutputFile = file; } const char* getRClassDir() const { return mRClassDir; } void setRClassDir(const char* dir) { mRClassDir = dir; } - const char* getConfigurations() const { return mConfigurations.size() > 0 ? mConfigurations.string() : NULL; } + const android::String8& getConfigurations() const { return mConfigurations; } void addConfigurations(const char* val) { if (mConfigurations.size() > 0) { mConfigurations.append(","); mConfigurations.append(val); } else { mConfigurations = val; } } - const char* getPreferredConfigurations() const { return mPreferredConfigurations.size() > 0 ? mPreferredConfigurations.string() : NULL; } - void addPreferredConfigurations(const char* val) { if (mPreferredConfigurations.size() > 0) { mPreferredConfigurations.append(","); mPreferredConfigurations.append(val); } else { mPreferredConfigurations = val; } } + const android::String8& getPreferredDensity() const { return mPreferredDensity; } + void setPreferredDensity(const char* val) { mPreferredDensity = val; } + void addSplitConfigurations(const char* val) { mPartialConfigurations.add(android::String8(val)); } + const android::Vector<android::String8>& getSplitConfigurations() const { return mPartialConfigurations; } const char* getResourceIntermediatesDir() const { return mResourceIntermediatesDir; } void setResourceIntermediatesDir(const char* dir) { mResourceIntermediatesDir = dir; } const android::Vector<const char*>& getPackageIncludes() const { return mPackageIncludes; } @@ -286,7 +288,8 @@ private: const char* mRClassDir; const char* mResourceIntermediatesDir; android::String8 mConfigurations; - android::String8 mPreferredConfigurations; + android::String8 mPreferredDensity; + android::Vector<android::String8> mPartialConfigurations; android::Vector<const char*> mPackageIncludes; android::Vector<const char*> mJarFiles; android::Vector<const char*> mNoCompressExtensions; diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp index 0af1ce1..0360200 100644 --- a/tools/aapt/Command.cpp +++ b/tools/aapt/Command.cpp @@ -3,6 +3,7 @@ // // Android Asset Packaging Tool main entry point. // +#include "ApkBuilder.h" #include "Main.h" #include "Bundle.h" #include "ResourceFilter.h" @@ -2034,6 +2035,47 @@ bail: return (result != NO_ERROR); } +static status_t addResourcesToBuilder(const sp<AaptDir>& dir, const sp<ApkBuilder>& builder) { + const size_t numDirs = dir->getDirs().size(); + for (size_t i = 0; i < numDirs; i++) { + status_t err = addResourcesToBuilder(dir->getDirs().valueAt(i), builder); + if (err != NO_ERROR) { + return err; + } + } + + const size_t numFiles = dir->getFiles().size(); + for (size_t i = 0; i < numFiles; i++) { + sp<AaptGroup> gp = dir->getFiles().valueAt(i); + const size_t numConfigs = gp->getFiles().size(); + for (size_t j = 0; j < numConfigs; j++) { + status_t err = builder->addEntry(gp->getPath(), gp->getFiles().valueAt(j)); + if (err != NO_ERROR) { + fprintf(stderr, "Failed to add %s (%s) to builder.\n", + gp->getPath().string(), gp->getFiles()[j]->getPrintableSource().string()); + return err; + } + } + } + return NO_ERROR; +} + +static String8 buildApkName(const String8& original, const sp<ApkSplit>& split) { + if (split->isBase()) { + return original; + } + + String8 ext(original.getPathExtension()); + if (ext == String8(".apk")) { + return String8::format("%s_%s%s", + original.getBasePath().string(), + split->getDirectorySafeName().string(), + ext.string()); + } + + return String8::format("%s_%s", original.string(), + split->getDirectorySafeName().string()); +} /* * Package up an asset directory and associated application files. @@ -2047,17 +2089,18 @@ int doPackage(Bundle* bundle) int N; FILE* fp; String8 dependencyFile; + sp<ApkBuilder> builder; // -c en_XA or/and ar_XB means do pseudolocalization - ResourceFilter filter; - err = filter.parse(bundle->getConfigurations()); + sp<WeakResourceFilter> configFilter = new WeakResourceFilter(); + err = configFilter->parse(bundle->getConfigurations()); if (err != NO_ERROR) { goto bail; } - if (filter.containsPseudo()) { + if (configFilter->containsPseudo()) { bundle->setPseudolocalize(bundle->getPseudolocalize() | PSEUDO_ACCENTED); } - if (filter.containsPseudoBidi()) { + if (configFilter->containsPseudoBidi()) { bundle->setPseudolocalize(bundle->getPseudolocalize() | PSEUDO_BIDI); } @@ -2105,9 +2148,32 @@ int doPackage(Bundle* bundle) assets->print(String8()); } + // Create the ApkBuilder, which will collect the compiled files + // to write to the final APK (or sets of APKs if we are building + // a Split APK. + builder = new ApkBuilder(configFilter); + + // If we are generating a Split APK, find out which configurations to split on. + if (bundle->getSplitConfigurations().size() > 0) { + const Vector<String8>& splitStrs = bundle->getSplitConfigurations(); + const size_t numSplits = splitStrs.size(); + for (size_t i = 0; i < numSplits; i++) { + std::set<ConfigDescription> configs; + if (!AaptConfig::parseCommaSeparatedList(splitStrs[i], &configs)) { + fprintf(stderr, "ERROR: failed to parse split configuration '%s'\n", splitStrs[i].string()); + goto bail; + } + + err = builder->createSplitForConfigs(configs); + if (err != NO_ERROR) { + goto bail; + } + } + } + // If they asked for any fileAs that need to be compiled, do so. if (bundle->getResourceSourceDirs().size() || bundle->getAndroidManifestFile()) { - err = buildResources(bundle, assets); + err = buildResources(bundle, assets, builder); if (err != 0) { goto bail; } @@ -2194,11 +2260,24 @@ int doPackage(Bundle* bundle) // Write the apk if (outputAPKFile) { - err = writeAPK(bundle, assets, String8(outputAPKFile)); + // Gather all resources and add them to the APK Builder. The builder will then + // figure out which Split they belong in. + err = addResourcesToBuilder(assets, builder); if (err != NO_ERROR) { - fprintf(stderr, "ERROR: packaging of '%s' failed\n", outputAPKFile); goto bail; } + + const Vector<sp<ApkSplit> >& splits = builder->getSplits(); + const size_t numSplits = splits.size(); + for (size_t i = 0; i < numSplits; i++) { + const sp<ApkSplit>& split = splits[i]; + String8 outputPath = buildApkName(String8(outputAPKFile), split); + err = writeAPK(bundle, outputPath, split); + if (err != NO_ERROR) { + fprintf(stderr, "ERROR: packaging of '%s' failed\n", outputPath.string()); + goto bail; + } + } } // If we've been asked to generate a dependency file, we need to finish up here. diff --git a/tools/aapt/ConfigDescription.h b/tools/aapt/ConfigDescription.h new file mode 100644 index 0000000..779c423 --- /dev/null +++ b/tools/aapt/ConfigDescription.h @@ -0,0 +1,57 @@ +/* + * 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. + */ + +#ifndef __CONFIG_DESCRIPTION_H +#define __CONFIG_DESCRIPTION_H + +#include <androidfw/ResourceTypes.h> + +/** + * Subclass of ResTable_config that adds convenient + * initialization and comparison methods. + */ +struct ConfigDescription : public android::ResTable_config { + ConfigDescription() { + memset(this, 0, sizeof(*this)); + size = sizeof(android::ResTable_config); + } + ConfigDescription(const android::ResTable_config&o) { + *static_cast<android::ResTable_config*>(this) = o; + size = sizeof(android::ResTable_config); + } + ConfigDescription(const ConfigDescription&o) { + *static_cast<android::ResTable_config*>(this) = o; + } + + ConfigDescription& operator=(const android::ResTable_config& o) { + *static_cast<android::ResTable_config*>(this) = o; + size = sizeof(android::ResTable_config); + return *this; + } + ConfigDescription& operator=(const ConfigDescription& o) { + *static_cast<android::ResTable_config*>(this) = o; + return *this; + } + + inline bool operator<(const ConfigDescription& o) const { return compare(o) < 0; } + inline bool operator<=(const ConfigDescription& o) const { return compare(o) <= 0; } + inline bool operator==(const ConfigDescription& o) const { return compare(o) == 0; } + inline bool operator!=(const ConfigDescription& o) const { return compare(o) != 0; } + inline bool operator>=(const ConfigDescription& o) const { return compare(o) >= 0; } + inline bool operator>(const ConfigDescription& o) const { return compare(o) > 0; } +}; + +#endif // __CONFIG_DESCRIPTION_H diff --git a/tools/aapt/Main.cpp b/tools/aapt/Main.cpp index 1cf4783..5a60014 100644 --- a/tools/aapt/Main.cpp +++ b/tools/aapt/Main.cpp @@ -70,6 +70,7 @@ void usage(void) " [-F apk-file] [-J R-file-dir] \\\n" " [--product product1,product2,...] \\\n" " [-c CONFIGS] [--preferred-configurations CONFIGS] \\\n" + " [--split CONFIGS [--split CONFIGS]] \\\n" " [raw-files-dir [raw-files-dir] ...] \\\n" " [--output-text-symbols DIR]\n" "\n" @@ -166,10 +167,12 @@ void usage(void) " generate dependency files in the same directories for R.java and resource package\n" " --auto-add-overlay\n" " Automatically add resources that are only in overlays.\n" - " --preferred-configurations\n" - " Like the -c option for filtering out unneeded configurations, but\n" - " only expresses a preference. If there is no resource available with\n" - " the preferred configuration then it will not be stripped.\n" + " --preferred-density\n" + " Specifies a preference for a particular density. Resources that do not\n" + " match this density and have variants that are a closer match are removed.\n" + " --split\n" + " Builds a separate split APK for the configurations listed. This can\n" + " be loaded alongside the base APK at runtime.\n" " --rename-manifest-package\n" " Rewrite the manifest so that its package name is the package name\n" " given here. Relative class names (for example .Foo) will be\n" @@ -568,15 +571,24 @@ int main(int argc, char* const argv[]) bundle.setGenDependencies(true); } else if (strcmp(cp, "-utf16") == 0) { bundle.setWantUTF16(true); - } else if (strcmp(cp, "-preferred-configurations") == 0) { + } else if (strcmp(cp, "-preferred-density") == 0) { argc--; argv++; if (!argc) { - fprintf(stderr, "ERROR: No argument supplied for '--preferred-configurations' option\n"); + fprintf(stderr, "ERROR: No argument supplied for '--preferred-density' option\n"); wantUsage = true; goto bail; } - bundle.addPreferredConfigurations(argv[0]); + bundle.setPreferredDensity(argv[0]); + } else if (strcmp(cp, "-split") == 0) { + argc--; + argv++; + if (!argc) { + fprintf(stderr, "ERROR: No argument supplied for '--split' option\n"); + wantUsage = true; + goto bail; + } + bundle.addSplitConfigurations(argv[0]); } else if (strcmp(cp, "-rename-manifest-package") == 0) { argc--; argv++; diff --git a/tools/aapt/Main.h b/tools/aapt/Main.h index a6b39ac..34c4496 100644 --- a/tools/aapt/Main.h +++ b/tools/aapt/Main.h @@ -10,8 +10,12 @@ #include <utils/threads.h> #include <utils/List.h> #include <utils/Errors.h> -#include "Bundle.h" +#include <utils/StrongPointer.h> + #include "AaptAssets.h" +#include "ApkBuilder.h" +#include "Bundle.h" +#include "ResourceFilter.h" #include "ZipFile.h" @@ -22,6 +26,8 @@ #include <time.h> #endif /* BENCHMARK */ +class OutputSet; + extern int doVersion(Bundle* bundle); extern int doList(Bundle* bundle); extern int doDump(Bundle* bundle); @@ -34,13 +40,13 @@ extern int doSingleCrunch(Bundle* bundle); extern int calcPercent(long uncompressedLen, long compressedLen); extern android::status_t writeAPK(Bundle* bundle, - const sp<AaptAssets>& assets, - const android::String8& outputFile); + const android::String8& outputFile, + const android::sp<OutputSet>& outputSet); extern android::status_t updatePreProcessedCache(Bundle* bundle); extern android::status_t buildResources(Bundle* bundle, - const sp<AaptAssets>& assets); + const sp<AaptAssets>& assets, sp<ApkBuilder>& builder); extern android::status_t writeResourceSymbols(Bundle* bundle, const sp<AaptAssets>& assets, const String8& pkgName, bool includePrivate); @@ -49,8 +55,6 @@ extern android::status_t writeProguardFile(Bundle* bundle, const sp<AaptAssets>& extern bool isValidResourceType(const String8& type); -ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<AaptAssets>& assets); - extern status_t filterResources(Bundle* bundle, const sp<AaptAssets>& assets); int dumpResources(Bundle* bundle); diff --git a/tools/aapt/OutputSet.h b/tools/aapt/OutputSet.h new file mode 100644 index 0000000..ea9ef70 --- /dev/null +++ b/tools/aapt/OutputSet.h @@ -0,0 +1,56 @@ +/* + * 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. + */ + +#ifndef __OUTPUT_SET_H +#define __OUTPUT_SET_H + +#include <set> +#include <utils/Errors.h> +#include <utils/String8.h> +#include <utils/StrongPointer.h> + +class AaptFile; + +class OutputEntry { +public: + OutputEntry() {} + OutputEntry(const android::String8& path, const android::sp<const AaptFile>& file) + : mPath(path), mFile(file) {} + + inline const android::sp<const AaptFile>& getFile() const { + return mFile; + } + + inline const android::String8& getPath() const { + return mPath; + } + + bool operator<(const OutputEntry& o) const { return getPath() < o.mPath; } + bool operator==(const OutputEntry& o) const { return getPath() == o.mPath; } + +private: + android::String8 mPath; + android::sp<const AaptFile> mFile; +}; + +class OutputSet : public virtual android::RefBase { +public: + virtual const std::set<OutputEntry>& getEntries() const = 0; + + virtual ~OutputSet() {} +}; + +#endif // __OUTPUT_SET_H diff --git a/tools/aapt/Package.cpp b/tools/aapt/Package.cpp index 872d95c..dc16e35 100644 --- a/tools/aapt/Package.cpp +++ b/tools/aapt/Package.cpp @@ -5,6 +5,7 @@ // #include "Main.h" #include "AaptAssets.h" +#include "OutputSet.h" #include "ResourceTable.h" #include "ResourceFilter.h" @@ -36,11 +37,8 @@ static const char* kNoCompressExt[] = { }; /* fwd decls, so I can write this downward */ -ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<AaptAssets>& assets); -ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<AaptDir>& dir, - const AaptGroupEntry& ge, const ResourceFilter* filter); -bool processFile(Bundle* bundle, ZipFile* zip, - const sp<AaptGroup>& group, const sp<AaptFile>& file); +ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<const OutputSet>& outputSet); +bool processFile(Bundle* bundle, ZipFile* zip, String8 storageName, const sp<const AaptFile>& file); bool okayToCompress(Bundle* bundle, const String8& pathName); ssize_t processJarFiles(Bundle* bundle, ZipFile* zip); @@ -51,8 +49,7 @@ ssize_t processJarFiles(Bundle* bundle, ZipFile* zip); * On success, "bundle->numPackages" will be the number of Zip packages * we created. */ -status_t writeAPK(Bundle* bundle, const sp<AaptAssets>& assets, - const String8& outputFile) +status_t writeAPK(Bundle* bundle, const String8& outputFile, const sp<OutputSet>& outputSet) { #if BENCHMARK fprintf(stdout, "BENCHMARK: Starting APK Bundling \n"); @@ -112,7 +109,7 @@ status_t writeAPK(Bundle* bundle, const sp<AaptAssets>& assets, printf("Writing all files...\n"); } - count = processAssets(bundle, zip, assets); + count = processAssets(bundle, zip, outputSet); if (count < 0) { fprintf(stderr, "ERROR: unable to process assets while packaging '%s'\n", outputFile.string()); @@ -218,72 +215,24 @@ bail: return result; } -ssize_t processAssets(Bundle* bundle, ZipFile* zip, - const sp<AaptAssets>& assets) -{ - ResourceFilter filter; - status_t status = filter.parse(bundle->getConfigurations()); - if (status != NO_ERROR) { - return -1; - } - - ssize_t count = 0; - - const size_t N = assets->getGroupEntries().size(); - for (size_t i=0; i<N; i++) { - const AaptGroupEntry& ge = assets->getGroupEntries()[i]; - - ssize_t res = processAssets(bundle, zip, assets, ge, &filter); - if (res < 0) { - return res; - } - - count += res; - } - - return count; -} - -ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<AaptDir>& dir, - const AaptGroupEntry& ge, const ResourceFilter* filter) +ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<const OutputSet>& outputSet) { ssize_t count = 0; - - const size_t ND = dir->getDirs().size(); - size_t i; - for (i=0; i<ND; i++) { - const sp<AaptDir>& subDir = dir->getDirs().valueAt(i); - - const bool filterable = filter != NULL && subDir->getLeaf().find("mipmap-") != 0; - - if (filterable && subDir->getLeaf() != subDir->getPath() && !filter->match(ge.toParams())) { - continue; - } - - ssize_t res = processAssets(bundle, zip, subDir, ge, filterable ? filter : NULL); - if (res < 0) { - return res; - } - count += res; - } - - if (filter != NULL && !filter->match(ge.toParams())) { - return count; - } - - const size_t NF = dir->getFiles().size(); - for (i=0; i<NF; i++) { - sp<AaptGroup> gp = dir->getFiles().valueAt(i); - ssize_t fi = gp->getFiles().indexOfKey(ge); - if (fi >= 0) { - sp<AaptFile> fl = gp->getFiles().valueAt(fi); - if (!processFile(bundle, zip, gp, fl)) { + const std::set<OutputEntry>& entries = outputSet->getEntries(); + std::set<OutputEntry>::const_iterator iter = entries.begin(); + for (; iter != entries.end(); iter++) { + const OutputEntry& entry = *iter; + if (entry.getFile() == NULL) { + fprintf(stderr, "warning: null file being processed.\n"); + } else { + String8 storagePath(entry.getPath()); + storagePath.convertToResPath(); + if (!processFile(bundle, zip, storagePath, entry.getFile())) { return UNKNOWN_ERROR; } count++; } } - return count; } @@ -294,12 +243,10 @@ ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<AaptDir>& dir, * delete the existing entry before adding the new one. */ bool processFile(Bundle* bundle, ZipFile* zip, - const sp<AaptGroup>& group, const sp<AaptFile>& file) + String8 storageName, const sp<const AaptFile>& file) { const bool hasData = file->hasData(); - String8 storageName(group->getPath()); - storageName.convertToResPath(); ZipEntry* entry; bool fromGzip = false; status_t result; @@ -403,8 +350,8 @@ bool processFile(Bundle* bundle, ZipFile* zip, fprintf(stderr, " Unable to add '%s': file already in archive (try '-u'?)\n", file->getPrintableSource().string()); } else { - fprintf(stderr, " Unable to add '%s': Zip add failed\n", - file->getPrintableSource().string()); + fprintf(stderr, " Unable to add '%s': Zip add failed (%d)\n", + file->getPrintableSource().string(), result); } return false; } diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp index 1348be3..e599643 100644 --- a/tools/aapt/Resource.cpp +++ b/tools/aapt/Resource.cpp @@ -179,24 +179,6 @@ bool isValidResourceType(const String8& type) || type == "color" || type == "menu" || type == "mipmap"; } -static sp<AaptFile> getResourceFile(const sp<AaptAssets>& assets, bool makeIfNecessary=true) -{ - sp<AaptGroup> group = assets->getFiles().valueFor(String8("resources.arsc")); - sp<AaptFile> file; - if (group != NULL) { - file = group->getFiles().valueFor(AaptGroupEntry()); - if (file != NULL) { - return file; - } - } - - if (!makeIfNecessary) { - return NULL; - } - return assets->addFile(String8("resources.arsc"), AaptGroupEntry(), String8(), - NULL, String8()); -} - static status_t parsePackage(Bundle* bundle, const sp<AaptAssets>& assets, const sp<AaptGroup>& grp) { @@ -359,23 +341,6 @@ static status_t preProcessImages(const Bundle* bundle, const sp<AaptAssets>& ass return (hasErrors || (res < NO_ERROR)) ? UNKNOWN_ERROR : NO_ERROR; } -status_t postProcessImages(const sp<AaptAssets>& assets, - ResourceTable* table, - const sp<ResourceTypeSet>& set) -{ - ResourceDirIterator it(set, String8("drawable")); - bool hasErrors = false; - ssize_t res; - while ((res=it.next()) == NO_ERROR) { - res = postProcessImage(assets, table, it.getFile()); - if (res < NO_ERROR) { - hasErrors = true; - } - } - - return (hasErrors || (res < NO_ERROR)) ? UNKNOWN_ERROR : NO_ERROR; -} - static void collect_files(const sp<AaptDir>& dir, KeyedVector<String8, sp<ResourceTypeSet> >* resources) { @@ -906,7 +871,38 @@ status_t updatePreProcessedCache(Bundle* bundle) return 0; } -status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets) +status_t generateAndroidManifestForSplit(const String16& package, const sp<ApkSplit>& split, + sp<AaptFile>& outFile) { + const String8 filename("AndroidManifest.xml"); + const String16 androidPrefix("android"); + const String16 androidNSUri("http://schemas.android.com/apk/res/android"); + sp<XMLNode> root = XMLNode::newNamespace(filename, androidPrefix, androidNSUri); + + // Build the <manifest> tag + sp<XMLNode> manifest = XMLNode::newElement(filename, String16(), String16("manifest")); + + // Add the 'package' attribute which is set to the original package name. + manifest->addAttribute(String16(), String16("package"), package); + + // Add the 'split' attribute which describes the configurations included. + String8 splitName("config_"); + splitName.append(split->getDirectorySafeName()); + manifest->addAttribute(String16(), String16("split"), String16(splitName)); + + // Build an empty <application> tag (required). + sp<XMLNode> app = XMLNode::newElement(filename, String16(), String16("application")); + manifest->addChild(app); + root->addChild(manifest); + + status_t err = root->flatten(outFile, true, true); + if (err != NO_ERROR) { + return err; + } + outFile->setCompressionMethod(ZipEntry::kCompressDeflated); + return NO_ERROR; +} + +status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets, sp<ApkBuilder>& builder) { // First, look for a package file to parse. This is required to // be able to generate the resource information. @@ -1122,12 +1118,6 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets) // -------------------------------------------------------------------- if (table.hasResources()) { - sp<AaptFile> resFile(getResourceFile(assets)); - if (resFile == NULL) { - fprintf(stderr, "Error: unable to generate entry for resource data\n"); - return UNKNOWN_ERROR; - } - err = table.assignResourceIds(); if (err < NO_ERROR) { return err; @@ -1235,16 +1225,24 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets) } if (drawables != NULL) { - err = postProcessImages(assets, &table, drawables); - if (err != NO_ERROR) { + ResourceDirIterator it(drawables, String8("drawable")); + while ((err=it.next()) == NO_ERROR) { + err = postProcessImage(assets, &table, it.getFile()); + if (err != NO_ERROR) { + hasErrors = true; + } + } + + if (err < NO_ERROR) { hasErrors = true; } + err = NO_ERROR; } if (colors != NULL) { ResourceDirIterator it(colors, String8("color")); while ((err=it.next()) == NO_ERROR) { - err = compileXmlFile(assets, it.getFile(), &table, xmlFlags); + err = compileXmlFile(assets, it.getFile(), &table, xmlFlags); if (err != NO_ERROR) { hasErrors = true; } @@ -1320,15 +1318,35 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets) return err; } - resFile = getResourceFile(assets); - if (resFile == NULL) { - fprintf(stderr, "Error: unable to generate entry for resource data\n"); - return UNKNOWN_ERROR; - } + Vector<sp<ApkSplit> >& splits = builder->getSplits(); + const size_t numSplits = splits.size(); + for (size_t i = 0; i < numSplits; i++) { + sp<ApkSplit>& split = splits.editItemAt(i); + sp<AaptFile> flattenedTable = new AaptFile(String8("resources.arsc"), + AaptGroupEntry(), String8()); + err = table.flatten(bundle, split->getResourceFilter(), flattenedTable); + if (err != NO_ERROR) { + fprintf(stderr, "Failed to generate resource table for split '%s'\n", + split->getPrintableName().string()); + return err; + } + split->addEntry(String8("resources.arsc"), flattenedTable); - err = table.flatten(bundle, resFile); - if (err < NO_ERROR) { - return err; + if (split->isBase()) { + resFile = flattenedTable; + finalResTable.add(flattenedTable->getData(), flattenedTable->getSize()); + } else { + sp<AaptFile> generatedManifest = new AaptFile(String8("AndroidManifest.xml"), + AaptGroupEntry(), String8()); + err = generateAndroidManifestForSplit(String16(assets->getPackage()), split, + generatedManifest); + if (err != NO_ERROR) { + fprintf(stderr, "Failed to generate AndroidManifest.xml for split '%s'\n", + split->getPrintableName().string()); + return err; + } + split->addEntry(String8("AndroidManifest.xml"), generatedManifest); + } } if (bundle->getPublicOutputFile()) { @@ -1344,18 +1362,13 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets) table.writePublicDefinitions(String16(assets->getPackage()), fp); fclose(fp); } - - // Read resources back in, - finalResTable.add(resFile->getData(), resFile->getSize()); - -#if 0 - NOISY( - printf("Generated resources:\n"); - finalResTable.print(); - ) -#endif + + if (finalResTable.getTableCount() == 0 || resFile == NULL) { + fprintf(stderr, "No resource table was generated.\n"); + return UNKNOWN_ERROR; + } } - + // Perform a basic validation of the manifest file. This time we // parse it with the comments intact, so that we can use them to // generate java docs... so we are not going to write this one diff --git a/tools/aapt/ResourceFilter.cpp b/tools/aapt/ResourceFilter.cpp index 8ca852e..de8b4fc 100644 --- a/tools/aapt/ResourceFilter.cpp +++ b/tools/aapt/ResourceFilter.cpp @@ -1,119 +1,92 @@ // -// Copyright 2011 The Android Open Source Project +// Copyright 2014 The Android Open Source Project // // Build resource files from raw assets. // #include "ResourceFilter.h" +#include "AaptUtil.h" +#include "AaptConfig.h" status_t -ResourceFilter::parse(const char* arg) +WeakResourceFilter::parse(const String8& str) { - if (arg == NULL) { - return 0; - } - - const char* p = arg; - const char* q; - - while (true) { - q = strchr(p, ','); - if (q == NULL) { - q = p + strlen(p); - } - - String8 part(p, q-p); - + Vector<String8> configStrs = AaptUtil::split(str, ','); + const size_t N = configStrs.size(); + mConfigs.clear(); + mConfigMask = 0; + mConfigs.resize(N); + for (size_t i = 0; i < N; i++) { + const String8& part = configStrs[i]; if (part == "en_XA") { mContainsPseudoAccented = true; } else if (part == "ar_XB") { mContainsPseudoBidi = true; } - int axis; - AxisValue value; - if (!AaptGroupEntry::parseFilterNamePart(part, &axis, &value)) { - fprintf(stderr, "Invalid configuration: %s\n", arg); - fprintf(stderr, " "); - for (int i=0; i<p-arg; i++) { - fprintf(stderr, " "); - } - for (int i=0; i<q-p; i++) { - fprintf(stderr, "^"); - } - fprintf(stderr, "\n"); - return 1; - } - ssize_t index = mData.indexOfKey(axis); - if (index < 0) { - mData.add(axis, SortedVector<AxisValue>()); - } - SortedVector<AxisValue>& sv = mData.editValueFor(axis); - sv.add(value); + std::pair<ConfigDescription, uint32_t>& entry = mConfigs.editItemAt(i); - // If it's a locale with a region, script or variant, we should also match an - // unmodified locale of the same language - if (axis == AXIS_LOCALE) { - if (value.localeValue.region[0] || value.localeValue.script[0] || - value.localeValue.variant[0]) { - AxisValue copy; - memcpy(copy.localeValue.language, value.localeValue.language, - sizeof(value.localeValue.language)); - sv.add(copy); - } + AaptLocaleValue val; + if (val.initFromFilterString(part)) { + // For backwards compatibility, we accept configurations that + // only specify locale in the standard 'en_US' format. + val.writeTo(&entry.first); + } else if (!AaptConfig::parse(part, &entry.first)) { + fprintf(stderr, "Invalid configuration: %s\n", part.string()); + return UNKNOWN_ERROR; } - p = q; - if (!*p) break; - p++; + + entry.second = mDefault.diff(entry.first); + + // Ignore the version + entry.second &= ~ResTable_config::CONFIG_VERSION; + + mConfigMask |= entry.second; } return NO_ERROR; } bool -ResourceFilter::isEmpty() const -{ - return mData.size() == 0; -} - -bool -ResourceFilter::match(int axis, const AxisValue& value) const +WeakResourceFilter::match(const ResTable_config& config) const { - if (value.intValue == 0 && (value.localeValue.language[0] == 0)) { - // they didn't specify anything so take everything - return true; - } - ssize_t index = mData.indexOfKey(axis); - if (index < 0) { - // we didn't request anything on this axis so take everything + uint32_t mask = mDefault.diff(config); + if ((mConfigMask & mask) == 0) { + // The two configurations don't have any common axis. return true; } - const SortedVector<AxisValue>& sv = mData.valueAt(index); - return sv.indexOf(value) >= 0; -} - -bool -ResourceFilter::match(int axis, const ResTable_config& config) const -{ - return match(axis, AaptGroupEntry::getConfigValueForAxis(config, axis)); -} -bool -ResourceFilter::match(const ResTable_config& config) const -{ - for (int i=AXIS_START; i<=AXIS_END; i++) { - if (!match(i, AaptGroupEntry::getConfigValueForAxis(config, i))) { - return false; + const size_t N = mConfigs.size(); + for (size_t i = 0; i < N; i++) { + const std::pair<ConfigDescription, uint32_t>& entry = mConfigs[i]; + uint32_t diff = entry.first.diff(config); + if ((diff & entry.second) == 0) { + return true; + } else if ((diff & entry.second) == ResTable_config::CONFIG_LOCALE) { + // If the locales differ, but the languages are the same and + // the locale we are matching only has a language specified, + // we match. + if (config.language[0] && memcmp(config.language, entry.first.language, sizeof(config.language)) == 0) { + if (config.country[0] == 0) { + return true; + } + } } } - return true; + return false; } -const SortedVector<AxisValue>* ResourceFilter::configsForAxis(int axis) const -{ - ssize_t index = mData.indexOfKey(axis); - if (index < 0) { - return NULL; +status_t +StrongResourceFilter::parse(const String8& str) { + Vector<String8> configStrs = AaptUtil::split(str, ','); + ConfigDescription config; + mConfigs.clear(); + for (size_t i = 0; i < configStrs.size(); i++) { + if (!AaptConfig::parse(configStrs[i], &config)) { + fprintf(stderr, "Invalid configuration: %s\n", configStrs[i].string()); + return UNKNOWN_ERROR; + } + mConfigs.insert(config); } - return &mData.valueAt(index); + return NO_ERROR; } diff --git a/tools/aapt/ResourceFilter.h b/tools/aapt/ResourceFilter.h index c57770e..f459584 100644 --- a/tools/aapt/ResourceFilter.h +++ b/tools/aapt/ResourceFilter.h @@ -7,31 +7,137 @@ #ifndef RESOURCE_FILTER_H #define RESOURCE_FILTER_H +#include <androidfw/ResourceTypes.h> +#include <set> +#include <utility> +#include <utils/Errors.h> +#include <utils/String8.h> +#include <utils/StrongPointer.h> +#include <utils/Vector.h> + #include "AaptAssets.h" +#include "ConfigDescription.h" + +class ResourceFilter : public virtual android::RefBase { +public: + virtual bool match(const android::ResTable_config& config) const = 0; +}; /** * Implements logic for parsing and handling "-c" and "--preferred-configurations" * options. */ -class ResourceFilter -{ +class WeakResourceFilter : public ResourceFilter { public: - ResourceFilter() : mData(), mContainsPseudoAccented(false), - mContainsPseudoBidi(false) {} - status_t parse(const char* arg); - bool isEmpty() const; - bool match(int axis, const ResTable_config& config) const; - bool match(const ResTable_config& config) const; - const SortedVector<AxisValue>* configsForAxis(int axis) const; - inline bool containsPseudo() const { return mContainsPseudoAccented; } - inline bool containsPseudoBidi() const { return mContainsPseudoBidi; } + WeakResourceFilter() + : mContainsPseudoAccented(false) + , mContainsPseudoBidi(false) {} + + android::status_t parse(const android::String8& str); + + bool match(const android::ResTable_config& config) const; + + inline bool isEmpty() const { + return mConfigMask == 0; + } + + inline bool containsPseudo() const { + return mContainsPseudoAccented; + } + + inline bool containsPseudoBidi() const { + return mContainsPseudoBidi; + } private: - bool match(int axis, const AxisValue& value) const; + ConfigDescription mDefault; + uint32_t mConfigMask; + android::Vector<std::pair<ConfigDescription, uint32_t> > mConfigs; - KeyedVector<int,SortedVector<AxisValue> > mData; bool mContainsPseudoAccented; bool mContainsPseudoBidi; }; +/** + * Matches resources that have at least one of the configurations + * that this filter is looking for. In order to match a configuration, + * the resource must have the exact same configuration. + * + * This filter acts as a logical OR when matching resources. + * + * For example, if the filter is looking for resources with + * fr-land, de-land, or sw600dp: + * + * (PASS) fr-land + * (FAIL) fr + * (PASS) de-land + * (FAIL) de + * (FAIL) de-sw600dp + * (PASS) sw600dp + * (FAIL) sw600dp-land + */ +class StrongResourceFilter : public ResourceFilter { +public: + StrongResourceFilter() {} + StrongResourceFilter(const std::set<ConfigDescription>& configs) + : mConfigs(configs) {} + + android::status_t parse(const android::String8& str); + + bool match(const android::ResTable_config& config) const { + std::set<ConfigDescription>::const_iterator iter = mConfigs.begin(); + for (; iter != mConfigs.end(); iter++) { + if (iter->compare(config) == 0) { + return true; + } + } + return false; + } + + inline const std::set<ConfigDescription>& getConfigs() const { + return mConfigs; + } + +private: + std::set<ConfigDescription> mConfigs; +}; + +/** + * Negates the response of the target filter. + */ +class InverseResourceFilter : public ResourceFilter { +public: + InverseResourceFilter(const android::sp<ResourceFilter>& filter) + : mFilter(filter) {} + + bool match(const android::ResTable_config& config) const { + return !mFilter->match(config); + } + +private: + const android::sp<ResourceFilter> mFilter; +}; + +/** + * A logical AND of all the added filters. + */ +class AndResourceFilter : public ResourceFilter { +public: + void addFilter(const android::sp<ResourceFilter>& filter) { + mFilters.add(filter); + } + + bool match(const android::ResTable_config& config) const { + const size_t N = mFilters.size(); + for (size_t i = 0; i < N; i++) { + if (!mFilters[i]->match(config)) { + return false; + } + } + return true; + } + +private: + android::Vector<android::sp<ResourceFilter> > mFilters; +}; #endif diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp index 6eab65b..efbba40 100644 --- a/tools/aapt/ResourceTable.cpp +++ b/tools/aapt/ResourceTable.cpp @@ -2095,10 +2095,10 @@ bool ResourceTable::hasResources() const { return mNumLocal > 0; } -sp<AaptFile> ResourceTable::flatten(Bundle* bundle) +sp<AaptFile> ResourceTable::flatten(Bundle* bundle, const sp<const ResourceFilter>& filter) { sp<AaptFile> data = new AaptFile(String8(), AaptGroupEntry(), String8()); - status_t err = flatten(bundle, data); + status_t err = flatten(bundle, filter, data); return err == NO_ERROR ? data : NULL; } @@ -2658,8 +2658,8 @@ ResourceTable::validateLocalizations(void) } // Check that all requested localizations are present for this string - if (mBundle->getConfigurations() != NULL && mBundle->getRequireLocalization()) { - const char* allConfigs = mBundle->getConfigurations(); + if (mBundle->getConfigurations().size() > 0 && mBundle->getRequireLocalization()) { + const char* allConfigs = mBundle->getConfigurations().string(); const char* start = allConfigs; const char* comma; @@ -2713,14 +2713,8 @@ ResourceTable::validateLocalizations(void) return err; } -status_t ResourceTable::flatten(Bundle* bundle, const sp<AaptFile>& dest) +status_t ResourceTable::flatten(Bundle* bundle, const sp<const ResourceFilter>& filter, const sp<AaptFile>& dest) { - ResourceFilter filter; - status_t err = filter.parse(bundle->getConfigurations()); - if (err != NO_ERROR) { - return err; - } - const ConfigDescription nullConfig; const size_t N = mOrderedPackages.size(); @@ -2795,7 +2789,7 @@ status_t ResourceTable::flatten(Bundle* bundle, const sp<AaptFile>& dest) const size_t N = c->getEntries().size(); for (size_t ei=0; ei<N; ei++) { ConfigDescription config = c->getEntries().keyAt(ei); - if (filterable && !filter.match(config)) { + if (filterable && !filter->match(config)) { continue; } sp<Entry> e = c->getEntries().valueAt(ei); @@ -2887,7 +2881,7 @@ status_t ResourceTable::flatten(Bundle* bundle, const sp<AaptFile>& dest) return amt; } - err = flattenLibraryTable(data, libraryPackages); + status_t err = flattenLibraryTable(data, libraryPackages); if (err != NO_ERROR) { fprintf(stderr, "ERROR: failed to write library table\n"); return err; @@ -2943,11 +2937,11 @@ status_t ResourceTable::flatten(Bundle* bundle, const sp<AaptFile>& dest) } const size_t CN = cl->getEntries().size(); for (size_t ci=0; ci<CN; ci++) { - if (filterable && !filter.match(cl->getEntries().keyAt(ci))) { + if (filterable && !filter->match(cl->getEntries().keyAt(ci))) { continue; } for (size_t cj=ci+1; cj<CN; cj++) { - if (filterable && !filter.match(cl->getEntries().keyAt(cj))) { + if (filterable && !filter->match(cl->getEntries().keyAt(cj))) { continue; } typeSpecFlags[ei] |= htodl( @@ -2989,7 +2983,7 @@ status_t ResourceTable::flatten(Bundle* bundle, const sp<AaptFile>& dest) config.screenHeightDp, config.layoutDirection)); - if (filterable && !filter.match(config)) { + if (filterable && !filter->match(config)) { continue; } @@ -3108,7 +3102,7 @@ status_t ResourceTable::flatten(Bundle* bundle, const sp<AaptFile>& dest) } ssize_t strStart = dest->getSize(); - err = valueStrings.writeStringBlock(dest); + status_t err = valueStrings.writeStringBlock(dest); if (err != NO_ERROR) { return err; } diff --git a/tools/aapt/ResourceTable.h b/tools/aapt/ResourceTable.h index ec8fd17..a73993c 100644 --- a/tools/aapt/ResourceTable.h +++ b/tools/aapt/ResourceTable.h @@ -7,8 +7,10 @@ #ifndef RESOURCE_TABLE_H #define RESOURCE_TABLE_H +#include "ConfigDescription.h" #include "StringPool.h" #include "SourcePos.h" +#include "ResourceFilter.h" #include <set> #include <map> @@ -76,37 +78,6 @@ public: class Type; class Entry; - struct ConfigDescription : public ResTable_config { - ConfigDescription() { - memset(this, 0, sizeof(*this)); - size = sizeof(ResTable_config); - } - ConfigDescription(const ResTable_config&o) { - *static_cast<ResTable_config*>(this) = o; - size = sizeof(ResTable_config); - } - ConfigDescription(const ConfigDescription&o) { - *static_cast<ResTable_config*>(this) = o; - } - - ConfigDescription& operator=(const ResTable_config& o) { - *static_cast<ResTable_config*>(this) = o; - size = sizeof(ResTable_config); - return *this; - } - ConfigDescription& operator=(const ConfigDescription& o) { - *static_cast<ResTable_config*>(this) = o; - return *this; - } - - inline bool operator<(const ConfigDescription& o) const { return compare(o) < 0; } - inline bool operator<=(const ConfigDescription& o) const { return compare(o) <= 0; } - inline bool operator==(const ConfigDescription& o) const { return compare(o) == 0; } - inline bool operator!=(const ConfigDescription& o) const { return compare(o) != 0; } - inline bool operator>=(const ConfigDescription& o) const { return compare(o) >= 0; } - inline bool operator>(const ConfigDescription& o) const { return compare(o) > 0; } - }; - ResourceTable(Bundle* bundle, const String16& assetsPackage); status_t addIncludedResources(Bundle* bundle, const sp<AaptAssets>& assets); @@ -182,7 +153,7 @@ public: size_t numLocalResources() const; bool hasResources() const; - sp<AaptFile> flatten(Bundle*); + sp<AaptFile> flatten(Bundle* bundle, const sp<const ResourceFilter>& filter); static inline uint32_t makeResId(uint32_t packageId, uint32_t typeId, @@ -223,7 +194,7 @@ public: void addLocalization(const String16& name, const String8& locale, const SourcePos& src); status_t validateLocalizations(void); - status_t flatten(Bundle*, const sp<AaptFile>& dest); + status_t flatten(Bundle* bundle, const sp<const ResourceFilter>& filter, const sp<AaptFile>& dest); status_t flattenLibraryTable(const sp<AaptFile>& dest, const Vector<sp<Package> >& libs); void writePublicDefinitions(const String16& package, FILE* fp); diff --git a/tools/aapt/tests/AaptConfig_test.cpp b/tools/aapt/tests/AaptConfig_test.cpp new file mode 100644 index 0000000..e795d81 --- /dev/null +++ b/tools/aapt/tests/AaptConfig_test.cpp @@ -0,0 +1,78 @@ +/* + * 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. + */ + +#include <utils/String8.h> +#include <gtest/gtest.h> + +#include "AaptConfig.h" +#include "ConfigDescription.h" +#include "TestHelper.h" + +using android::String8; + +static ::testing::AssertionResult TestParse(const String8& input, ConfigDescription* config=NULL) { + if (AaptConfig::parse(String8(input), config)) { + return ::testing::AssertionSuccess() << input << " was successfully parsed"; + } + return ::testing::AssertionFailure() << input << " could not be parsed"; +} + +static ::testing::AssertionResult TestParse(const char* input, ConfigDescription* config=NULL) { + return TestParse(String8(input), config); +} + +TEST(AaptConfigTest, ParseFailWhenQualifiersAreOutOfOrder) { + EXPECT_FALSE(TestParse("en-sw600dp-ldrtl")); + EXPECT_FALSE(TestParse("land-en")); + EXPECT_FALSE(TestParse("hdpi-320dpi")); +} + +TEST(AaptConfigTest, ParseFailWhenQualifiersAreNotMatched) { + EXPECT_FALSE(TestParse("en-sw600dp-ILLEGAL")); +} + +TEST(AaptConfigTest, ParseFailWhenQualifiersHaveTrailingDash) { + EXPECT_FALSE(TestParse("en-sw600dp-land-")); +} + +TEST(AaptConfigTest, ParseBasicQualifiers) { + ConfigDescription config; + EXPECT_TRUE(TestParse("", &config)); + EXPECT_EQ(String8(""), config.toString()); + + EXPECT_TRUE(TestParse("fr-land", &config)); + EXPECT_EQ(String8("fr-land"), config.toString()); + + EXPECT_TRUE(TestParse("mcc310-pl-sw720dp-normal-long-port-night-" + "xhdpi-keyssoft-qwerty-navexposed-nonav", &config)); + EXPECT_EQ(String8("mcc310-pl-sw720dp-normal-long-port-night-" + "xhdpi-keyssoft-qwerty-navexposed-nonav-v13"), config.toString()); +} + +TEST(AaptConfigTest, ParseLocales) { + ConfigDescription config; + EXPECT_TRUE(TestParse("en-rUS", &config)); + EXPECT_EQ(String8("en-US"), config.toString()); +} + +TEST(AaptConfigTest, ParseQualifierAddedInApi13) { + ConfigDescription config; + EXPECT_TRUE(TestParse("sw600dp", &config)); + EXPECT_EQ(String8("sw600dp-v13"), config.toString()); + + EXPECT_TRUE(TestParse("sw600dp-v8", &config)); + EXPECT_EQ(String8("sw600dp-v13"), config.toString()); +} diff --git a/tools/aapt/tests/AaptGroupEntry_test.cpp b/tools/aapt/tests/AaptGroupEntry_test.cpp new file mode 100644 index 0000000..7348a08 --- /dev/null +++ b/tools/aapt/tests/AaptGroupEntry_test.cpp @@ -0,0 +1,54 @@ +/* + * 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. + */ + +#include <utils/String8.h> +#include <gtest/gtest.h> + +#include "AaptAssets.h" +#include "ResourceFilter.h" +#include "TestHelper.h" + +using android::String8; + +static ::testing::AssertionResult TestParse(AaptGroupEntry& entry, const String8& dirName, + String8* outType) { + if (entry.initFromDirName(dirName, outType)) { + return ::testing::AssertionSuccess() << dirName << " was successfully parsed"; + } + return ::testing::AssertionFailure() << dirName << " could not be parsed"; +} + +static ::testing::AssertionResult TestParse(AaptGroupEntry& entry, const char* input, + String8* outType) { + return TestParse(entry, String8(input), outType); +} + +TEST(AaptGroupEntryTest, ParseNoQualifier) { + AaptGroupEntry entry; + String8 type; + EXPECT_TRUE(TestParse(entry, "menu", &type)); + EXPECT_EQ(String8("menu"), type); +} + +TEST(AaptGroupEntryTest, ParseCorrectType) { + AaptGroupEntry entry; + String8 type; + EXPECT_TRUE(TestParse(entry, "anim", &type)); + EXPECT_EQ(String8("anim"), type); + + EXPECT_TRUE(TestParse(entry, "animator", &type)); + EXPECT_EQ(String8("animator"), type); +} diff --git a/tools/aapt/tests/ResourceFilter_test.cpp b/tools/aapt/tests/ResourceFilter_test.cpp new file mode 100644 index 0000000..30697bb --- /dev/null +++ b/tools/aapt/tests/ResourceFilter_test.cpp @@ -0,0 +1,128 @@ +/* + * 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. + */ + +#include <androidfw/ResourceTypes.h> +#include <utils/String8.h> +#include <gtest/gtest.h> + +#include "AaptConfig.h" +#include "ResourceFilter.h" +#include "ConfigDescription.h" + +using android::String8; + +// In this context, 'Axis' represents a particular field in the configuration, +// such as language or density. + +TEST(WeakResourceFilterTest, EmptyFilterMatchesAnything) { + WeakResourceFilter filter; + ASSERT_EQ(NO_ERROR, filter.parse(String8(""))); + + ConfigDescription config; + config.density = 320; + + EXPECT_TRUE(filter.match(config)); + + config.language[0] = 'f'; + config.language[1] = 'r'; + + EXPECT_TRUE(filter.match(config)); +} + +TEST(WeakResourceFilterTest, MatchesConfigWithUnrelatedAxis) { + WeakResourceFilter filter; + ASSERT_EQ(NO_ERROR, filter.parse(String8("fr"))); + + ConfigDescription config; + config.density = 320; + + EXPECT_TRUE(filter.match(config)); +} + +TEST(WeakResourceFilterTest, MatchesConfigWithSameValueAxis) { + WeakResourceFilter filter; + ASSERT_EQ(NO_ERROR, filter.parse(String8("fr"))); + + ConfigDescription config; + config.language[0] = 'f'; + config.language[1] = 'r'; + + EXPECT_TRUE(filter.match(config)); +} + +TEST(WeakResourceFilterTest, MatchesConfigWithSameValueAxisAndOtherUnrelatedAxis) { + WeakResourceFilter filter; + ASSERT_EQ(NO_ERROR, filter.parse(String8("fr"))); + + ConfigDescription config; + config.language[0] = 'f'; + config.language[1] = 'r'; + config.density = 320; + + EXPECT_TRUE(filter.match(config)); +} + +TEST(WeakResourceFilterTest, DoesNotMatchConfigWithDifferentValueAxis) { + WeakResourceFilter filter; + ASSERT_EQ(NO_ERROR, filter.parse(String8("fr"))); + + ConfigDescription config; + config.language[0] = 'd'; + config.language[1] = 'e'; + + EXPECT_FALSE(filter.match(config)); +} + +TEST(WeakResourceFilterTest, MatchesConfigWithSameLanguageButNoRegionSpecified) { + WeakResourceFilter filter; + ASSERT_EQ(NO_ERROR, filter.parse(String8("de-rDE"))); + + ConfigDescription config; + config.language[0] = 'd'; + config.language[1] = 'e'; + + EXPECT_TRUE(filter.match(config)); +} + +TEST(WeakResourceFilterTest, ParsesStandardLocaleOnlyString) { + WeakResourceFilter filter; + EXPECT_EQ(NO_ERROR, filter.parse(String8("de_DE"))); +} + +TEST(WeakResourceFilterTest, IgnoresVersion) { + WeakResourceFilter filter; + ASSERT_EQ(NO_ERROR, filter.parse(String8("normal-v4"))); + + ConfigDescription config; + config.smallestScreenWidthDp = 600; + config.version = 13; + + // The configs don't match on any axis besides version, which should be ignored. + EXPECT_TRUE(filter.match(config)); +} + +TEST(WeakResourceFilterTest, MatchesConfigWithRegion) { + WeakResourceFilter filter; + ASSERT_EQ(NO_ERROR, filter.parse(String8("kok,kok_IN,kok_419"))); + + ConfigDescription config; + AaptLocaleValue val; + ASSERT_TRUE(val.initFromFilterString(String8("kok_IN"))); + val.writeTo(&config); + + EXPECT_TRUE(filter.match(config)); +} + diff --git a/tools/aapt/tests/TestHelper.h b/tools/aapt/tests/TestHelper.h new file mode 100644 index 0000000..7917483 --- /dev/null +++ b/tools/aapt/tests/TestHelper.h @@ -0,0 +1,33 @@ +/* + * 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. + */ + +#ifndef __TEST_HELPER_H +#define __TEST_HELPER_H + +#include <utils/String8.h> + +namespace android { + +/** + * Stream operator for nicely printing String8's in gtest output. + */ +inline std::ostream& operator<<(std::ostream& stream, const String8& str) { + return stream << str.string(); +} + +} + +#endif |